diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index fb9d3160b..6a2ff0db4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: SwiftLint - uses: docker://norionomura/swiftlint:0.53.0 + uses: docker://norionomura/swiftlint:0.54.0_swift-5.9.0 with: args: swiftlint --reporter github-actions-logging --strict diff --git a/.swiftlint.yml b/.swiftlint.yml index a4eea04d7..b24e7660e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,22 +1,82 @@ +allow_zero_lintable_files: true + disabled_rules: - - trailing_whitespace + - no_space_in_method_call + - multiple_closures_with_trailing_closure + - block_based_kvo + - compiler_protocol_init + - unused_setter_value + - line_length + - type_name + - implicit_getter + - function_parameter_count - trailing_comma - nesting + - opening_brace + +opt_in_rules: + - file_header + - explicit_init + +custom_rules: + explicit_non_final_class: + included: ".*\\.swift" + name: "Implicitly non-final class" + regex: "^\\s*(class) (?!func|var)" + capture_group: 0 + match_kinds: + - keyword + message: "Classes should be `final` by default, use explicit `internal` or `public` for non-final classes." + severity: error + enforce_os_log_wrapper: + included: ".*\\.swift" + name: "Use `import Common` for os_log instead of `import os.log`" + regex: "^(import (?:os\\.log|os|OSLog))$" + capture_group: 0 + message: "os_log wrapper ensures log args are @autoclosures (computed when needed) and to be able to use String Interpolation." + severity: error -line_length: - warning: 150 - ignores_comments: true +analyzer_rules: # Rules run by `swiftlint analyze` + - explicit_self +# Rule Config identifier_name: min_length: 1 max_length: 1000 +file_length: + warning: 1200 + error: 1200 +type_body_length: + warning: 500 + error: 500 +large_tuple: + warning: 4 + error: 5 +file_header: + required_pattern: | + \/\/ + \/\/ SWIFTLINT_CURRENT_FILENAME + \/\/ + \/\/ Copyright © \d{4} DuckDuckGo\. All rights reserved\. + \/\/ + \/\/ Licensed under the Apache License, Version 2\.0 \(the \"License\"\); + \/\/ you may not use this file except in compliance with the License\. + \/\/ You may obtain a copy of the License at + \/\/ + \/\/ http:\/\/www\.apache\.org\/licenses\/LICENSE-2\.0 + \/\/ + \/\/ Unless required by applicable law or agreed to in writing, software + \/\/ distributed under the License is distributed on an \"AS IS\" BASIS, + \/\/ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\. + \/\/ See the License for the specific language governing permissions and + \/\/ limitations under the License\. + \/\/ -type_name: - min_length: 3 - max_length: - warning: 80 - error: 100 - +# General Config excluded: + - Package.swift - .build - scripts/ + - Sources/RemoteMessaging/Model/AnyDecodable.swift + - Sources/Common/Concurrency/AsyncStream.swift + diff --git a/Package.swift b/Package.swift index 6a4b2d300..f150b03be 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,8 @@ let package = Package( .library(name: "SyncDataProviders", targets: ["SyncDataProviders"]), .library(name: "NetworkProtection", targets: ["NetworkProtection"]), .library(name: "NetworkProtectionTestUtils", targets: ["NetworkProtectionTestUtils"]), - .library(name: "SecureStorage", targets: ["SecureStorage"]) + .library(name: "SecureStorage", targets: ["SecureStorage"]), + .plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"]), ], dependencies: [ .package(url: "https://github.com/duckduckgo/duckduckgo-autofill.git", exact: "10.0.2"), @@ -41,7 +42,7 @@ let package = Package( .package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "4.52.0"), .package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"), .package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"), - .package(url: "https://github.com/duckduckgo/wireguard-apple", exact: "1.1.1") + .package(url: "https://github.com/duckduckgo/wireguard-apple", exact: "1.1.1"), ], targets: [ .target( @@ -65,12 +66,18 @@ let package = Package( ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "Persistence", dependencies: [ "Common" - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "Bookmarks", @@ -80,20 +87,27 @@ let package = Package( ], resources: [ .process("BookmarksModel.xcdatamodeld") - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), - .executableTarget(name: "BookmarksTestDBBuilder", - dependencies: [ - "Bookmarks", - "Persistence" - ], - path: "Sources/BookmarksTestDBBuilder" + .executableTarget( + name: "BookmarksTestDBBuilder", + dependencies: [ + "Bookmarks", + "Persistence" + ], + path: "Sources/BookmarksTestDBBuilder", + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "BookmarksTestsUtils", dependencies: [ "Bookmarks" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "BloomFilterObjC", @@ -106,7 +120,8 @@ let package = Package( "BloomFilterObjC" ]), .target( - name: "Crashes" + name: "Crashes", + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "DDGSync", @@ -118,7 +133,11 @@ let package = Package( resources: [ .process("SyncMetadata.xcdatamodeld"), .process("SyncPDFTemplate.png") - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "Common", @@ -130,13 +149,19 @@ let package = Package( ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "ContentBlocking", dependencies: [ "TrackerRadarKit" - ]), + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "Navigation", dependencies: [ @@ -151,12 +176,18 @@ let package = Package( .define("_FRAME_HANDLE_ENABLED", .when(platforms: [.macOS])), .define("PRIVATE_NAVIGATION_DID_FINISH_CALLBACKS_ENABLED", .when(platforms: [.macOS])), .define("TERMINATE_WITH_REASON_ENABLED", .when(platforms: [.macOS])), - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "UserScript", dependencies: [ "Common" - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "PrivacyDashboard", @@ -167,7 +198,11 @@ let package = Package( "ContentBlocking", .product(name: "PrivacyDashboardResources", package: "privacy-dashboard") ], - path: "Sources/PrivacyDashboard" + path: "Sources/PrivacyDashboard", + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "Configuration", @@ -175,18 +210,32 @@ let package = Package( "Networking", "BrowserServicesKit", "Common" - ]), + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "Networking", dependencies: [ "Common" - ]), + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "RemoteMessaging", dependencies: [ "Common", "BrowserServicesKit" - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "SyncDataProviders", @@ -198,12 +247,19 @@ let package = Package( .product(name: "GRDB", package: "GRDB.swift"), "Persistence", "SecureStorage" - ]), + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "TestUtils", dependencies: [ "Networking" - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "NetworkProtection", dependencies: [ @@ -213,26 +269,34 @@ let package = Package( ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .target( name: "SecureStorage", dependencies: [ "Common", .product(name: "GRDB", package: "GRDB.swift") - ] + ], + swiftSettings: [ + .define("DEBUG", .when(configuration: .debug)) + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target( name: "SecureStorageTestsUtils", dependencies: [ "SecureStorage" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .target(name: "WireGuardC"), .target( name: "NetworkProtectionTestUtils", dependencies: [ "NetworkProtection" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), // MARK: - Test Targets @@ -255,8 +319,10 @@ let package = Package( .copy("Resources/Bookmarks_V3.sqlite-wal"), .copy("Resources/Bookmarks_V4.sqlite"), .copy("Resources/Bookmarks_V4.sqlite-shm"), - .copy("Resources/Bookmarks_V4.sqlite-wal") - ]), + .copy("Resources/Bookmarks_V4.sqlite-wal"), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "BrowserServicesKitTests", dependencies: [ @@ -266,28 +332,37 @@ let package = Package( ], resources: [ .copy("Resources") - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .testTarget( name: "DDGSyncTests", dependencies: [ "DDGSync" - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "DDGSyncCryptoTests", dependencies: [ .product(name: "DDGSyncCrypto", package: "sync_crypto") - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "CommonTests", dependencies: [ "Common" - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "NetworkingTests", dependencies: [ "TestUtils" - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "NavigationTests", dependencies: [ @@ -301,7 +376,9 @@ let package = Package( .define("_IS_USER_INITIATED_ENABLED", .when(platforms: [.macOS])), .define("_FRAME_HANDLE_ENABLED", .when(platforms: [.macOS])), .define("PRIVATE_NAVIGATION_DID_FINISH_CALLBACKS_ENABLED", .when(platforms: [.macOS])), - ]), + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), .testTarget( name: "UserScriptTests", dependencies: [ @@ -309,21 +386,24 @@ let package = Package( ], resources: [ .process("testUserScript.js") - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .testTarget( name: "PersistenceTests", dependencies: [ "Persistence", "TrackerRadarKit" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .testTarget( name: "ConfigurationTests", dependencies: [ "Configuration", "TestUtils" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .testTarget( name: "SyncDataProvidersTests", @@ -331,8 +411,21 @@ let package = Package( "BookmarksTestsUtils", "SecureStorageTestsUtils", "SyncDataProviders" + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), + .plugin( + name: "SwiftLintPlugin", + capability: .buildTool(), + dependencies: [ + .target(name: "SwiftLintBinary", condition: .when(platforms: [.macOS])) ] ), + .binaryTarget( + name: "SwiftLintBinary", + url: "https://github.com/realm/SwiftLint/releases/download/0.54.0/SwiftLintBinary-macos.artifactbundle.zip", + checksum: "963121d6babf2bf5fd66a21ac9297e86d855cbc9d28322790646b88dceca00f1" + ), .testTarget( name: "NetworkProtectionTests", dependencies: [ @@ -343,15 +436,34 @@ let package = Package( .copy("Resources/servers-original-endpoint.json"), .copy("Resources/servers-updated-endpoint.json"), .copy("Resources/locations-endpoint.json") - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), .testTarget( name: "SecureStorageTests", dependencies: [ "SecureStorage", "SecureStorageTestsUtils" - ] + ], + plugins: [.plugin(name: "SwiftLintPlugin")] ), ], cxxLanguageStandard: .cxx11 ) + +// validate all targets have swiftlint plugin +for target in package.targets { + let targetsWithSwiftlintDisabled: Set = [ + "SwiftLintPlugin", + "SwiftLintBinary", + "BloomFilterObjC", + "BloomFilterWrapper", + "WireGuardC", + ] + guard !targetsWithSwiftlintDisabled.contains(target.name) else { continue } + guard target.plugins?.contains(where: { "\($0)" == "\(Target.PluginUsage.plugin(name: "SwiftLintPlugin"))" }) == true else { + assertionFailure("\nTarget \(target.name) is missing SwiftLintPlugin dependency.\nIf this is intended, add \"\(target.name)\" to targetsWithSwiftlintDisabled\nTarget plugins: " + + (target.plugins?.map { "\($0)" }.joined(separator: ", ") ?? "")) + continue + } +} diff --git a/Plugins/SwiftLintPlugin/InputListItem.swift b/Plugins/SwiftLintPlugin/InputListItem.swift new file mode 100644 index 000000000..8cf1a8f2b --- /dev/null +++ b/Plugins/SwiftLintPlugin/InputListItem.swift @@ -0,0 +1,36 @@ +// +// InputListItem.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct InputListItem: Codable { + + let modified: Date + private(set) var diagnostics: [String]? + + init(modified: Date) { + self.modified = modified + } + + mutating func appendDiagnosticsMessage(_ message: String) { + diagnostics?.append(message) ?? { + diagnostics = [message] + }() + } + +} diff --git a/Plugins/SwiftLintPlugin/PathExtension.swift b/Plugins/SwiftLintPlugin/PathExtension.swift new file mode 100644 index 000000000..0a0a44a4d --- /dev/null +++ b/Plugins/SwiftLintPlugin/PathExtension.swift @@ -0,0 +1,73 @@ +// +// PathExtension.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PackagePlugin + +extension Path { + + static let mv = Path("/bin/mv") + static let echo = Path("/bin/echo") + static let cat = Path("/bin/cat") + static let sh = Path("/bin/sh") + + private static let swiftlintConfig = ".swiftlint.yml" + + /// Scans the receiver, then all of its parents looking for a configuration file with the name ".swiftlint.yml". + /// + /// - returns: Path to the configuration file, or nil if one cannot be found. + func firstParentContainingConfigFile() -> Path? { + let proposedDirectory = sequence( + first: self, + next: { path in + guard path.stem.count > 1 else { + // Check we're not at the root of this filesystem, as `removingLastComponent()` + // will continually return the root from itself. + return nil + } + + return path.removingLastComponent() + } + ).first { path in + let potentialConfigurationFile = path.appending(subpath: Self.swiftlintConfig) + return potentialConfigurationFile.isAccessible() + } + return proposedDirectory + } + + /// Safe way to check if the file is accessible from within the current process sandbox. + private func isAccessible() -> Bool { + let result = string.withCString { pointer in + access(pointer, R_OK) + } + + return result == 0 + } + + /// Get file modification date + var modified: Date { + get throws { + try FileManager.default.attributesOfItem(atPath: self.string)[.modificationDate] as? Date ?? { throw CocoaError(.fileReadUnknown) }() + } + } + + var url: URL { + URL(fileURLWithPath: self.string) + } + +} diff --git a/Plugins/SwiftLintPlugin/ProcessInfo+EnvironmentType.swift b/Plugins/SwiftLintPlugin/ProcessInfo+EnvironmentType.swift new file mode 100644 index 000000000..20a4742a5 --- /dev/null +++ b/Plugins/SwiftLintPlugin/ProcessInfo+EnvironmentType.swift @@ -0,0 +1,39 @@ +// +// ProcessInfo+EnvironmentType.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension ProcessInfo { + + enum EnvironmentType { + case xcode + case xcodebuild + case ci + } + + var environmentType: EnvironmentType { + if self.environment["GITHUB_ACTIONS"] != nil { + return .ci + } else if self.environment["UsePerConfigurationBuildLocations"] != nil { + return .xcode + } else { + return .xcodebuild + } + } + +} diff --git a/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift b/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift new file mode 100644 index 000000000..c816954e5 --- /dev/null +++ b/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift @@ -0,0 +1,222 @@ +// +// SwiftLintPlugin.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import PackagePlugin + +@main +struct SwiftLintPlugin: BuildToolPlugin { + + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + // disable output for SPM modules built in RELEASE mode + guard let target = target as? SourceModuleTarget else { + assertionFailure("invalid target") + return [] + } + + guard (target as? SwiftSourceModuleTarget)?.compilationConditions.contains(.debug) != false || target.kind == .test else { + print("SwiftLint: \(target.name): Skipping for RELEASE build") + return [] + } + + let inputFiles = target.sourceFiles(withSuffix: "swift").map(\.path) + guard !inputFiles.isEmpty else { + print("SwiftLint: \(target.name): No input files") + return [] + } + + return try createBuildCommands( + target: target.name, + inputFiles: inputFiles, + packageDirectory: context.package.directory.firstParentContainingConfigFile() ?? context.package.directory, + workingDirectory: context.pluginWorkDirectory, + tool: context.tool(named:) + ) + } + + // swiftlint:disable function_body_length + private func createBuildCommands( + target: String, + inputFiles: [Path], + packageDirectory: Path, + workingDirectory: Path, + tool: (String) throws -> PluginContext.Tool + ) throws -> [Command] { + + // only lint when built from Xcode (disable for CI or xcodebuild) + guard case .xcode = ProcessInfo().environmentType else { return [] } + + let fm = FileManager() + + let cacheURL = URL(fileURLWithPath: workingDirectory.appending("cache.json").string) + let outputPath = workingDirectory.appending("output.txt").string + + // if clean build: clear cache + let buildDir = workingDirectory.removingLastComponent() // BrowserServicesKit + .removingLastComponent() // browserserviceskit.output + .removingLastComponent() // plugins + .removingLastComponent() // SourcePackages + .removingLastComponent() // DerivedData/DuckDuckGo-xxxx + .appending("Build") + if let buildDirContents = try? fm.contentsOfDirectory(atPath: buildDir.string), + !buildDirContents.contains("Products") { + print("SwiftLint: \(target): Clean Build") + + try? fm.removeItem(at: cacheURL) + try? fm.removeItem(atPath: outputPath) + } + + // read cached data + var cache = (try? JSONDecoder().decode([String: InputListItem].self, from: Data(contentsOf: cacheURL))) ?? [:] + // read diagnostics from last pass + let lastOutput = cache.isEmpty ? "" : (try? String(contentsOfFile: outputPath)) ?? { + // no diagnostics file – reset + cache = [:] + return "" + }() + + // analyze new/modified files and output cached diagnostics for non-modified files + var filesToProcess = Set() + var newCache = [String: InputListItem]() + for inputFile in inputFiles { + try autoreleasepool { + + let modified = try inputFile.modified + if let cacheItem = cache[inputFile.string], modified == cacheItem.modified { + // file not modified + newCache[inputFile.string] = cacheItem + return + } + + // updated modification date in cache and re-process + newCache[inputFile.string] = .init(modified: modified) + + filesToProcess.insert(inputFile.string) + } + } + + // merge diagnostics from last linter pass into cache + for outputLint in lastOutput.split(separator: "\n") { + guard let filePath = outputLint.split(separator: ":", maxSplits: 1).first.map(String.init), + !filesToProcess.contains(filePath) else { continue } + + newCache[filePath]?.appendDiagnosticsMessage(String(outputLint)) + } + + // collect cached diagnostic messages from cache + let cachedDiagnostics = newCache.values.reduce(into: [String]()) { + $0 += $1.diagnostics ?? [] + } + + // We are not producing output files and this is needed only to not include cache files into bundle + let outputFilesDirectory = workingDirectory.appending("Output") + try? fm.createDirectory(at: outputFilesDirectory.url, withIntermediateDirectories: true) + try? fm.removeItem(at: cacheURL.appendingPathExtension("tmp")) + try? fm.removeItem(atPath: outputPath + ".tmp") + + var result = [Command]() + if !filesToProcess.isEmpty { + print("SwiftLint: \(target): Processing \(filesToProcess.count) files") + + // write updated cache into temporary file, cache file will be overwritten when linting completes + try JSONEncoder().encode(newCache).write(to: cacheURL.appendingPathExtension("tmp")) + + let swiftlint = try tool("swiftlint").path + let lintCommand = """ + cd "\(packageDirectory)" && "\(swiftlint)" lint --quiet --force-exclude --cache-path "\(workingDirectory)" \ + \(filesToProcess.map { "\"\($0)\"" }.joined(separator: " ")) \ + | tee -a "\(outputPath).tmp" + """ + + result = [ + .prebuildCommand( + displayName: "\(target): SwiftLint", + executable: .sh, + arguments: ["-c", lintCommand], + outputFilesDirectory: outputFilesDirectory + ) + ] + + } else { + print("SwiftLint: \(target): No new files to process") + try JSONEncoder().encode(newCache).write(to: cacheURL) + try "".write(toFile: outputPath, atomically: false, encoding: .utf8) + } + + // output cached diagnostic messages from previous run + result.append(.prebuildCommand( + displayName: "SwiftLint: \(target): cached \(cacheURL.path)", + executable: .echo, + arguments: [cachedDiagnostics.joined(separator: "\n")], + outputFilesDirectory: outputFilesDirectory + )) + + if !filesToProcess.isEmpty { + // when ready put temporary cache and output into place + result.append(.prebuildCommand( + displayName: "SwiftLint: \(target): Cache results", + executable: .mv, + arguments: ["\(outputPath).tmp", outputPath], + outputFilesDirectory: outputFilesDirectory + )) + result.append(.prebuildCommand( + displayName: "SwiftLint: \(target): Cache source files modification dates", + executable: .mv, + arguments: [cacheURL.appendingPathExtension("tmp").path, cacheURL.path], + outputFilesDirectory: outputFilesDirectory + )) + } + + return result + } + // swiftlint:enable function_body_length + +} + +#if canImport(XcodeProjectPlugin) + +import XcodeProjectPlugin + +extension SwiftLintPlugin: XcodeBuildToolPlugin { + func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { + let inputFiles = target.inputFiles.filter { + $0.type == .source && $0.path.extension == "swift" + }.map(\.path) + + guard !inputFiles.isEmpty else { + print("SwiftLint: \(target): No input files") + return [] + } + + return try createBuildCommands( + target: target.displayName, + inputFiles: inputFiles, + packageDirectory: context.xcodeProject.directory, + workingDirectory: context.pluginWorkDirectory, + tool: context.tool(named:) + ) + } +} + +#endif + +extension String { + static let swiftlintConfigFileName = ".swiftlint.yml" + + static let debug = "DEBUG" +} diff --git a/Sources/Bookmarks/BookmarkEditorViewModel.swift b/Sources/Bookmarks/BookmarkEditorViewModel.swift index c680380b3..08244c200 100644 --- a/Sources/Bookmarks/BookmarkEditorViewModel.swift +++ b/Sources/Bookmarks/BookmarkEditorViewModel.swift @@ -65,7 +65,7 @@ public class BookmarkEditorViewModel: ObservableObject { bookmarksDatabase: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode, errorEvents: EventMapping?) { - + externalUpdates = subject.eraseToAnyPublisher() self.errorEvents = errorEvents self.context = bookmarksDatabase.makeContext(concurrencyType: .mainQueueConcurrencyType) @@ -87,12 +87,12 @@ public class BookmarkEditorViewModel: ObservableObject { self.observer = nil } } - + public init(creatingFolderWithParentID parentFolderID: NSManagedObjectID?, bookmarksDatabase: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode, errorEvents: EventMapping?) { - + externalUpdates = subject.eraseToAnyPublisher() self.errorEvents = errorEvents self.context = bookmarksDatabase.makeContext(concurrencyType: .mainQueueConcurrencyType) @@ -105,7 +105,7 @@ public class BookmarkEditorViewModel: ObservableObject { parent = BookmarkUtils.fetchRootFolder(context) } assert(parent != nil) - + // We don't support creating bookmarks from scratch at this time, so it must be a folder self.bookmark = BookmarkEntity.makeFolder(title: "", parent: parent!, @@ -152,7 +152,7 @@ public class BookmarkEditorViewModel: ObservableObject { func descendInto(_ folders: [BookmarkEntity], depth: Int) { folders.forEach { entity in - if entity.isFolder, + if entity.isFolder, entity.uuid != bookmark.uuid { locations.append(Location(bookmark: entity, depth: depth)) diff --git a/Sources/Bookmarks/BookmarkEntity.swift b/Sources/Bookmarks/BookmarkEntity.swift index 23b2f6e0f..41dd930b3 100644 --- a/Sources/Bookmarks/BookmarkEntity.swift +++ b/Sources/Bookmarks/BookmarkEntity.swift @@ -1,6 +1,5 @@ // // BookmarkEntity.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -35,7 +34,7 @@ public enum FavoritesFolderID: String, CaseIterable { @objc(BookmarkEntity) public class BookmarkEntity: NSManagedObject { - + public enum Constants { public static let rootFolderID = "bookmarks_root" public static let favoriteFoldersIDs: Set = Set(FavoritesFolderID.allCases.map(\.rawValue)) @@ -51,11 +50,11 @@ public class BookmarkEntity: NSManagedObject { case invalidFavoritesFolder case invalidFavoritesStatus } - + @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "BookmarkEntity") } - + public class func entity(in context: NSManagedObjectContext) -> NSEntityDescription { return NSEntityDescription.entity(forEntityName: "BookmarkEntity", in: context)! } @@ -87,10 +86,10 @@ public class BookmarkEntity: NSManagedObject { self.init(entity: BookmarkEntity.entity(in: moc), insertInto: moc) } - + public override func awakeFromInsert() { super.awakeFromInsert() - + uuid = UUID().uuidString } @@ -127,16 +126,16 @@ public class BookmarkEntity: NSManagedObject { try super.validateForUpdate() try validate() } - + public var urlObject: URL? { guard let url = url else { return nil } return url.isBookmarklet() ? url.toEncodedBookmarklet() : URL(string: url) } - + public var isRoot: Bool { uuid == Constants.rootFolderID } - + public var childrenArray: [BookmarkEntity] { let children = children?.array as? [BookmarkEntity] ?? [] return children.filter { $0.isPendingDeletion == false } @@ -174,7 +173,7 @@ public class BookmarkEntity: NSManagedObject { let object = BookmarkEntity(context: context) object.title = title object.isFolder = true - + if insertAtBeginning { parent.insertIntoChildren(object, at: 0) } else { @@ -182,7 +181,7 @@ public class BookmarkEntity: NSManagedObject { } return object } - + public static func makeBookmark(title: String, url: String, parent: BookmarkEntity, @@ -192,7 +191,7 @@ public class BookmarkEntity: NSManagedObject { object.title = title object.url = url object.isFolder = false - + if insertAtBeginning { parent.insertIntoChildren(object, at: 0) } else { @@ -200,7 +199,7 @@ public class BookmarkEntity: NSManagedObject { } return object } - + // If `insertAt` is nil, it is inserted at the end. public func addToFavorites(insertAt: Int? = nil, favoritesRoot root: BookmarkEntity) { @@ -325,7 +324,7 @@ extension BookmarkEntity { // MARK: Generated accessors for favorites extension BookmarkEntity { - + @objc(insertObject:inFavoritesAtIndex:) @NSManaged private func insertIntoFavorites(_ value: BookmarkEntity, at idx: Int) @@ -340,7 +339,7 @@ extension BookmarkEntity { @objc(removeFavorites:) @NSManaged private func removeFromFavorites(_ values: NSOrderedSet) - + } // MARK: Generated accessors for favoriteFolders diff --git a/Sources/Bookmarks/BookmarkErrors.swift b/Sources/Bookmarks/BookmarkErrors.swift index 4b526e537..0d8c81f11 100644 --- a/Sources/Bookmarks/BookmarkErrors.swift +++ b/Sources/Bookmarks/BookmarkErrors.swift @@ -1,6 +1,6 @@ // // BookmarkErrors.swift -// +// // Copyright © 2022 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,30 +23,30 @@ public enum BookmarksCoreDataError: Error { } public enum BookmarksModelError: Error, Equatable { - + public enum ObjectType: String { case favorite case bookmark } - + public enum ModelType: String { case favorites case bookmarks case menu case edit } - + case fetchingRootItemFailed(ModelType) case saveFailed(ModelType) case indexOutOfRange(ModelType) - + case missingParent(ObjectType) - + case bookmarkFolderExpected case bookmarksListMissingFolder case bookmarksListIndexNotMatchingBookmark case favoritesListIndexNotMatchingBookmark case orphanedBookmarksPresent - + case editorNewParentMissing } diff --git a/Sources/Bookmarks/BookmarkListViewModel.swift b/Sources/Bookmarks/BookmarkListViewModel.swift index a2bb9a3d2..8e1edb50a 100644 --- a/Sources/Bookmarks/BookmarkListViewModel.swift +++ b/Sources/Bookmarks/BookmarkListViewModel.swift @@ -1,6 +1,6 @@ // // BookmarkListViewModel.swift -// +// // Copyright © 2021 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,14 +25,14 @@ import Persistence public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { public let currentFolder: BookmarkEntity? - + let context: NSManagedObjectContext public var favoritesDisplayMode: FavoritesDisplayMode { didSet { reloadData() } } - + public var bookmarks = [BookmarkEntity]() private var observer: NSObjectProtocol? @@ -42,7 +42,7 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { public var localUpdates: AnyPublisher private let errorEvents: EventMapping? - + public init(bookmarksDatabase: CoreDataDatabase, parentID: NSManagedObjectID?, favoritesDisplayMode: FavoritesDisplayMode, @@ -71,7 +71,7 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { } self.bookmarks = fetchBookmarksInFolder(currentFolder) - + registerForChanges() } @@ -98,7 +98,7 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { } save() } - + private func registerForChanges() { observer = NotificationCenter.default.addObserver(forName: NSManagedObjectContext.didSaveObjectsNotification, object: nil, @@ -232,7 +232,7 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { errorEvents?.fire(.saveFailed(.bookmarks), error: error) } } - + // MARK: - Read public func countBookmarksForDomain(_ domain: String) -> Int { @@ -259,7 +259,7 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject { #keyPath(BookmarkEntity.isFolder), #keyPath(BookmarkEntity.isPendingDeletion) ) - + return (try? context.count(for: countRequest)) ?? 0 } diff --git a/Sources/Bookmarks/BookmarkUtils.swift b/Sources/Bookmarks/BookmarkUtils.swift index d08a6c671..089ba7105 100644 --- a/Sources/Bookmarks/BookmarkUtils.swift +++ b/Sources/Bookmarks/BookmarkUtils.swift @@ -1,5 +1,5 @@ // -// +// BookmarkUtils.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,13 +20,13 @@ import Foundation import CoreData public struct BookmarkUtils { - + public static func fetchRootFolder(_ context: NSManagedObjectContext) -> BookmarkEntity? { let request = BookmarkEntity.fetchRequest() request.predicate = NSPredicate(format: "%K == %@", #keyPath(BookmarkEntity.uuid), BookmarkEntity.Constants.rootFolderID) request.returnsObjectsAsFaults = false request.fetchLimit = 1 - + return try? context.fetch(request).first } @@ -138,7 +138,7 @@ public struct BookmarkUtils { request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [urlPredicate, predicate]) request.returnsObjectsAsFaults = false request.fetchLimit = 1 - + return try? context.fetch(request).first } diff --git a/Sources/Bookmarks/BookmarksDatabaseCleaner.swift b/Sources/Bookmarks/BookmarksDatabaseCleaner.swift index 61a596676..ba76c8e0e 100644 --- a/Sources/Bookmarks/BookmarksDatabaseCleaner.swift +++ b/Sources/Bookmarks/BookmarksDatabaseCleaner.swift @@ -1,6 +1,5 @@ // -// BookmarkDatabaseCleaner.swift -// DuckDuckGo +// BookmarksDatabaseCleaner.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/BookmarksModel.swift b/Sources/Bookmarks/BookmarksModel.swift index 85084c283..79ea9edaa 100644 --- a/Sources/Bookmarks/BookmarksModel.swift +++ b/Sources/Bookmarks/BookmarksModel.swift @@ -35,7 +35,7 @@ public protocol BookmarkListInteracting: BookmarkStoring, AnyObject { var currentFolder: BookmarkEntity? { get } var bookmarks: [BookmarkEntity] { get } var totalBookmarksCount: Int { get } - + func bookmark(at index: Int) -> BookmarkEntity? func bookmark(with id: NSManagedObjectID) -> BookmarkEntity? @@ -74,9 +74,9 @@ public protocol MenuBookmarksInteracting { var favoritesDisplayMode: FavoritesDisplayMode { get set } func createOrToggleFavorite(title: String, url: URL) - + func createBookmark(title: String, url: URL) - + func favorite(for url: URL) -> BookmarkEntity? func bookmark(for url: URL) -> BookmarkEntity? } diff --git a/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcher.swift b/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcher.swift index ee02273eb..9a8943bec 100644 --- a/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcher.swift +++ b/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcher.swift @@ -1,6 +1,5 @@ // // BookmarksFaviconsFetcher.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcherStateStore.swift b/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcherStateStore.swift index 284ac19a7..3ea024d5c 100644 --- a/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcherStateStore.swift +++ b/Sources/Bookmarks/FaviconsFetcher/BookmarksFaviconsFetcherStateStore.swift @@ -1,6 +1,5 @@ // // BookmarksFaviconsFetcherStateStore.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/FaviconsFetcher/FaviconFetcher.swift b/Sources/Bookmarks/FaviconsFetcher/FaviconFetcher.swift index 970fed75d..0009ac189 100644 --- a/Sources/Bookmarks/FaviconsFetcher/FaviconFetcher.swift +++ b/Sources/Bookmarks/FaviconsFetcher/FaviconFetcher.swift @@ -1,6 +1,5 @@ // // FaviconFetcher.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/FaviconsFetcher/FaviconsFetchOperation.swift b/Sources/Bookmarks/FaviconsFetcher/FaviconsFetchOperation.swift index 8f21a44ac..161aed5c8 100644 --- a/Sources/Bookmarks/FaviconsFetcher/FaviconsFetchOperation.swift +++ b/Sources/Bookmarks/FaviconsFetcher/FaviconsFetchOperation.swift @@ -1,6 +1,5 @@ // // FaviconsFetchOperation.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/FavoriteListViewModel.swift b/Sources/Bookmarks/FavoriteListViewModel.swift index f5dfbe33b..fa56b5457 100644 --- a/Sources/Bookmarks/FavoriteListViewModel.swift +++ b/Sources/Bookmarks/FavoriteListViewModel.swift @@ -1,5 +1,5 @@ // -// FavoritesListViewModel.swift +// FavoriteListViewModel.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -23,7 +23,7 @@ import Persistence import Common public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject { - + let context: NSManagedObjectContext public var favorites = [BookmarkEntity]() @@ -75,7 +75,7 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject self.observer = nil } } - + private func registerForChanges() { observer = NotificationCenter.default.addObserver(forName: NSManagedObjectContext.didSaveObjectsNotification, object: nil, @@ -105,7 +105,7 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject favorites = [] return } - + readFavorites(with: favoriteFolder) } @@ -114,7 +114,7 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject errorEvents?.fire(.indexOutOfRange(.favorites)) return nil } - + return favorites[index] } @@ -127,10 +127,10 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject favorite.removeFromFavorites(with: favoritesDisplayMode) save() - + readFavorites(with: favoriteFolder) } - + public func moveFavorite(_ favorite: BookmarkEntity, fromIndex: Int, toIndex: Int) { @@ -138,7 +138,7 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject errorEvents?.fire(.fetchingRootItemFailed(.favorites)) return } - + let visibleChildren = favoriteFolder.favoritesArray guard fromIndex < visibleChildren.count, @@ -146,12 +146,12 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject errorEvents?.fire(.indexOutOfRange(.favorites)) return } - + guard visibleChildren[fromIndex] == favorite else { errorEvents?.fire(.favoritesListIndexNotMatchingBookmark) return } - + // Take into account bookmarks that are pending deletion let mutableChildrenSet = favoriteFolder.mutableOrderedSetValue(forKeyPath: #keyPath(BookmarkEntity.favorites)) @@ -165,12 +165,12 @@ public class FavoritesListViewModel: FavoritesListInteracting, ObservableObject } mutableChildrenSet.moveObjects(at: IndexSet(integer: actualFromIndex), to: actualToIndex) - + save() - + readFavorites(with: favoriteFolder) } - + private func save() { do { try context.save() diff --git a/Sources/Bookmarks/FavoritesDisplayMode.swift b/Sources/Bookmarks/FavoritesDisplayMode.swift index 8c1058857..ea17b92a0 100644 --- a/Sources/Bookmarks/FavoritesDisplayMode.swift +++ b/Sources/Bookmarks/FavoritesDisplayMode.swift @@ -1,6 +1,5 @@ // // FavoritesDisplayMode.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/ImportExport/BookmarkCoreDataImporter.swift b/Sources/Bookmarks/ImportExport/BookmarkCoreDataImporter.swift index 4a9202039..b4f04154d 100644 --- a/Sources/Bookmarks/ImportExport/BookmarkCoreDataImporter.swift +++ b/Sources/Bookmarks/ImportExport/BookmarkCoreDataImporter.swift @@ -1,6 +1,6 @@ // // BookmarkCoreDataImporter.swift -// +// // Copyright © 2022 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,29 +21,29 @@ import CoreData import Persistence public class BookmarkCoreDataImporter { - + let context: NSManagedObjectContext let favoritesDisplayMode: FavoritesDisplayMode - + public init(database: CoreDataDatabase, favoritesDisplayMode: FavoritesDisplayMode) { self.context = database.makeContext(concurrencyType: .privateQueueConcurrencyType) self.favoritesDisplayMode = favoritesDisplayMode } - + public func importBookmarks(_ bookmarks: [BookmarkOrFolder]) async throws { - + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - - context.performAndWait { () -> Void in + + context.performAndWait { () in do { let favoritesFolders = BookmarkUtils.fetchFavoritesFolders(for: favoritesDisplayMode, in: context) guard let topLevelBookmarksFolder = BookmarkUtils.fetchRootFolder(context) else { throw BookmarksCoreDataError.fetchingExistingItemFailed } - + var bookmarkURLToIDMap = try bookmarkURLToID(in: context) - + try recursivelyCreateEntities(from: bookmarks, parent: topLevelBookmarksFolder, favoritesFolders: favoritesFolders, @@ -56,7 +56,7 @@ public class BookmarkCoreDataImporter { } } } - + private func bookmarkURLToID(in context: NSManagedObjectContext) throws -> [String: NSManagedObjectID] { let fetch = NSFetchRequest(entityName: "BookmarkEntity") fetch.predicate = NSPredicate( @@ -65,20 +65,20 @@ public class BookmarkCoreDataImporter { #keyPath(BookmarkEntity.isPendingDeletion) ) fetch.resultType = .dictionaryResultType - + let idDescription = NSExpressionDescription() idDescription.name = "objectID" idDescription.expression = NSExpression.expressionForEvaluatedObject() idDescription.expressionResultType = .objectIDAttributeType - + fetch.propertiesToFetch = [idDescription, #keyPath(BookmarkEntity.url)] - + let dict = try context.fetch(fetch) as? [Dictionary] - + if let result = dict?.reduce(into: [String: NSManagedObjectID](), { partialResult, data in guard let urlString = data[#keyPath(BookmarkEntity.url)] as? String, let objectID = data["objectID"] as? NSManagedObjectID else { return } - + partialResult[urlString] = objectID }) { return result @@ -136,7 +136,7 @@ public class BookmarkCoreDataImporter { } } } - + private func containsBookmark(with url: URL) -> Bool { return false } diff --git a/Sources/Bookmarks/ImportExport/BookmarkOrFolder.swift b/Sources/Bookmarks/ImportExport/BookmarkOrFolder.swift index 21246c3b8..57d14c5e9 100644 --- a/Sources/Bookmarks/ImportExport/BookmarkOrFolder.swift +++ b/Sources/Bookmarks/ImportExport/BookmarkOrFolder.swift @@ -1,6 +1,5 @@ // // BookmarkOrFolder.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/Bookmarks/MenuBookmarksViewModel.swift b/Sources/Bookmarks/MenuBookmarksViewModel.swift index edb25fcad..0fd251068 100644 --- a/Sources/Bookmarks/MenuBookmarksViewModel.swift +++ b/Sources/Bookmarks/MenuBookmarksViewModel.swift @@ -22,7 +22,7 @@ import Common import Persistence public class MenuBookmarksViewModel: MenuBookmarksInteracting { - + let context: NSManagedObjectContext public var favoritesDisplayMode: FavoritesDisplayMode = .displayNative(.mobile) { didSet { @@ -34,19 +34,19 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { private var rootFolder: BookmarkEntity? { if _rootFolder == nil { _rootFolder = BookmarkUtils.fetchRootFolder(context) - + if _rootFolder == nil { errorEvents?.fire(.fetchingRootItemFailed(.menu)) } } return _rootFolder } - + private var _favoritesFolder: BookmarkEntity? private var favoritesFolder: BookmarkEntity? { if _favoritesFolder == nil { _favoritesFolder = BookmarkUtils.fetchFavoritesFolder(withUUID: favoritesDisplayMode.displayedFolder.rawValue, in: context) - + if _favoritesFolder == nil { errorEvents?.fire(.fetchingRootItemFailed(.menu)) } @@ -55,9 +55,9 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { } private var observer: NSObjectProtocol? - + private let errorEvents: EventMapping? - + public init(bookmarksDatabase: CoreDataDatabase, errorEvents: EventMapping?) { self.errorEvents = errorEvents self.context = bookmarksDatabase.makeContext(concurrencyType: .mainQueueConcurrencyType) @@ -94,14 +94,14 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { errorEvents?.fire(.saveFailed(.menu), error: error) } } - + public func createOrToggleFavorite(title: String, url: URL) { guard let rootFolder = rootFolder else { return } - + let queriedBookmark = favorite(for: url) ?? bookmark(for: url) - + if let bookmark = queriedBookmark { if bookmark.isFavorite(on: favoritesDisplayMode.displayedFolder) { bookmark.removeFromFavorites(with: favoritesDisplayMode) @@ -115,10 +115,10 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { context: context) favorite.addToFavorites(with: favoritesDisplayMode, in: context) } - + save() } - + public func createBookmark(title: String, url: URL) { guard let rootFolder = rootFolder else { return @@ -129,7 +129,7 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { context: context) save() } - + public func favorite(for url: URL) -> BookmarkEntity? { guard let favoritesFolder else { return nil @@ -143,9 +143,9 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting { ), context: context) } - + public func bookmark(for url: URL) -> BookmarkEntity? { BookmarkUtils.fetchBookmark(for: url, context: context) } - + } diff --git a/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift b/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift index 5a7745764..f8d170d13 100644 --- a/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift +++ b/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift @@ -1,5 +1,7 @@ // -// Copyright © 2023 DuckDuckGo. All rights reserved. +// BookmarkFormFactorFavoritesMigration.swift +// +// Copyright © 2021 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,7 +33,7 @@ public class BookmarkFormFactorFavoritesMigration { guard let metadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: dbFileURL), let latestModel = CoreDataDatabase.loadModel(from: bundle, named: "BookmarksModel"), - !latestModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) + !latestModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) else { return nil } diff --git a/Sources/BookmarksTestDBBuilder/BookmarksTestDBBuilder.swift b/Sources/BookmarksTestDBBuilder/BookmarksTestDBBuilder.swift index f452fb3e5..1318a072d 100644 --- a/Sources/BookmarksTestDBBuilder/BookmarksTestDBBuilder.swift +++ b/Sources/BookmarksTestDBBuilder/BookmarksTestDBBuilder.swift @@ -1,6 +1,5 @@ // // BookmarksTestDBBuilder.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BookmarksTestsUtils/BookmarkTree.swift b/Sources/BookmarksTestsUtils/BookmarkTree.swift index dd0b538e4..1991dbb65 100644 --- a/Sources/BookmarksTestsUtils/BookmarkTree.swift +++ b/Sources/BookmarksTestsUtils/BookmarkTree.swift @@ -1,6 +1,5 @@ // // BookmarkTree.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -210,7 +209,6 @@ public struct BookmarkTree { return (rootFolder, orphans) } - // swiftlint:disable large_tuple @discardableResult public func createEntitiesForCheckingModifiedAt(in context: NSManagedObjectContext) -> (BookmarkEntity, [BookmarkEntity], [String: ModifiedAtConstraint]) { let rootFolder = BookmarkUtils.fetchRootFolder(context)! @@ -236,7 +234,6 @@ public struct BookmarkTree { } return (rootFolder, orphans, modifiedAtConstraints) } - // swiftlint:enable large_tuple let modifiedAt: Date? let lastChildrenArrayReceivedFromSync: [String]? diff --git a/Sources/BookmarksTestsUtils/ModelAccessHelper.swift b/Sources/BookmarksTestsUtils/ModelAccessHelper.swift index 8b6a8a01c..099a989f8 100644 --- a/Sources/BookmarksTestsUtils/ModelAccessHelper.swift +++ b/Sources/BookmarksTestsUtils/ModelAccessHelper.swift @@ -1,6 +1,5 @@ // // ModelAccessHelper.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+Email.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+Email.swift index aa1b51b49..ebc87d4af 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+Email.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+Email.swift @@ -1,6 +1,5 @@ // // AutofillUserScript+Email.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -213,7 +212,7 @@ extension AutofillUserScript { } func closeEmailProtectionTab(_ message: UserScriptMessage, replyHandler: @escaping MessageReplyHandler) { - emailDelegate?.autofillUserScriptDidCompleteInContextSignup(self) + emailDelegate?.autofillUserScriptDidCompleteInContextSignup(self) replyHandler(nil) } diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift index 2acd30a95..e3a00c4bf 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift @@ -1,6 +1,5 @@ // // AutofillUserScript+SecureVault.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,8 +20,6 @@ import WebKit import Common import UserScript -// swiftlint:disable line_length file_length - public enum RequestVaultCredentialsAction: String, Codable { case none case fill @@ -50,7 +47,7 @@ public protocol AutofillSecureVaultDelegate: AnyObject { subType: AutofillUserScript.GetAutofillDataSubType, trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, SecureVaultModels.CredentialsProvider, RequestVaultCredentialsAction) -> Void) - + func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForAccount accountId: String, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, SecureVaultModels.CredentialsProvider) -> Void) func autofillUserScript(_: AutofillUserScript, didRequestCreditCardWithId creditCardId: Int64, @@ -185,9 +182,9 @@ extension AutofillUserScript { self.origin = credentialsProvider == SecureVaultModels.CredentialsProvider.Name.bitwarden.rawValue ? nil : origin } } - + // MARK: - Requests - + public struct IncomingCredentials: Equatable { private enum Constants { @@ -196,22 +193,22 @@ extension AutofillUserScript { static let passwordKey = "password" static let autogeneratedKey = "autogenerated" } - + let username: String? let password: String? var autogenerated: Bool - + init(username: String?, password: String?, autogenerated: Bool = false) { self.username = username self.password = password self.autogenerated = autogenerated } - + init?(autofillDictionary: [String: Any]) { guard let credentialsDictionary = autofillDictionary[Constants.credentialsKey] as? [String: Any] else { return nil } - + // Usernames are optional, as the Autofill script can pass a generated password through without a corresponding username. self.init(username: credentialsDictionary[Constants.usernameKey] as? String, password: credentialsDictionary[Constants.passwordKey] as? String, @@ -219,7 +216,7 @@ extension AutofillUserScript { } } - + /// Represents the incoming Autofill data provided by the user script. /// /// Identities and Credit Cards can be converted to their final model objects directly, but credentials cannot as they have to looked up in the Secure Vault first, hence the existence of a standalone @@ -234,11 +231,11 @@ extension AutofillUserScript { public var credentials: IncomingCredentials? public let creditCard: SecureVaultModels.CreditCard? public let trigger: GetTriggerType? - + var hasAutogeneratedCredentials: Bool { return credentials?.autogenerated ?? false } - + init(dictionary: [String: Any]) { self.identity = .init(autofillDictionary: dictionary) self.creditCard = .init(autofillDictionary: dictionary) @@ -249,19 +246,18 @@ extension AutofillUserScript { self.trigger = nil } } - + init(identity: SecureVaultModels.Identity?, credentials: AutofillUserScript.IncomingCredentials?, creditCard: SecureVaultModels.CreditCard?, trigger: GetTriggerType?) { self.identity = identity self.credentials = credentials self.creditCard = creditCard self.trigger = trigger } - + } // MARK: - Responses - // swiftlint:disable nesting struct RequestAutoFillInitDataResponse: Codable { struct AutofillInitSuccess: Codable { @@ -339,8 +335,6 @@ extension AutofillUserScript { let success: IncontextSignupDismissedAt } - // swiftlint:enable nesting - struct RequestAutoFillCreditCardResponse: Codable { let success: CreditCardObject let error: String? @@ -358,7 +352,7 @@ extension AutofillUserScript { let success: [CredentialObject] } - + struct CredentialResponse: Codable { let id: String // When bitwarden is locked use id = "provider_locked" @@ -396,16 +390,15 @@ extension AutofillUserScript { } // GetAutofillDataResponse: https://github.com/duckduckgo/duckduckgo-autofill/blob/main/src/deviceApiCalls/schemas/getAutofillData.result.json - // swiftlint:disable nesting struct RequestVaultCredentialsForDomainResponse: Codable { struct RequestVaultCredentialsResponseContents: Codable { let credentials: CredentialResponse? let action: RequestVaultCredentialsAction } - + let success: RequestVaultCredentialsResponseContents - + static func responseFromSecureVaultWebsiteCredentials(_ credentials: SecureVaultModels.WebsiteCredentials?, credentialsProvider: SecureVaultModels.CredentialsProvider, action: RequestVaultCredentialsAction) -> Self { @@ -419,17 +412,15 @@ extension AutofillUserScript { } else { credential = nil } - + return RequestVaultCredentialsForDomainResponse(success: RequestVaultCredentialsResponseContents(credentials: credential, action: action)) } } - + struct RequestVaultCredentialsForAccountResponse: Codable { let success: CredentialResponse } - // swiftlint:enable nesting - // MARK: - Message Handlers func getRuntimeConfiguration(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { @@ -475,7 +466,7 @@ extension AutofillUserScript { case username case password } - + // https://github.com/duckduckgo/duckduckgo-autofill/blob/main/src/deviceApiCalls/schemas/getAutofillData.params.json public enum GetTriggerType: String, Codable { case userInitiated @@ -571,14 +562,14 @@ extension AutofillUserScript { defer { replyHandler(nil) } - + guard let body = message.messageBody as? [String: Any] else { return } - + let incomingData = DetectedAutofillData(dictionary: body) let domain = hostProvider.hostForMessage(message) - + vaultDelegate?.autofillUserScript(self, didRequestStoreDataForDomain: domain, data: incomingData) } @@ -599,7 +590,7 @@ extension AutofillUserScript { } func pmGetAutofillCredentials(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { - + guard let body = message.messageBody as? [String: Any], let id = body["id"] as? String else { return @@ -664,7 +655,7 @@ extension AutofillUserScript { } } } - + func askToUnlockProvider(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { let domain = hostForMessage(message) let email = emailDelegate?.autofillUserScriptDidRequestSignedInStatus(self) ?? false @@ -682,7 +673,7 @@ extension AutofillUserScript { } }) } - + // On Catalina we poll this method every x seconds from all tabs func checkCredentialsProviderStatus(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { if #available(macOS 11, *) { @@ -923,5 +914,3 @@ extension AutofillUserScript.AskToUnlockProviderResponse { } } - -// swiftlint:enable line_length file_length diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift index ed553daf7..2487d5036 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift @@ -1,6 +1,5 @@ // // AutofillUserScript+SourceProvider.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift index 5b9ad133e..a140494c7 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift @@ -1,6 +1,5 @@ // // AutofillUserScript.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -51,10 +50,10 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti case getAvailableInputTypes case getAutofillData case storeFormData - + case askToUnlockProvider case checkCredentialsProviderStatus - + case sendJSPixel case setIncontextSignupPermanentlyDismissedAt @@ -120,7 +119,7 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti os_log("Failed to parse Autofill User Script message: '%{public}s'", log: .userScripts, type: .debug, messageName) return nil } - + os_log("AutofillUserScript: received '%{public}s'", log: .userScripts, type: .debug, messageName) switch message { @@ -148,7 +147,7 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti case .pmHandlerOpenManageCreditCards: return pmOpenManageCreditCards case .pmHandlerOpenManageIdentities: return pmOpenManageIdentities case .pmHandlerOpenManagePasswords: return pmOpenManagePasswords - + case .askToUnlockProvider: return askToUnlockProvider case .checkCredentialsProviderStatus: return checkCredentialsProviderStatus diff --git a/Sources/BrowserServicesKit/Autofill/Matchers/AutofillAccountMatcher.swift b/Sources/BrowserServicesKit/Autofill/Matchers/AutofillAccountMatcher.swift index d6d977386..d59933347 100644 --- a/Sources/BrowserServicesKit/Autofill/Matchers/AutofillAccountMatcher.swift +++ b/Sources/BrowserServicesKit/Autofill/Matchers/AutofillAccountMatcher.swift @@ -1,6 +1,5 @@ // // AutofillAccountMatcher.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/Matchers/AutofillUrlMatcher.swift b/Sources/BrowserServicesKit/Autofill/Matchers/AutofillUrlMatcher.swift index 5bce667ef..33c9479de 100644 --- a/Sources/BrowserServicesKit/Autofill/Matchers/AutofillUrlMatcher.swift +++ b/Sources/BrowserServicesKit/Autofill/Matchers/AutofillUrlMatcher.swift @@ -1,6 +1,5 @@ // // AutofillUrlMatcher.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/OverlayAutofillUserScript.swift b/Sources/BrowserServicesKit/Autofill/OverlayAutofillUserScript.swift index e6c36c54c..43fbc780a 100644 --- a/Sources/BrowserServicesKit/Autofill/OverlayAutofillUserScript.swift +++ b/Sources/BrowserServicesKit/Autofill/OverlayAutofillUserScript.swift @@ -1,6 +1,5 @@ // // OverlayAutofillUserScript.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/Sort/AutofillUrlSort.swift b/Sources/BrowserServicesKit/Autofill/Sort/AutofillUrlSort.swift index 5f511953e..7f371ade9 100644 --- a/Sources/BrowserServicesKit/Autofill/Sort/AutofillUrlSort.swift +++ b/Sources/BrowserServicesKit/Autofill/Sort/AutofillUrlSort.swift @@ -1,6 +1,5 @@ // // AutofillUrlSort.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Autofill/WebsiteAutofillUserScript.swift b/Sources/BrowserServicesKit/Autofill/WebsiteAutofillUserScript.swift index 94860ad9e..7d89624be 100644 --- a/Sources/BrowserServicesKit/Autofill/WebsiteAutofillUserScript.swift +++ b/Sources/BrowserServicesKit/Autofill/WebsiteAutofillUserScript.swift @@ -1,6 +1,5 @@ // // WebsiteAutofillUserScript.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -56,7 +55,7 @@ public class WebsiteAutofillUserScript: AutofillUserScript { case closeAutofillParent case getSelectedCredentials case showAutofillParent - } + } public override func messageHandlerFor(_ messageName: String) -> MessageHandler? { guard let websiteAutofillMessageName = WebsiteAutofillMessageName(rawValue: messageName) else { @@ -92,7 +91,7 @@ public class WebsiteAutofillUserScript: AutofillUserScript { } // Sets the last message host, so we can check when it messages back lastOpenHost = hostProvider.hostForMessage(message) - + currentOverlayTab.websiteAutofillUserScript(self, willDisplayOverlayAtClick: clickPoint, serializedInputContext: serializedInputContext, diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounter.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounter.swift index 57e60ffd8..8fa55c934 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounter.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounter.swift @@ -1,6 +1,5 @@ // // AdClickAttributionCounter.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,7 +21,7 @@ import Persistence /// This class aggregates detected Ad Attributions on a websites and stores that count over a certain time interval. public class AdClickAttributionCounter { - + public enum Constant { public static let pageLoadsCountKey = "AdClickAttributionCounter_Count" @@ -30,11 +29,11 @@ public class AdClickAttributionCounter { public static let sendInterval: Double = 60 * 60 * 24 // 24 hours } - + private let store: KeyValueStoring private let onSend: (_ count: Int) -> Void private let sendInterval: Double - + public init(store: KeyValueStoring = AdClickAttributionCounterStore(), sendInterval: Double = Constant.sendInterval, onSendRequest: @escaping (_ count: Int) -> Void) { @@ -42,12 +41,12 @@ public class AdClickAttributionCounter { self.onSend = onSendRequest self.sendInterval = sendInterval } - + public func onAttributionActive(currentTime: Date = Date()) { save(pageLoadsCount: pageLoadsCount + 1) sendEventsIfNeeded(currentTime: currentTime) } - + public func sendEventsIfNeeded(currentTime: Date = Date()) { guard let lastSendAt else { save(lastSendAt: currentTime) diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounterStore.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounterStore.swift index b1bbe5ef4..e377ab94b 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounterStore.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionCounterStore.swift @@ -1,6 +1,5 @@ // -// AdClickAttributionCounter.swift -// DuckDuckGo +// AdClickAttributionCounterStore.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionDetection.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionDetection.swift index 648f6266e..6b56daabc 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionDetection.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionDetection.swift @@ -1,6 +1,5 @@ // // AdClickAttributionDetection.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,24 +20,24 @@ import Foundation import Common public protocol AdClickAttributionDetectionDelegate: AnyObject { - + func attributionDetection(_ detection: AdClickAttributionDetection, didDetectVendor vendorHost: String) } public class AdClickAttributionDetection { - + enum State { case idle // Waiting for detection to start case detecting(String?) // Detection is in progress, parameter is vendor obtained from domain detection mechanism } - + private let attributionFeature: AdClickAttributing - + private var state = State.idle - + private let tld: TLD private let getLog: () -> OSLog private var log: OSLog { @@ -46,9 +45,9 @@ public class AdClickAttributionDetection { } private let eventReporting: EventMapping? private let errorReporting: EventMapping? - + public weak var delegate: AdClickAttributionDetectionDelegate? - + public init(feature: AdClickAttributing, tld: TLD, eventReporting: EventMapping? = nil, @@ -60,21 +59,21 @@ public class AdClickAttributionDetection { self.errorReporting = errorReporting self.getLog = log } - + // MARK: - Public API public func onStartNavigation(url: URL?) { guard attributionFeature.isEnabled, let url = url, attributionFeature.isMatchingAttributionFormat(url) else { return } - + os_log(.debug, log: log, "Starting Attribution detection for %{private}s", url.host ?? "nil") - + var vendorDomain: String? if attributionFeature.isDomainDetectionEnabled, let adDomainParameterName = attributionFeature.attributionDomainParameterName(for: url), let domainFromParameter = url.getParameter(named: adDomainParameterName), !domainFromParameter.isEmpty { - + if let eTLDp1 = tld.eTLDplus1(domainFromParameter)?.lowercased() { vendorDomain = eTLDp1 delegate?.attributionDetection(self, didDetectVendor: eTLDp1) @@ -82,45 +81,45 @@ public class AdClickAttributionDetection { errorReporting?.fire(.adAttributionDetectionInvalidDomainInParameter) } } - + if attributionFeature.isHeuristicDetectionEnabled { state = .detecting(vendorDomain) } else { fireDetectionPixel(serpBasedDomain: vendorDomain, heuristicBasedDomain: nil) } } - + public func on2XXResponse(url: URL?) { guard let host = url?.host else { return } - + heuristicDetection(forHost: host) } - + public func onDidFailNavigation() { os_log(.debug, log: log, "Attribution detection has been cancelled") state = .idle } - + public func onDidFinishNavigation(url: URL?) { guard let host = url?.host else { return } - + heuristicDetection(forHost: host) } - + // MARK: - Private functionality - + private func heuristicDetection(forHost host: String) { guard case .detecting(let domainFromParameter) = state else { return } - + os_log(.debug, log: log, "Attribution detected for %{private}s", host) state = .idle - + let detectedDomain = tld.eTLDplus1(host)?.lowercased() if domainFromParameter == nil { if let vendorDomain = detectedDomain { @@ -129,14 +128,14 @@ public class AdClickAttributionDetection { errorReporting?.fire(.adAttributionDetectionHeuristicsDidNotMatchDomain) } } - + fireDetectionPixel(serpBasedDomain: domainFromParameter, heuristicBasedDomain: detectedDomain) } - + private func fireDetectionPixel(serpBasedDomain: String?, heuristicBasedDomain: String?) { - + let domainDetection: String - + if serpBasedDomain != nil && serpBasedDomain == heuristicBasedDomain { domainDetection = "matched" } else if serpBasedDomain != nil && !attributionFeature.isHeuristicDetectionEnabled { @@ -148,7 +147,7 @@ public class AdClickAttributionDetection { } else { domainDetection = "none" } - + let parameters = [AdClickAttributionEvents.Parameters.domainDetection: domainDetection, AdClickAttributionEvents.Parameters.domainDetectionEnabled: attributionFeature.isDomainDetectionEnabled ? "1" : "0", AdClickAttributionEvents.Parameters.heuristicDetectionEnabled: attributionFeature.isHeuristicDetectionEnabled ? "1" : "0"] diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionEvents.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionEvents.swift index 94c33bffb..46f24a87c 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionEvents.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionEvents.swift @@ -1,6 +1,5 @@ // -// AdClickAttributionDebugEvents.swift -// DuckDuckGo +// AdClickAttributionEvents.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,21 +19,21 @@ import Foundation public enum AdClickAttributionEvents { - + public enum Parameters { public static let domainDetection = "domainDetection" public static let heuristicDetectionEnabled = "heuristicDetectionEnabled" public static let domainDetectionEnabled = "domainDetectionEnabled" public static let count = "count" } - + case adAttributionDetected case adAttributionActive case adAttributionPageLoads } public enum AdClickAttributionDebugEvents { - + case adAttributionGlobalAttributedRulesDoNotExist case adAttributionCompilationFailedForAttributedRulesList case adAttributionLogicRequestingAttributionTimedOut @@ -45,5 +44,5 @@ public enum AdClickAttributionDebugEvents { case adAttributionLogicWrongVendorOnFailedCompilation case adAttributionDetectionInvalidDomainInParameter case adAttributionDetectionHeuristicsDidNotMatchDomain - + } diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionLogic.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionLogic.swift index 1c0d33eab..0eb51a6a0 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionLogic.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionLogic.swift @@ -1,6 +1,5 @@ // // AdClickAttributionLogic.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,15 +21,14 @@ import ContentBlocking import Common public protocol AdClickAttributionLogicDelegate: AnyObject { - + func attributionLogic(_ logic: AdClickAttributionLogic, didRequestRuleApplication rules: ContentBlockerRulesManager.Rules?, forVendor vendor: String?) } -// swiftlint:disable:next type_body_length public class AdClickAttributionLogic { - + public enum State { case noAttribution @@ -42,19 +40,19 @@ public class AdClickAttributionLogic { return false } } - + public struct SessionInfo { // Start of the attribution public let attributionStartedAt: Date // Present when we leave webpage associated with the attribution public let leftAttributionContextAt: Date? - + init(start: Date = Date(), leftContextAt: Date? = nil) { attributionStartedAt = start leftAttributionContextAt = leftContextAt } } - + private let featureConfig: AdClickAttributing private let rulesProvider: AdClickAttributionRulesProviding private let tld: TLD @@ -71,11 +69,11 @@ public class AdClickAttributionLogic { public private(set) var state = State.noAttribution private var registerFirstActivity = false - + private var attributionTimeout: DispatchWorkItem? - + public weak var delegate: AdClickAttributionLogicDelegate? - + public init(featureConfig: AdClickAttributing, rulesProvider: AdClickAttributionRulesProviding, tld: TLD, @@ -92,12 +90,12 @@ public class AdClickAttributionLogic { public func applyInheritedAttribution(state: State?) { guard let state = state else { return } - + if case .noAttribution = self.state {} else { errorReporting?.fire(.adAttributionLogicUnexpectedStateOnInheritedAttribution) assert(NSClassFromString("XCTest") != nil /* allow when running tests */, "unexpected initial attribution state \(self.state)") } - + switch state { case .noAttribution: self.state = state @@ -111,7 +109,7 @@ public class AdClickAttributionLogic { } } } - + public func onRulesChanged(latestRules: [ContentBlockerRulesManager.Rules]) { switch state { case .noAttribution: @@ -122,18 +120,18 @@ public class AdClickAttributionLogic { requestAttribution(forVendor: vendor) } } - + public func reapplyCurrentRules() { applyRules() } - + public func onBackForwardNavigation(mainFrameURL: URL?) { guard case .activeAttribution(let vendor, let session, let rules) = state, let host = mainFrameURL?.host, let currentETLDp1 = tld.eTLDplus1(host) else { return } - + if vendor == currentETLDp1 { if session.leftAttributionContextAt != nil { state = .activeAttribution(vendor: vendor, @@ -170,7 +168,7 @@ public class AdClickAttributionLogic { completion() } } - + @MainActor public func onProvisionalNavigation() async { await withCheckedContinuation { continuation in @@ -188,13 +186,13 @@ public class AdClickAttributionLogic { if tld.eTLDplus1(host) == vendor { counter.onAttributionActive() } - + if currentTime.timeIntervalSince(session.attributionStartedAt) >= featureConfig.totalExpiration { os_log(.debug, log: log, "Attribution has expired - total expiration") disableAttribution() return } - + if let leftAttributionContextAt = session.leftAttributionContextAt { if currentTime.timeIntervalSince(leftAttributionContextAt) >= featureConfig.navigationExpiration { os_log(.debug, log: log, "Attribution has expired - navigational expiration") @@ -213,11 +211,11 @@ public class AdClickAttributionLogic { rules: rules) } } - + public func onRequestDetected(request: DetectedRequest) { guard registerFirstActivity, BlockingState.allowed(reason: .adClickAttribution) == request.state else { return } - + eventReporting?.fire(.adAttributionActive) registerFirstActivity = false } @@ -227,7 +225,7 @@ public class AdClickAttributionLogic { state = .noAttribution applyRules() } - + private func onAttributedRulesCompiled(forVendor vendor: String, _ rules: ContentBlockerRulesManager.Rules) { guard case .preparingAttribution(let expectedVendor, let session, let completionBlocks) = state else { os_log(.error, log: log, "Attributed Rules received unexpectedly") @@ -273,13 +271,13 @@ public class AdClickAttributionLogic { delegate?.attributionLogic(self, didRequestRuleApplication: rulesProvider.globalAttributionRules, forVendor: nil) } } - + /// Request attribution when we detect it is needed private func requestAttribution(forVendor vendorHost: String, attributionStartedAt: Date = Date(), completionBlocks: [() -> Void] = []) { state = .preparingAttribution(vendor: vendorHost, session: SessionInfo(start: attributionStartedAt), completionBlocks: completionBlocks) - + scheduleTimeout(forVendor: vendorHost) rulesProvider.requestAttribution(forVendor: vendorHost) { [weak self] rules in self?.cancelTimeout() @@ -290,7 +288,7 @@ public class AdClickAttributionLogic { } } } - + private func scheduleTimeout(forVendor vendor: String) { let timeoutWorkItem = DispatchWorkItem { [weak self] in self?.onAttributedRulesCompilationFailed(forVendor: vendor) @@ -303,12 +301,12 @@ public class AdClickAttributionLogic { DispatchQueue.main.asyncAfter(deadline: .now() + 4.0, execute: timeoutWorkItem) } - + private func cancelTimeout() { attributionTimeout?.cancel() attributionTimeout = nil } - + /// Respond to new requests for attribution private func onAttributionRequested(forVendor vendorHost: String) { @@ -332,16 +330,16 @@ public class AdClickAttributionLogic { } } } - + } extension AdClickAttributionLogic: AdClickAttributionDetectionDelegate { - + public func attributionDetection(_ detection: AdClickAttributionDetection, didDetectVendor vendorHost: String) { os_log(.debug, log: log, "Detected attribution requests for %{private}s", vendorHost) onAttributionRequested(forVendor: vendorHost) registerFirstActivity = true } - + } diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesMutator.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesMutator.swift index 622997349..dac883060 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesMutator.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesMutator.swift @@ -1,6 +1,5 @@ // // AdClickAttributionRulesMutator.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,29 +20,29 @@ import TrackerRadarKit import Foundation public class AdClickAttributionRulesMutator { - + var trackerData: TrackerData var config: AdClickAttributing - + public init(trackerData: TrackerData, config: AdClickAttributing) { self.trackerData = trackerData self.config = config } - + public func addException(vendorDomain: String) -> TrackerData { guard config.isEnabled else { return trackerData } - + let attributedMatching = KnownTracker.Rule.Matching(domains: [vendorDomain.droppingWwwPrefix()], types: nil) - + var attributedTrackers = [TrackerData.TrackerDomain: KnownTracker]() - + for (entity, tracker) in trackerData.trackers { let allowlistEntries = config.allowlist.filter { $0.entity == entity } guard !allowlistEntries.isEmpty else { attributedTrackers[entity] = tracker continue } - + var updatedRules = tracker.rules ?? [] for allowlistEntry in allowlistEntries { updatedRules.insert(KnownTracker.Rule(rule: normalizeRule(allowlistEntry.host), @@ -53,7 +52,7 @@ public class AdClickAttributionRulesMutator { exceptions: attributedMatching), at: 0) } - + attributedTrackers[entity] = KnownTracker(domain: tracker.domain, defaultAction: tracker.defaultAction, owner: tracker.owner, @@ -62,13 +61,13 @@ public class AdClickAttributionRulesMutator { categories: tracker.categories, rules: updatedRules) } - + return TrackerData(trackers: attributedTrackers, entities: trackerData.entities, domains: trackerData.domains, cnames: trackerData.cnames) } - + private func normalizeRule(_ rule: String) -> String { var rule = rule.hasSuffix("/") ? rule : rule + "/" let index = rule.firstIndex(of: "/") diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesProvider.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesProvider.swift index 6d4baa4ba..11a03971e 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesProvider.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesProvider.swift @@ -1,6 +1,5 @@ // -// AdClickAttributionRulesSource.swift -// DuckDuckGo +// AdClickAttributionRulesProvider.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,40 +21,40 @@ import TrackerRadarKit import Common public protocol AdClickAttributionRulesProviding { - + var globalAttributionRules: ContentBlockerRulesManager.Rules? { get } func requestAttribution(forVendor vendor: String, completion: @escaping (ContentBlockerRulesManager.Rules?) -> Void) } public class AdClickAttributionRulesProvider: AdClickAttributionRulesProviding { - + public enum Constants { public static let attributedTempRuleListName = "TemporaryAttributed" } - + struct AttributionTask: Equatable { - + let sourceRulesIdentifier: String let vendor: String let completion: (ContentBlockerRulesManager.Rules?) -> Void - + static func == (lhs: AdClickAttributionRulesProvider.AttributionTask, rhs: AdClickAttributionRulesProvider.AttributionTask) -> Bool { return lhs.vendor == rhs.vendor && lhs.sourceRulesIdentifier == rhs.sourceRulesIdentifier } } - + private let attributionConfig: AdClickAttributing private let compiledRulesSource: CompiledRuleListsSource private let exceptionsSource: ContentBlockerRulesExceptionsSource private let errorReporting: EventMapping? private let compilationErrorReporting: EventMapping? - + private let lock = NSLock() private var tasks = [AttributionTask]() private var isProcessingTask = false - + private let workQueue = DispatchQueue(label: "AdAttribution compilation queue", qos: .userInitiated) private let getLog: () -> OSLog @@ -76,46 +75,46 @@ public class AdClickAttributionRulesProvider: AdClickAttributionRulesProviding { self.compilationErrorReporting = compilationErrorReporting self.getLog = log } - + public var globalAttributionRules: ContentBlockerRulesManager.Rules? { return compiledRulesSource.currentAttributionRules } - + public func requestAttribution(forVendor vendor: String, completion: @escaping (ContentBlockerRulesManager.Rules?) -> Void) { lock.lock() defer { lock.unlock() } - + os_log(.debug, log: log, "Preparing attribution rules for vendor %{private}s", vendor) - + guard let globalAttributionRules = compiledRulesSource.currentAttributionRules else { errorReporting?.fire(.adAttributionGlobalAttributedRulesDoNotExist) os_log(.error, log: log, "Global attribution list does not exist") completion(nil) return } - + let task = AttributionTask(sourceRulesIdentifier: globalAttributionRules.identifier.stringValue, vendor: vendor, completion: completion) tasks.append(task) - + workQueue.async { self.popTaskAndExecute() } } - + private func popTaskAndExecute() { lock.lock() defer { lock.unlock() } - + guard !isProcessingTask, !tasks.isEmpty else { return } - + let task = tasks.removeFirst() isProcessingTask = true prepareRules(for: task) } - + private func prepareRules(for task: AttributionTask) { guard let sourceRules = compiledRulesSource.currentAttributionRules else { isProcessingTask = false @@ -124,13 +123,13 @@ public class AdClickAttributionRulesProvider: AdClickAttributionRulesProviding { } return } - + os_log(.debug, log: log, "Compiling attribution rules for vendor %{private}s", task.vendor) - + let mutator = AdClickAttributionRulesMutator(trackerData: sourceRules.trackerData, config: attributionConfig) let attributedRules = mutator.addException(vendorDomain: task.vendor) - + let attributedDataSet = TrackerDataManager.DataSet(tds: attributedRules, etag: sourceRules.etag) let attributedRulesList = ContentBlockerRulesList(name: Constants.attributedTempRuleListName, @@ -142,48 +141,48 @@ public class AdClickAttributionRulesProvider: AdClickAttributionRulesProviding { exceptionsSource: exceptionsSource, errorReporting: compilationErrorReporting, log: log) - + let compilationTask = ContentBlockerRulesManager.CompilationTask(workQueue: workQueue, rulesList: attributedRulesList, sourceManager: sourceManager) - + compilationTask.start(ignoreCache: true) { compilationTask, _ in self.onTaskCompleted(attributionTask: task, compilationTask: compilationTask) } } - + private func onTaskCompleted(attributionTask: AttributionTask, compilationTask: ContentBlockerRulesManager.CompilationTask) { lock.lock() defer { lock.unlock() } - + isProcessingTask = false - + // Take all tasks with same parameters (rules & vendor) and report completion // This is optimization: in case multiple tabs request same attribution at the same time, we will respond quickly. var matchingTasks = tasks.filter { $0 == attributionTask } tasks.removeAll(where: { $0 == attributionTask }) matchingTasks.append(attributionTask) - + os_log(.debug, log: log, "Returning attribution rules for vendor %{private}s to %{public}d caller(s)", attributionTask.vendor, matchingTasks.count) - + var rules: ContentBlockerRulesManager.Rules? if let result = compilationTask.result { rules = .init(compilationResult: result) } - + DispatchQueue.main.async { for task in matchingTasks { task.completion(rules) } } - + if rules == nil { errorReporting?.fire(.adAttributionCompilationFailedForAttributedRulesList) } - + workQueue.async { self.popTaskAndExecute() } diff --git a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesSplitter.swift b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesSplitter.swift index 9f456fcf8..3e2b2ffd6 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesSplitter.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/AdClickAttribution/AdClickAttributionRulesSplitter.swift @@ -1,6 +1,5 @@ // // AdClickAttributionRulesSplitter.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,17 +19,17 @@ import TrackerRadarKit public struct AdClickAttributionRulesSplitter { - + public enum Constants { public static let attributionRuleListNamePrefix = "Attribution_" public static let attributionRuleListETagPrefix = "A_" } - + private let rulesList: ContentBlockerRulesList private let allowlistedTrackerNames: [String] - + // MARK: - API - + /// - Parameters: /// - rulesList: Rules list to be split /// - allowlistedTrackerNames: Tracker names to split by @@ -38,33 +37,33 @@ public struct AdClickAttributionRulesSplitter { self.rulesList = rulesList self.allowlistedTrackerNames = allowlistedTrackerNames } - + static public func blockingAttributionRuleListName(forListNamed name: String) -> String { return "\(Constants.attributionRuleListNamePrefix)\(name)" } - + /// - Returns: Split rules only if the input rulesList contains given tracker names to split by public func split() -> (ContentBlockerRulesList, ContentBlockerRulesList)? { guard !allowlistedTrackerNames.isEmpty, rulesList.contains(allowlistedTrackerNames) else { return nil } - + let splitTDS = rulesList.trackerData != nil ? split(tds: rulesList.trackerData!) : nil return (ContentBlockerRulesList(name: rulesList.name, trackerData: splitTDS?.0, fallbackTrackerData: split(tds: rulesList.fallbackTrackerData).0), ContentBlockerRulesList(name: Self.blockingAttributionRuleListName(forListNamed: rulesList.name), trackerData: splitTDS?.1, fallbackTrackerData: split(tds: rulesList.fallbackTrackerData).1)) } - + private func split(tds: TrackerDataManager.DataSet) -> (TrackerDataManager.DataSet, TrackerDataManager.DataSet) { let regularTrackerData = makeRegularTrackerData(from: tds.tds) let attributionTrackerData = makeTrackerDataForAttribution(from: tds.tds) - + // Tweak ETag to prevent caching issues between changed lists return ((tds: regularTrackerData, etag: Constants.attributionRuleListETagPrefix + tds.etag), (tds: attributionTrackerData, etag: Constants.attributionRuleListETagPrefix + tds.etag)) } - + private func makeRegularTrackerData(from trackerData: TrackerData) -> TrackerData { let trackers = trackerData.trackers.filter { !allowlistedTrackerNames.contains($0.key) } return TrackerData(trackers: trackers, @@ -72,18 +71,18 @@ public struct AdClickAttributionRulesSplitter { domains: trackerData.domains, cnames: trackerData.cnames) } - + private func makeTrackerDataForAttribution(from trackerData: TrackerData) -> TrackerData { let allowlistedTrackers = trackerData.trackers.filter { allowlistedTrackerNames.contains($0.key) } let allowlistedTrackersOwners = allowlistedTrackers.values.compactMap { $0.owner?.name } - + var entities = [String: Entity]() for ownerName in allowlistedTrackersOwners { if let entity = trackerData.entities[ownerName] { entities[ownerName] = entity } } - + var domains = [String: String]() for entity in entities { for domain in entity.value.domains ?? [] { @@ -92,21 +91,21 @@ public struct AdClickAttributionRulesSplitter { } return TrackerData(trackers: allowlistedTrackers, entities: entities, domains: domains, cnames: nil) } - + } private extension ContentBlockerRulesList { - + func contains(_ trackerNames: [String]) -> Bool { trackerData?.tds.contains(trackerNames) ?? false || fallbackTrackerData.tds.contains(trackerNames) } - + } private extension TrackerData { - + func contains(_ trackerNames: [String]) -> Bool { !Set(trackers.keys).isDisjoint(with: Set(trackerNames)) } - + } diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerDebugEvents.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerDebugEvents.swift index 8972a9bd9..4f7b174ee 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerDebugEvents.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerDebugEvents.swift @@ -1,6 +1,5 @@ // // ContentBlockerDebugEvents.swift -// DuckDuckGo // // Copyright © 2019 DuckDuckGo. All rights reserved. // @@ -25,17 +24,17 @@ public enum ContentBlockerDebugEvents { static let etag = "etag" static let errorDescription = "error_desc" } - + public enum Component: String, CustomStringConvertible, CaseIterable { - + public var description: String { rawValue } - + case tds case allowlist case tempUnprotected case localUnprotected case fallbackTds - + } case trackerDataParseFailed @@ -44,7 +43,7 @@ public enum ContentBlockerDebugEvents { case privacyConfigurationReloadFailed case privacyConfigurationParseFailed case privacyConfigurationCouldNotBeLoaded - + case contentBlockingCompilationFailed(listName: String, component: Component) case contentBlockingCompilationTime diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesIdentifier.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesIdentifier.swift index 1c5757e79..245fcbac2 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesIdentifier.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesIdentifier.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesIdentifier.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,68 +19,68 @@ import Foundation public class ContentBlockerRulesIdentifier: Equatable, Codable { - + let name: String let tdsEtag: String let tempListId: String let allowListId: String let unprotectedSitesHash: String - + public var stringValue: String { return name + tdsEtag + tempListId + allowListId + unprotectedSitesHash } - + public struct Difference: OptionSet { public let rawValue: Int - + public init(rawValue: Int) { self.rawValue = rawValue } - + public static let tdsEtag = Difference(rawValue: 1 << 0) public static let tempListId = Difference(rawValue: 1 << 1) public static let allowListId = Difference(rawValue: 1 << 2) public static let unprotectedSites = Difference(rawValue: 1 << 3) - + public static let all: Difference = [.tdsEtag, .tempListId, .allowListId, .unprotectedSites] } - + private class func normalize(identifier: String?) -> String { // Ensure identifier is in double quotes guard var identifier = identifier else { return "\"\"" } - + if !identifier.hasSuffix("\"") { identifier += "\"" } - + if !identifier.hasPrefix("\"") || identifier.count == 1 { identifier = "\"" + identifier } - + return identifier } - + public class func hash(domains: [String]?) -> String { guard let domains = domains, !domains.isEmpty else { return "" } - + return domains.joined().sha1 } - + public init(name: String, tdsEtag: String, tempListId: String?, allowListId: String?, unprotectedSitesHash: String?) { - + self.name = Self.normalize(identifier: name) self.tdsEtag = Self.normalize(identifier: tdsEtag) self.tempListId = Self.normalize(identifier: tempListId) self.allowListId = Self.normalize(identifier: allowListId) self.unprotectedSitesHash = Self.normalize(identifier: unprotectedSitesHash) } - + public func compare(with id: ContentBlockerRulesIdentifier) -> Difference { - + var result = Difference() if tdsEtag != id.tdsEtag { result.insert(.tdsEtag) @@ -95,7 +94,7 @@ public class ContentBlockerRulesIdentifier: Equatable, Codable { if unprotectedSitesHash != id.unprotectedSitesHash { result.insert(.unprotectedSites) } - + return result } diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesManager.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesManager.swift index 52c02d635..b980a60d9 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesManager.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesManager.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesManager.swift -// DuckDuckGo // // Copyright © 2020 DuckDuckGo. All rights reserved. // @@ -23,16 +22,14 @@ import TrackerRadarKit import Combine import Common -// swiftlint:disable file_length type_body_length - public protocol CompiledRuleListsSource { - + // Represent set of all latest rules that has been compiled var currentRules: [ContentBlockerRulesManager.Rules] { get } - + // Set of core rules: TDS minus Ad Attribution rules var currentMainRules: ContentBlockerRulesManager.Rules? { get } - + // Rules related to Ad Attribution feature, extracted from TDS set. var currentAttributionRules: ContentBlockerRulesManager.Rules? { get } } @@ -80,7 +77,7 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { self.etag = etag self.identifier = identifier } - + internal init(compilationResult: (compiledRulesList: WKContentRuleList, model: ContentBlockerRulesSourceModel)) { let surrogateTDS = ContentBlockerRulesManager.extractSurrogates(from: compilationResult.model.tds) let encodedData = try? JSONEncoder().encode(surrogateTDS) @@ -131,7 +128,7 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { private var compilationStartTime: TimeInterval? private let workQueue = DispatchQueue(label: "ContentBlockerManagerQueue", qos: .userInitiated) - + private let lastCompiledRulesStore: LastCompiledRulesStore? public init(rulesSource: ContentBlockerRulesListsSource, @@ -159,7 +156,7 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { } } } - + /** Variables protected by this lock: - state @@ -181,11 +178,11 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { lock.unlock() } } - + public var currentMainRules: Rules? { currentRules.first(where: { $0.name == DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName }) } - + public var currentAttributionRules: Rules? { currentRules.first(where: { let tdsName = DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName @@ -295,7 +292,7 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { } } } - + private func startCompilationProcess() { prepareSourceManagers() @@ -380,10 +377,10 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { lock.unlock() } - + private func applyRules(_ rules: [Rules], changes: [String: ContentBlockerRulesIdentifier.Difference] = [:]) { lock.lock() - + _currentRules = rules let completionTokens: [CompletionToken] @@ -408,9 +405,9 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { assertionFailure("Unexpected state") completionTokens = [] } - + lock.unlock() - + let currentIdentifiers: [String] = rules.map { $0.identifier.stringValue } updatesSubject.send(UpdateEvent(rules: rules, changes: changes, completionTokens: completionTokens)) @@ -449,5 +446,3 @@ public class ContentBlockerRulesManager: CompiledRuleListsSource { } } - -// swiftlint:enable file_length type_body_length diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSource.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSource.swift index e3c9a37d0..a981f7ee4 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSource.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSource.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesSource.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -56,7 +55,7 @@ public class ContentBlockerRulesList { }() public let name: String - + public init(name: String, trackerData: @escaping @autoclosure () -> TrackerDataManager.DataSet?, fallbackTrackerData: @escaping @autoclosure () -> TrackerDataManager.DataSet) { @@ -67,7 +66,7 @@ public class ContentBlockerRulesList { } open class DefaultContentBlockerRulesListsSource: ContentBlockerRulesListsSource { - + public struct Constants { public static let trackerDataSetRulesListName = "TrackerDataSet" } @@ -77,7 +76,7 @@ open class DefaultContentBlockerRulesListsSource: ContentBlockerRulesListsSource public init(trackerDataManager: TrackerDataManager) { self.trackerDataManager = trackerDataManager } - + open var contentBlockerRulesLists: [ContentBlockerRulesList] { return [ContentBlockerRulesList(name: Constants.trackerDataSetRulesListName, trackerData: self.trackerDataManager.fetchedData, diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSourceManager.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSourceManager.swift index b67712c6f..16f8305e0 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSourceManager.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockerRulesSourceManager.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesSourceManager.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -73,7 +72,7 @@ public class ContentBlockerRulesSourceModel: ContentBlockerRulesSourceIdentifier Manages sources that are used to compile Content Blocking Rules, handles possible broken state by filtering out sources that are potentially corrupted. */ public class ContentBlockerRulesSourceManager { - + public class RulesSourceBreakageInfo { public internal(set) var tdsIdentifier: String? @@ -177,7 +176,7 @@ public class ContentBlockerRulesSourceManager { return result } - + /** Process information about last failed compilation in order to update `brokenSources` state. */ @@ -199,7 +198,7 @@ public class ContentBlockerRulesSourceManager { private func compilationFailed(for input: ContentBlockerRulesSourceIdentifiers, with error: Error, brokenSources: RulesSourceBreakageInfo) { - + if input.tdsIdentifier != rulesList.fallbackTrackerData.etag { os_log(.debug, log: log, "Falling back to embedded TDS") // We failed compilation for non-embedded TDS, marking it as broken. @@ -261,5 +260,5 @@ public class ContentBlockerRulesSourceManager { fatalError("Could not compile embedded rules list") } } - + } diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesCompilationTask.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesCompilationTask.swift index 638f7be9f..74cf980ae 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesCompilationTask.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesCompilationTask.swift @@ -1,6 +1,5 @@ // // ContentBlockingRulesCompilationTask.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLastCompiledRulesLookupTask.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLastCompiledRulesLookupTask.swift index a7a06996a..021e058ed 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLastCompiledRulesLookupTask.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLastCompiledRulesLookupTask.swift @@ -1,6 +1,5 @@ // // ContentBlockingRulesLastCompiledRulesLookupTask.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,16 +21,16 @@ import WebKit import TrackerRadarKit extension ContentBlockerRulesManager { - + final class LastCompiledRulesLookupTask { - + struct CachedRulesList { let name: String let rulesList: WKContentRuleList let tds: TrackerData let rulesIdentifier: ContentBlockerRulesIdentifier } - + private let sourceRules: [ContentBlockerRulesList] private let lastCompiledRules: [LastCompiledRules] diff --git a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLookupTask.swift b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLookupTask.swift index 0f1396267..974c984ac 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLookupTask.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/ContentBlockingRulesLookupTask.swift @@ -1,6 +1,5 @@ // // ContentBlockingRulesLookupTask.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/ContentBlocking/DomainsProtectionStore.swift b/Sources/BrowserServicesKit/ContentBlocking/DomainsProtectionStore.swift index fd0bfc13f..db15bf545 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/DomainsProtectionStore.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/DomainsProtectionStore.swift @@ -1,6 +1,5 @@ // // DomainsProtectionStore.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/ContentBlocking/FailedCompilationsStore.swift b/Sources/BrowserServicesKit/ContentBlocking/FailedCompilationsStore.swift index f41a2a932..ea97f1385 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/FailedCompilationsStore.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/FailedCompilationsStore.swift @@ -1,6 +1,5 @@ // // FailedCompilationsStore.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/ContentBlocking/LastCompiledRulesStore.swift b/Sources/BrowserServicesKit/ContentBlocking/LastCompiledRulesStore.swift index 9d180b340..6c576f6dc 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/LastCompiledRulesStore.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/LastCompiledRulesStore.swift @@ -1,6 +1,5 @@ // // LastCompiledRulesStore.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,17 +20,17 @@ import Foundation import TrackerRadarKit public protocol LastCompiledRules { - + var name: String { get } var trackerData: TrackerData { get } var etag: String { get } var identifier: ContentBlockerRulesIdentifier { get } - + } public protocol LastCompiledRulesStore { - + var rules: [LastCompiledRules] { get } func update(with contentBlockerRules: [ContentBlockerRulesManager.Rules]) - + } diff --git a/Sources/BrowserServicesKit/ContentBlocking/TrackerDataManager.swift b/Sources/BrowserServicesKit/ContentBlocking/TrackerDataManager.swift index 45c6aaaa5..b98719a8d 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/TrackerDataManager.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/TrackerDataManager.swift @@ -1,6 +1,5 @@ // // TrackerDataManager.swift -// DuckDuckGo // // Copyright © 2019 DuckDuckGo. All rights reserved. // @@ -29,17 +28,17 @@ public protocol TrackerDataProvider { } public class TrackerDataManager { - + public enum ReloadResult: Equatable { case embedded case embeddedFallback case downloaded } - + public typealias DataSet = (tds: TrackerData, etag: String) - + private let lock = NSLock() - + private var _fetchedData: DataSet? private(set) public var fetchedData: DataSet? { get { @@ -54,7 +53,7 @@ public class TrackerDataManager { lock.unlock() } } - + private var _embeddedData: DataSet! private(set) public var embeddedData: DataSet { get { @@ -78,7 +77,7 @@ public class TrackerDataManager { lock.unlock() } } - + public var trackerData: TrackerData { if let data = fetchedData { return data.tds @@ -101,13 +100,13 @@ public class TrackerDataManager { @discardableResult public func reload(etag: String?, data: Data?) -> ReloadResult { - + let result: ReloadResult - + if let etag = etag, let data = data { result = .downloaded - + do { // This might fail if the downloaded data is corrupt or format has changed unexpectedly let data = try JSONDecoder().decode(TrackerData.self, from: data) @@ -121,7 +120,7 @@ public class TrackerDataManager { fetchedData = nil result = .embedded } - + return result } } diff --git a/Sources/BrowserServicesKit/ContentBlocking/TrackerDataQueryExtension.swift b/Sources/BrowserServicesKit/ContentBlocking/TrackerDataQueryExtension.swift index 00b211c48..4959160d8 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/TrackerDataQueryExtension.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/TrackerDataQueryExtension.swift @@ -1,6 +1,5 @@ // // TrackerDataQueryExtension.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,11 +20,11 @@ import Foundation import TrackerRadarKit extension TrackerData { - + public func findEntity(byName name: String) -> Entity? { return entities[name] } - + public func findEntity(forHost host: String) -> Entity? { for host in variations(of: host) { if let entityName = domains[host] { @@ -45,23 +44,23 @@ extension TrackerData { } return domains } - + public func findTracker(forUrl url: String) -> KnownTracker? { guard let host = URL(string: url)?.host else { return nil } - + let variations = variations(of: host) for host in variations { if let tracker = trackers[host] { return tracker } } - + return nil } - + public func findTrackerByCname(forUrl url: String) -> KnownTracker? { guard let host = URL(string: url)?.host else { return nil } - + let variations = variations(of: host) for host in variations { if let cname = cnames?[host] { @@ -70,7 +69,7 @@ extension TrackerData { return tracker } } - + return nil } } diff --git a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/ContentBlockerRulesUserScript.swift b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/ContentBlockerRulesUserScript.swift index b7d4703d5..108ca597d 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/ContentBlockerRulesUserScript.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/ContentBlockerRulesUserScript.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesUserScript.swift -// DuckDuckGo // // Copyright © 2020 DuckDuckGo. All rights reserved. // @@ -56,7 +55,7 @@ public class DefaultContentBlockerUserScriptConfig: ContentBlockerUserScriptConf ctlTrackerData: TrackerData?, tld: TLD, trackerDataManager: TrackerDataManager? = nil) { - + if trackerData == nil { // Fallback to embedded self.trackerData = trackerDataManager?.trackerData @@ -74,7 +73,7 @@ public class DefaultContentBlockerUserScriptConfig: ContentBlockerUserScriptConf } open class ContentBlockerRulesUserScript: NSObject, UserScript { - + struct ContentBlockerKey { static let url = "url" static let resourceType = "resourceType" @@ -89,20 +88,20 @@ open class ContentBlockerRulesUserScript: NSObject, UserScript { super.init() } - + public var source: String { return configuration.source } public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart - + public var forMainFrameOnly: Bool = false - + public var messageNames: [String] = [ "processRule" ] - + public var supplementaryTrackerData = [TrackerData]() public var currentAdClickAttributionVendor: String? - + public weak var delegate: ContentBlockerRulesUserScriptDelegate? private var _temporaryUnprotectedDomainsCache = [String: [String]]() @@ -124,36 +123,36 @@ open class ContentBlockerRulesUserScript: NSObject, UserScript { guard let delegate = delegate else { return } guard delegate.contentBlockerRulesUserScriptShouldProcessTrackers(self) else { return } let ctlEnabled = delegate.contentBlockerRulesUserScriptShouldProcessCTLTrackers(self) - + guard let dict = message.body as? [String: Any] else { return } - + // False if domain is in unprotected list guard let blocked = dict[ContentBlockerKey.blocked] as? Bool else { return } guard let trackerUrlString = dict[ContentBlockerKey.url] as? String else { return } let resourceType = (dict[ContentBlockerKey.resourceType] as? String) ?? "unknown" guard let pageUrlStr = dict[ContentBlockerKey.pageUrl] as? String else { return } - + guard let currentTrackerData = configuration.trackerData else { return } let privacyConfiguration = configuration.privacyConfiguration - + var additionalTDSSets = supplementaryTrackerData - + if ctlEnabled, let ctlTrackerData = configuration.ctlTrackerData { additionalTDSSets.append(ctlTrackerData) } - + var detectedTracker: DetectedRequest? - + for trackerData in additionalTDSSets { let resolver = TrackerResolver(tds: trackerData, unprotectedSites: privacyConfiguration.userUnprotectedDomains, tempList: temporaryUnprotectedDomains, tld: configuration.tld, adClickAttributionVendor: currentAdClickAttributionVendor) - + if let tracker = resolver.trackerFromUrl(trackerUrlString, pageUrlString: pageUrlStr, resourceType: resourceType, @@ -172,21 +171,21 @@ open class ContentBlockerRulesUserScript: NSObject, UserScript { unprotectedSites: privacyConfiguration.userUnprotectedDomains, tempList: temporaryUnprotectedDomains, tld: configuration.tld) - + if let tracker = resolver.trackerFromUrl(trackerUrlString, pageUrlString: pageUrlStr, resourceType: resourceType, potentiallyBlocked: blocked && privacyConfiguration.isEnabled(featureKey: .contentBlocking)) { detectedTracker = tracker } - + if let tracker = detectedTracker { guard !isFirstParty(requestURL: tracker.url, websiteURL: pageUrlStr) else { return } delegate.contentBlockerRulesUserScript(self, detectedTracker: tracker) } else { guard let requestETLDp1 = configuration.tld.eTLDplus1(forStringURL: trackerUrlString), !isFirstParty(requestURL: trackerUrlString, websiteURL: pageUrlStr) else { return } - + let entity = currentTrackerData.findEntity(forHost: requestETLDp1) ?? Entity(displayName: requestETLDp1, domains: nil, prevalence: nil) let thirdPartyRequest = DetectedRequest(url: trackerUrlString, eTLDplus1: requestETLDp1, @@ -197,12 +196,12 @@ open class ContentBlockerRulesUserScript: NSObject, UserScript { delegate.contentBlockerRulesUserScript(self, detectedThirdPartyRequest: thirdPartyRequest) } } - + private func isFirstParty(requestURL: String, websiteURL: String) -> Bool { guard let requestDomain = configuration.tld.eTLDplus1(forStringURL: requestURL), let websiteDomain = configuration.tld.eTLDplus1(forStringURL: websiteURL) else { return false } - + return requestDomain == websiteDomain } diff --git a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/SurrogatesUserScript.swift b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/SurrogatesUserScript.swift index 8d61331dd..ba2b69cfb 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/SurrogatesUserScript.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/SurrogatesUserScript.swift @@ -1,6 +1,5 @@ // // SurrogatesUserScript.swift -// Core // // Copyright © 2020 DuckDuckGo. All rights reserved. // @@ -24,7 +23,7 @@ import ContentBlocking import Common public protocol SurrogatesUserScriptDelegate: NSObjectProtocol { - + func surrogatesUserScriptShouldProcessTrackers(_ script: SurrogatesUserScript) -> Bool func surrogatesUserScript(_ script: SurrogatesUserScript, detectedTracker tracker: DetectedRequest, @@ -51,7 +50,7 @@ public class DefaultSurrogatesUserScriptConfig: SurrogatesUserScriptConfig { public let tld: TLD public let source: String - + public init(privacyConfig: PrivacyConfiguration, surrogates: String, trackerData: TrackerData?, diff --git a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/TrackerResolver.swift b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/TrackerResolver.swift index f2fa6ace2..7e1d98d51 100644 --- a/Sources/BrowserServicesKit/ContentBlocking/UserScripts/TrackerResolver.swift +++ b/Sources/BrowserServicesKit/ContentBlocking/UserScripts/TrackerResolver.swift @@ -1,6 +1,5 @@ // // TrackerResolver.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -23,13 +22,13 @@ import Common import ContentBlocking public class TrackerResolver { - + let tds: TrackerData let unprotectedSites: [String] let tempList: [String] let tld: TLD let adClickAttributionVendor: String? - + public init(tds: TrackerData, unprotectedSites: [String], tempList: [String], @@ -41,7 +40,7 @@ public class TrackerResolver { self.tld = tld self.adClickAttributionVendor = adClickAttributionVendor } - + public func trackerFromUrl(_ trackerUrlString: String, pageUrlString: String, resourceType: String, @@ -58,11 +57,11 @@ public class TrackerResolver { } else { return nil } - + guard let entity = tds.findEntity(byName: tracker.owner?.name ?? "") else { return nil } - + if isPageAffiliatedWithTrackerEntity(pageUrlString: pageUrlString, trackerEntity: entity) { return DetectedRequest(url: trackerUrlString, eTLDplus1: tld.eTLDplus1(forStringURL: trackerUrlString), @@ -71,7 +70,7 @@ public class TrackerResolver { state: .allowed(reason: .ownedByFirstParty), pageUrl: pageUrlString) } - + let blockingState = calculateBlockingState(tracker: tracker, trackerUrlString: trackerUrlString, resourceType: resourceType, @@ -85,15 +84,15 @@ public class TrackerResolver { state: blockingState, pageUrl: pageUrlString) } - + private func isPageAffiliatedWithTrackerEntity(pageUrlString: String, trackerEntity: Entity) -> Bool { guard let pageHost = URL(string: pageUrlString)?.host, let pageEntity = tds.findEntity(forHost: pageHost) else { return false } - + return pageEntity.displayName == trackerEntity.displayName } - + private func calculateBlockingState(tracker: KnownTracker, trackerUrlString: String, resourceType: String, @@ -101,7 +100,7 @@ public class TrackerResolver { pageUrlString: String) -> BlockingState { let blockingState: BlockingState - + if isPageOnUnprotectedSitesOrTempList(pageUrlString) { blockingState = .allowed(reason: .protectionDisabled) // maybe we should not differentiate } else { @@ -130,7 +129,7 @@ public class TrackerResolver { blockingState = potentiallyBlocked ? .blocked : .allowed(reason: .ruleException) } } - + return blockingState } @@ -150,20 +149,20 @@ public class TrackerResolver { } return nil } - + private func isPageOnUnprotectedSitesOrTempList(_ pageUrlString: String) -> Bool { guard let pageHost = URL(string: pageUrlString)?.host else { return false } - + return unprotectedSites.contains(pageHost) || tempList.contains(pageHost) } - + private func isVendorMatchingCurrentPage(vendor: String, pageUrlString: String) -> Bool { vendor == URL(string: pageUrlString)?.host?.droppingWwwPrefix() } - + private func isVendorOnExceptionsList(vendor: String, exceptions: KnownTracker.Rule.Matching?) -> Bool { guard let domains = exceptions?.domains else { return false } - + return domains.contains(vendor) } @@ -203,7 +202,7 @@ fileprivate extension KnownTracker.Rule { guard let pattern = rule, let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return false } return regex.firstMatch(in: urlString, options: [], range: urlString.fullRange) != nil } - + func action(type: String, host: String) -> TrackerResolver.RuleAction? { // If there is a rule its default action is always block var resultAction: KnownTracker.ActionType? = action ?? .block @@ -220,7 +219,7 @@ fileprivate extension KnownTracker.Rule { } private extension KnownTracker.ActionType { - + func toTrackerResolverRuleAction() -> TrackerResolver.RuleAction { self == .block ? .blockRequest : .allowRequest } diff --git a/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift b/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift index 920b069b7..ad7c306d7 100644 --- a/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift +++ b/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift @@ -22,7 +22,6 @@ import Combine import ContentScopeScripts import UserScript import Common -import os.log public final class ContentScopeProperties: Encodable { public let globalPrivacyControlValue: Bool diff --git a/Sources/BrowserServicesKit/ContentScopeScript/SpecialPagesUserScript.swift b/Sources/BrowserServicesKit/ContentScopeScript/SpecialPagesUserScript.swift index b03943305..e33ea8c51 100644 --- a/Sources/BrowserServicesKit/ContentScopeScript/SpecialPagesUserScript.swift +++ b/Sources/BrowserServicesKit/ContentScopeScript/SpecialPagesUserScript.swift @@ -22,7 +22,6 @@ import Combine import ContentScopeScripts import UserScript import Common -import os.log public final class SpecialPagesUserScript: NSObject, UserScript, UserScriptMessaging { public var source: String = "" diff --git a/Sources/BrowserServicesKit/Email/EmailKeychainManager.swift b/Sources/BrowserServicesKit/Email/EmailKeychainManager.swift index de11fa90a..7dc68f424 100644 --- a/Sources/BrowserServicesKit/Email/EmailKeychainManager.swift +++ b/Sources/BrowserServicesKit/Email/EmailKeychainManager.swift @@ -1,6 +1,5 @@ // -// EmailKeyChainManager.swift -// DuckDuckGo +// EmailKeychainManager.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -34,11 +33,11 @@ extension EmailKeychainManager: EmailManagerStorage { public func getUsername() throws -> String? { try EmailKeychainManager.getString(forField: .username) } - + public func getToken() throws -> String? { try EmailKeychainManager.getString(forField: .token) } - + public func getAlias() throws -> String? { try EmailKeychainManager.getString(forField: .alias) } @@ -50,11 +49,11 @@ extension EmailKeychainManager: EmailManagerStorage { public func getLastUseDate() throws -> String? { try EmailKeychainManager.getString(forField: .lastUseDate) } - + public func store(token: String, username: String, cohort: String?) throws { try EmailKeychainManager.add(token: token, forUsername: username, cohort: cohort) } - + public func store(alias: String) throws { try EmailKeychainManager.add(alias: alias) } @@ -62,11 +61,11 @@ extension EmailKeychainManager: EmailManagerStorage { public func store(lastUseDate: String) throws { try EmailKeychainManager.add(lastUseDate: lastUseDate) } - + public func deleteAlias() throws { try EmailKeychainManager.deleteItem(forField: .alias) } - + public func deleteAuthenticationState() throws { try EmailKeychainManager.deleteAuthenticationState() } @@ -92,24 +91,24 @@ private extension EmailKeychainManager { case waitlistTimestamp = ".email.waitlistTimestamp" case inviteCode = ".email.inviteCode" case cohort = ".email.cohort" - + var keyValue: String { (Bundle.main.bundleIdentifier ?? "com.duckduckgo") + rawValue } } - + static func getString(forField field: EmailKeychainField) throws -> String? { guard let data = try retrieveData(forField: field) else { return nil } - + if let decodedString = String(data: data, encoding: String.Encoding.utf8) { return decodedString } else { throw EmailKeychainAccessError.failedToDecodeKeychainDataAsString } } - + static func retrieveData(forField field: EmailKeychainField, useDataProtectionKeychain: Bool = true) throws -> Data? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, @@ -118,7 +117,7 @@ private extension EmailKeychainManager { kSecReturnData as String: true, kSecUseDataProtectionKeychain as String: useDataProtectionKeychain ] - + var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) @@ -134,7 +133,7 @@ private extension EmailKeychainManager { throw EmailKeychainAccessError.keychainLookupFailure(status) } } - + static func add(token: String, forUsername username: String, cohort: String?) throws { guard let tokenData = token.data(using: .utf8), let usernameData = username.data(using: .utf8) else { @@ -155,7 +154,7 @@ private extension EmailKeychainManager { try add(data: cohortData, forField: .cohort) } } - + static func add(alias: String) throws { try add(string: alias, forField: .alias) } @@ -168,11 +167,11 @@ private extension EmailKeychainManager { guard let stringData = string.data(using: .utf8) else { return } - + try deleteItem(forField: field) try add(data: stringData, forField: field) } - + static func add(data: Data, forField field: EmailKeychainField, useDataProtectionKeychain: Bool = true) throws { let query = [ kSecClass: kSecClassGenericPassword, @@ -181,14 +180,14 @@ private extension EmailKeychainManager { kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock, kSecValueData: data, kSecUseDataProtectionKeychain: useDataProtectionKeychain] as [String: Any] - + let status = SecItemAdd(query as CFDictionary, nil) - + if status != errSecSuccess { throw EmailKeychainAccessError.keychainSaveFailure(status) } } - + static func deleteAuthenticationState() throws { try deleteItem(forField: .username) try deleteItem(forField: .token) @@ -196,15 +195,15 @@ private extension EmailKeychainManager { try deleteItem(forField: .cohort) try deleteItem(forField: .lastUseDate) } - + static func deleteItem(forField field: EmailKeychainField, useDataProtectionKeychain: Bool = true) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: field.keyValue, kSecUseDataProtectionKeychain as String: useDataProtectionKeychain] - + let status = SecItemDelete(query as CFDictionary) - + if status != errSecSuccess && status != errSecItemNotFound { throw EmailKeychainAccessError.keychainDeleteFailure(status) } @@ -231,12 +230,12 @@ public extension EmailKeychainManager { // MARK: - Keychain Migration Extensions extension EmailKeychainManager { - + /// Takes data from the login keychain and moves it to the data protection keychain. /// Reference: https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain static func migrateItemsToDataProtectionKeychain() { #if os(macOS) - + for field in EmailKeychainField.allCases { if let data = try? retrieveData(forField: field, useDataProtectionKeychain: false) { try? add(data: data, forField: field, useDataProtectionKeychain: true) @@ -246,5 +245,5 @@ extension EmailKeychainManager { #endif } - + } diff --git a/Sources/BrowserServicesKit/Email/EmailManager.swift b/Sources/BrowserServicesKit/Email/EmailManager.swift index a5f88c9a9..91548f6b1 100644 --- a/Sources/BrowserServicesKit/Email/EmailManager.swift +++ b/Sources/BrowserServicesKit/Email/EmailManager.swift @@ -1,6 +1,5 @@ // // EmailManager.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,8 +19,6 @@ import Foundation import Common -// swiftlint:disable file_length - public enum EmailKeychainAccessType: String { case getUsername case getToken @@ -43,7 +40,7 @@ public enum EmailKeychainAccessError: Error, Equatable { case keychainDeleteFailure(OSStatus) case keychainLookupFailure(OSStatus) case keychainFailedToSaveUsernameAfterSavingToken(OSStatus) - + public var errorDescription: String { switch self { case .failedToDecodeKeychainValueAsData: return "failedToDecodeKeychainValueAsData" @@ -105,7 +102,7 @@ public protocol EmailManagerRequestDelegate: AnyObject { timeoutInterval: TimeInterval) async throws -> Data func emailManagerKeychainAccessFailed(_ emailManager: EmailManager, - accessType: EmailKeychainAccessType, + accessType: EmailKeychainAccessType, error: EmailKeychainAccessError) } @@ -137,7 +134,7 @@ public struct EmailUrls { var emailAliasAPI: URL { return URL(string: Url.emailAlias)! } - + public init() { } } @@ -156,19 +153,19 @@ public enum EmailAliasStatus { } public class EmailManager { - + public static let emailDomain = "duck.com" private static let inContextEmailSignupPromptDismissedPermanentlyAtKey = "Autofill.InContextEmailSignup.dismissed.permanently.at" private let storage: EmailManagerStorage public weak var aliasPermissionDelegate: EmailManagerAliasPermissionDelegate? public weak var requestDelegate: EmailManagerRequestDelegate? - + public enum NotificationParameter { public static let cohort = "cohort" public static let isForcedSignOut = "isForcedSignOut" } - + private lazy var emailUrls = EmailUrls() private lazy var aliasAPIURL = emailUrls.emailAliasAPI @@ -191,7 +188,7 @@ public class EmailManager { } else { assertionFailure("Expected EmailKeychainAccessFailure") } - + return nil } } @@ -210,7 +207,7 @@ public class EmailManager { } else { assertionFailure("Expected EmailKeychainAccessFailure") } - + return nil } } @@ -229,7 +226,7 @@ public class EmailManager { } else { assertionFailure("Expected EmailKeychainAccessFailure") } - + return nil } } @@ -248,7 +245,7 @@ public class EmailManager { } else { assertionFailure("Expected EmailKeychainAccessFailure") } - + return nil } } @@ -267,7 +264,7 @@ public class EmailManager { } else { assertionFailure("Expected EmailKeychainAccessFailure") } - + return "" } } @@ -279,7 +276,7 @@ public class EmailManager { } let dateString = dateFormatter.string(from: Date()) - + do { try storage.store(lastUseDate: dateString) } catch { @@ -294,7 +291,7 @@ public class EmailManager { public var isSignedIn: Bool { return token != nil && username != nil } - + public var userEmail: String? { guard let username = username else { return nil } return username + "@" + EmailManager.emailDomain @@ -309,14 +306,14 @@ public class EmailManager { UserDefaults().set(newValue, forKey: Self.inContextEmailSignupPromptDismissedPermanentlyAtKey) } } - + public init(storage: EmailManagerStorage = EmailKeychainManager()) { self.storage = storage dateFormatter.formatOptions = [.withFullDate, .withDashSeparatorInDate] dateFormatter.timeZone = TimeZone(identifier: "America/New_York") // Use ET time zone } - + public func signOut(isForced: Bool = false) throws { Self.lock.lock() defer { @@ -427,25 +424,25 @@ extension EmailManager: AutofillEmailDelegate { public func autofillUserScriptDidRequestSignOut(_: AutofillUserScript) { try? self.signOut() } - + public func autofillUserScript(_: AutofillUserScript, didRequestAliasAndRequiresUserPermission requiresUserPermission: Bool, shouldConsumeAliasIfProvided: Bool, completionHandler: @escaping AliasAutosaveCompletion) { - + getAliasIfNeeded { [weak self] newAlias, error in guard let newAlias = newAlias, error == nil, let self = self else { completionHandler(nil, false, error) return } - + if requiresUserPermission { guard let delegate = self.aliasPermissionDelegate else { assertionFailure("EmailUserScript requires permission to provide Alias") completionHandler(nil, false, .permissionDelegateNil) return } - + delegate.emailManager(self, didRequestPermissionToProvideAliasWithCompletion: { [weak self] permissionType, autosave in switch permissionType { case .user: @@ -472,11 +469,11 @@ extension EmailManager: AutofillEmailDelegate { } } } - + public func autofillUserScriptDidRequestRefreshAlias(_: AutofillUserScript) { self.consumeAliasAndReplace() } - + public func autofillUserScript(_: AutofillUserScript, didRequestStoreToken token: String, username: String, cohort: String?) { try? storeToken(token, username: username, cohort: cohort) } @@ -587,14 +584,14 @@ private extension EmailManager { } typealias HTTPHeaders = [String: String] - + var emailHeaders: HTTPHeaders { guard let token = token else { return [:] } return ["Authorization": "Bearer " + token] } - + func consumeAliasAndReplace() { Self.lock.lock() defer { @@ -613,7 +610,7 @@ private extension EmailManager { fetchAndStoreAlias() } - + func getAliasIfNeeded(timeoutInterval: TimeInterval = 4.0, completionHandler: @escaping AliasCompletion) { if let alias = alias { completionHandler(alias, nil) @@ -666,7 +663,7 @@ private extension EmailManager { completionHandler?(nil, .signedOut) return } - + Task.detached { [aliasAPIURL, emailHeaders] in let result: Result do { @@ -768,5 +765,3 @@ private extension EmailManager { } } - -// swiftlint:enable file_length diff --git a/Sources/BrowserServicesKit/FeatureFlagger/FeatureFlagger.swift b/Sources/BrowserServicesKit/FeatureFlagger/FeatureFlagger.swift index 23abd039a..3e5f87f57 100644 --- a/Sources/BrowserServicesKit/FeatureFlagger/FeatureFlagger.swift +++ b/Sources/BrowserServicesKit/FeatureFlagger/FeatureFlagger.swift @@ -1,6 +1,5 @@ // // FeatureFlagger.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/GPC/GPCRequestFactory.swift b/Sources/BrowserServicesKit/GPC/GPCRequestFactory.swift index 610a22adc..11df31432 100644 --- a/Sources/BrowserServicesKit/GPC/GPCRequestFactory.swift +++ b/Sources/BrowserServicesKit/GPC/GPCRequestFactory.swift @@ -1,6 +1,5 @@ // // GPCRequestFactory.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,9 +19,9 @@ import Foundation public struct GPCRequestFactory { - + public init() { } - + public struct Constants { public static let secGPCHeader = "Sec-GPC" } @@ -50,21 +49,21 @@ public struct GPCRequestFactory { return false } - + public func requestForGPC(basedOn incomingRequest: URLRequest, config: PrivacyConfiguration, gpcEnabled: Bool) -> URLRequest? { - + func removingHeader(fromRequest incomingRequest: URLRequest) -> URLRequest? { var request = incomingRequest if let headers = request.allHTTPHeaderFields, headers.firstIndex(where: { $0.key == Constants.secGPCHeader }) != nil { request.setValue(nil, forHTTPHeaderField: Constants.secGPCHeader) return request } - + return nil } - + /* For now, the GPC header is only applied to sites known to be honoring GPC (nytimes.com, washingtonpost.com), while the DOM signal is available to all websites. @@ -74,7 +73,7 @@ public struct GPCRequestFactory { // Remove GPC header if its still there (or nil) return removingHeader(fromRequest: incomingRequest) } - + // Add GPC header if needed if config.isEnabled(featureKey: .gpc) && gpcEnabled { var request = incomingRequest @@ -87,7 +86,7 @@ public struct GPCRequestFactory { // Check if GPC header is still there and remove it return removingHeader(fromRequest: incomingRequest) } - + return nil } } diff --git a/Sources/BrowserServicesKit/InternalUserDecider/InternalUserDecider.swift b/Sources/BrowserServicesKit/InternalUserDecider/InternalUserDecider.swift index 9accf7019..397bbd7f3 100644 --- a/Sources/BrowserServicesKit/InternalUserDecider/InternalUserDecider.swift +++ b/Sources/BrowserServicesKit/InternalUserDecider/InternalUserDecider.swift @@ -1,6 +1,5 @@ // // InternalUserDecider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,10 +20,10 @@ import Foundation import Combine public protocol InternalUserDecider { - + var isInternalUser: Bool { get } var isInternalUserPublisher: AnyPublisher { get } - + @discardableResult func markUserAsInternalIfNeeded(forUrl url: URL?, response: HTTPURLResponse?) -> Bool } @@ -37,7 +36,7 @@ public class DefaultInternalUserDecider: InternalUserDecider { var store: InternalUserStoring private static let internalUserVerificationURLHost = "use-login.duckduckgo.com" private let isInternalUserSubject: CurrentValueSubject - + public init(store: InternalUserStoring) { self.store = store isInternalUserSubject = CurrentValueSubject(store.isInternalUser) @@ -62,20 +61,20 @@ public class DefaultInternalUserDecider: InternalUserDecider { if isInternalUser { // If we're already an internal user, we don't need to do anything return false } - + if shouldMarkUserAsInternal(forUrl: url, statusCode: response?.statusCode) { isInternalUser = true return true } return false } - + func shouldMarkUserAsInternal(forUrl url: URL?, statusCode: Int?) -> Bool { if let statusCode = statusCode, statusCode == 200, let url = url, url.host == DefaultInternalUserDecider.internalUserVerificationURLHost { - + return true } return false diff --git a/Sources/BrowserServicesKit/LinkProtection/AMPCanonicalExtractor.swift b/Sources/BrowserServicesKit/LinkProtection/AMPCanonicalExtractor.swift index 13f653b8c..dba49856e 100644 --- a/Sources/BrowserServicesKit/LinkProtection/AMPCanonicalExtractor.swift +++ b/Sources/BrowserServicesKit/LinkProtection/AMPCanonicalExtractor.swift @@ -1,6 +1,5 @@ // // AMPCanonicalExtractor.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,9 +20,9 @@ import Foundation import WebKit import Common -public class AMPCanonicalExtractor: NSObject { +public final class AMPCanonicalExtractor: NSObject { - class CompletionHandler { + final class CompletionHandler { private var completion: ((URL?) -> Void)? @@ -49,17 +48,17 @@ public class AMPCanonicalExtractor: NSObject { } private let completionHandler = CompletionHandler() - + private var webView: WKWebView? private var imageBlockingRules: WKContentRuleList? - + private let linkCleaner: LinkCleaner private let privacyManager: PrivacyConfigurationManaging private let contentBlockingManager: CompiledRuleListsSource private let errorReporting: EventMapping? - + private var privacyConfig: PrivacyConfiguration { privacyManager.privacyConfig } - + public init(linkCleaner: LinkCleaner, privacyManager: PrivacyConfigurationManaging, contentBlockingManager: CompiledRuleListsSource, @@ -68,12 +67,12 @@ public class AMPCanonicalExtractor: NSObject { self.privacyManager = privacyManager self.contentBlockingManager = contentBlockingManager self.errorReporting = errorReporting - + super.init() - + loadImageBlockingRules() } - + private func loadImageBlockingRules() { WKContentRuleListStore.default().lookUpContentRuleList(forIdentifier: Constants.ruleListIdentifier) { [weak self] ruleList, _ in if let ruleList = ruleList { @@ -83,7 +82,7 @@ public class AMPCanonicalExtractor: NSObject { } } } - + private func compileImageRules() { let ruleSource = """ [ @@ -98,7 +97,7 @@ public class AMPCanonicalExtractor: NSObject { } ] """ - + WKContentRuleListStore.default().compileContentRuleList(forIdentifier: Constants.ruleListIdentifier, encodedContentRuleList: ruleSource) { [weak self] ruleList, error in if let error = error { @@ -106,24 +105,24 @@ public class AMPCanonicalExtractor: NSObject { self?.errorReporting?.fire(.ampBlockingRulesCompilationFailed) return } - + self?.imageBlockingRules = ruleList } } - + public func urlContainsAMPKeyword(_ url: URL?) -> Bool { linkCleaner.lastAMPURLString = nil guard privacyConfig.isEnabled(featureKey: .ampLinks) else { return false } guard let url = url, !linkCleaner.isURLExcluded(url: url) else { return false } let urlStr = url.absoluteString - + let ampKeywords = TrackingLinkSettings(fromConfig: privacyConfig).ampKeywords return ampKeywords.contains { keyword in return urlStr.contains(keyword) } } - + private func buildUserScript() -> WKUserScript { let source = """ (function() { @@ -135,10 +134,10 @@ public class AMPCanonicalExtractor: NSObject { }) })() """ - + return WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: true) } - + public func cancelOngoingExtraction() { webView?.stopLoading() webView = nil @@ -162,7 +161,7 @@ public class AMPCanonicalExtractor: NSObject { completion(nil) return } - + completionHandler.setCompletionHandler(completion: completion) assert(Thread.isMainThread) @@ -170,7 +169,7 @@ public class AMPCanonicalExtractor: NSObject { webView?.navigationDelegate = self webView?.load(URLRequest(url: url)) } - + public func getCanonicalUrl(initiator: URL?, url: URL?) async -> URL? { await withCheckedContinuation { continuation in @@ -179,7 +178,7 @@ public class AMPCanonicalExtractor: NSObject { } } } - + private func makeConfiguration() -> WKWebViewConfiguration { let configuration = WKWebViewConfiguration() configuration.websiteDataStore = .nonPersistent() @@ -206,9 +205,9 @@ public class AMPCanonicalExtractor: NSObject { extension AMPCanonicalExtractor: WKScriptMessageHandler { public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == Constants.sendCanonical else { return } - + webView = nil - + if let dict = message.body as? [String: AnyObject], let canonical = dict[Constants.canonicalKey] as? String { if let canonicalUrl = URL(string: canonical), !linkCleaner.isURLExcluded(url: canonicalUrl) { @@ -227,7 +226,7 @@ extension AMPCanonicalExtractor: WKNavigationDelegate { public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { completionHandler.completeWithURL(nil) } - + public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { completionHandler.completeWithURL(nil) } diff --git a/Sources/BrowserServicesKit/LinkProtection/AMPProtectionDebugEvents.swift b/Sources/BrowserServicesKit/LinkProtection/AMPProtectionDebugEvents.swift index fad548a89..f1bfdbcfa 100644 --- a/Sources/BrowserServicesKit/LinkProtection/AMPProtectionDebugEvents.swift +++ b/Sources/BrowserServicesKit/LinkProtection/AMPProtectionDebugEvents.swift @@ -1,6 +1,5 @@ // -// ContentBlockerDebugEvents.swift -// DuckDuckGo +// AMPProtectionDebugEvents.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,5 +21,5 @@ import Foundation public enum AMPProtectionDebugEvents { case ampBlockingRulesCompilationFailed - + } diff --git a/Sources/BrowserServicesKit/LinkProtection/LinkCleaner.swift b/Sources/BrowserServicesKit/LinkProtection/LinkCleaner.swift index 3ff914a58..515675759 100644 --- a/Sources/BrowserServicesKit/LinkProtection/LinkCleaner.swift +++ b/Sources/BrowserServicesKit/LinkProtection/LinkCleaner.swift @@ -1,6 +1,5 @@ // // LinkCleaner.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,33 +19,33 @@ import Foundation public class LinkCleaner { - + public var lastAMPURLString: String? public var urlParametersRemoved: Bool = false - + private let privacyManager: PrivacyConfigurationManaging private var privacyConfig: PrivacyConfiguration { privacyManager.privacyConfig } public init(privacyManager: PrivacyConfigurationManaging) { self.privacyManager = privacyManager } - + public func ampFormat(matching url: URL) -> String? { let ampFormats = TrackingLinkSettings(fromConfig: privacyConfig).ampLinkFormats for format in ampFormats where url.absoluteString.matches(pattern: format) { return format } - + return nil } - + public func isURLExcluded(url: URL, feature: PrivacyFeature = .ampLinks) -> Bool { guard let host = url.host else { return true } guard url.scheme == "http" || url.scheme == "https" else { return true } - + return !privacyConfig.isFeature(feature, enabledForDomain: host) } - + public func extractCanonicalFromAMPLink(initiator: URL?, destination url: URL?) -> URL? { lastAMPURLString = nil guard privacyConfig.isEnabled(featureKey: .ampLinks) else { return nil } @@ -54,9 +53,9 @@ public class LinkCleaner { if let initiator = initiator, isURLExcluded(url: initiator) { return nil } - + guard let ampFormat = ampFormat(matching: url) else { return nil } - + do { let ampStr = url.absoluteString let regex = try NSRegularExpression(pattern: ampFormat, options: [.caseInsensitive]) @@ -65,14 +64,14 @@ public class LinkCleaner { range: NSRange(ampStr.startIndex.. URL? { urlParametersRemoved = false guard privacyConfig.isEnabled(featureKey: .trackingParameters) else { return url } @@ -92,25 +91,25 @@ public class LinkCleaner { if let initiator = initiator, isURLExcluded(url: initiator, feature: .trackingParameters) { return url } - + guard var urlsComps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url } guard let queryParams = urlsComps.percentEncodedQueryItems, queryParams.count > 0 else { return url } - + let trackingParams = TrackingLinkSettings(fromConfig: privacyConfig).trackingParameters - + let preservedParams: [URLQueryItem] = queryParams.filter { param in if trackingParams.contains(where: { $0 == param.name }) { urlParametersRemoved = true return false } - + return true } - + if urlParametersRemoved { urlsComps.percentEncodedQueryItems = preservedParams.count > 0 ? preservedParams : nil return urlsComps.url diff --git a/Sources/BrowserServicesKit/LinkProtection/LinkProtection.swift b/Sources/BrowserServicesKit/LinkProtection/LinkProtection.swift index 7dd8b0986..907316b03 100644 --- a/Sources/BrowserServicesKit/LinkProtection/LinkProtection.swift +++ b/Sources/BrowserServicesKit/LinkProtection/LinkProtection.swift @@ -20,12 +20,12 @@ import WebKit import Common public struct LinkProtection { - + private let linkCleaner: LinkCleaner private let ampExtractor: AMPCanonicalExtractor - + private var mainFrameUrl: URL? - + public init(privacyManager: PrivacyConfigurationManaging, contentBlockingManager: CompiledRuleListsSource, errorReporting: EventMapping) { @@ -35,11 +35,11 @@ public struct LinkProtection { contentBlockingManager: contentBlockingManager, errorReporting: errorReporting) } - + public mutating func setMainFrameUrl(_ url: URL?) { mainFrameUrl = url } - + public func getCleanURL(from url: URL, onStartExtracting: () -> Void, onFinishExtracting: @escaping () -> Void, @@ -48,7 +48,7 @@ public struct LinkProtection { if let cleanURL = linkCleaner.cleanTrackingParameters(initiator: nil, url: urlToLoad) { urlToLoad = cleanURL } - + if let cleanURL = linkCleaner.extractCanonicalFromAMPLink(initiator: nil, destination: urlToLoad) { completion(cleanURL) } else if ampExtractor.urlContainsAMPKeyword(urlToLoad) { @@ -74,7 +74,7 @@ public struct LinkProtection { } } } - + // swiftlint:disable function_parameter_count public func requestTrackingLinkRewrite(initiatingURL: URL?, destinationURL: URL, @@ -87,7 +87,7 @@ public struct LinkProtection { // We do not rewrite redirects due to breakage concerns return false } - + var didRewriteLink = false if let newURL = linkCleaner.extractCanonicalFromAMPLink(initiator: initiatingURL, destination: destinationURL) { policyDecisionHandler(false) @@ -101,7 +101,7 @@ public struct LinkProtection { policyDecisionHandler(true) return } - + policyDecisionHandler(false) onLinkRewrite(canonical) } @@ -113,7 +113,7 @@ public struct LinkProtection { didRewriteLink = true } } - + return didRewriteLink } @@ -146,16 +146,16 @@ public struct LinkProtection { onLinkRewrite: onLinkRewrite) { navigationActionPolicy in continuation.resume(returning: navigationActionPolicy) } - + if !didRewriteLink { continuation.resume(returning: nil) } } } - + public func cancelOngoingExtraction() { ampExtractor.cancelOngoingExtraction() } - + public var lastAMPURLString: String? { linkCleaner.lastAMPURLString } public var urlParametersRemoved: Bool { linkCleaner.urlParametersRemoved } - + } diff --git a/Sources/BrowserServicesKit/LinkProtection/TrackingLinkSettings.swift b/Sources/BrowserServicesKit/LinkProtection/TrackingLinkSettings.swift index 30bcc43a5..f832567df 100644 --- a/Sources/BrowserServicesKit/LinkProtection/TrackingLinkSettings.swift +++ b/Sources/BrowserServicesKit/LinkProtection/TrackingLinkSettings.swift @@ -1,6 +1,5 @@ // // TrackingLinkSettings.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -20,24 +19,24 @@ import Foundation struct TrackingLinkSettings { - + let ampLinkFormats: [String] let ampKeywords: [String] let trackingParameters: [String] - + struct Constants { static let ampLinkFormats = "linkFormats" static let ampKeywords = "keywords" static let trackingParameters = "parameters" } - + init(fromConfig config: PrivacyConfiguration) { let ampFeatureSettings = config.settings(for: .ampLinks) let trackingParametersSettings = config.settings(for: .trackingParameters) - + ampLinkFormats = ampFeatureSettings[Constants.ampLinkFormats] as? [String] ?? [] ampKeywords = ampFeatureSettings[Constants.ampKeywords] as? [String] ?? [] trackingParameters = trackingParametersSettings[Constants.trackingParameters] as? [String] ?? [] } - + } diff --git a/Sources/BrowserServicesKit/PrivacyConfig/AppPrivacyConfiguration.swift b/Sources/BrowserServicesKit/PrivacyConfig/AppPrivacyConfiguration.swift index 3d5356c86..0922005cc 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/AppPrivacyConfiguration.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/AppPrivacyConfiguration.swift @@ -1,6 +1,5 @@ // // AppPrivacyConfiguration.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation import Common public struct AppPrivacyConfiguration: PrivacyConfiguration { - + private enum Constants { static let enabledKey = "enabled" static let lastRolloutCountKey = "lastRolloutCount" @@ -29,7 +28,7 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { } private(set) public var identifier: String - + private let data: PrivacyConfigurationData private let locallyUnprotected: DomainsProtectionStore private let internalUserDecider: InternalUserDecider @@ -53,7 +52,7 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { public var userUnprotectedDomains: [String] { return Array(locallyUnprotected.unprotectedDomains).normalizedDomainsForContentBlocking().sorted() } - + public var tempUnprotectedDomains: [String] { return data.unprotectedTemporary.map { $0.domain }.normalizedDomainsForContentBlocking() } @@ -61,22 +60,22 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { public var trackerAllowlist: PrivacyConfigurationData.TrackerAllowlist { return data.trackerAllowlist } - + func parse(versionString: String) -> [Int] { return versionString.split(separator: ".").map { Int($0) ?? 0 } } - + func satisfiesMinVersion(_ version: String?, versionProvider: AppVersionProvider) -> Bool { if let minSupportedVersion = version, let appVersion = versionProvider.appVersion() { let minVersion = parse(versionString: minSupportedVersion) let currentVersion = parse(versionString: appVersion) - + for i in 0.. minSegment { return true } @@ -85,7 +84,7 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { } } } - + return true } @@ -115,25 +114,25 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { default: return false } } - + private func isRolloutEnabled(subfeature: any PrivacySubfeature, rolloutSteps: [PrivacyConfigurationData.PrivacyFeature.Feature.RolloutStep], randomizer: (Range) -> Double) -> Bool { // Empty rollouts should be default enabled guard !rolloutSteps.isEmpty else { return true } - + let defsPrefix = "config.\(subfeature.parent.rawValue).\(subfeature.rawValue)" if userDefaults.bool(forKey: "\(defsPrefix).\(Constants.enabledKey)") { return true } - + var willEnable = false let rollouts = Array(Set(rolloutSteps.filter({ $0.percent >= 0.0 && $0.percent <= 100.0 }))).sorted(by: { $0.percent < $1.percent }) if let rolloutSize = userDefaults.value(forKey: "\(defsPrefix).\(Constants.lastRolloutCountKey)") as? Int { guard rolloutSize < rollouts.count else { return false } // Sanity check as we need at least two values to compute the new probability guard rollouts.count > 1 else { return false } - + // If the user has seen the rollout before, and the rollout count has changed // Try again with the new probability let y = rollouts[rollouts.count - 1].percent @@ -153,7 +152,7 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { userDefaults.set(rollouts.count, forKey: "\(defsPrefix).\(Constants.lastRolloutCountKey)") return false } - + userDefaults.set(true, forKey: "\(defsPrefix).\(Constants.enabledKey)") return true } @@ -168,14 +167,14 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { let subfeatures = subfeatures(for: subfeature.parent) let subfeatureData = subfeatures[subfeature.rawValue] let satisfiesMinVersion = satisfiesMinVersion(subfeatureData?.minSupportedVersion, versionProvider: versionProvider) - + // Handle Rollouts if let rollout = subfeatureData?.rollout { if !isRolloutEnabled(subfeature: subfeature, rolloutSteps: rollout.steps, randomizer: randomizer) { return false } } - + switch subfeatureData?.state { case PrivacyConfigurationData.State.enabled: return satisfiesMinVersion case PrivacyConfigurationData.State.internal: return internalUserDecider.isInternalUser && satisfiesMinVersion @@ -186,10 +185,10 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { private func subfeatures(for feature: PrivacyFeature) -> PrivacyConfigurationData.PrivacyFeature.Features { return data.features[feature.rawValue]?.features ?? [:] } - + public func exceptionsList(forFeature featureKey: PrivacyFeature) -> [String] { guard let feature = data.features[featureKey.rawValue] else { return [] } - + return feature.exceptions.map { $0.domain }.normalizedDomainsForContentBlocking() } @@ -261,11 +260,11 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration { public func userDisabledProtection(forDomain domain: String) { locallyUnprotected.disableProtection(forDomain: domain.punycodeEncodedHostname.lowercased()) } - + } extension Array where Element == String { - + func normalizedDomainsForContentBlocking() -> [String] { map { domain in domain.punycodeEncodedHostname.lowercased() diff --git a/Sources/BrowserServicesKit/PrivacyConfig/AppVersionProvider.swift b/Sources/BrowserServicesKit/PrivacyConfig/AppVersionProvider.swift index 045a08a61..cf497cde0 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/AppVersionProvider.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/AppVersionProvider.swift @@ -1,6 +1,5 @@ // // AppVersionProvider.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation import Common open class AppVersionProvider { - + open func appVersion() -> String? { Bundle.main.releaseVersionNumber } public init() { } diff --git a/Sources/BrowserServicesKit/PrivacyConfig/Features/AdClickAttributionFeature.swift b/Sources/BrowserServicesKit/PrivacyConfig/Features/AdClickAttributionFeature.swift index 49dc45f9b..d335ef39d 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/Features/AdClickAttributionFeature.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/Features/AdClickAttributionFeature.swift @@ -1,6 +1,5 @@ // // AdClickAttributionFeature.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,42 +20,42 @@ import Foundation import Combine public protocol AdClickAttributing { - + var isEnabled: Bool { get } var allowlist: [AdClickAttributionFeature.AllowlistEntry] { get } var navigationExpiration: Double { get } var totalExpiration: Double { get } var isHeuristicDetectionEnabled: Bool { get } var isDomainDetectionEnabled: Bool { get } - + func isMatchingAttributionFormat(_ url: URL) -> Bool func attributionDomainParameterName(for: URL) -> String? } public class AdClickAttributionFeature: AdClickAttributing { - + private class LinkFormats { - + // Map host to related link formats private var linkFormats: [String: [LinkFormat]] - + init(linkFormatsJSON: [[String: String]]) { var linkFormatsMap = [String: [LinkFormat]]() for entry in linkFormatsJSON { guard let urlString = entry["url"], let url = URL(string: URL.URLProtocol.https.scheme + urlString), let host = url.host else { continue } - + let linkFormat = LinkFormat(url: url, adDomainParameterName: entry["adDomainParameterName"]) linkFormatsMap[host, default: []].append(linkFormat) } self.linkFormats = linkFormatsMap } - + func linkFormat(for url: URL) -> LinkFormat? { guard let domain = url.host else { return nil } - + for linkFormat in linkFormats[domain] ?? [] where linkFormat.url.host == domain && url.path == linkFormat.url.path { if let parameterMatching = linkFormat.adDomainParameterName, @@ -76,10 +75,10 @@ public class AdClickAttributionFeature: AdClickAttributing { static let heuristicDetectionKey = "heuristicDetection" static let domainDetectionKey = "domainDetection" } - + private let configManager: PrivacyConfigurationManaging var updateCancellable: AnyCancellable? - + public private(set) var isEnabled = false private var navigationLinkFormats = LinkFormats(linkFormatsJSON: []) public private(set) var allowlist = [AllowlistEntry]() @@ -87,18 +86,18 @@ public class AdClickAttributionFeature: AdClickAttributing { public private(set) var totalExpiration: Double = 0 public private(set) var isHeuristicDetectionEnabled: Bool = false public private(set) var isDomainDetectionEnabled: Bool = false - + public init(with manager: PrivacyConfigurationManaging) { - + configManager = manager - + updateCancellable = configManager.updatesPublisher.receive(on: DispatchQueue.main).sink { [weak self] in guard let strongSelf = self else { return } strongSelf.update(with: strongSelf.configManager.privacyConfig) } update(with: manager.privacyConfig) } - + public func update(with config: PrivacyConfiguration) { isEnabled = config.isEnabled(featureKey: .adClickAttribution) guard isEnabled else { @@ -109,12 +108,12 @@ public class AdClickAttributionFeature: AdClickAttributing { isDomainDetectionEnabled = false return } - + let settings = config.settings(for: .adClickAttribution) - + let linkFormats = settings[Constants.linkFormatsSettingsKey] as? [[String: String]] ?? [] navigationLinkFormats = LinkFormats(linkFormatsJSON: linkFormats) - + if let allowlist = settings[Constants.allowlistSettingsKey] as? [[String: String]] { self.allowlist = allowlist.compactMap({ entry in guard let host = entry["host"], let blocklistEntry = entry["blocklistEntry"] else { return nil } @@ -123,27 +122,27 @@ public class AdClickAttributionFeature: AdClickAttributing { } else { self.allowlist = [] } - + if let navigationExpiration = settings[Constants.navigationExpirationSettingsKey] as? NSNumber { self.navigationExpiration = navigationExpiration.doubleValue } else { navigationExpiration = 1800 } - + if let totalExpiration = settings[Constants.totalExpirationSettingsKey] as? NSNumber { self.totalExpiration = totalExpiration.doubleValue } else { totalExpiration = 604800 } - + isHeuristicDetectionEnabled = (settings[Constants.heuristicDetectionKey] as? String) == PrivacyConfigurationData.State.enabled isDomainDetectionEnabled = (settings[Constants.domainDetectionKey] as? String) == PrivacyConfigurationData.State.enabled } - + public func isMatchingAttributionFormat(_ url: URL) -> Bool { navigationLinkFormats.linkFormat(for: url) != nil } - + public func attributionDomainParameterName(for url: URL) -> String? { navigationLinkFormats.linkFormat(for: url)?.adDomainParameterName } @@ -151,13 +150,13 @@ public class AdClickAttributionFeature: AdClickAttributing { public struct AllowlistEntry { public let entity: String public let host: String - + public init(entity: String, host: String) { self.entity = entity self.host = host } } - + private struct LinkFormat { let url: URL let adDomainParameterName: String? diff --git a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift index efab8d425..d8e85a604 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift @@ -1,6 +1,5 @@ // // PrivacyFeature.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfiguration.swift b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfiguration.swift index 4be6d13ed..74f249289 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfiguration.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfiguration.swift @@ -1,6 +1,5 @@ // // PrivacyConfiguration.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationData.swift b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationData.swift index ffe12ff63..7deba1283 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationData.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationData.swift @@ -1,6 +1,5 @@ // // PrivacyConfigurationData.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -19,7 +18,6 @@ import Foundation -// swiftlint:disable nesting public struct PrivacyConfigurationData { public typealias FeatureName = String @@ -140,12 +138,12 @@ public struct PrivacyConfigurationData { } public let percent: Double - + public init(json: [String: Any]) { self.percent = json[CodingKeys.percent.rawValue] as? Double ?? 0 } } - + public let state: FeatureState public let minSupportedVersion: FeatureSupportedVersion? public let rollout: Rollout? @@ -281,4 +279,3 @@ public struct PrivacyConfigurationData { } } } -// swiftlint:enable nesting diff --git a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationManager.swift b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationManager.swift index 66be2be0c..a6ec94c66 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationManager.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/PrivacyConfigurationManager.swift @@ -1,6 +1,5 @@ // // PrivacyConfigurationManager.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -37,7 +36,7 @@ public protocol PrivacyConfigurationManaging: AnyObject { } public class PrivacyConfigurationManager: PrivacyConfigurationManaging { - + public enum ReloadResult: Equatable { case embedded case embeddedFallback @@ -48,9 +47,8 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { case dataMismatch } - // swiftlint:disable:next large_tuple public typealias ConfigurationData = (rawData: Data, data: PrivacyConfigurationData, etag: String) - + private let lock = NSLock() private let embeddedDataProvider: EmbeddedDataProvider private let localProtection: DomainsProtectionStore @@ -62,7 +60,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { public var updatesPublisher: AnyPublisher { updatesSubject.eraseToAnyPublisher() } - + private var _fetchedConfigData: ConfigurationData? private(set) public var fetchedConfigData: ConfigurationData? { get { @@ -77,7 +75,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { lock.unlock() } } - + private var _embeddedConfigData: ConfigurationData! private(set) public var embeddedConfigData: ConfigurationData { get { @@ -120,7 +118,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { reload(etag: fetchedETag, data: fetchedData) } - + public var privacyConfig: PrivacyConfiguration { if let fetchedData = fetchedConfigData { return AppPrivacyConfiguration(data: fetchedData.data, @@ -136,7 +134,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { internalUserDecider: internalUserDecider, installDate: installDate) } - + public var currentConfig: Data { if let fetchedData = fetchedConfigData { return fetchedData.rawData @@ -146,14 +144,14 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { @discardableResult public func reload(etag: String?, data: Data?) -> ReloadResult { - + defer { self.updatesSubject.send() } - + let result: ReloadResult - + if let etag = etag, let data = data { result = .downloaded - + do { // This might fail if the downloaded data is corrupt or format has changed unexpectedly let configData = try PrivacyConfigurationData(data: data) @@ -167,7 +165,7 @@ public class PrivacyConfigurationManager: PrivacyConfigurationManaging { fetchedConfigData = nil result = .embedded } - + return result } } diff --git a/Sources/BrowserServicesKit/ReferrerTrimming/ReferrerTrimming.swift b/Sources/BrowserServicesKit/ReferrerTrimming/ReferrerTrimming.swift index 016037f48..8fdd57fe9 100644 --- a/Sources/BrowserServicesKit/ReferrerTrimming/ReferrerTrimming.swift +++ b/Sources/BrowserServicesKit/ReferrerTrimming/ReferrerTrimming.swift @@ -22,26 +22,26 @@ import TrackerRadarKit import Common public class ReferrerTrimming { - + struct Constants { static let headerName = "Referer" static let policyName = "Referrer-Policy" } - + public enum TrimmingState { case idle case navigating(destination: URL) } - + private let privacyManager: PrivacyConfigurationManaging private var privacyConfig: PrivacyConfiguration { privacyManager.privacyConfig } - + private let contentBlockingManager: CompiledRuleListsSource - + private var state: TrimmingState = .idle - + private var tld: TLD - + public init(privacyManager: PrivacyConfigurationManaging, contentBlockingManager: CompiledRuleListsSource, tld: TLD) { @@ -49,39 +49,39 @@ public class ReferrerTrimming { self.contentBlockingManager = contentBlockingManager self.tld = tld } - + public func onBeginNavigation(to destination: URL?) { guard let destination = destination else { return } - + state = .navigating(destination: destination) } - + public func onFinishNavigation() { state = .idle } - + public func onFailedNavigation() { state = .idle } - + func getTrimmedReferrer(originUrl: URL, destUrl: URL, referrerUrl: URL?, trackerData: TrackerData) -> String? { func isSameEntity(a: Entity?, b: Entity?) -> Bool { if a == nil && b == nil { return !originUrl.isThirdParty(to: destUrl, tld: tld) } - + return a?.displayName == b?.displayName } - + guard let originHost = originUrl.host else { return nil } guard let destHost = destUrl.host else { return nil } - + guard privacyConfig.isFeature(.referrer, enabledForDomain: originHost), privacyConfig.isFeature(.referrer, enabledForDomain: destHost) else { return nil @@ -91,10 +91,10 @@ public class ReferrerTrimming { let referrerHost = referrerUrl.host else { return nil } - + let referEntity = trackerData.findEntity(forHost: originHost) let destEntity = trackerData.findEntity(forHost: destHost) - + var newReferrer: String? if !isSameEntity(a: referEntity, b: destEntity) { newReferrer = "\(referrerScheme)://\(referrerHost)/" @@ -105,11 +105,11 @@ public class ReferrerTrimming { !isSameEntity(a: referEntity, b: destEntity) { newReferrer = "\(referrerScheme)://\(referrerHost)/" } - + if newReferrer == referrerUrl.absoluteString { return nil } - + return newReferrer } @@ -134,15 +134,15 @@ public class ReferrerTrimming { case .idle: onBeginNavigation(to: destUrl) } - + guard let trackerData = contentBlockingManager.currentMainRules?.trackerData else { return nil } - + guard let referrerHeader = request.value(forHTTPHeaderField: Constants.headerName) else { return nil } - + if let newReferrer = getTrimmedReferrer(originUrl: originUrl, destUrl: destUrl, referrerUrl: URL(string: referrerHeader) ?? nil, @@ -151,7 +151,7 @@ public class ReferrerTrimming { request.setValue(newReferrer, forHTTPHeaderField: Constants.headerName) return request } - + return nil } } diff --git a/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift b/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift index f4ff9d6ab..f4af0b3d2 100644 --- a/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift +++ b/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift @@ -74,7 +74,6 @@ public protocol AutofillDatabaseProvider: SecureStorageDatabaseProvider { func updateSyncTimestamp(in database: Database, tableName: String, objectId: Int64, timestamp: Date?) throws } -// swiftlint:disable:next type_body_length public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabaseProvider, AutofillDatabaseProvider { public static func defaultDatabaseURL() -> URL { @@ -226,7 +225,7 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro do { var account = credentials.account account.title = account.patternMatchedTitle() - + try account.update(database) try database.execute(sql: """ UPDATE @@ -258,7 +257,7 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro do { var account = credentials.account account.title = account.patternMatchedTitle() - + try account.insert(database) let id = database.lastInsertedRowID try database.execute(sql: """ @@ -765,9 +764,9 @@ extension DefaultAutofillDatabaseProvider { let oldTableName = SecureVaultModels.CreditCard.databaseTableName + "Old" try database.rename(table: SecureVaultModels.CreditCard.databaseTableName, to: oldTableName) - + // 2. Create the new table with suffix and card data values: - + try database.create(table: SecureVaultModels.CreditCard.databaseTableName) { $0.autoIncrementedPrimaryKey(SecureVaultModels.CreditCard.Columns.id.name) @@ -782,13 +781,13 @@ extension DefaultAutofillDatabaseProvider { $0.column(SecureVaultModels.CreditCard.Columns.expirationMonth.name, .integer) $0.column(SecureVaultModels.CreditCard.Columns.expirationYear.name, .integer) } - + // 3. Iterate over existing records - read their numbers, store the suffixes, and then update the new table: - + let rows = try Row.fetchCursor(database, sql: "SELECT * FROM \(oldTableName)") while let row = try rows.next() { - + // Generate the encrypted card number and plaintext suffix: let number: String = row[SecureVaultModels.CreditCard.DeprecatedColumns.cardNumber.name] @@ -796,9 +795,9 @@ extension DefaultAutofillDatabaseProvider { let encryptedCardNumber = try MigrationUtility.l2encrypt(data: number.data(using: .utf8)!, cryptoProvider: cryptoProvider, keyStoreProvider: keyStoreProvider) - + // Insert data from the old table into the new one: - + try database.execute(sql: """ INSERT INTO \(SecureVaultModels.CreditCard.databaseTableName) @@ -831,19 +830,19 @@ extension DefaultAutofillDatabaseProvider { row[SecureVaultModels.CreditCard.Columns.expirationYear.name] ]) } - + // 4. Drop the old database: try database.drop(table: oldTableName) } - + static func migrateV7(database: Database) throws { try database.alter(table: SecureVaultModels.WebsiteAccount.databaseTableName) { $0.add(column: SecureVaultModels.WebsiteAccount.Columns.notes.name, .text) } } - + static func migrateV8(database: Database) throws { try database.alter(table: SecureVaultModels.WebsiteAccount.databaseTableName) { $0.add(column: SecureVaultModels.WebsiteAccount.Columns.signature.name, .text) @@ -913,9 +912,9 @@ extension DefaultAutofillDatabaseProvider { ifNotExists: false ) } - + static func migrateV11(database: Database) throws { - + // Remove WWW from titles and ignore titles containing known export format let accountRows = try Row.fetchCursor(database, sql: "SELECT * FROM \(SecureVaultModels.WebsiteAccount.databaseTableName)") while let accountRow = try accountRows.next() { @@ -925,9 +924,9 @@ extension DefaultAutofillDatabaseProvider { domain: accountRow[SecureVaultModels.WebsiteAccount.Columns.domain.name], created: accountRow[SecureVaultModels.WebsiteAccount.Columns.created.name], lastUpdated: accountRow[SecureVaultModels.WebsiteAccount.Columns.lastUpdated.name]) - + let cleanTitle = account.patternMatchedTitle() - + // Update the accounts table with the new hash value try database.execute(sql: """ UPDATE @@ -937,7 +936,7 @@ extension DefaultAutofillDatabaseProvider { WHERE \(SecureVaultModels.WebsiteAccount.Columns.id.name) = ? """, arguments: [cleanTitle, account.id]) - + } } @@ -1009,10 +1008,10 @@ extension DefaultAutofillDatabaseProvider { // MARK: - Utility functions struct MigrationUtility { - + static func l2encrypt(data: Data, cryptoProvider: SecureStorageCryptoProvider, keyStoreProvider: SecureStorageKeyStoreProvider) throws -> Data { let (crypto, keyStore) = try AutofillSecureVaultFactory.createAndInitializeEncryptionProviders() - + guard let generatedPassword = try keyStore.generatedPassword() else { throw SecureStorageError.noL2Key } @@ -1024,13 +1023,13 @@ struct MigrationUtility { } let decryptedL2Key = try crypto.decrypt(encryptedL2Key, withKey: decryptionKey) - + return try crypto.encrypt(data, withKey: decryptedL2Key) } - + static func l2decrypt(data: Data, cryptoProvider: SecureStorageCryptoProvider, keyStoreProvider: SecureStorageKeyStoreProvider) throws -> Data { let (crypto, keyStore) = (cryptoProvider, keyStoreProvider) - + guard let generatedPassword = try keyStore.generatedPassword() else { throw SecureStorageError.noL2Key } @@ -1071,7 +1070,7 @@ extension SecureVaultModels.WebsiteAccount: PersistableRecord, FetchableRecord { container[Columns.title] = title container[Columns.username] = username container[Columns.domain] = domain - container[Columns.signature] = signature + container[Columns.signature] = signature container[Columns.notes] = notes container[Columns.created] = created container[Columns.lastUpdated] = Date() @@ -1118,7 +1117,7 @@ extension SecureVaultModels.CreditCard: PersistableRecord, FetchableRecord { case title case created case lastUpdated - + case cardNumberData case cardSuffix case cardholderName @@ -1127,7 +1126,7 @@ extension SecureVaultModels.CreditCard: PersistableRecord, FetchableRecord { case expirationMonth case expirationYear } - + enum DeprecatedColumns: String, ColumnExpression { case cardNumber } @@ -1176,7 +1175,7 @@ extension SecureVaultModels.Note: PersistableRecord, FetchableRecord { lastUpdated = row[Columns.lastUpdated] associatedDomain = row[Columns.associatedDomain] text = row[Columns.text] - + displayTitle = generateDisplayTitle() displaySubtitle = generateDisplaySubtitle() } @@ -1246,7 +1245,7 @@ extension SecureVaultModels.Identity: PersistableRecord, FetchableRecord { homePhone = row[Columns.homePhone] mobilePhone = row[Columns.mobilePhone] emailAddress = row[Columns.emailAddress] - + autofillEqualityName = normalizedAutofillName() autofillEqualityAddressStreet = addressStreet?.autofillNormalized() } diff --git a/Sources/BrowserServicesKit/SecureVault/AutofillKeyStoreProvider.swift b/Sources/BrowserServicesKit/SecureVault/AutofillKeyStoreProvider.swift index 609c60fc9..3f211aeae 100644 --- a/Sources/BrowserServicesKit/SecureVault/AutofillKeyStoreProvider.swift +++ b/Sources/BrowserServicesKit/SecureVault/AutofillKeyStoreProvider.swift @@ -56,9 +56,9 @@ final class AutofillKeyStoreProvider: SecureStorageKeyStoreProvider { var query = attributesForEntry(named: name, serviceName: serviceName) query[kSecReturnData as String] = true query[kSecAttrService as String] = serviceName - + var item: CFTypeRef? - + let status = SecItemCopyMatching(query as CFDictionary, &item) switch status { case errSecSuccess: @@ -75,15 +75,15 @@ final class AutofillKeyStoreProvider: SecureStorageKeyStoreProvider { } return data } - + case errSecItemNotFound: - + // Look for an older key and try to migrate if serviceName == Constants.defaultServiceName { return try? migrateV1Key(name: name) } return nil - + default: throw SecureStorageError.keystoreError(status: status) } diff --git a/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift b/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift index a0a3256e4..85e4cdd62 100644 --- a/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift +++ b/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift @@ -21,8 +21,6 @@ import Common import GRDB import SecureStorage -// swiftlint:disable file_length type_body_length - public typealias AutofillVaultFactory = SecureVaultFactory> // swiftlint:disable:next identifier_name @@ -54,7 +52,7 @@ public protocol AutofillSecureVault: SecureVault { func authWith(password: Data) throws -> any AutofillSecureVault func resetL2Password(oldPassword: Data?, newPassword: Data) throws - + func accounts() throws -> [SecureVaultModels.WebsiteAccount] func accountsFor(domain: String) throws -> [SecureVaultModels.WebsiteAccount] func accountsWithPartialMatchesFor(eTLDplus1: String) throws -> [SecureVaultModels.WebsiteAccount] @@ -458,10 +456,10 @@ public class DefaultAutofillSecureVault: AutofillSe try self.providers.database.deleteIdentityForIdentityId(identityId) } } - + public func existingIdentityForAutofill(matching proposedIdentity: SecureVaultModels.Identity) throws -> SecureVaultModels.Identity? { let identities = try self.identities() - + return identities.first { existingIdentity in existingIdentity.hasAutofillEquality(comparedTo: proposedIdentity) } @@ -472,14 +470,14 @@ public class DefaultAutofillSecureVault: AutofillSe public func creditCards() throws -> [SecureVaultModels.CreditCard] { return try executeThrowingDatabaseOperation { let cards = try self.providers.database.creditCards() - + let decryptedCards: [SecureVaultModels.CreditCard] = try cards.map { card in var mutableCard = card mutableCard.cardNumberData = try self.l2Decrypt(data: mutableCard.cardNumberData) - + return mutableCard } - + return decryptedCards } } @@ -495,10 +493,10 @@ public class DefaultAutofillSecureVault: AutofillSe return card } } - + public func existingCardForAutofill(matching proposedCard: SecureVaultModels.CreditCard) throws -> SecureVaultModels.CreditCard? { let cards = try self.creditCards() - + return cards.first { existingCard in existingCard.hasAutofillEquality(comparedTo: proposedCard) } @@ -508,10 +506,10 @@ public class DefaultAutofillSecureVault: AutofillSe public func storeCreditCard(_ card: SecureVaultModels.CreditCard) throws -> Int64 { return try executeThrowingDatabaseOperation { var mutableCard = card - + mutableCard.cardSuffix = SecureVaultModels.CreditCard.suffix(from: mutableCard.cardNumber) mutableCard.cardNumberData = try self.l2Encrypt(data: mutableCard.cardNumberData) - + return try self.providers.database.storeCreditCard(mutableCard) } } @@ -596,7 +594,7 @@ public class DefaultAutofillSecureVault: AutofillSe } return try providers.crypto.decrypt(encryptedL2Key, withKey: decryptionKey) } - + private func l2Encrypt(data: Data, using l2Key: Data? = nil) throws -> Data { let key: Data = try { if let l2Key { @@ -619,5 +617,3 @@ public class DefaultAutofillSecureVault: AutofillSe return try providers.crypto.decrypt(data, withKey: key) } } - -// swiftlint:enable file_length type_body_length diff --git a/Sources/BrowserServicesKit/SecureVault/CredentialsDatabaseCleaner.swift b/Sources/BrowserServicesKit/SecureVault/CredentialsDatabaseCleaner.swift index ce6f4b232..04bb3c61a 100644 --- a/Sources/BrowserServicesKit/SecureVault/CredentialsDatabaseCleaner.swift +++ b/Sources/BrowserServicesKit/SecureVault/CredentialsDatabaseCleaner.swift @@ -1,6 +1,5 @@ // // CredentialsDatabaseCleaner.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift index 3b7b44652..67cb3f0bb 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift @@ -1,6 +1,5 @@ // // SecureVaultManager.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -22,8 +21,6 @@ import Combine import Common import SecureStorage -// swiftlint:disable file_length - public enum AutofillType { case password case card @@ -38,9 +35,9 @@ public struct AutofillData { } public protocol SecureVaultManagerDelegate: AnyObject, SecureVaultErrorReporting { - + func secureVaultManagerIsEnabledStatus(_ manager: SecureVaultManager, forType type: AutofillType?) -> Bool - + func secureVaultManagerShouldSaveData(_: SecureVaultManager) -> Bool func secureVaultManager(_: SecureVaultManager, @@ -56,11 +53,11 @@ public protocol SecureVaultManagerDelegate: AnyObject, SecureVaultErrorReporting func secureVaultManager(_: SecureVaultManager, promptUserWithGeneratedPassword password: String, completionHandler: @escaping (Bool) -> Void) - + func secureVaultManager(_: SecureVaultManager, isAuthenticatedFor type: AutofillType, completionHandler: @escaping (Bool) -> Void) - + func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) func secureVaultManager(_: SecureVaultManager, didRequestAuthenticationWithCompletionHandler: @escaping (Bool) -> Void) @@ -104,7 +101,7 @@ public protocol PasswordManager: AnyObject { public class SecureVaultManager { public weak var delegate: SecureVaultManagerDelegate? - + private let vault: (any AutofillSecureVault)? // Third party password manager @@ -117,7 +114,7 @@ public class SecureVaultManager { // Keeps track of partial account created from autogenerated credentials (Private Email + Pwd) private var autosaveAccount: SecureVaultModels.WebsiteAccount? - + // Tracks if the autosave account was created in the current session // To prevent automatically updating username/password for an an existing account private var autosaveAccountCreatedInSession = false @@ -127,7 +124,7 @@ public class SecureVaultManager { private var autogeneratedPassword: Bool = false private var autogeneratedCredentials: Bool { return autogeneratedUserName || autogeneratedPassword - } + } public lazy var autofillWebsiteAccountMatcher: AutofillWebsiteAccountMatcher? = { guard let tld = tld else { return nil } @@ -163,17 +160,17 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { return } let vault = try self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate) - + var identities: [SecureVaultModels.Identity] = [] if delegate.secureVaultManagerIsEnabledStatus(self, forType: .identity) { identities = try vault.identities() } - + var cards: [SecureVaultModels.CreditCard] = [] if delegate.secureVaultManagerIsEnabledStatus(self, forType: .card) { cards = try vault.creditCards() } - + if delegate.secureVaultManagerIsEnabledStatus(self, forType: .password) { getAccounts(for: domain, from: vault, @@ -200,7 +197,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { public func autofillUserScript(_: AutofillUserScript, didRequestCreditCardsManagerForDomain domain: String) { delegate?.secureVaultManager(self, didRequestCreditCardsManagerForDomain: domain) } - + public func autofillUserScript(_: AutofillUserScript, didRequestIdentitiesManagerForDomain domain: String) { delegate?.secureVaultManager(self, didRequestIdentitiesManagerForDomain: domain) } @@ -222,13 +219,13 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { var autofilldata = data let vault = try? self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate) - var autoSavedCredentials: SecureVaultModels.WebsiteCredentials? - + var autoSavedCredentials: SecureVaultModels.WebsiteCredentials? + // If the user navigated away from current domain, remove autosave data if domain != autosaveAccount?.domain { autogeneratedUserName = false - autogeneratedPassword = false - autosaveAccount = nil + autogeneratedPassword = false + autosaveAccount = nil autosaveAccountCreatedInSession = false } @@ -262,11 +259,11 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { let existingPassword = credentials.password.flatMap { String(decoding: $0, as: UTF8.self) } let submittedUserName = data.credentials?.username let submittedPassword = data.credentials?.password - + if existingUsername != submittedUserName && submittedUserName != "" { autogeneratedUserName = false } - + if existingPassword != submittedPassword && submittedPassword != "" { autogeneratedPassword = false } @@ -342,7 +339,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { } } - + public func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForDomain domain: String, subType: AutofillUserScript.GetAutofillDataSubType, @@ -410,7 +407,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { do { let vault = try self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate) - + self.delegate?.secureVaultManager(self, isAuthenticatedFor: .password, completionHandler: { result in if result == true { self.getCredentials(for: accountId, from: vault, or: self.passwordManager) { [weak self] credentials, error in @@ -426,8 +423,8 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { } else { return } - }) - + }) + } catch { os_log(.error, "Error requesting credentials: %{public}@", error.localizedDescription) completionHandler(nil, credentialsProvider) @@ -449,7 +446,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { completionHandler(nil) } }) - + delegate?.secureVaultManager(self, didAutofill: .card, withObjectId: String(creditCardId)) } catch { os_log(.error, "Error requesting credit card: %{public}@", error.localizedDescription) @@ -530,7 +527,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { completionHandler(useGeneratedPassword) } } - + public func autofillUserScript(_: AutofillUserScript, didSendPixel pixel: AutofillUserScript.JSPixel) { delegate?.secureVaultManager(self, didReceivePixel: pixel) } @@ -551,7 +548,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { os_log("Did not meet conditions for silently saving autogenerated credentials, returning early", log: .passwordManager) return } - + let user: String = credentials.username ?? "" let pass: String = credentials.password ?? "" @@ -579,7 +576,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { // created in this visit to the page, and the user has not navigated anywhere. if autosaveAccountCreatedInSession { if let id = currentAccount.id { - + // Update password if provided, and existing account does not have one let pwd = credentials.password ?? "" var pwdData: Data @@ -589,15 +586,15 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { } else { pwdData = Data(pwd.utf8) } - + // Update username if provided if user != "" { currentAccount.username = user } - - // Save + + // Save try vault.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: currentAccount, password: pwdData)) - + // Update the autosave account with changes autosaveAccount = currentAccount } @@ -619,7 +616,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { autofillData: AutofillUserScript.DetectedAutofillData ) throws -> AutofillData { let vault = try self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate) - + let proposedIdentity = try existingIdentity(with: autofillData, vault: vault) let proposedCredentials: SecureVaultModels.WebsiteCredentials? if let passwordManager = passwordManager, passwordManager.isEnabled { @@ -633,13 +630,13 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { } let proposedCard = try existingPaymentMethod(with: autofillData, vault: vault) - + return AutofillData(identity: proposedIdentity, credentials: proposedCredentials, creditCard: proposedCard, automaticallySavedCredentials: autofillData.hasAutogeneratedCredentials) } - + private func existingIdentity(with autofillData: AutofillUserScript.DetectedAutofillData, vault: any AutofillSecureVault) throws -> SecureVaultModels.Identity? { if let identity = autofillData.identity, try vault.existingIdentityForAutofill(matching: identity) == nil { @@ -650,7 +647,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { return nil } } - + private func existingCredentials(with autofillData: AutofillUserScript.DetectedAutofillData, domain: String, vault: any AutofillSecureVault) throws -> SecureVaultModels.WebsiteCredentials? { @@ -659,11 +656,11 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { let passwordData = credentials.password?.data(using: .utf8) else { return nil } - + guard let accounts = try? vault.accountsFor(domain: domain), // Matching account (username) or account with empty username for domain var account = accounts.first(where: { $0.username == credentials.username || $0.username == "" }) else { - + // No existing credentials found. This is a new entry let account = SecureVaultModels.WebsiteAccount(username: credentials.username ?? "", domain: domain) return SecureVaultModels.WebsiteCredentials(account: account, password: passwordData) @@ -696,7 +693,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { return nil } - + private func existingPaymentMethod(with autofillData: AutofillUserScript.DetectedAutofillData, vault: any AutofillSecureVault) throws -> SecureVaultModels.CreditCard? { if let card = autofillData.creditCard, try vault.existingCardForAutofill(matching: card) == nil { @@ -810,5 +807,3 @@ fileprivate extension AutofillSecureVault { } } - -// swiftlint:enable file_length diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels+Sync.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels+Sync.swift index 16c8299c8..d9caea156 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels+Sync.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels+Sync.swift @@ -1,6 +1,5 @@ // // SecureVaultModels+Sync.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift index d9a35ccce..94ef9e4f9 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift @@ -19,15 +19,13 @@ import Foundation import Common -// swiftlint:disable file_length type_body_length - /// The models used by the secure vault. -/// +/// /// Future models include: /// * Generated Password - a password generated for a site, but not used yet /// * Duck Address - a duck address used on a partcular site public struct SecureVaultModels { - + /// A username and password was saved for a given site. Password is stored seperately so that /// it can be queried independently. public struct WebsiteCredentials { @@ -52,7 +50,7 @@ public struct SecureVaultModels { public var notes: String? public let created: Date public let lastUpdated: Date - + public enum CommonTitlePatterns: String, CaseIterable { /* Matches the following title patterns @@ -120,7 +118,7 @@ public struct SecureVaultModels { } return hash } - + public func name(tld: TLD, autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher) -> String { if let title = self.title, !title.isEmpty { return title @@ -128,11 +126,11 @@ public struct SecureVaultModels { return autofillDomainNameUrlMatcher.normalizeUrlForWeb(domain ?? "") } } - + public func firstTLDLetter(tld: TLD, autofillDomainNameUrlSort: AutofillDomainNameUrlSort) -> String? { return autofillDomainNameUrlSort.firstCharacterForGrouping(self, tld: tld)?.uppercased() } - + public func patternMatchedTitle() -> String { guard let title = title, !title.isEmpty else { return "" @@ -155,7 +153,7 @@ public struct SecureVaultModels { } } } - + // If no pattern matched, return the original title return title } @@ -632,5 +630,3 @@ private extension Date { return calendar.date(from: dateComponents) ?? self } } - -// swiftlint:enable file_length type_body_length diff --git a/Sources/BrowserServicesKit/SmarterEncryption/EmbeddedBloomFilterResources.swift b/Sources/BrowserServicesKit/SmarterEncryption/EmbeddedBloomFilterResources.swift index 17b41914e..c0bb6b898 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/EmbeddedBloomFilterResources.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/EmbeddedBloomFilterResources.swift @@ -1,6 +1,5 @@ // // EmbeddedBloomFilterResources.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSBloomFilterSpecification.swift b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSBloomFilterSpecification.swift index 6ee65c702..5b0a0c0f2 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSBloomFilterSpecification.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSBloomFilterSpecification.swift @@ -19,12 +19,12 @@ import Foundation public struct HTTPSBloomFilterSpecification: Equatable, Decodable, Sendable { - + public let bitCount: Int public let errorRate: Double public let totalEntries: Int public let sha256: String - + public init(bitCount: Int, errorRate: Double, totalEntries: Int, sha256: String) { self.bitCount = bitCount self.errorRate = errorRate diff --git a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSExcludedDomains.swift b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSExcludedDomains.swift index 5005b081d..6eb1d5331 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSExcludedDomains.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSExcludedDomains.swift @@ -1,6 +1,5 @@ // // HTTPSExcludedDomains.swift -// Core // // Copyright © 2020 DuckDuckGo. All rights reserved. // @@ -20,7 +19,7 @@ import Foundation public struct HTTPSExcludedDomains: Decodable { - + public let data: [String] - + } diff --git a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgrade.swift b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgrade.swift index 7d461d2fb..6d7fad50d 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgrade.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgrade.swift @@ -1,6 +1,5 @@ // -// ContentBlockerDebugEvents.swift -// DuckDuckGo +// HTTPSUpgrade.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeParser.swift b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeParser.swift index 319f5a5a9..374ae5e92 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeParser.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeParser.swift @@ -1,6 +1,5 @@ // // HTTPSUpgradeParser.swift -// DuckDuckGo // // Copyright © 2018 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation import Common public final class HTTPSUpgradeParser { - + public static func convertExcludedDomainsData(_ data: Data) throws -> [String] { do { let decoder = JSONDecoder() @@ -32,7 +31,7 @@ public final class HTTPSUpgradeParser { throw JsonError.typeMismatch } } - + public static func convertBloomFilterSpecification(fromJSONData data: Data) throws -> HTTPSBloomFilterSpecification { do { let decoder = JSONDecoder() diff --git a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeStore.swift b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeStore.swift index 99bdb8a07..241e83cb4 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeStore.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/HTTPSUpgradeStore.swift @@ -25,7 +25,7 @@ public protocol HTTPSUpgradeStore { func loadBloomFilter() -> BloomFilter? func persistBloomFilter(specification: HTTPSBloomFilterSpecification, data: Data) throws - + // MARK: - Excluded domains func hasExcludedDomain(_ domain: String) -> Bool diff --git a/Sources/BrowserServicesKit/SmarterEncryption/Store/AppHTTPSUpgradeStore.swift b/Sources/BrowserServicesKit/SmarterEncryption/Store/AppHTTPSUpgradeStore.swift index 714f5b1f5..d2f96a8d2 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/Store/AppHTTPSUpgradeStore.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/Store/AppHTTPSUpgradeStore.swift @@ -1,6 +1,5 @@ // // AppHTTPSUpgradeStore.swift -// DuckDuckGo // // Copyright © 2018 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSExcludedDomain.swift b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSExcludedDomain.swift index c1b927c02..3353061ae 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSExcludedDomain.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSExcludedDomain.swift @@ -1,6 +1,5 @@ // // HTTPSExcludedDomain.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -22,7 +21,7 @@ import CoreData @objc(HTTPSExcludedDomain) public class HTTPSExcludedDomain: NSManagedObject { - + @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "HTTPSExcludedDomain") } diff --git a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSStoredBloomFilterSpecification.swift b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSStoredBloomFilterSpecification.swift index d39b1a769..d92da267c 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSStoredBloomFilterSpecification.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSStoredBloomFilterSpecification.swift @@ -1,6 +1,5 @@ // // HTTPSStoredBloomFilterSpecification.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgradeManagedObjectModel.swift b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgradeManagedObjectModel.swift index fe30e0a4d..7c905d284 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgradeManagedObjectModel.swift +++ b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgradeManagedObjectModel.swift @@ -1,6 +1,5 @@ // // HTTPSUpgradeManagedObjectModel.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Statistics/StatisticsStore.swift b/Sources/BrowserServicesKit/Statistics/StatisticsStore.swift index 931078c6e..036ec5a37 100644 --- a/Sources/BrowserServicesKit/Statistics/StatisticsStore.swift +++ b/Sources/BrowserServicesKit/Statistics/StatisticsStore.swift @@ -1,6 +1,5 @@ // // StatisticsStore.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/BrowserServicesKit/Statistics/VariantManager.swift b/Sources/BrowserServicesKit/Statistics/VariantManager.swift index 13deb37c6..7a4b07561 100644 --- a/Sources/BrowserServicesKit/Statistics/VariantManager.swift +++ b/Sources/BrowserServicesKit/Statistics/VariantManager.swift @@ -1,6 +1,5 @@ // // VariantManager.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,16 +20,16 @@ import Foundation /// Define new experimental features by extending the struct in client project. public struct FeatureName: RawRepresentable, Equatable { - + public var rawValue: String // Used for unit tests public static let dummy = FeatureName(rawValue: "dummy") - + public init(rawValue: String) { self.rawValue = rawValue } - + } public protocol VariantManager { diff --git a/Sources/BrowserServicesKit/Suggestions/Suggestion.swift b/Sources/BrowserServicesKit/Suggestions/Suggestion.swift index 90261c38a..2303dfda5 100644 --- a/Sources/BrowserServicesKit/Suggestions/Suggestion.swift +++ b/Sources/BrowserServicesKit/Suggestions/Suggestion.swift @@ -19,7 +19,7 @@ import Foundation public enum Suggestion: Equatable { - + case phrase(phrase: String) case website(url: URL) case bookmark(title: String, url: URL, isFavorite: Bool, allowedInTopHits: Bool) diff --git a/Sources/BrowserServicesKit/Suggestions/SuggestionProcessing.swift b/Sources/BrowserServicesKit/Suggestions/SuggestionProcessing.swift index 91f89c021..398a0a78f 100644 --- a/Sources/BrowserServicesKit/Suggestions/SuggestionProcessing.swift +++ b/Sources/BrowserServicesKit/Suggestions/SuggestionProcessing.swift @@ -103,7 +103,7 @@ final class SuggestionProcessing { default: score = 0 } - + return (item, score) } // Filter not relevant @@ -111,7 +111,7 @@ final class SuggestionProcessing { // Sort according to the score .sorted { $0.score > $1.score } // Create suggestion array - .compactMap { + .compactMap { switch $0.item { case let bookmark as Bookmark: return Suggestion(bookmark: bookmark) diff --git a/Sources/BrowserServicesKit/Suggestions/SuggestionResult.swift b/Sources/BrowserServicesKit/Suggestions/SuggestionResult.swift index f0f4fe1d3..a7aa1d872 100644 --- a/Sources/BrowserServicesKit/Suggestions/SuggestionResult.swift +++ b/Sources/BrowserServicesKit/Suggestions/SuggestionResult.swift @@ -1,5 +1,5 @@ // -// File.swift +// SuggestionResult.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/AppVersion.swift b/Sources/Common/AppVersion.swift index 1e8441a62..385ff9480 100644 --- a/Sources/Common/AppVersion.swift +++ b/Sources/Common/AppVersion.swift @@ -1,6 +1,5 @@ // // AppVersion.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -22,7 +21,7 @@ import Foundation public struct AppVersion { public static let shared = AppVersion() - + private let bundle: InfoBundle public init(bundle: InfoBundle = Bundle.main) { @@ -36,7 +35,7 @@ public struct AppVersion { public var identifier: String { return bundle.object(forInfoDictionaryKey: Bundle.Key.identifier) as? String ?? "" } - + public var majorVersionNumber: String { return String(versionNumber.split(separator: ".").first ?? "") } @@ -48,11 +47,11 @@ public struct AppVersion { public var buildNumber: String { return bundle.object(forInfoDictionaryKey: Bundle.Key.buildNumber) as? String ?? "" } - + public var versionAndBuildNumber: String { return "\(versionNumber).\(buildNumber)" } - + public var localized: String { return "\(name) \(versionAndBuildNumber)" } @@ -61,5 +60,5 @@ public struct AppVersion { let os = ProcessInfo().operatingSystemVersion return "\(os.majorVersion).\(os.minorVersion).\(os.patchVersion)" } - + } diff --git a/Sources/Common/Combine/ScheduledFuture.swift b/Sources/Common/Combine/ScheduledFuture.swift index 265f6a02a..ecd345417 100644 --- a/Sources/Common/Combine/ScheduledFuture.swift +++ b/Sources/Common/Combine/ScheduledFuture.swift @@ -1,5 +1,5 @@ // -// ScheduledFuture.swift.swift +// ScheduledFuture.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Debug.swift b/Sources/Common/Debug.swift index 9759a4698..8b8a33946 100644 --- a/Sources/Common/Debug.swift +++ b/Sources/Common/Debug.swift @@ -1,6 +1,5 @@ // // Debug.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -18,7 +17,6 @@ // import Foundation -import os.log #if DEBUG diff --git a/Sources/Common/DecodableHelper.swift b/Sources/Common/DecodableHelper.swift index e72a85bed..5a66476be 100644 --- a/Sources/Common/DecodableHelper.swift +++ b/Sources/Common/DecodableHelper.swift @@ -17,7 +17,6 @@ // import Foundation -import os.log public struct DecodableHelper { public static func decode(from input: Input) -> Target? { diff --git a/Sources/Common/EventMapping.swift b/Sources/Common/EventMapping.swift index 9141fbd31..b152f7370 100644 --- a/Sources/Common/EventMapping.swift +++ b/Sources/Common/EventMapping.swift @@ -1,6 +1,5 @@ // // EventMapping.swift -// DuckDuckGo // // Copyright © 2019 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Extensions/ArrayExtension.swift b/Sources/Common/Extensions/ArrayExtension.swift index 321d39f4c..785715d72 100644 --- a/Sources/Common/Extensions/ArrayExtension.swift +++ b/Sources/Common/Extensions/ArrayExtension.swift @@ -1,6 +1,5 @@ // // ArrayExtension.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Extensions/BundleExtension.swift b/Sources/Common/Extensions/BundleExtension.swift index e8fb9e54b..79500e3dc 100644 --- a/Sources/Common/Extensions/BundleExtension.swift +++ b/Sources/Common/Extensions/BundleExtension.swift @@ -1,6 +1,5 @@ // // BundleExtension.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -22,7 +21,7 @@ import Foundation extension Bundle { enum Key { - + static let name = kCFBundleNameKey as String static let identifier = kCFBundleIdentifierKey as String static let buildNumber = kCFBundleVersionKey as String diff --git a/Sources/Common/Extensions/CalendarExtension.swift b/Sources/Common/Extensions/CalendarExtension.swift index 3d8e5d33d..cf18a7728 100644 --- a/Sources/Common/Extensions/CalendarExtension.swift +++ b/Sources/Common/Extensions/CalendarExtension.swift @@ -1,6 +1,5 @@ // // CalendarExtension.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -24,5 +23,5 @@ extension Calendar { let numberOfDays = dateComponents([.day], from: from, to: to) return numberOfDays.day } - + } diff --git a/Sources/Common/Extensions/FileManagerExtension.swift b/Sources/Common/Extensions/FileManagerExtension.swift index b2a3a40c6..0ce9d419b 100644 --- a/Sources/Common/Extensions/FileManagerExtension.swift +++ b/Sources/Common/Extensions/FileManagerExtension.swift @@ -1,6 +1,5 @@ // // FileManagerExtension.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,7 +19,7 @@ import Foundation extension FileManager { - + public func applicationSupportDirectoryForComponent(named name: String) -> URL { #if os(macOS) let sandboxPathComponent = "Containers/\(Bundle.main.bundleIdentifier!)/Data/Library/Application Support/" @@ -33,5 +32,5 @@ extension FileManager { #endif return dir.appendingPathComponent(name) } - + } diff --git a/Sources/Common/Extensions/HashExtension.swift b/Sources/Common/Extensions/HashExtension.swift index 45639ce34..b6752cf57 100644 --- a/Sources/Common/Extensions/HashExtension.swift +++ b/Sources/Common/Extensions/HashExtension.swift @@ -1,6 +1,5 @@ // // HashExtension.swift -// DuckDuckGo // // Copyright © 2019 DuckDuckGo. All rights reserved. // @@ -21,17 +20,17 @@ import Foundation import CommonCrypto extension Data { - + private typealias Algorithm = (UnsafeRawPointer?, CC_LONG, UnsafeMutablePointer?) -> UnsafeMutablePointer? - + public var sha1: String { return hash(algorithm: CC_SHA1, length: CC_SHA1_DIGEST_LENGTH) } - + public var sha256: String { return hash(algorithm: CC_SHA256, length: CC_SHA256_DIGEST_LENGTH) } - + private func hash(algorithm: Algorithm, length: Int32) -> String { var hash = [UInt8](repeating: 0, count: Int(length)) let dataBytes = [UInt8](self) @@ -41,10 +40,10 @@ extension Data { } extension String { - + public var sha1: String { let dataBytes = data(using: .utf8)! return dataBytes.sha1 } - + } diff --git a/Sources/Common/Extensions/JSONExtensions.swift b/Sources/Common/Extensions/JSONExtensions.swift index 4746cf74a..d75524bbc 100644 --- a/Sources/Common/Extensions/JSONExtensions.swift +++ b/Sources/Common/Extensions/JSONExtensions.swift @@ -1,6 +1,5 @@ // // JSONExtensions.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Extensions/NSObjectExtension.swift b/Sources/Common/Extensions/NSObjectExtension.swift index 816f8098b..783bfecfa 100644 --- a/Sources/Common/Extensions/NSObjectExtension.swift +++ b/Sources/Common/Extensions/NSObjectExtension.swift @@ -1,6 +1,5 @@ // // NSObjectExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Extensions/RunLoopExtension.swift b/Sources/Common/Extensions/RunLoopExtension.swift index abb13516b..54058b3e8 100644 --- a/Sources/Common/Extensions/RunLoopExtension.swift +++ b/Sources/Common/Extensions/RunLoopExtension.swift @@ -1,6 +1,5 @@ // // RunLoopExtension.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Extensions/StringExtension.swift b/Sources/Common/Extensions/StringExtension.swift index 112c83cd7..613c4cb4c 100644 --- a/Sources/Common/Extensions/StringExtension.swift +++ b/Sources/Common/Extensions/StringExtension.swift @@ -74,16 +74,16 @@ public extension String { } return self } - + func autofillNormalized() -> String { let autofillCharacterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters).union(.symbols) - + var normalizedString = self normalizedString = normalizedString.removingCharacters(in: autofillCharacterSet) normalizedString = normalizedString.folding(options: .diacriticInsensitive, locale: .current) normalizedString = normalizedString.localizedLowercase - + return normalizedString } diff --git a/Sources/Common/Extensions/URLExtension.swift b/Sources/Common/Extensions/URLExtension.swift index 8739d1644..786bf87d4 100644 --- a/Sources/Common/Extensions/URLExtension.swift +++ b/Sources/Common/Extensions/URLExtension.swift @@ -18,7 +18,6 @@ import Foundation -// swiftlint:disable file_length extension URL { public static let empty = (NSURL(string: "") ?? NSURL()) as URL @@ -59,7 +58,7 @@ extension URL { components.password = nil return components.url } - + public var isRoot: Bool { (path.isEmpty || path == "/") && query == nil && @@ -73,7 +72,7 @@ extension URL { host: self.host ?? "", port: self.port ?? 0) } - + public func isPart(ofDomain domain: String) -> Bool { guard let host = host else { return false } return host == domain || host.hasSuffix(".\(domain)") @@ -112,7 +111,7 @@ extension URL { public static var schemesWithRemovableBasicAuth: [NavigationalScheme] { return [.http, .https, .ftp, .file] } - + public static var hypertextSchemes: [NavigationalScheme] { return [.http, .https] } @@ -240,7 +239,6 @@ extension URL { self.init(string: url) } - // swiftlint:disable:next large_tuple private static func fixupAndSplitURLString(_ s: String) -> (authData: String.SubSequence?, domainAndPath: String.SubSequence, query: String)? { let urlAndFragment = s.split(separator: "#", maxSplits: 1) guard !urlAndFragment.isEmpty else { return nil } @@ -284,15 +282,15 @@ extension URL { domainAndPath: urlAndQuery[0], query: query) } - + public func replacing(host: String?) -> URL? { guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { return self } components.host = host return components.url } - + // MARK: - HTTP/HTTPS - + public enum URLProtocol: String { case http case https @@ -308,20 +306,20 @@ extension URL { components.scheme = URLProtocol.https.rawValue return components.url } - + public var isHttp: Bool { scheme == "http" } - + public var isHttps: Bool { scheme == "https" } // MARK: - Parameters - + public func appendingParameters(_ parameters: QueryParams, allowedReservedCharacters: CharacterSet? = nil) -> URL where QueryParams.Element == (key: String, value: String) { - + return parameters.reduce(self) { partialResult, parameter in partialResult.appendingParameter( name: parameter.key, @@ -358,7 +356,7 @@ extension URL { }) return queryItem?.value } - + public func isThirdParty(to otherUrl: URL, tld: TLD) -> Bool { guard let thisHost = host else { return false @@ -368,7 +366,7 @@ extension URL { } let thisRoot = tld.eTLDplus1(thisHost) let otherRoot = tld.eTLDplus1(otherHost) - + return thisRoot != otherRoot } @@ -452,4 +450,3 @@ extension URLQueryItem { } } -// swiftlint:enable file_length diff --git a/Sources/Common/Extensions/UnsafeMutableRawPointerExtension.swift b/Sources/Common/Extensions/UnsafeMutableRawPointerExtension.swift index d1b36bd69..990d58792 100644 --- a/Sources/Common/Extensions/UnsafeMutableRawPointerExtension.swift +++ b/Sources/Common/Extensions/UnsafeMutableRawPointerExtension.swift @@ -1,6 +1,5 @@ // // UnsafeMutableRawPointerExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/InfoBundle.swift b/Sources/Common/InfoBundle.swift index 262fe1cdb..64f957801 100644 --- a/Sources/Common/InfoBundle.swift +++ b/Sources/Common/InfoBundle.swift @@ -1,6 +1,5 @@ // // InfoBundle.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,9 +19,9 @@ import Foundation public protocol InfoBundle { - + func object(forInfoDictionaryKey key: String) -> Any? - + } extension Bundle: InfoBundle { } diff --git a/Sources/Common/JsonError.swift b/Sources/Common/JsonError.swift index eb2ad4040..798a3a0de 100644 --- a/Sources/Common/JsonError.swift +++ b/Sources/Common/JsonError.swift @@ -1,6 +1,5 @@ // // JsonError.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Sources/Common/Logging.swift b/Sources/Common/Logging.swift index 79250940c..fe93cb221 100644 --- a/Sources/Common/Logging.swift +++ b/Sources/Common/Logging.swift @@ -1,6 +1,5 @@ // // Logging.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -18,7 +17,7 @@ // import Foundation -import os +import os // swiftlint:disable:this enforce_os_log_wrapper public typealias OSLog = os.OSLog diff --git a/Sources/Common/TLD/TLD.swift b/Sources/Common/TLD/TLD.swift index 2a7beb2c4..bb3616b9d 100644 --- a/Sources/Common/TLD/TLD.swift +++ b/Sources/Common/TLD/TLD.swift @@ -1,6 +1,5 @@ // // TLD.swift -// DuckDuckGo // // Copyright © 2018 DuckDuckGo. All rights reserved. // @@ -32,13 +31,13 @@ public class TLD { public init() { guard let url = Bundle.module.url(forResource: "tlds", withExtension: "json") else { return } guard let data = try? Data(contentsOf: url) else { return } - + let asString = String(decoding: data, as: UTF8.self) let asStringWithoutComments = asString.replacingOccurrences(of: "(?m)^//.*", with: "", options: .regularExpression) guard let cleanedData: Data = asStringWithoutComments.data(using: .utf8) else { return } - + guard let tlds = try? JSONDecoder().decode([String].self, from: cleanedData) else { return } self.tlds = Set(tlds) } @@ -52,20 +51,20 @@ public class TLD { guard let host = host else { return nil } let parts = [String](host.components(separatedBy: ".").reversed()) - + var stack = "" var knownTLDFound = false for part in parts { stack = !stack.isEmpty ? part + "." + stack : part - + if tlds.contains(stack) { knownTLDFound = true } else if knownTLDFound { break } } - + // If host does not contain tld treat it as invalid if knownTLDFound { return stack @@ -83,7 +82,7 @@ public class TLD { guard let domain = domain(host), !tlds.contains(domain) else { return nil } return domain } - + public func eTLDplus1(forStringURL stringURL: String) -> String? { guard let escapedStringURL = stringURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } guard let host = URL(string: escapedStringURL)?.host else { return nil } diff --git a/Sources/Configuration/Configuration.swift b/Sources/Configuration/Configuration.swift index 9ed575983..b37f55f80 100644 --- a/Sources/Configuration/Configuration.swift +++ b/Sources/Configuration/Configuration.swift @@ -1,6 +1,5 @@ // // Configuration.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,13 +19,13 @@ import Foundation public protocol ConfigurationURLProviding { - + func url(for configuration: Configuration) -> URL - + } public enum Configuration: String, CaseIterable, Sendable { - + case bloomFilterBinary case bloomFilterSpec case bloomFilterExcludedDomains @@ -34,15 +33,15 @@ public enum Configuration: String, CaseIterable, Sendable { case surrogates case trackerDataSet case FBConfig - + private static var urlProvider: ConfigurationURLProviding? public static func setURLProvider(_ urlProvider: ConfigurationURLProviding) { self.urlProvider = urlProvider } - + var url: URL { guard let urlProvider = Self.urlProvider else { fatalError("Please set the urlProvider before accessing url.") } return urlProvider.url(for: self) } - + } diff --git a/Sources/Configuration/ConfigurationFetching.swift b/Sources/Configuration/ConfigurationFetching.swift index e75dd2dc0..bf214ccc0 100644 --- a/Sources/Configuration/ConfigurationFetching.swift +++ b/Sources/Configuration/ConfigurationFetching.swift @@ -1,6 +1,5 @@ // // ConfigurationFetching.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -31,9 +30,9 @@ protocol ConfigurationFetching { typealias ConfigurationFetchResult = (etag: String, data: Data?) public final class ConfigurationFetcher: ConfigurationFetching { - + public enum Error: Swift.Error { - + case apiRequest(APIRequest.Error) case invalidPayload @@ -82,7 +81,7 @@ public final class ConfigurationFetcher: ConfigurationFetching { } try store(fetchResult, for: configuration) } - + /** Downloads and stores the configurations provided in parallel using a throwing task group. This function throws an error if any of the configurations fail to fetch or validate. @@ -120,14 +119,14 @@ public final class ConfigurationFetcher: ConfigurationFetching { } } } - + private func etag(for configuration: Configuration) -> String? { if let etag = store.loadEtag(for: configuration), store.loadData(for: configuration) != nil { return etag } return store.loadEmbeddedEtag(for: configuration) } - + private func fetch(from url: URL, withEtag etag: String?, requirements: APIResponseRequirements) async throws -> ConfigurationFetchResult { let configuration = APIRequest.Configuration(url: url, headers: APIRequest.Headers(etag: etag), @@ -144,5 +143,5 @@ public final class ConfigurationFetcher: ConfigurationFetching { try store.saveEtag(result.etag, for: configuration) } } - + } diff --git a/Sources/Configuration/ConfigurationStoring.swift b/Sources/Configuration/ConfigurationStoring.swift index e6e96cb1c..b44a26ace 100644 --- a/Sources/Configuration/ConfigurationStoring.swift +++ b/Sources/Configuration/ConfigurationStoring.swift @@ -1,6 +1,5 @@ // // ConfigurationStoring.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,12 +19,12 @@ import Foundation public protocol ConfigurationStoring { - + func loadData(for configuration: Configuration) -> Data? func loadEtag(for configuration: Configuration) -> String? func loadEmbeddedEtag(for configuration: Configuration) -> String? - + mutating func saveData(_ data: Data, for configuration: Configuration) throws mutating func saveEtag(_ etag: String, for configuration: Configuration) throws - + } diff --git a/Sources/Configuration/ConfigurationValidating.swift b/Sources/Configuration/ConfigurationValidating.swift index 1be6f7f5d..d1e015969 100644 --- a/Sources/Configuration/ConfigurationValidating.swift +++ b/Sources/Configuration/ConfigurationValidating.swift @@ -22,9 +22,9 @@ import TrackerRadarKit import Common protocol ConfigurationValidating { - + func validate(_ data: Data, for configuration: Configuration) throws - + } public struct ConfigurationValidator: ConfigurationValidating { @@ -58,5 +58,5 @@ public struct ConfigurationValidator: ConfigurationValidating { private func validateTrackerDataSet(with data: Data) throws { _ = try JSONDecoder().decode(TrackerData.self, from: data) } - + } diff --git a/Sources/ContentBlocking/DetectedRequest.swift b/Sources/ContentBlocking/DetectedRequest.swift index b87c389dd..935370a14 100644 --- a/Sources/ContentBlocking/DetectedRequest.swift +++ b/Sources/ContentBlocking/DetectedRequest.swift @@ -1,6 +1,5 @@ // // DetectedRequest.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -32,10 +31,10 @@ public enum AllowReason: String, Codable { case adClickAttribution case otherThirdPartyRequest } - + // Populated with relevant info at the point of detection. public struct DetectedRequest: Encodable { - + public let url: String public let eTLDplus1: String? public let state: BlockingState @@ -44,7 +43,7 @@ public struct DetectedRequest: Encodable { public let category: String? public let prevalence: Double? public let pageUrl: String - + public init(url: String, eTLDplus1: String?, knownTracker: KnownTracker?, entity: Entity?, state: BlockingState, pageUrl: String) { self.url = url self.eTLDplus1 = eTLDplus1 @@ -60,11 +59,11 @@ public struct DetectedRequest: Encodable { guard let escapedStringURL = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil } return URL(string: escapedStringURL)?.host } - + public var networkNameForDisplay: String { entityName ?? eTLDplus1 ?? url } - + public var isBlocked: Bool { state == .blocked } @@ -88,7 +87,7 @@ extension DetectedRequest: Hashable, Equatable { } extension BlockingState: Equatable, Hashable { - + public static func == (lhs: BlockingState, rhs: BlockingState) -> Bool { switch (lhs, rhs) { case (.blocked, .blocked): @@ -99,5 +98,5 @@ extension BlockingState: Equatable, Hashable { return false } } - + } diff --git a/Sources/Crashes/CrashCollection.swift b/Sources/Crashes/CrashCollection.swift index 9fa65323b..8c6ca4ac6 100644 --- a/Sources/Crashes/CrashCollection.swift +++ b/Sources/Crashes/CrashCollection.swift @@ -31,7 +31,7 @@ public struct CrashCollection { MXMetricManager.shared.add(collector) } - class CrashCollector: NSObject, MXMetricManagerSubscriber { + final class CrashCollector: NSObject, MXMetricManagerSubscriber { var completion: ([String: String]) -> Void = { _ in } diff --git a/Sources/DDGSync/DDGSyncing.swift b/Sources/DDGSync/DDGSyncing.swift index bd0f619e2..76869d90c 100644 --- a/Sources/DDGSync/DDGSyncing.swift +++ b/Sources/DDGSync/DDGSyncing.swift @@ -97,7 +97,7 @@ public protocol DDGSyncing: DDGSyncingDebuggingSupport { * Generate secure keys * Call /signup endpoint * Store Primary Key, Secret Key, User Id and JWT token - + Notes: * The primary key in combination with the user id, is the recovery code. This can be used to (re)connect devices. * The JWT token contains the authorisation required to call an endpoint. If a device is removed from sync the token will be invalidated on the server and subsequent calls will fail. diff --git a/Sources/DDGSync/DataProvider.swift b/Sources/DDGSync/DataProvider.swift index 3fca66c76..e733851d3 100644 --- a/Sources/DDGSync/DataProvider.swift +++ b/Sources/DDGSync/DataProvider.swift @@ -1,6 +1,5 @@ // // DataProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/RecoveryPDFGenerator.swift b/Sources/DDGSync/RecoveryPDFGenerator.swift index ca165a98d..a7120ac0b 100644 --- a/Sources/DDGSync/RecoveryPDFGenerator.swift +++ b/Sources/DDGSync/RecoveryPDFGenerator.swift @@ -101,7 +101,7 @@ public struct RecoveryPDFGenerator { func qrcode(_ text: String, size: Int) -> CGImage { let data = Data(text.utf8) - let qrCodeFilter: CIFilter = CIFilter.init(name: "CIQRCodeGenerator")! + let qrCodeFilter: CIFilter = CIFilter(name: "CIQRCodeGenerator")! qrCodeFilter.setValue(data, forKey: "inputMessage") qrCodeFilter.setValue("H", forKey: "inputCorrectionLevel") diff --git a/Sources/DDGSync/SyncFeatureEntity.swift b/Sources/DDGSync/SyncFeatureEntity.swift index 34f3d07b9..896ce1bfb 100644 --- a/Sources/DDGSync/SyncFeatureEntity.swift +++ b/Sources/DDGSync/SyncFeatureEntity.swift @@ -1,6 +1,5 @@ // // SyncFeatureEntity.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/SyncModels.swift b/Sources/DDGSync/SyncModels.swift index 4156031cb..88215a5e0 100644 --- a/Sources/DDGSync/SyncModels.swift +++ b/Sources/DDGSync/SyncModels.swift @@ -1,6 +1,5 @@ // -// File.swift -// DuckDuckGo +// SyncModels.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/SyncableSettingsMetadata.swift b/Sources/DDGSync/SyncableSettingsMetadata.swift index 62e568724..d762d47c2 100644 --- a/Sources/DDGSync/SyncableSettingsMetadata.swift +++ b/Sources/DDGSync/SyncableSettingsMetadata.swift @@ -1,6 +1,5 @@ // // SyncableSettingsMetadata.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/internal/BookmarkUpdate.swift b/Sources/DDGSync/internal/BookmarkUpdate.swift index 7d28767c8..d874a945c 100644 --- a/Sources/DDGSync/internal/BookmarkUpdate.swift +++ b/Sources/DDGSync/internal/BookmarkUpdate.swift @@ -19,7 +19,7 @@ import Foundation struct BookmarkUpdate: Codable { - + let id: String? let next: String? let parent: String? @@ -30,15 +30,15 @@ struct BookmarkUpdate: Codable { let folder: Folder? let deleted: String? - + struct Page: Codable { let url: String? } - + struct Favorite: Codable { let next: String? } - + struct Folder: Codable { } } diff --git a/Sources/DDGSync/internal/Crypter.swift b/Sources/DDGSync/internal/Crypter.swift index 47741e926..fff7ec7ff 100644 --- a/Sources/DDGSync/internal/Crypter.swift +++ b/Sources/DDGSync/internal/Crypter.swift @@ -95,7 +95,7 @@ struct Crypter: CryptingInternal { func extractLoginInfo(recoveryKey: SyncCode.RecoveryKey) throws -> ExtractedLoginInfo { let primaryKeySize = Int(DDGSYNCCRYPTO_PRIMARY_KEY_SIZE.rawValue) - + var primaryKeyBytes = [UInt8](repeating: 0, count: primaryKeySize) var passwordHashBytes = [UInt8](repeating: 0, count: Int(DDGSYNCCRYPTO_HASH_SIZE.rawValue)) var strechedPrimaryKeyBytes = [UInt8](repeating: 0, count: Int(DDGSYNCCRYPTO_STRETCHED_PRIMARY_KEY_SIZE.rawValue)) @@ -106,7 +106,7 @@ struct Crypter: CryptingInternal { guard DDGSYNCCRYPTO_OK == result else { throw SyncError.failedToCreateAccountKeys("ddgSyncPrepareForLogin failed: \(result)") } - + return ExtractedLoginInfo( userId: recoveryKey.userId, primaryKey: Data(primaryKeyBytes), diff --git a/Sources/DDGSync/internal/Endpoints.swift b/Sources/DDGSync/internal/Endpoints.swift index 161394dad..1985314bf 100644 --- a/Sources/DDGSync/internal/Endpoints.swift +++ b/Sources/DDGSync/internal/Endpoints.swift @@ -18,7 +18,7 @@ import Foundation -class Endpoints { +final class Endpoints { private(set) var baseURL: URL diff --git a/Sources/DDGSync/internal/KeyValueStore.swift b/Sources/DDGSync/internal/KeyValueStore.swift index 1d108d99d..5100945b6 100644 --- a/Sources/DDGSync/internal/KeyValueStore.swift +++ b/Sources/DDGSync/internal/KeyValueStore.swift @@ -1,5 +1,5 @@ // -// SecureStorage.swift +// KeyValueStore.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/internal/ProductionDependencies.swift b/Sources/DDGSync/internal/ProductionDependencies.swift index 06682ac8e..c40bfa019 100644 --- a/Sources/DDGSync/internal/ProductionDependencies.swift +++ b/Sources/DDGSync/internal/ProductionDependencies.swift @@ -37,7 +37,7 @@ struct ProductionDependencies: SyncDependencies { private let getLog: () -> OSLog init(serverEnvironment: ServerEnvironment, errorEvents: EventMapping, log: @escaping @autoclosure () -> OSLog = .disabled) { - + self.init(fileStorageUrl: FileManager.default.applicationSupportDirectoryForComponent(named: "Sync"), serverEnvironment: serverEnvironment, keyValueStore: KeyValueStore(), @@ -45,7 +45,7 @@ struct ProductionDependencies: SyncDependencies { errorEvents: errorEvents, log: log()) } - + init( fileStorageUrl: URL, serverEnvironment: ServerEnvironment, diff --git a/Sources/DDGSync/internal/RecoveryKeyTransmitter.swift b/Sources/DDGSync/internal/RecoveryKeyTransmitter.swift index bc8a2719a..a05de4615 100644 --- a/Sources/DDGSync/internal/RecoveryKeyTransmitter.swift +++ b/Sources/DDGSync/internal/RecoveryKeyTransmitter.swift @@ -1,6 +1,5 @@ // // RecoveryKeyTransmitter.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/internal/RemoteAPIRequestCreatingExtensions.swift b/Sources/DDGSync/internal/RemoteAPIRequestCreatingExtensions.swift index 3149d5339..e67c1e7f5 100644 --- a/Sources/DDGSync/internal/RemoteAPIRequestCreatingExtensions.swift +++ b/Sources/DDGSync/internal/RemoteAPIRequestCreatingExtensions.swift @@ -1,6 +1,5 @@ // -// RemoteAPIRequestCreating.swift -// DuckDuckGo +// RemoteAPIRequestCreatingExtensions.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/internal/RemoteAPIRequestCreator.swift b/Sources/DDGSync/internal/RemoteAPIRequestCreator.swift index 1ed3b51d2..c0cf85b45 100644 --- a/Sources/DDGSync/internal/RemoteAPIRequestCreator.swift +++ b/Sources/DDGSync/internal/RemoteAPIRequestCreator.swift @@ -1,5 +1,5 @@ // -// RemoteAPIRequestCreating.swift +// RemoteAPIRequestCreator.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/DDGSync/internal/RemoteConnector.swift b/Sources/DDGSync/internal/RemoteConnector.swift index f9c729a59..cc00c85e8 100644 --- a/Sources/DDGSync/internal/RemoteConnector.swift +++ b/Sources/DDGSync/internal/RemoteConnector.swift @@ -1,5 +1,5 @@ // -// RemoteAPIRequestCreating.swift +// RemoteConnector.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -18,8 +18,8 @@ import Foundation -class RemoteConnector: RemoteConnecting { - +final class RemoteConnector: RemoteConnecting { + let code: String let connectInfo: ConnectInfo diff --git a/Sources/DDGSync/internal/SecureStorage.swift b/Sources/DDGSync/internal/SecureStorage.swift index 361be331d..74871c650 100644 --- a/Sources/DDGSync/internal/SecureStorage.swift +++ b/Sources/DDGSync/internal/SecureStorage.swift @@ -23,17 +23,17 @@ struct SecureStorage: SecureStoring { // DO NOT CHANGE except if you want to deliberately invalidate all users's sync accounts. // The keys have a uid to deter casual hacker from easily seeing which keychain entry is related to what. private static let encodedKey = "833CC26A-3804-4D37-A82A-C245BC670692".data(using: .utf8) - + private static let defaultQuery: [AnyHashable: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "\(Bundle.main.bundleIdentifier ?? "com.duckduckgo").sync", kSecAttrGeneric: encodedKey as Any, kSecAttrAccount: encodedKey as Any ] - + func persistAccount(_ account: SyncAccount) throws { let data = try JSONEncoder.snakeCaseKeys.encode(account) - + var query = Self.defaultQuery query[kSecUseDataProtectionKeychain] = true query[kSecAttrAccessible] = kSecAttrAccessibleWhenUnlocked @@ -63,11 +63,11 @@ struct SecureStorage: SecureStoring { guard [errSecSuccess, errSecItemNotFound].contains(status) else { throw SyncError.failedToReadSecureStore(status: status) } - + if let data = item as? Data { return try JSONDecoder.snakeCaseKeys.decode(SyncAccount.self, from: data) } - + return nil } @@ -77,5 +77,5 @@ struct SecureStorage: SecureStoring { throw SyncError.failedToRemoveSecureStore(status: status) } } - + } diff --git a/Sources/DDGSync/internal/SyncOperation.swift b/Sources/DDGSync/internal/SyncOperation.swift index e702f687f..498a48624 100644 --- a/Sources/DDGSync/internal/SyncOperation.swift +++ b/Sources/DDGSync/internal/SyncOperation.swift @@ -1,6 +1,5 @@ // // SyncOperation.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation import Combine import Common -class SyncOperation: Operation { +final class SyncOperation: Operation { let dataProviders: [DataProviding] let storage: SecureStoring diff --git a/Sources/DDGSync/internal/SyncQueue.swift b/Sources/DDGSync/internal/SyncQueue.swift index 889c758bf..9230a426b 100644 --- a/Sources/DDGSync/internal/SyncQueue.swift +++ b/Sources/DDGSync/internal/SyncQueue.swift @@ -1,6 +1,5 @@ // // SyncQueue.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -30,9 +29,9 @@ struct SyncOperationError: Error { let perFeatureErrors: [Feature: Error] init(featureErrors: [FeatureError]) { - perFeatureErrors = featureErrors.reduce(into: .init(), { partialResult, featureError in + perFeatureErrors = featureErrors.reduce(into: .init()) { partialResult, featureError in partialResult[featureError.feature] = featureError.underlyingError - }) + } } } @@ -53,7 +52,7 @@ struct SyncResult { } } -class SyncQueue { +final class SyncQueue { let dataProviders: [DataProviding] let storage: SecureStoring diff --git a/Sources/DDGSync/internal/SyncRequestMaker.swift b/Sources/DDGSync/internal/SyncRequestMaker.swift index 57bd507c3..12382e667 100644 --- a/Sources/DDGSync/internal/SyncRequestMaker.swift +++ b/Sources/DDGSync/internal/SyncRequestMaker.swift @@ -1,6 +1,5 @@ // // SyncRequestMaker.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -26,7 +25,7 @@ protocol SyncRequestMaking { struct SyncRequestMaker: SyncRequestMaking { let storage: SecureStoring - let api: RemoteAPIRequestCreating + let api: RemoteAPIRequestCreating let endpoints: Endpoints let dateFormatter = ISO8601DateFormatter() diff --git a/Sources/DDGSync/internal/SyncScheduler.swift b/Sources/DDGSync/internal/SyncScheduler.swift index 0d5c28a06..b99e016a8 100644 --- a/Sources/DDGSync/internal/SyncScheduler.swift +++ b/Sources/DDGSync/internal/SyncScheduler.swift @@ -1,6 +1,5 @@ // // SyncScheduler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -34,7 +33,7 @@ protocol SchedulingInternal: AnyObject, Scheduling { var resumeSyncPublisher: AnyPublisher { get } } -class SyncScheduler: SchedulingInternal { +final class SyncScheduler: SchedulingInternal { func notifyDataChanged() { if isEnabled { syncTriggerSubject.send() diff --git a/Sources/Navigation/AuthChallengeDisposition.swift b/Sources/Navigation/AuthChallengeDisposition.swift index 30f5926e6..059c62b1f 100644 --- a/Sources/Navigation/AuthChallengeDisposition.swift +++ b/Sources/Navigation/AuthChallengeDisposition.swift @@ -48,7 +48,7 @@ public enum AuthChallengeDisposition: Sendable { return "rejectProtectionSpace" } } - + } extension AuthChallengeDisposition? { diff --git a/Sources/Navigation/DistributedNavigationDelegate.swift b/Sources/Navigation/DistributedNavigationDelegate.swift index 1966393e8..3f94ac3c0 100644 --- a/Sources/Navigation/DistributedNavigationDelegate.swift +++ b/Sources/Navigation/DistributedNavigationDelegate.swift @@ -21,8 +21,6 @@ import Common import Foundation import WebKit -// swiftlint:disable file_length -// swiftlint:disable line_length public final class DistributedNavigationDelegate: NSObject { internal var responders = ResponderChain() @@ -951,7 +949,7 @@ extension DistributedNavigationDelegate { assert((handler.ref.responder as? NSObject)!.responds(to: selector)) customDelegateMethodHandlers[selector] = handler.ref } - + public func registerCustomDelegateMethodHandler(_ handler: ResponderRefMaker, forSelectorsNamed selectors: [String]) { for selector in selectors { registerCustomDelegateMethodHandler(handler, forSelectorNamed: selector) @@ -970,6 +968,3 @@ extension DistributedNavigationDelegate { } } - -// swiftlint:enable line_length -// swiftlint:enable file_length diff --git a/Sources/Navigation/Extensions/WKErrorExtension.swift b/Sources/Navigation/Extensions/WKErrorExtension.swift index 540593fc5..f1a5c238d 100644 --- a/Sources/Navigation/Extensions/WKErrorExtension.swift +++ b/Sources/Navigation/Extensions/WKErrorExtension.swift @@ -35,23 +35,9 @@ extension WKError { } -private protocol WKErrorProtocol { - static var _WebKitErrorDomain: String { get } -} - -extension WKError: WKErrorProtocol { +extension WKError { - // suppress WebKit.WebKitErrorDomain deprecation warning - @available(macOS, introduced: 10.3, deprecated: 10.14) - fileprivate static var _WebKitErrorDomain: String { -#if os(macOS) - assert(WebKit.WebKitErrorDomain == "WebKitErrorDomain") - return WebKit.WebKitErrorDomain -#else - return "WebKitErrorDomain" -#endif - } - static var WebKitErrorDomain: String { (self as WKErrorProtocol.Type)._WebKitErrorDomain } + static let WebKitErrorDomain = "WebKitErrorDomain" } diff --git a/Sources/Navigation/Extensions/WKFrameInfoExtension.swift b/Sources/Navigation/Extensions/WKFrameInfoExtension.swift index 88ebd3be5..b2badbff0 100644 --- a/Sources/Navigation/Extensions/WKFrameInfoExtension.swift +++ b/Sources/Navigation/Extensions/WKFrameInfoExtension.swift @@ -72,7 +72,7 @@ public extension WKFrameInfo { breakByRaisingSigInt("Don‘t use `WKFrameInfo.request` as it has incorrect nullability\n" + "Use `WKFrameInfo.safeRequest` instead") } - + return self.swizzledRequest() // call the original } diff --git a/Sources/Navigation/Extensions/WKNavigationResponseExtension.swift b/Sources/Navigation/Extensions/WKNavigationResponseExtension.swift index 14bff5572..d60939f7c 100644 --- a/Sources/Navigation/Extensions/WKNavigationResponseExtension.swift +++ b/Sources/Navigation/Extensions/WKNavigationResponseExtension.swift @@ -1,5 +1,5 @@ // -// WKNavigationActionExtension.swift +// WKNavigationResponseExtension.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/Navigation/NavigationResponse.swift b/Sources/Navigation/NavigationResponse.swift index c5f503324..c82e86bae 100644 --- a/Sources/Navigation/NavigationResponse.swift +++ b/Sources/Navigation/NavigationResponse.swift @@ -70,7 +70,7 @@ public extension NavigationResponse { extension NavigationResponse: CustomDebugStringConvertible { public var debugDescription: String { - let statusCode = self.httpStatusCode.map { String.init($0) } ?? "-" + let statusCode = self.httpStatusCode.map { String($0) } ?? "-" return "" } } diff --git a/Sources/Navigation/NavigationType.swift b/Sources/Navigation/NavigationType.swift index ea324233f..ae4773ed1 100644 --- a/Sources/Navigation/NavigationType.swift +++ b/Sources/Navigation/NavigationType.swift @@ -105,7 +105,7 @@ public extension NavigationType { if case .redirect = self { return true } return false } - + var redirect: RedirectType? { if case .redirect(let redirect) = self { return redirect } return nil diff --git a/Sources/NetworkProtection/Controllers/TunnelController.swift b/Sources/NetworkProtection/Controllers/TunnelController.swift index d95f1348c..ea8834a9c 100644 --- a/Sources/NetworkProtection/Controllers/TunnelController.swift +++ b/Sources/NetworkProtection/Controllers/TunnelController.swift @@ -19,7 +19,7 @@ import Foundation /// This protocol offers an interface to control the tunnel. -/// +/// public protocol TunnelController { // MARK: - Starting & Stopping the VPN @@ -33,6 +33,6 @@ public protocol TunnelController { func stop() async /// Whether the tunnel is connected - /// + /// var isConnected: Bool { get async } } diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift index 11856ca30..508afb3cd 100644 --- a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift @@ -1,6 +1,5 @@ // // NetworkProtectionError.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionLatencyMonitor.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionLatencyMonitor.swift index 7a582dfac..3271c447f 100644 --- a/Sources/NetworkProtection/Diagnostics/NetworkProtectionLatencyMonitor.swift +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionLatencyMonitor.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionTunnelFailureMonitor.swift +// NetworkProtectionLatencyMonitor.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -149,7 +149,7 @@ final public class NetworkProtectionLatencyMonitor { } else { self?.subject.send(.error) } - + os_log("⚫️ Average: %{public}f milliseconds", log: .networkProtectionPixel, type: .debug, measurements.average) return measurements @@ -157,7 +157,7 @@ final public class NetworkProtectionLatencyMonitor { .map { ConnectionQuality(average: $0.average) } .sink { [weak self] quality in let now = Date() - if let self, + if let self, (now.timeIntervalSince1970 - self.lastLatencyReported.timeIntervalSince1970 >= Self.reportThreshold) || ignoreThreshold { self.subject.send(.quality(quality)) self.lastLatencyReported = now diff --git a/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift b/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift index d23d13e3e..ae8b5486a 100644 --- a/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift +++ b/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift @@ -1,6 +1,5 @@ // -// WireguardAdapterError+NetworkProtectionErrorConvertible.swift -// DuckDuckGo +// WireGuardAdapterError+NetworkProtectionErrorConvertible.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/ExtensionMessage/ExtensionMessage.swift b/Sources/NetworkProtection/ExtensionMessage/ExtensionMessage.swift index 57b478cfa..c2a5ae7cd 100644 --- a/Sources/NetworkProtection/ExtensionMessage/ExtensionMessage.swift +++ b/Sources/NetworkProtection/ExtensionMessage/ExtensionMessage.swift @@ -133,7 +133,7 @@ public enum ExtensionMessage: RawRepresentable { case .simulateConnectionInterruption: self = .simulateConnectionInterruption - + case .none: assertionFailure("Invalid data") return nil diff --git a/Sources/NetworkProtection/KeyManagement/KeyPair.swift b/Sources/NetworkProtection/KeyManagement/KeyPair.swift index fefcebab6..62ceabf1b 100644 --- a/Sources/NetworkProtection/KeyManagement/KeyPair.swift +++ b/Sources/NetworkProtection/KeyManagement/KeyPair.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionKeyStore.swift +// KeyPair.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/KeyManagement/NetworkProtectionKeychainStore.swift b/Sources/NetworkProtection/KeyManagement/NetworkProtectionKeychainStore.swift index 659cb691e..b28996c26 100644 --- a/Sources/NetworkProtection/KeyManagement/NetworkProtectionKeychainStore.swift +++ b/Sources/NetworkProtection/KeyManagement/NetworkProtectionKeychainStore.swift @@ -44,7 +44,7 @@ final class NetworkProtectionKeychainStore { init(label: String, serviceName: String, keychainType: KeychainType) { - + self.label = label self.serviceName = serviceName self.keychainType = keychainType diff --git a/Sources/NetworkProtection/Models/AnyIPAddress.swift b/Sources/NetworkProtection/Models/AnyIPAddress.swift index c435f8fec..6677e0833 100644 --- a/Sources/NetworkProtection/Models/AnyIPAddress.swift +++ b/Sources/NetworkProtection/Models/AnyIPAddress.swift @@ -131,7 +131,7 @@ extension AnyIPAddress: Codable { public init(from decoder: Decoder) throws { let string = try decoder.singleValueContainer().decode(String.self) - guard let address = Self.init(string) else { + guard let address = Self(string) else { throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Could not decode IP from \(string)", underlyingError: nil)) diff --git a/Sources/NetworkProtection/Models/NetworkProtectionLocation.swift b/Sources/NetworkProtection/Models/NetworkProtectionLocation.swift index 6317d1ea9..7f38da2ef 100644 --- a/Sources/NetworkProtection/Models/NetworkProtectionLocation.swift +++ b/Sources/NetworkProtection/Models/NetworkProtectionLocation.swift @@ -1,6 +1,5 @@ // // NetworkProtectionLocation.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Networking/NWConnectionExtension.swift b/Sources/NetworkProtection/Networking/NWConnectionExtension.swift index 1b260e4db..e3d3ed867 100644 --- a/Sources/NetworkProtection/Networking/NWConnectionExtension.swift +++ b/Sources/NetworkProtection/Networking/NWConnectionExtension.swift @@ -24,7 +24,7 @@ extension NWConnection { var stateUpdateStream: AsyncStream { let (stream, continuation) = AsyncStream.makeStream(of: State.self) - class ConnectionLifeTimeTracker { + final class ConnectionLifeTimeTracker { let continuation: AsyncStream.Continuation init(continuation: AsyncStream.Continuation) { self.continuation = continuation diff --git a/Sources/NetworkProtection/Networking/Pinger.swift b/Sources/NetworkProtection/Networking/Pinger.swift index 743851029..bdb70981e 100644 --- a/Sources/NetworkProtection/Networking/Pinger.swift +++ b/Sources/NetworkProtection/Networking/Pinger.swift @@ -16,7 +16,7 @@ // limitations under the License. // -// swiftlint:disable file_length identifier_name +// swiftlint:disable identifier_name import Darwin import Foundation @@ -493,7 +493,9 @@ struct Socket { func setopt(_ level: Int32, _ opt: Int32, value: T) { var value = value - let result = setsockopt(socket, level, opt, &value, socklen_t(MemoryLayout.size)) + let result = withUnsafePointer(to: &value) { valuePtr in + setsockopt(socket, level, opt, valuePtr, socklen_t(MemoryLayout.size)) + } assert(result == 0) } @@ -626,4 +628,4 @@ struct Socket { } -// swiftlint:enable file_length identifier_name +// swiftlint:enable identifier_name diff --git a/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift b/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift index 12108a1e8..359c318d5 100644 --- a/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift +++ b/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift @@ -1,5 +1,5 @@ // -// DistributedNotification.swift +// NetworkProtectionNotification.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index f2634d486..ad85bb378 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -25,7 +25,7 @@ import Foundation import NetworkExtension import UserNotifications -// swiftlint:disable file_length type_body_length line_length +// swiftlint:disable:next type_body_length open class PacketTunnelProvider: NEPacketTunnelProvider { public enum Event { @@ -125,7 +125,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } } - public let lastSelectedServerInfoPublisher = CurrentValueSubject.init(nil) + public let lastSelectedServerInfoPublisher = CurrentValueSubject(nil) private var includedRoutes: [IPAddressRange]? @@ -255,9 +255,9 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } }() - public lazy var tunnelFailureMonitor = NetworkProtectionTunnelFailureMonitor(tunnelProvider: self, - timerQueue: timerQueue, - log: .networkProtectionPixel) + public lazy var tunnelFailureMonitor = NetworkProtectionTunnelFailureMonitor(tunnelProvider: self, + timerQueue: timerQueue, + log: .networkProtectionPixel) public lazy var latencyMonitor = NetworkProtectionLatencyMonitor(serverIP: { [weak self] in self?.lastSelectedServerInfo?.ipv4 }, timerQueue: timerQueue, @@ -581,7 +581,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } private func startTunnel(with tunnelConfiguration: TunnelConfiguration, onDemand: Bool, completionHandler: @escaping (Error?) -> Void) { - + adapter.start(tunnelConfiguration: tunnelConfiguration) { [weak self] error in if let error { os_log("🔵 Starting tunnel failed with %{public}@", log: .networkProtection, type: .error, error.localizedDescription) @@ -1144,4 +1144,3 @@ extension WireGuardAdapterError: LocalizedError, CustomDebugStringConvertible { } } -// swiftlint:enable file_length type_body_length line_length diff --git a/Sources/NetworkProtection/Repositories/NetworkProtectionLocationListRepository.swift b/Sources/NetworkProtection/Repositories/NetworkProtectionLocationListRepository.swift index 207fe5020..c4ac37443 100644 --- a/Sources/NetworkProtection/Repositories/NetworkProtectionLocationListRepository.swift +++ b/Sources/NetworkProtection/Repositories/NetworkProtectionLocationListRepository.swift @@ -29,7 +29,7 @@ final public class NetworkProtectionLocationListCompositeRepository: NetworkProt private let tokenStore: NetworkProtectionTokenStore private let errorEvents: EventMapping - convenience public init(environment: VPNSettings.SelectedEnvironment, + convenience public init(environment: VPNSettings.SelectedEnvironment, tokenStore: NetworkProtectionTokenStore, errorEvents: EventMapping) { self.init( @@ -39,7 +39,7 @@ final public class NetworkProtectionLocationListCompositeRepository: NetworkProt ) } - init(client: NetworkProtectionClient, + init(client: NetworkProtectionClient, tokenStore: NetworkProtectionTokenStore, errorEvents: EventMapping) { self.client = client diff --git a/Sources/NetworkProtection/Settings/Extensions/UserDefaults+vpnFirstEnabled.swift b/Sources/NetworkProtection/Settings/Extensions/UserDefaults+vpnFirstEnabled.swift index dbf9b1f30..0dbd4f4c1 100644 --- a/Sources/NetworkProtection/Settings/Extensions/UserDefaults+vpnFirstEnabled.swift +++ b/Sources/NetworkProtection/Settings/Extensions/UserDefaults+vpnFirstEnabled.swift @@ -1,5 +1,5 @@ // -// UserDefaults+showInMenuBar.swift +// UserDefaults+vpnFirstEnabled.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Settings/VPNSettings.swift b/Sources/NetworkProtection/Settings/VPNSettings.swift index e9c7fb3db..8e7411190 100644 --- a/Sources/NetworkProtection/Settings/VPNSettings.swift +++ b/Sources/NetworkProtection/Settings/VPNSettings.swift @@ -19,8 +19,6 @@ import Combine import Foundation -// swiftlint:disable type_body_length file_length - /// Persists and publishes changes to tunnel settings. /// /// It's strongly recommended to use shared `UserDefaults` to initialize this class, as `VPNSettings` @@ -426,5 +424,3 @@ public final class VPNSettings { } } } - -// swiftlint:enable type_body_length file_length diff --git a/Sources/NetworkProtection/Status/ConnectionErrorObserver/ConnectionErrorObserverThroughSession.swift b/Sources/NetworkProtection/Status/ConnectionErrorObserver/ConnectionErrorObserverThroughSession.swift index ced2281e9..52dcbc348 100644 --- a/Sources/NetworkProtection/Status/ConnectionErrorObserver/ConnectionErrorObserverThroughSession.swift +++ b/Sources/NetworkProtection/Status/ConnectionErrorObserver/ConnectionErrorObserverThroughSession.swift @@ -1,5 +1,5 @@ // -// ConnectionErrorObserver.swift +// ConnectionErrorObserverThroughSession.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserver.swift b/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserver.swift index a540b64ba..770b168c7 100644 --- a/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserver.swift +++ b/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserver.swift @@ -1,5 +1,5 @@ // -// ConnectionStatusObserver.swift +// ConnectionServerInfoObserver.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserverThroughSession.swift b/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserverThroughSession.swift index a445a24e8..8bd716219 100644 --- a/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserverThroughSession.swift +++ b/Sources/NetworkProtection/Status/ConnectionServerInfoObserver/ConnectionServerInfoObserverThroughSession.swift @@ -32,7 +32,7 @@ public class ConnectionServerInfoObserverThroughSession: ConnectionServerInfoObs } private let subject = CurrentValueSubject(.unknown) - + // MARK: - Notifications private let notificationCenter: NotificationCenter diff --git a/Sources/NetworkProtection/Status/UserNotifications/NetworkProtectionNotificationsPresenterTogglableDecorator.swift b/Sources/NetworkProtection/Status/UserNotifications/NetworkProtectionNotificationsPresenterTogglableDecorator.swift index 8a1b06050..1fe81d07d 100644 --- a/Sources/NetworkProtection/Status/UserNotifications/NetworkProtectionNotificationsPresenterTogglableDecorator.swift +++ b/Sources/NetworkProtection/Status/UserNotifications/NetworkProtectionNotificationsPresenterTogglableDecorator.swift @@ -1,6 +1,5 @@ // // NetworkProtectionNotificationsPresenterTogglableDecorator.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionSelectedServerStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionSelectedServerStore.swift index d93197465..f87ade666 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionSelectedServerStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionSelectedServerStore.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionServerSelection.swift +// NetworkProtectionSelectedServerStore.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -19,7 +19,7 @@ import Foundation /* protocol NetworkProtectionSelectedServerStore: AnyObject { - + var selectedServer: SelectedNetworkProtectionServer { get set } func reset() } diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionServerListStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionServerListStore.swift index dda442fe0..e3f36833f 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionServerListStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionServerListStore.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionServer.swift +// NetworkProtectionServerListStore.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionSimulationOptionsStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionSimulationOptionsStore.swift index 498638aaf..ccb56a530 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionSimulationOptionsStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionSimulationOptionsStore.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionSimulationOption.swift +// NetworkProtectionSimulationOptionsStore.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionTunnelHealthStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionTunnelHealthStore.swift index 10e334afb..9a0badd35 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionTunnelHealthStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionTunnelHealthStore.swift @@ -57,7 +57,7 @@ public final class NetworkProtectionTunnelHealthStore { get { userDefaults.bool(forKey: Self.isHavingConnectivityIssuesKey) } - + set { guard newValue != userDefaults.bool(forKey: Self.isHavingConnectivityIssuesKey) else { return diff --git a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift index c25b70578..6da060ae1 100644 --- a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift +++ b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift @@ -7,8 +7,6 @@ import NetworkExtension import WireGuard import Common -// swiftlint:disable file_length - public enum WireGuardAdapterError: Error { /// Failure to locate tunnel file descriptor. case cannotLocateTunnelFileDescriptor @@ -38,7 +36,6 @@ private enum State { case temporaryShutdown(_ settingsGenerator: PacketTunnelSettingsGenerator) } -// swiftlint:disable:next type_body_length public class WireGuardAdapter { public typealias LogHandler = (WireGuardLogLevel, String) -> Void @@ -228,14 +225,14 @@ public class WireGuardAdapter { continuation.resume(throwing: GetBytesTransmittedError.couldNotObtainAdapterConfiguration) return } - + var numberOfSeconds = UInt64(0) let lines = configuration.components(separatedBy: .newlines) for line in lines where line.hasPrefix(ConfigurationFields.mostRecentHandshake.configLinePrefix) { numberOfSeconds = UInt64(line.dropFirst(ConfigurationFields.mostRecentHandshake.configLinePrefix.count)) ?? 0 break } - + continuation.resume(returning: TimeInterval(numberOfSeconds)) } } @@ -582,5 +579,3 @@ private extension Network.NWPath.Status { } } } - -// swiftlint:enable file_length diff --git a/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift b/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift index bb36af2a2..44a12cae6 100644 --- a/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift +++ b/Sources/NetworkProtectionTestUtils/Controllers/MockTunnelController.swift @@ -1,6 +1,5 @@ // // MockTunnelController.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtectionTestUtils/FeatureActivation/NetworkProtectionCodeRedemptionCoordinatorTestExtensions.swift b/Sources/NetworkProtectionTestUtils/FeatureActivation/NetworkProtectionCodeRedemptionCoordinatorTestExtensions.swift index 7e52003b9..8642e7628 100644 --- a/Sources/NetworkProtectionTestUtils/FeatureActivation/NetworkProtectionCodeRedemptionCoordinatorTestExtensions.swift +++ b/Sources/NetworkProtectionTestUtils/FeatureActivation/NetworkProtectionCodeRedemptionCoordinatorTestExtensions.swift @@ -1,6 +1,5 @@ // -// NetworkProtectionRedemptionCoordinatorTestExtensions.swift -// DuckDuckGo +// NetworkProtectionCodeRedemptionCoordinatorTestExtensions.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/NetworkProtectionTestUtils/KeyManagement/MockNetworkProtectionTokenStore.swift b/Sources/NetworkProtectionTestUtils/KeyManagement/MockNetworkProtectionTokenStore.swift index e313ed9ba..673ee410a 100644 --- a/Sources/NetworkProtectionTestUtils/KeyManagement/MockNetworkProtectionTokenStore.swift +++ b/Sources/NetworkProtectionTestUtils/KeyManagement/MockNetworkProtectionTokenStore.swift @@ -1,6 +1,5 @@ // -// MockNetworkProtectionTokenStorage.swift -// DuckDuckGo +// MockNetworkProtectionTokenStore.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -23,7 +22,7 @@ import NetworkProtection public final class MockNetworkProtectionTokenStorage: NetworkProtectionTokenStore { public init() { - + } var spyToken: String? diff --git a/Sources/NetworkProtectionTestUtils/Networking/MockNetworkProtectionClient.swift b/Sources/NetworkProtectionTestUtils/Networking/MockNetworkProtectionClient.swift index a4d4a840e..a845e1a8b 100644 --- a/Sources/NetworkProtectionTestUtils/Networking/MockNetworkProtectionClient.swift +++ b/Sources/NetworkProtectionTestUtils/Networking/MockNetworkProtectionClient.swift @@ -1,6 +1,5 @@ // // MockNetworkProtectionClient.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -35,7 +34,7 @@ public final class MockNetworkProtectionClient: NetworkProtectionClient { spyGetLocationsAuthToken = authToken return stubGetLocations } - + public var spyRedeemInviteCode: String? public var stubRedeem: Result = .success("") public var redeemCalled: Bool { diff --git a/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift b/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift index c17dc19be..b8487ccad 100644 --- a/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift +++ b/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift @@ -1,5 +1,5 @@ // -// NetworkProtectionStatusReporter.swift +// MockNetworkProtectionStatusReporter.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -54,7 +54,7 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR // MARK: - Forcing Refreshes public func forceRefresh() { - + } } diff --git a/Sources/Networking/APIHeaders.swift b/Sources/Networking/APIHeaders.swift index 33160e3b3..8cae4962b 100644 --- a/Sources/Networking/APIHeaders.swift +++ b/Sources/Networking/APIHeaders.swift @@ -1,6 +1,5 @@ // // APIHeaders.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -22,15 +21,15 @@ import Foundation public typealias HTTPHeaders = [String: String] public extension APIRequest { - + struct Headers { - + public typealias UserAgent = String private static var userAgent: UserAgent? public static func setUserAgent(_ userAgent: UserAgent) { self.userAgent = userAgent } - + let userAgent: UserAgent let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5" let acceptLanguage: String = { @@ -48,7 +47,7 @@ public extension APIRequest { self.etag = etag self.additionalHeaders = additionalHeaders } - + public var httpHeaders: HTTPHeaders { var headers = [ HTTPHeaderField.acceptEncoding: acceptEncoding, @@ -63,7 +62,7 @@ public extension APIRequest { } return headers } - + } - + } diff --git a/Sources/Networking/APIRequest.swift b/Sources/Networking/APIRequest.swift index 6d374f9d4..40409e1e7 100644 --- a/Sources/Networking/APIRequest.swift +++ b/Sources/Networking/APIRequest.swift @@ -1,6 +1,5 @@ // // APIRequest.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -24,7 +23,7 @@ public typealias APIResponse = (data: Data?, response: HTTPURLResponse) public typealias APIRequestCompletion = (APIResponse?, APIRequest.Error?) -> Void public struct APIRequest { - + private let request: URLRequest private let requirements: APIResponseRequirements private let urlSession: URLSession @@ -41,10 +40,10 @@ public struct APIRequest { self.requirements = requirements self.urlSession = urlSession self.getLog = log - + assertUserAgentIsPresent() } - + private func assertUserAgentIsPresent() { guard request.allHTTPHeaderFields?[HTTPHeaderField.userAgent] != nil else { assertionFailure("A user agent must be included in the request's HTTP header fields.") @@ -77,7 +76,7 @@ public struct APIRequest { task.resume() return task } - + private func validateAndUnwrap(data: Data?, response: URLResponse) throws -> APIResponse { let httpResponse = try response.asHTTPURLResponse() @@ -87,7 +86,7 @@ public struct APIRequest { request.httpMethod ?? "", request.url?.absoluteString ?? "", httpResponse.statusCode) - + var data = data if requirements.contains(.allowHTTPNotModified), httpResponse.statusCode == HTTPURLResponse.Constants.notModifiedStatusCode { data = nil // avoid returning empty data @@ -98,11 +97,11 @@ public struct APIRequest { throw APIRequest.Error.emptyData } } - + if requirements.contains(.requireETagHeader), httpResponse.etag == nil { throw APIRequest.Error.missingEtagInResponse } - + return (data, httpResponse) } @@ -116,7 +115,7 @@ public struct APIRequest { let (data, response) = try await fetch(for: request) return try validateAndUnwrap(data: data, response: response) } - + private func fetch(for request: URLRequest) async throws -> (Data, URLResponse) { do { return try await urlSession.data(for: request) diff --git a/Sources/Networking/APIRequestConfiguration.swift b/Sources/Networking/APIRequestConfiguration.swift index a9af67a0d..b9a7b9387 100644 --- a/Sources/Networking/APIRequestConfiguration.swift +++ b/Sources/Networking/APIRequestConfiguration.swift @@ -1,6 +1,5 @@ // // APIRequestConfiguration.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,9 +20,9 @@ import Foundation import Common extension APIRequest { - + public struct Configuration where QueryParams.Element == (key: String, value: String) { - + let url: URL let method: HTTPMethod let queryParameters: QueryParams @@ -33,7 +32,7 @@ extension APIRequest { let timeoutInterval: TimeInterval let attribution: URLRequestAttribution? let cachePolicy: URLRequest.CachePolicy? - + public init(url: URL, method: HTTPMethod = .get, queryParameters: QueryParams = [], @@ -53,7 +52,7 @@ extension APIRequest { self.attribution = attribution self.cachePolicy = cachePolicy } - + var request: URLRequest { let url = url.appendingParameters(queryParameters, allowedReservedCharacters: allowedQueryReservedCharacters) var request = URLRequest(url: url, timeoutInterval: timeoutInterval) @@ -70,7 +69,7 @@ extension APIRequest { } return request } - + } - + } diff --git a/Sources/Networking/APIRequestError.swift b/Sources/Networking/APIRequestError.swift index b85def5d3..98e54df6f 100644 --- a/Sources/Networking/APIRequestError.swift +++ b/Sources/Networking/APIRequestError.swift @@ -1,6 +1,5 @@ // // APIRequestError.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,15 +19,15 @@ import Foundation extension APIRequest { - + public enum Error: Swift.Error, LocalizedError { - + case urlSession(Swift.Error) case invalidResponse case missingEtagInResponse case emptyData case invalidStatusCode(Int) - + public var errorDescription: String? { switch self { case .urlSession(let error): @@ -44,5 +43,5 @@ extension APIRequest { } } } - + } diff --git a/Sources/Networking/APIResponseRequirements.swift b/Sources/Networking/APIResponseRequirements.swift index a97498bfe..32c0ea35b 100644 --- a/Sources/Networking/APIResponseRequirements.swift +++ b/Sources/Networking/APIResponseRequirements.swift @@ -1,6 +1,5 @@ // -// APIResponseRequirement.swift -// DuckDuckGo +// APIResponseRequirements.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,12 +19,12 @@ import Foundation public struct APIResponseRequirements: OptionSet { - + public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } - + /// The API response must have non-empty data. public static let requireNonEmptyData = APIResponseRequirements(rawValue: 1 << 0) /// The API response must include an ETag header. @@ -33,8 +32,8 @@ public struct APIResponseRequirements: OptionSet { /// Allows HTTP 304 (Not Modified) response status code. /// When this is set, requireNonEmptyData is not honored, since URLSession returns empty data on HTTP 304. public static let allowHTTPNotModified = APIResponseRequirements(rawValue: 1 << 2) - + public static let `default`: APIResponseRequirements = [.requireNonEmptyData, .requireETagHeader] public static let all: APIResponseRequirements = [.requireNonEmptyData, .requireETagHeader, .allowHTTPNotModified] - + } diff --git a/Sources/Networking/Extensions/HTTPConstants.swift b/Sources/Networking/Extensions/HTTPConstants.swift index f83198279..e1ceff9f5 100644 --- a/Sources/Networking/Extensions/HTTPConstants.swift +++ b/Sources/Networking/Extensions/HTTPConstants.swift @@ -1,6 +1,5 @@ // // HTTPConstants.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,20 +19,20 @@ import Foundation extension APIRequest { - + public enum HTTPHeaderField { - + public static let acceptEncoding = "Accept-Encoding" public static let acceptLanguage = "Accept-Language" public static let userAgent = "User-Agent" public static let etag = "ETag" public static let ifNoneMatch = "If-None-Match" public static let moreInfo = "X-DuckDuckGo-MoreInfo" - + } - + public enum HTTPMethod: String { - + case get = "GET" case head = "HEAD" case post = "POST" @@ -43,7 +42,7 @@ extension APIRequest { case options = "OPTIONS" case trace = "TRACE" case patch = "PATCH" - + } - + } diff --git a/Sources/Networking/Extensions/HTTPURLResponseExtension.swift b/Sources/Networking/Extensions/HTTPURLResponseExtension.swift index 498975c37..26c9a9dcd 100644 --- a/Sources/Networking/Extensions/HTTPURLResponseExtension.swift +++ b/Sources/Networking/Extensions/HTTPURLResponseExtension.swift @@ -1,6 +1,5 @@ // // HTTPURLResponseExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,23 +20,23 @@ import Foundation import Common public extension HTTPURLResponse { - + enum Constants { - + static let weakEtagPrefix = "W/" static let successfulStatusCodes = 200..<300 static let notModifiedStatusCode = 304 - + } - + func assertStatusCode(_ acceptedStatusCodes: S) throws where S.Iterator.Element == Int { guard acceptedStatusCodes.contains(statusCode) else { throw APIRequest.Error.invalidStatusCode(statusCode) } } - + func assertSuccessfulStatusCode() throws { try assertStatusCode(Constants.successfulStatusCodes) } - + var isSuccessfulResponse: Bool { do { try assertSuccessfulStatusCode() @@ -46,7 +45,7 @@ public extension HTTPURLResponse { return false } } - + func etag(droppingWeakPrefix: Bool) -> String? { let etag = value(forHTTPHeaderField: APIRequest.HTTPHeaderField.etag) if droppingWeakPrefix { @@ -54,7 +53,7 @@ public extension HTTPURLResponse { } return etag } - + var etag: String? { etag(droppingWeakPrefix: true) } - + } diff --git a/Sources/Networking/Extensions/URLRequestAttribution.swift b/Sources/Networking/Extensions/URLRequestAttribution.swift index 27a3e9221..d54e88cdb 100644 --- a/Sources/Networking/Extensions/URLRequestAttribution.swift +++ b/Sources/Networking/Extensions/URLRequestAttribution.swift @@ -1,6 +1,5 @@ // // URLRequestAttribution.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,11 +20,11 @@ import Foundation import Common public enum URLRequestAttribution { - + case unattributed case developer case user - + @available(iOS 15.0, macOS 12.0, *) public var urlRequestAttribution: URLRequest.Attribution? { switch self { @@ -37,5 +36,5 @@ public enum URLRequestAttribution { return nil } } - + } diff --git a/Sources/Networking/Extensions/URLResponseExtension.swift b/Sources/Networking/Extensions/URLResponseExtension.swift index f00a75fe0..e930bee90 100644 --- a/Sources/Networking/Extensions/URLResponseExtension.swift +++ b/Sources/Networking/Extensions/URLResponseExtension.swift @@ -1,6 +1,5 @@ // // URLResponseExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,12 +19,12 @@ import Foundation extension URLResponse { - + func asHTTPURLResponse() throws -> HTTPURLResponse { guard let httpResponse = self as? HTTPURLResponse else { throw APIRequest.Error.invalidResponse } return httpResponse } - + } diff --git a/Sources/Networking/Extensions/URLSessionExtension.swift b/Sources/Networking/Extensions/URLSessionExtension.swift index 39e82c562..367bee1d5 100644 --- a/Sources/Networking/Extensions/URLSessionExtension.swift +++ b/Sources/Networking/Extensions/URLSessionExtension.swift @@ -1,6 +1,5 @@ // // URLSessionExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -20,7 +19,7 @@ import Foundation extension URLSession { - + private static var defaultCallbackQueue: OperationQueue = { let queue = OperationQueue() queue.name = "APIRequest default callback queue" @@ -28,7 +27,7 @@ extension URLSession { queue.maxConcurrentOperationCount = 1 return queue }() - + private static let defaultCallback = URLSession(configuration: .default, delegate: nil, delegateQueue: defaultCallbackQueue) private static let defaultCallbackEphemeral = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: defaultCallbackQueue) @@ -42,5 +41,5 @@ extension URLSession { return ephemeral ? defaultCallbackEphemeral : defaultCallback } } - + } diff --git a/Sources/Persistence/CoreDataDatabase.swift b/Sources/Persistence/CoreDataDatabase.swift index 63b41224e..f29ba2d5d 100644 --- a/Sources/Persistence/CoreDataDatabase.swift +++ b/Sources/Persistence/CoreDataDatabase.swift @@ -21,12 +21,12 @@ import CoreData import Common public protocol ManagedObjectContextFactory { - + func makeContext(concurrencyType: NSManagedObjectContextConcurrencyType, name: String?) -> NSManagedObjectContext } public class CoreDataDatabase: ManagedObjectContextFactory { - + public enum Error: Swift.Error { case containerLocationCouldNotBePrepared(underlyingError: Swift.Error) } @@ -40,15 +40,15 @@ public class CoreDataDatabase: ManagedObjectContextFactory { return FileManager.default.fileExists(atPath: containerURL.path) } - + public var model: NSManagedObjectModel { return container.managedObjectModel } - + public var coordinator: NSPersistentStoreCoordinator { return container.persistentStoreCoordinator } - + public static func loadModel(from bundle: Bundle, named name: String) -> NSManagedObjectModel? { let momdUrl = bundle.url(forResource: name, withExtension: "momd") ?? bundle.resourceURL!.appendingPathComponent(name + ".momd") @@ -69,19 +69,19 @@ public class CoreDataDatabase: ManagedObjectContextFactory { } #endif guard FileManager.default.fileExists(atPath: momdUrl.path) else { return nil } - + return NSManagedObjectModel(contentsOf: momdUrl) } - + public init(name: String, containerLocation: URL, model: NSManagedObjectModel, readOnly: Bool = false, options: [String: NSObject] = [:]) { - + self.container = NSPersistentContainer(name: name, managedObjectModel: model) self.containerLocation = containerLocation - + let description = NSPersistentStoreDescription(url: containerLocation.appendingPathComponent("\(name).sqlite")) description.type = NSSQLiteStoreType description.isReadOnly = readOnly @@ -89,25 +89,25 @@ public class CoreDataDatabase: ManagedObjectContextFactory { for (key, value) in options { description.setOption(value, forKey: key) } - + self.container.persistentStoreDescriptions = [description] } - + public func loadStore(completion: @escaping (NSManagedObjectContext?, Swift.Error?) -> Void = { _, _ in }) { - + do { try FileManager.default.createDirectory(at: containerLocation, withIntermediateDirectories: true) } catch { completion(nil, Error.containerLocationCouldNotBePrepared(underlyingError: error)) return } - + container.loadPersistentStores { _, error in if let error = error { completion(nil, error) return } - + let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) context.persistentStoreCoordinator = self.container.persistentStoreCoordinator context.name = "Migration" @@ -117,7 +117,7 @@ public class CoreDataDatabase: ManagedObjectContextFactory { } } } - + public func tearDown(deleteStores: Bool) throws { typealias StoreInfo = (url: URL?, type: String) var storesToDelete = [StoreInfo]() @@ -125,7 +125,7 @@ public class CoreDataDatabase: ManagedObjectContextFactory { storesToDelete.append((url: store.url, type: store.type)) try container.persistentStoreCoordinator.remove(store) } - + if deleteStores { for (url, type) in storesToDelete { if let url = url { @@ -134,14 +134,14 @@ public class CoreDataDatabase: ManagedObjectContextFactory { } } } - + public func makeContext(concurrencyType: NSManagedObjectContextConcurrencyType, name: String? = nil) -> NSManagedObjectContext { RunLoop.current.run(until: storeLoadedCondition) let context = NSManagedObjectContext(concurrencyType: concurrencyType) context.persistentStoreCoordinator = container.persistentStoreCoordinator context.name = name - + return context } } @@ -164,7 +164,7 @@ extension NSManagedObjectContext { for entityDescription in entityDescriptions { let request = NSFetchRequest() request.entity = entityDescription - + deleteAll(matching: request) } } diff --git a/Sources/Persistence/CoreDataErrorsParser.swift b/Sources/Persistence/CoreDataErrorsParser.swift index a8c9b450e..b915054c5 100644 --- a/Sources/Persistence/CoreDataErrorsParser.swift +++ b/Sources/Persistence/CoreDataErrorsParser.swift @@ -1,6 +1,6 @@ // // CoreDataErrorsParser.swift -// +// // Copyright © 2022 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,39 +20,39 @@ import Foundation import CoreData public class CoreDataErrorsParser { - + public struct ErrorInfo: Equatable { public let code: Int public let domain: String public let entity: String? public let property: String? } - + public static func parse(error: NSError) -> [ErrorInfo] { - + let unwrapped = unwrapErrorIfNeeded(error) return unwrapped.compactMap(checkError(_:)) } - + private static func unwrapErrorIfNeeded(_ error: NSError) -> [NSError] { if let errors = error.userInfo[NSDetailedErrorsKey] as? [NSError] { return errors } return [error] } - + private static func checkError(_ error: NSError) -> ErrorInfo { if let info = checkValidationError(error) { return info } - + if let info = checkConflictError(error) { return info } - + return ErrorInfo(code: error.code, domain: error.domain, entity: nil, property: nil) } - + private static func checkValidationError(_ error: NSError) -> ErrorInfo? { guard let validationInfo = error.userInfo[NSValidationKeyErrorKey] as? String, let managedObject = error.userInfo[NSValidationObjectErrorKey] as? NSManagedObject else { @@ -63,15 +63,15 @@ public class CoreDataErrorsParser { entity: managedObject.entity.name, property: validationInfo) } - + private static func checkConflictError(_ error: NSError) -> ErrorInfo? { guard error.code == NSManagedObjectMergeError, let conflicts = error.userInfo[NSPersistentStoreSaveConflictsErrorKey] as? [NSMergeConflict], let firstConflict = conflicts.first else { return nil } - + return ErrorInfo(code: error.code, domain: error.domain, entity: firstConflict.sourceObject.entity.name, property: nil) } - + } diff --git a/Sources/Persistence/KeyValueStoring.swift b/Sources/Persistence/KeyValueStoring.swift index c82b56b42..f1e912423 100644 --- a/Sources/Persistence/KeyValueStoring.swift +++ b/Sources/Persistence/KeyValueStoring.swift @@ -1,6 +1,5 @@ // // KeyValueStoring.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation /// Key-value store compatible with base UserDefaults API public protocol KeyValueStoring { - + func object(forKey defaultName: String) -> Any? func set(_ value: Any?, forKey defaultName: String) func removeObject(forKey defaultName: String) diff --git a/Sources/PrivacyDashboard/Model/AllowedPermission.swift b/Sources/PrivacyDashboard/Model/AllowedPermission.swift index 1d73c60e3..c4d36e9ea 100644 --- a/Sources/PrivacyDashboard/Model/AllowedPermission.swift +++ b/Sources/PrivacyDashboard/Model/AllowedPermission.swift @@ -26,7 +26,7 @@ public struct AllowedPermission: Codable { var used: Bool var paused: Bool var options: [[String: String]] - + public init(key: String, icon: String, title: String, diff --git a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift index 475d24931..c86872932 100644 --- a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift +++ b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift @@ -24,7 +24,7 @@ public struct CookieConsentInfo: Encodable { let optoutFailed: Bool? let selftestFailed: Bool? let configurable = true - + public init(consentManaged: Bool, cosmetic: Bool?, optoutFailed: Bool?, selftestFailed: Bool?) { self.consentManaged = consentManaged self.cosmetic = cosmetic diff --git a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift index 2fc310faa..af69ba1a5 100644 --- a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift +++ b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift @@ -1,6 +1,5 @@ // // ProtectionStatus.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,12 +19,12 @@ import Foundation public struct ProtectionStatus: Encodable { - + let unprotectedTemporary: Bool let enabledFeatures: [String] let allowlisted: Bool let denylisted: Bool - + public init(unprotectedTemporary: Bool, enabledFeatures: [String], allowlisted: Bool, denylisted: Bool) { self.unprotectedTemporary = unprotectedTemporary self.enabledFeatures = enabledFeatures diff --git a/Sources/PrivacyDashboard/Model/TrackerInfo.swift b/Sources/PrivacyDashboard/Model/TrackerInfo.swift index 8b5b051b7..5494c40da 100644 --- a/Sources/PrivacyDashboard/Model/TrackerInfo.swift +++ b/Sources/PrivacyDashboard/Model/TrackerInfo.swift @@ -21,51 +21,51 @@ import TrackerRadarKit import ContentBlocking public struct TrackerInfo: Encodable { - + enum CodingKeys: String, CodingKey { case requests case installedSurrogates } - + public private (set) var trackers = Set() private(set) var thirdPartyRequests = Set() public private(set) var installedSurrogates = Set() - + public init() { } - + // MARK: - Collecting detected elements - + public mutating func addDetectedTracker(_ tracker: DetectedRequest, onPageWithURL url: URL) { guard tracker.pageUrl == url.absoluteString else { return } trackers.insert(tracker) } - + public mutating func add(detectedThirdPartyRequest request: DetectedRequest) { thirdPartyRequests.insert(request) } - + public mutating func addInstalledSurrogateHost(_ host: String, for tracker: DetectedRequest, onPageWithURL url: URL) { guard tracker.pageUrl == url.absoluteString else { return } installedSurrogates.insert(host) } - + // MARK: - Helper accessors - + public var trackersBlocked: [DetectedRequest] { trackers.filter { $0.state == .blocked } } - + public var trackersDetected: [DetectedRequest] { trackers.filter { $0.state != .blocked } } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - + let allRequests = [] + trackers + thirdPartyRequests - + try container.encode(allRequests, forKey: .requests) try container.encode(installedSurrogates, forKey: .installedSurrogates) } - + } diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index 9c715d5ef..28996dd60 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -31,34 +31,34 @@ public protocol PrivacyDashboardNavigationDelegate: AnyObject { #if os(iOS) func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) #endif - + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) } /// `Report broken site` web page delegate public protocol PrivacyDashboardReportBrokenSiteDelegate: AnyObject { - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) } /// `Privacy Dasboard` web page delegate public protocol PrivacyDashboardControllerDelegate: AnyObject { - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch protectionState: ProtectionState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestOpenUrlInNewTab url: URL) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget) func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboardController) - + #if os(macOS) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetPermission permissionName: String, to state: PermissionAuthorizationState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, setPermission permissionName: String, paused: Bool) #endif } @@ -68,25 +68,25 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject { /// 2- Direct access to the `Report broken site` page /// Which flow is used is decided at `setup(...)` time, where if `reportBrokenSiteOnly` is true then the `Report broken site` page is opened directly. @MainActor public final class PrivacyDashboardController: NSObject { - + // Delegates public weak var privacyDashboardDelegate: PrivacyDashboardControllerDelegate? public weak var privacyDashboardNavigationDelegate: PrivacyDashboardNavigationDelegate? public weak var privacyDashboardReportBrokenSiteDelegate: PrivacyDashboardReportBrokenSiteDelegate? - + @Published public var theme: PrivacyDashboardTheme? public var preferredLocale: String? @Published public var allowedPermissions: [AllowedPermission] = [] public private(set) weak var privacyInfo: PrivacyInfo? - + private weak var webView: WKWebView? private let privacyDashboardScript = PrivacyDashboardUserScript() private var cancellables = Set() - + public init(privacyInfo: PrivacyInfo?) { self.privacyInfo = privacyInfo } - + /// Configure the webview for showing `Privacy Dasboard` or `Report broken site` /// - Parameters: /// - webView: The webview to use @@ -94,73 +94,73 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject { public func setup(for webView: WKWebView, reportBrokenSiteOnly: Bool) { self.webView = webView webView.navigationDelegate = self - + setupPrivacyDashboardUserScript() loadPrivacyDashboardHTML(reportBrokenSiteOnly: reportBrokenSiteOnly) } - + public func updatePrivacyInfo(_ privacyInfo: PrivacyInfo?) { cancellables.removeAll() self.privacyInfo = privacyInfo - + subscribeToDataModelChanges() sendProtectionStatus() } - + public func cleanUp() { cancellables.removeAll() - + privacyDashboardScript.messageNames.forEach { messageName in webView?.configuration.userContentController.removeScriptMessageHandler(forName: messageName) } } - + public func didStartRulesCompilation() { guard let webView = self.webView else { return } privacyDashboardScript.setIsPendingUpdates(true, webView: webView) } - + public func didFinishRulesCompilation() { guard let webView = self.webView else { return } privacyDashboardScript.setIsPendingUpdates(false, webView: webView) } - + private func setupPrivacyDashboardUserScript() { guard let webView = self.webView else { return } - + privacyDashboardScript.delegate = self - + webView.configuration.userContentController.addUserScript(privacyDashboardScript.makeWKUserScriptSync()) - + privacyDashboardScript.messageNames.forEach { messageName in webView.configuration.userContentController.add(privacyDashboardScript, name: messageName) } } - + private func loadPrivacyDashboardHTML(reportBrokenSiteOnly: Bool) { guard var url = Bundle.privacyDashboardURL else { return } - + if reportBrokenSiteOnly { url = url.appendingParameter(name: "screen", value: ProtectionState.EventOriginScreen.breakageForm.rawValue) } - + webView?.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent().deletingLastPathComponent()) } } extension PrivacyDashboardController: WKNavigationDelegate { - + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { subscribeToDataModelChanges() - + sendProtectionStatus() sendParentEntity() sendCurrentLocale() } - + private func subscribeToDataModelChanges() { cancellables.removeAll() - + subscribeToTheme() subscribeToTrackerInfo() subscribeToConnectionUpgradedTo() @@ -168,7 +168,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { subscribeToConsentManaged() subscribeToAllowedPermissions() } - + private func subscribeToTheme() { $theme .removeDuplicates() @@ -179,7 +179,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToTrackerInfo() { privacyInfo?.$trackerInfo .receive(on: DispatchQueue.main) @@ -190,7 +190,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToConnectionUpgradedTo() { privacyInfo?.$connectionUpgradedTo .receive(on: DispatchQueue.main) @@ -201,7 +201,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToServerTrust() { privacyInfo?.$serverTrust .receive(on: DispatchQueue.global(qos: .userInitiated)) @@ -215,7 +215,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToConsentManaged() { privacyInfo?.$cookieConsentManaged .receive(on: DispatchQueue.main) @@ -225,7 +225,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToAllowedPermissions() { $allowedPermissions .receive(on: DispatchQueue.main) @@ -235,75 +235,75 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func sendProtectionStatus() { guard let webView = self.webView, let protectionStatus = privacyInfo?.protectionStatus else { return } - + privacyDashboardScript.setProtectionStatus(protectionStatus, webView: webView) } - + private func sendParentEntity() { guard let webView = self.webView else { return } privacyDashboardScript.setParentEntity(privacyInfo?.parentEntity, webView: webView) } - + private func sendCurrentLocale() { guard let webView = self.webView else { return } - + let locale = preferredLocale ?? "en" privacyDashboardScript.setLocale(locale, webView: webView) } } extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate { - + func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenSettings target: String) { let settingsTarget = PrivacyDashboardOpenSettingsTarget(rawValue: target) ?? .general privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenSettings: settingsTarget) } - + func userScript(_ userScript: PrivacyDashboardUserScript, didChangeProtectionState protectionState: ProtectionState) { - + switch protectionState.eventOrigin.screen { case .primaryScreen: privacyDashboardDelegate?.privacyDashboardController(self, didChangeProtectionSwitch: protectionState) case .breakageForm: privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, reportBrokenSiteDidChangeProtectionSwitch: protectionState) } - + } - + func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenUrlInNewTab url: URL) { privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenUrlInNewTab: url) } - + func userScriptDidRequestClosing(_ userScript: PrivacyDashboardUserScript) { #if os(iOS) privacyDashboardNavigationDelegate?.privacyDashboardControllerDidTapClose(self) #endif } - + func userScriptDidRequestShowReportBrokenSite(_ userScript: PrivacyDashboardUserScript) { privacyDashboardDelegate?.privacyDashboardControllerDidRequestShowReportBrokenSite(self) } - + func userScript(_ userScript: PrivacyDashboardUserScript, setHeight height: Int) { privacyDashboardNavigationDelegate?.privacyDashboardController(self, didSetHeight: height) } - + func userScript(_ userScript: PrivacyDashboardUserScript, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { - privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, + privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) } - + func userScript(_ userScript: PrivacyDashboardUserScript, didSetPermission permission: String, to state: PermissionAuthorizationState) { #if os(macOS) privacyDashboardDelegate?.privacyDashboardController(self, didSetPermission: permission, to: state) #endif } - + func userScript(_ userScript: PrivacyDashboardUserScript, setPermission permission: String, paused: Bool) { #if os(macOS) privacyDashboardDelegate?.privacyDashboardController(self, setPermission: permission, paused: paused) diff --git a/Sources/PrivacyDashboard/PrivacyInfo.swift b/Sources/PrivacyDashboard/PrivacyInfo.swift index fd488e382..a4ca9265f 100644 --- a/Sources/PrivacyDashboard/PrivacyInfo.swift +++ b/Sources/PrivacyDashboard/PrivacyInfo.swift @@ -21,32 +21,32 @@ import TrackerRadarKit import Common public final class PrivacyInfo { - + public private(set) var url: URL private(set) var parentEntity: Entity? - + @Published public var trackerInfo: TrackerInfo @Published private(set) var protectionStatus: ProtectionStatus @Published public var serverTrust: SecTrust? @Published public var connectionUpgradedTo: URL? @Published public var cookieConsentManaged: CookieConsentInfo? - + public init(url: URL, parentEntity: Entity?, protectionStatus: ProtectionStatus) { self.url = url self.parentEntity = parentEntity self.protectionStatus = protectionStatus - + trackerInfo = TrackerInfo() } - + public var https: Bool { return url.isHttps } - + public var domain: String? { return url.host } - + public func isFor(_ url: URL?) -> Bool { return self.url.host == url?.host } diff --git a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift index 1df481f4d..5c57ef390 100644 --- a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift +++ b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift @@ -42,11 +42,11 @@ public enum PrivacyDashboardTheme: String, Encodable { public struct ProtectionState: Decodable { public let isProtected: Bool public let eventOrigin: EventOrigin - + public struct EventOrigin: Decodable { public let screen: EventOriginScreen } - + public enum EventOriginScreen: String, Decodable { case primaryScreen case breakageForm @@ -54,7 +54,7 @@ public struct ProtectionState: Decodable { } final class PrivacyDashboardUserScript: NSObject, StaticUserScript { - + enum MessageNames: String, CaseIterable { case privacyDashboardSetProtection case privacyDashboardSetSize @@ -66,21 +66,21 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { case privacyDashboardSetPermission case privacyDashboardSetPermissionPaused } - + static var injectionTime: WKUserScriptInjectionTime { .atDocumentStart } static var forMainFrameOnly: Bool { false } static var source: String = "" static var script: WKUserScript = PrivacyDashboardUserScript.makeWKUserScript() var messageNames: [String] { MessageNames.allCases.map(\.rawValue) } - + weak var delegate: PrivacyDashboardUserScriptDelegate? - + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let messageType = MessageNames(rawValue: message.name) else { assertionFailure("PrivacyDashboardUserScript: unexpected message name \(message.name)") return } - + switch messageType { case .privacyDashboardSetProtection: handleSetProtection(message: message) @@ -102,37 +102,37 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { handleOpenSettings(message: message) } } - + // MARK: - JS message handlers - + private func handleSetProtection(message: WKScriptMessage) { - + guard let protectionState: ProtectionState = DecodableHelper.decode(from: message.messageBody) else { assertionFailure("privacyDashboardSetProtection: expected ProtectionState") return } - + delegate?.userScript(self, didChangeProtectionState: protectionState) } - + private func handleSetSize(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let height = dict["height"] as? Int else { assertionFailure("privacyDashboardSetHeight: expected height to be an Int") return } - + delegate?.userScript(self, setHeight: height) } - + private func handleClose() { delegate?.userScriptDidRequestClosing(self) } - + private func handleShowReportBrokenSite() { delegate?.userScriptDidRequestShowReportBrokenSite(self) } - + private func handleSubmitBrokenSiteReport(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let category = dict["category"] as? String, @@ -140,10 +140,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetHeight: expected { category: String, description: String }") return } - + delegate?.userScript(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) } - + private func handleOpenUrlInNewTab(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let urlString = dict["url"] as? String, @@ -152,10 +152,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenUrlInNewTab: expected { url: '...' } ") return } - + delegate?.userScript(self, didRequestOpenUrlInNewTab: url) } - + private func handleOpenSettings(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let target = dict["target"] as? String @@ -163,10 +163,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenSettings: expected { target: '...' } ") return } - + delegate?.userScript(self, didRequestOpenSettings: target) } - + private func handleSetPermission(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -175,10 +175,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetPermission: expected { permission: PermissionType, value: PermissionAuthorizationState }") return } - + delegate?.userScript(self, didSetPermission: permission, to: state) } - + private func handleSetPermissionPaused(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -187,86 +187,86 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleSetPermissionPaused: expected { permission: PermissionType, paused: Bool }") return } - + delegate?.userScript(self, setPermission: permission, paused: paused) } - + // MARK: - Calls to script's JS API - + func setTrackerInfo(_ tabUrl: URL, trackerInfo: TrackerInfo, webView: WKWebView) { guard let trackerBlockingDataJson = try? JSONEncoder().encode(trackerInfo).utf8String() else { assertionFailure("Can't encode trackerInfoViewModel into JSON") return } - + guard let safeTabUrl = try? JSONEncoder().encode(tabUrl).utf8String() else { assertionFailure("Can't encode tabUrl into JSON") return } - + evaluate(js: "window.onChangeRequestData(\(safeTabUrl), \(trackerBlockingDataJson))", in: webView) } - + func setProtectionStatus(_ protectionStatus: ProtectionStatus, webView: WKWebView) { guard let protectionStatusJson = try? JSONEncoder().encode(protectionStatus).utf8String() else { assertionFailure("Can't encode mockProtectionStatus into JSON") return } - + evaluate(js: "window.onChangeProtectionStatus(\(protectionStatusJson))", in: webView) } - + func setUpgradedHttps(_ upgradedHttps: Bool, webView: WKWebView) { evaluate(js: "window.onChangeUpgradedHttps(\(upgradedHttps))", in: webView) } - + func setParentEntity(_ parentEntity: Entity?, webView: WKWebView) { if parentEntity == nil { return } - + guard let parentEntityJson = try? JSONEncoder().encode(parentEntity).utf8String() else { assertionFailure("Can't encode parentEntity into JSON") return } - + evaluate(js: "window.onChangeParentEntity(\(parentEntityJson))", in: webView) } - + func setTheme(_ theme: PrivacyDashboardTheme?, webView: WKWebView) { if theme == nil { return } - + guard let themeJson = try? JSONEncoder().encode(theme).utf8String() else { assertionFailure("Can't encode themeName into JSON") return } - + evaluate(js: "window.onChangeTheme(\(themeJson))", in: webView) } - + func setServerTrust(_ serverTrustViewModel: ServerTrustViewModel, webView: WKWebView) { guard let certificateDataJson = try? JSONEncoder().encode(serverTrustViewModel).utf8String() else { assertionFailure("Can't encode serverTrustViewModel into JSON") return } - + evaluate(js: "window.onChangeCertificateData(\(certificateDataJson))", in: webView) } - + func setIsPendingUpdates(_ isPendingUpdates: Bool, webView: WKWebView) { evaluate(js: "window.onIsPendingUpdates(\(isPendingUpdates))", in: webView) } - + func setLocale(_ currentLocale: String, webView: WKWebView) { struct LocaleSetting: Encodable { var locale: String } - + guard let localeSettingJson = try? JSONEncoder().encode(LocaleSetting(locale: currentLocale)).utf8String() else { assertionFailure("Can't encode consentInfo into JSON") return } evaluate(js: "window.onChangeLocale(\(localeSettingJson))", in: webView) } - + func setConsentManaged(_ consentManaged: CookieConsentInfo?, webView: WKWebView) { guard let consentDataJson = try? JSONEncoder().encode(consentManaged).utf8String() else { assertionFailure("Can't encode consentInfo into JSON") @@ -274,26 +274,26 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { } evaluate(js: "window.onChangeConsentManaged(\(consentDataJson))", in: webView) } - + func setPermissions(allowedPermissions: [AllowedPermission], webView: WKWebView) { guard let allowedPermissionsJson = try? JSONEncoder().encode(allowedPermissions).utf8String() else { assertionFailure("PrivacyDashboardUserScript: could not serialize permissions object") return } - + self.evaluate(js: "window.onChangeAllowedPermissions(\(allowedPermissionsJson))", in: webView) } - + private func evaluate(js: String, in webView: WKWebView) { webView.evaluateJavaScript(js) } - + } extension Data { - + func utf8String() -> String? { return String(data: self, encoding: .utf8) } - + } diff --git a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift index 0862cb89d..eb94d1db3 100644 --- a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift +++ b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift @@ -19,28 +19,28 @@ import Foundation public struct ServerTrustViewModel: Encodable { - + struct SecCertificateViewModel: Encodable { - + let summary: String? let commonName: String? let emails: [String]? let publicKey: SecKeyViewModel? - + public init(secCertificate: SecCertificate) { summary = SecCertificateCopySubjectSummary(secCertificate) as String? ?? "" - + var commonName: CFString? SecCertificateCopyCommonName(secCertificate, &commonName) self.commonName = commonName as String? ?? "" - + var emails: CFArray? if errSecSuccess == SecCertificateCopyEmailAddresses(secCertificate, &emails) { self.emails = emails as? [String] } else { self.emails = nil } - + var secTrust: SecTrust? if errSecSuccess == SecTrustCreateWithCertificates(secCertificate, SecPolicyCreateBasicX509(), &secTrust), let certTrust = secTrust { if #available(iOS 14.0, macOS 11.0, *) { @@ -53,11 +53,11 @@ public struct ServerTrustViewModel: Encodable { publicKey = nil } } - + } - + struct SecKeyViewModel: Encodable { - + static func typeToString(_ type: String) -> String? { switch type as CFString { case kSecAttrKeyTypeRSA: return "RSA" @@ -66,14 +66,14 @@ public struct ServerTrustViewModel: Encodable { default: return nil } } - + let keyId: Data? let externalRepresentation: Data? - + let bitSize: Int? let blockSize: Int? let effectiveSize: Int? - + let canDecrypt: Bool let canDerive: Bool let canEncrypt: Bool @@ -81,20 +81,20 @@ public struct ServerTrustViewModel: Encodable { let canUnwrap: Bool let canVerify: Bool let canWrap: Bool - + let isPermanent: Bool? let type: String? - + init?(secKey: SecKey?) { guard let secKey = secKey else { return nil } - + blockSize = SecKeyGetBlockSize(secKey) externalRepresentation = SecKeyCopyExternalRepresentation(secKey, nil) as Data? - + let attrs: NSDictionary? = SecKeyCopyAttributes(secKey) - + bitSize = attrs?[kSecAttrKeySizeInBits] as? Int effectiveSize = attrs?[kSecAttrEffectiveKeySize] as? Int canDecrypt = attrs?[kSecAttrCanDecrypt] as? Bool ?? false @@ -106,36 +106,36 @@ public struct ServerTrustViewModel: Encodable { canWrap = attrs?[kSecAttrCanWrap] as? Bool ?? false isPermanent = attrs?[kSecAttrIsPermanent] as? Bool ?? false keyId = attrs?[kSecAttrApplicationLabel] as? Data - + if let type = attrs?[kSecAttrType] as? String { self.type = Self.typeToString(type) } else { self.type = nil } - + } - + } - + let secCertificateViewModels: [SecCertificateViewModel] - + public init?(serverTrust: SecTrust?) { guard let serverTrust = serverTrust else { return nil } - + let secTrust = serverTrust let count = SecTrustGetCertificateCount(secTrust) guard count != 0 else { return nil } - + var secCertificateViewModels = [SecCertificateViewModel]() for i in 0 ..< count { guard let certificate = SecTrustGetCertificateAtIndex(secTrust, i) else { return nil } let certificateViewModel = SecCertificateViewModel(secCertificate: certificate) secCertificateViewModels.append(certificateViewModel) } - + self.secCertificateViewModels = secCertificateViewModels } - + } diff --git a/Sources/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapper.swift b/Sources/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapper.swift index ca61b4757..56caf5c64 100644 --- a/Sources/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapper.swift +++ b/Sources/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapper.swift @@ -1,6 +1,5 @@ // // JsonToRemoteConfigModelMapper.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Mappers/JsonToRemoteMessageModelMapper.swift b/Sources/RemoteMessaging/Mappers/JsonToRemoteMessageModelMapper.swift index ab3ce12a2..5a119c7c2 100644 --- a/Sources/RemoteMessaging/Mappers/JsonToRemoteMessageModelMapper.swift +++ b/Sources/RemoteMessaging/Mappers/JsonToRemoteMessageModelMapper.swift @@ -1,6 +1,5 @@ // // JsonToRemoteMessageModelMapper.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Matchers/AppAttributeMatcher.swift b/Sources/RemoteMessaging/Matchers/AppAttributeMatcher.swift index 1e010cf9e..18a74e21a 100644 --- a/Sources/RemoteMessaging/Matchers/AppAttributeMatcher.swift +++ b/Sources/RemoteMessaging/Matchers/AppAttributeMatcher.swift @@ -1,6 +1,5 @@ // // AppAttributeMatcher.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -55,7 +54,7 @@ public struct AppAttributeMatcher: AttributeMatcher { guard let value = matchingAttribute.value else { return .fail } - + return BooleanMatchingAttribute(value).matches(value: isInternalUser) case let matchingAttribute as AppIdMatchingAttribute: guard let value = matchingAttribute.value, !value.isEmpty else { diff --git a/Sources/RemoteMessaging/Matchers/AttributeMatcher.swift b/Sources/RemoteMessaging/Matchers/AttributeMatcher.swift index 42b21a639..e7ba2d3f6 100644 --- a/Sources/RemoteMessaging/Matchers/AttributeMatcher.swift +++ b/Sources/RemoteMessaging/Matchers/AttributeMatcher.swift @@ -1,6 +1,5 @@ // // AttributeMatcher.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Matchers/DeviceAttributeMatcher.swift b/Sources/RemoteMessaging/Matchers/DeviceAttributeMatcher.swift index 1a33d10d7..2dfa74b7b 100644 --- a/Sources/RemoteMessaging/Matchers/DeviceAttributeMatcher.swift +++ b/Sources/RemoteMessaging/Matchers/DeviceAttributeMatcher.swift @@ -1,6 +1,5 @@ // // DeviceAttributeMatcher.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Matchers/EvaluationResult.swift b/Sources/RemoteMessaging/Matchers/EvaluationResult.swift index d52d51dfc..fa17c2d71 100644 --- a/Sources/RemoteMessaging/Matchers/EvaluationResult.swift +++ b/Sources/RemoteMessaging/Matchers/EvaluationResult.swift @@ -1,6 +1,5 @@ // // EvaluationResult.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Matchers/UserAttributeMatcher.swift b/Sources/RemoteMessaging/Matchers/UserAttributeMatcher.swift index d3ecea04a..a4043560f 100644 --- a/Sources/RemoteMessaging/Matchers/UserAttributeMatcher.swift +++ b/Sources/RemoteMessaging/Matchers/UserAttributeMatcher.swift @@ -1,6 +1,5 @@ // // UserAttributeMatcher.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Model/AnyDecodable.swift b/Sources/RemoteMessaging/Model/AnyDecodable.swift index b419af128..385e934d6 100644 --- a/Sources/RemoteMessaging/Model/AnyDecodable.swift +++ b/Sources/RemoteMessaging/Model/AnyDecodable.swift @@ -32,15 +32,15 @@ import Foundation // swiftlint:disable type_name @usableFromInline -protocol _AnyDecodable { +protocol AnyDecodableProtocol { var value: Any { get } init(_ value: T?) } // swiftlint:enable type_name -extension AnyDecodable: _AnyDecodable {} +extension AnyDecodable: AnyDecodableProtocol {} -extension _AnyDecodable { +extension AnyDecodableProtocol { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Sources/RemoteMessaging/Model/JsonRemoteMessagingConfig.swift b/Sources/RemoteMessaging/Model/JsonRemoteMessagingConfig.swift index 16336dbc6..1b73be27f 100644 --- a/Sources/RemoteMessaging/Model/JsonRemoteMessagingConfig.swift +++ b/Sources/RemoteMessaging/Model/JsonRemoteMessagingConfig.swift @@ -1,6 +1,5 @@ // // JsonRemoteMessagingConfig.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -15,6 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// import Foundation diff --git a/Sources/RemoteMessaging/Model/MatchingAttributes.swift b/Sources/RemoteMessaging/Model/MatchingAttributes.swift index 1934ac896..26419d465 100644 --- a/Sources/RemoteMessaging/Model/MatchingAttributes.swift +++ b/Sources/RemoteMessaging/Model/MatchingAttributes.swift @@ -1,6 +1,5 @@ // // MatchingAttributes.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -20,8 +19,6 @@ import Foundation import Common -// swiftlint:disable file_length - private enum RuleAttributes { static let min = "min" static let max = "max" diff --git a/Sources/RemoteMessaging/Model/RemoteConfigModel.swift b/Sources/RemoteMessaging/Model/RemoteConfigModel.swift index 9123b5b3d..a94021801 100644 --- a/Sources/RemoteMessaging/Model/RemoteConfigModel.swift +++ b/Sources/RemoteMessaging/Model/RemoteConfigModel.swift @@ -1,6 +1,5 @@ // // RemoteConfigModel.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/Model/RemoteMessageModel.swift b/Sources/RemoteMessaging/Model/RemoteMessageModel.swift index d149929b4..2bf6a39e0 100644 --- a/Sources/RemoteMessaging/Model/RemoteMessageModel.swift +++ b/Sources/RemoteMessaging/Model/RemoteMessageModel.swift @@ -1,6 +1,5 @@ // // RemoteMessageModel.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -83,7 +82,7 @@ public struct RemoteMessageModel: Equatable, Codable { placeholder: placeholder, actionText: translation.primaryActionText ?? actionText, action: action) - + } } } diff --git a/Sources/RemoteMessaging/Model/RemoteMessagingConfig.swift b/Sources/RemoteMessaging/Model/RemoteMessagingConfig.swift index 7aed17b04..f3e9bb6cd 100644 --- a/Sources/RemoteMessaging/Model/RemoteMessagingConfig.swift +++ b/Sources/RemoteMessaging/Model/RemoteMessagingConfig.swift @@ -1,6 +1,5 @@ // // RemoteMessagingConfig.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/RemoteMessagingConfigMatcher.swift b/Sources/RemoteMessaging/RemoteMessagingConfigMatcher.swift index 0d5682812..88e251d28 100644 --- a/Sources/RemoteMessaging/RemoteMessagingConfigMatcher.swift +++ b/Sources/RemoteMessaging/RemoteMessagingConfigMatcher.swift @@ -1,6 +1,5 @@ // // RemoteMessagingConfigMatcher.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/RemoteMessaging/RemoteMessagingConfigProcessor.swift b/Sources/RemoteMessaging/RemoteMessagingConfigProcessor.swift index df46f443e..233864732 100644 --- a/Sources/RemoteMessaging/RemoteMessagingConfigProcessor.swift +++ b/Sources/RemoteMessaging/RemoteMessagingConfigProcessor.swift @@ -1,6 +1,5 @@ // // RemoteMessagingConfigProcessor.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/SecureStorage/SecureStorageCryptoProvider.swift b/Sources/SecureStorage/SecureStorageCryptoProvider.swift index d4b41506a..9136b7c32 100644 --- a/Sources/SecureStorage/SecureStorageCryptoProvider.swift +++ b/Sources/SecureStorage/SecureStorageCryptoProvider.swift @@ -42,7 +42,7 @@ public protocol SecureStorageCryptoProvider { var keychainServiceName: String { get } - var keychainAccountName: String { get } + var keychainAccountName: String { get } } diff --git a/Sources/SecureStorage/SecureStorageError.swift b/Sources/SecureStorage/SecureStorageError.swift index b74e6da4a..1d482cf06 100644 --- a/Sources/SecureStorage/SecureStorageError.swift +++ b/Sources/SecureStorage/SecureStorageError.swift @@ -42,7 +42,7 @@ public enum SecureStorageError: Error { case duplicateRecord case keystoreError(status: Int32) case secError(status: Int32) - case generalCryptoError + case generalCryptoError case encodingFailed } diff --git a/Sources/SecureStorage/SecureVaultFactory.swift b/Sources/SecureStorage/SecureVaultFactory.swift index 8e75375a1..390242686 100644 --- a/Sources/SecureStorage/SecureVaultFactory.swift +++ b/Sources/SecureStorage/SecureVaultFactory.swift @@ -78,7 +78,7 @@ public class SecureVaultFactory { } } } - + public func makeSecureStorageProviders() throws -> SecureStorageProviders { let (cryptoProvider, keystoreProvider): (SecureStorageCryptoProvider, SecureStorageKeyStoreProvider) do { @@ -95,11 +95,11 @@ public class SecureVaultFactory { throw SecureStorageError.failedToOpenDatabase(cause: error) } } - + public func createAndInitializeEncryptionProviders() throws -> (SecureStorageCryptoProvider, SecureStorageKeyStoreProvider) { let cryptoProvider = makeCryptoProvider() let keystoreProvider = makeKeyStoreProvider() - + if try keystoreProvider.l1Key() != nil { return (cryptoProvider, keystoreProvider) } else { @@ -112,7 +112,7 @@ public class SecureVaultFactory { try keystoreProvider.storeEncryptedL2Key(encryptedL2Key) try keystoreProvider.storeGeneratedPassword(password) try keystoreProvider.storeL1Key(l1Key) - + return (cryptoProvider, keystoreProvider) } } diff --git a/Sources/SecureStorageTestsUtils/MockCryptoProvider.swift b/Sources/SecureStorageTestsUtils/MockCryptoProvider.swift index 7509a4fc9..5c15834fc 100644 --- a/Sources/SecureStorageTestsUtils/MockCryptoProvider.swift +++ b/Sources/SecureStorageTestsUtils/MockCryptoProvider.swift @@ -1,6 +1,5 @@ // // MockCryptoProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SecureStorageTestsUtils/MockKeystoreProvider.swift b/Sources/SecureStorageTestsUtils/MockKeystoreProvider.swift index 12adec911..699b9a1af 100644 --- a/Sources/SecureStorageTestsUtils/MockKeystoreProvider.swift +++ b/Sources/SecureStorageTestsUtils/MockKeystoreProvider.swift @@ -1,6 +1,5 @@ // // MockKeystoreProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SecureStorageTestsUtils/NoOpCryptoProvider.swift b/Sources/SecureStorageTestsUtils/NoOpCryptoProvider.swift index d6ef9dba7..e5b4285bf 100644 --- a/Sources/SecureStorageTestsUtils/NoOpCryptoProvider.swift +++ b/Sources/SecureStorageTestsUtils/NoOpCryptoProvider.swift @@ -1,6 +1,5 @@ // // NoOpCryptoProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Bookmarks/internal/BookmarkEntity+Syncable.swift b/Sources/SyncDataProviders/Bookmarks/internal/BookmarkEntity+Syncable.swift index 4737d02df..1247d0df0 100644 --- a/Sources/SyncDataProviders/Bookmarks/internal/BookmarkEntity+Syncable.swift +++ b/Sources/SyncDataProviders/Bookmarks/internal/BookmarkEntity+Syncable.swift @@ -1,6 +1,5 @@ // // BookmarkEntity+Syncable.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Bookmarks/internal/BookmarksResponseHandler.swift b/Sources/SyncDataProviders/Bookmarks/internal/BookmarksResponseHandler.swift index 1fc79f9e9..09ff60ecb 100644 --- a/Sources/SyncDataProviders/Bookmarks/internal/BookmarksResponseHandler.swift +++ b/Sources/SyncDataProviders/Bookmarks/internal/BookmarksResponseHandler.swift @@ -1,6 +1,5 @@ // // BookmarksResponseHandler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Bookmarks/internal/SyncableBookmarkAdapter.swift b/Sources/SyncDataProviders/Bookmarks/internal/SyncableBookmarkAdapter.swift index cd3c46c6a..742521cef 100644 --- a/Sources/SyncDataProviders/Bookmarks/internal/SyncableBookmarkAdapter.swift +++ b/Sources/SyncDataProviders/Bookmarks/internal/SyncableBookmarkAdapter.swift @@ -1,6 +1,5 @@ // // SyncableBookmarkAdapter.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Common/MetricsEvent.swift b/Sources/SyncDataProviders/Common/MetricsEvent.swift index 262a191bb..84a15dd79 100644 --- a/Sources/SyncDataProviders/Common/MetricsEvent.swift +++ b/Sources/SyncDataProviders/Common/MetricsEvent.swift @@ -1,6 +1,5 @@ // // MetricsEvent.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Credentials/CredentialsProvider.swift b/Sources/SyncDataProviders/Credentials/CredentialsProvider.swift index 0488328db..6b6ab139f 100644 --- a/Sources/SyncDataProviders/Credentials/CredentialsProvider.swift +++ b/Sources/SyncDataProviders/Credentials/CredentialsProvider.swift @@ -1,6 +1,5 @@ // // CredentialsProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -84,7 +83,7 @@ public final class CredentialsProvider: DataProvider { let syncableCredentials = try secureVault.modifiedSyncableCredentials() let encryptionKey = try crypter.fetchSecretKey() return try syncableCredentials.map { credentials in - try Syncable.init( + try Syncable( syncableCredentials: credentials, encryptedUsing: { try crypter.encryptAndBase64Encode($0, using: encryptionKey) } ) diff --git a/Sources/SyncDataProviders/Credentials/internal/CredentialsResponseHandler.swift b/Sources/SyncDataProviders/Credentials/internal/CredentialsResponseHandler.swift index 31164c437..451854e82 100644 --- a/Sources/SyncDataProviders/Credentials/internal/CredentialsResponseHandler.swift +++ b/Sources/SyncDataProviders/Credentials/internal/CredentialsResponseHandler.swift @@ -1,6 +1,5 @@ // // CredentialsResponseHandler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Credentials/internal/SyncableCredentialsAdapter.swift b/Sources/SyncDataProviders/Credentials/internal/SyncableCredentialsAdapter.swift index 72bc4c05a..da0bad218 100644 --- a/Sources/SyncDataProviders/Credentials/internal/SyncableCredentialsAdapter.swift +++ b/Sources/SyncDataProviders/Credentials/internal/SyncableCredentialsAdapter.swift @@ -1,6 +1,5 @@ // // SyncableCredentialsAdapter.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/SettingsProvider.swift b/Sources/SyncDataProviders/Settings/SettingsProvider.swift index 549d68549..3348f9be2 100644 --- a/Sources/SyncDataProviders/Settings/SettingsProvider.swift +++ b/Sources/SyncDataProviders/Settings/SettingsProvider.swift @@ -1,6 +1,5 @@ // // SettingsProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -42,7 +41,6 @@ public struct SettingsSyncMetadataSaveError: Error { } } -// swiftlint:disable:next type_body_length public final class SettingsProvider: DataProvider, SettingSyncHandlingDelegate { public struct Setting: Hashable { diff --git a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailManager+SyncSupporting.swift b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailManager+SyncSupporting.swift index 17923f1e9..4652f5141 100644 --- a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailManager+SyncSupporting.swift +++ b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailManager+SyncSupporting.swift @@ -1,6 +1,5 @@ // // EmailManager+SyncSupporting.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailProtectionSyncHandler.swift b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailProtectionSyncHandler.swift index d1bb4181b..73dfcbff1 100644 --- a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailProtectionSyncHandler.swift +++ b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/EmailProtectionSyncHandler.swift @@ -1,6 +1,5 @@ // // EmailProtectionSyncHandler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/FavoritesDisplayModeSyncHandlerBase.swift b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/FavoritesDisplayModeSyncHandlerBase.swift index 0414e1da3..bd5406d41 100644 --- a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/FavoritesDisplayModeSyncHandlerBase.swift +++ b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/FavoritesDisplayModeSyncHandlerBase.swift @@ -1,6 +1,5 @@ // // FavoritesDisplayModeSyncHandlerBase.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandler.swift b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandler.swift index 5331d2767..3031bf435 100644 --- a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandler.swift +++ b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandler.swift @@ -1,6 +1,5 @@ // // SettingSyncHandler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandling.swift b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandling.swift index 0d9948d6b..37d412a4f 100644 --- a/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandling.swift +++ b/Sources/SyncDataProviders/Settings/SettingsSyncHandlers/SettingSyncHandling.swift @@ -1,6 +1,5 @@ // // SettingSyncHandling.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/internal/SettingsResponseHandler.swift b/Sources/SyncDataProviders/Settings/internal/SettingsResponseHandler.swift index 18dd57798..fd1e90c59 100644 --- a/Sources/SyncDataProviders/Settings/internal/SettingsResponseHandler.swift +++ b/Sources/SyncDataProviders/Settings/internal/SettingsResponseHandler.swift @@ -1,6 +1,5 @@ // // SettingsResponseHandler.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/SyncDataProviders/Settings/internal/SyncableSettingAdapter.swift b/Sources/SyncDataProviders/Settings/internal/SyncableSettingAdapter.swift index a16784629..8d86efcdf 100644 --- a/Sources/SyncDataProviders/Settings/internal/SyncableSettingAdapter.swift +++ b/Sources/SyncDataProviders/Settings/internal/SyncableSettingAdapter.swift @@ -1,6 +1,5 @@ // // SyncableSettingAdapter.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/TestUtils/MockURLProtocol.swift b/Sources/TestUtils/MockURLProtocol.swift index 59a5ef5fe..103d2302b 100644 --- a/Sources/TestUtils/MockURLProtocol.swift +++ b/Sources/TestUtils/MockURLProtocol.swift @@ -1,6 +1,5 @@ // // MockURLProtocol.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,20 +20,20 @@ import Foundation /// A catch-all URL protocol that returns successful response and records all requests. final class MockURLProtocol: URLProtocol { - + static var lastRequest: URLRequest? static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data?))? - + override class func canInit(with request: URLRequest) -> Bool { true } - + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } - + override func startLoading() { guard let handler = MockURLProtocol.requestHandler else { fatalError("Handler is unavailable.") } MockURLProtocol.lastRequest = request - + do { let (response, data) = try handler(request) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) @@ -46,7 +45,7 @@ final class MockURLProtocol: URLProtocol { client?.urlProtocol(self, didFailWithError: error) } } - + override func stopLoading() { } - + } diff --git a/Sources/TestUtils/Utils/HTTPURLResponseExtension.swift b/Sources/TestUtils/Utils/HTTPURLResponseExtension.swift index 6414dbaff..9135b285e 100644 --- a/Sources/TestUtils/Utils/HTTPURLResponseExtension.swift +++ b/Sources/TestUtils/Utils/HTTPURLResponseExtension.swift @@ -1,6 +1,5 @@ // -// Configuration.swift -// DuckDuckGo +// HTTPURLResponseExtension.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,28 +20,28 @@ import Foundation @testable import Networking extension HTTPURLResponse { - + static let testEtag = "test-etag" static let testUrl = URL(string: "www.example.com")! - + static let ok = HTTPURLResponse(url: testUrl, statusCode: 200, httpVersion: nil, headerFields: [APIRequest.HTTPHeaderField.etag: testEtag])! - + static let okNoEtag = HTTPURLResponse(url: testUrl, statusCode: 200, httpVersion: nil, headerFields: [:])! - + static let notModified = HTTPURLResponse(url: testUrl, statusCode: 304, httpVersion: nil, headerFields: [APIRequest.HTTPHeaderField.etag: testEtag])! - + static let internalServerError = HTTPURLResponse(url: testUrl, statusCode: 500, httpVersion: nil, headerFields: [:])! - + } diff --git a/Sources/UserScript/StaticUserScript.swift b/Sources/UserScript/StaticUserScript.swift index 88a207444..425ab77c7 100644 --- a/Sources/UserScript/StaticUserScript.swift +++ b/Sources/UserScript/StaticUserScript.swift @@ -1,6 +1,5 @@ // -// UserScript.swift -// Core +// StaticUserScript.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/UserScript/UserScript.swift b/Sources/UserScript/UserScript.swift index 9319c0431..3b35ddc42 100644 --- a/Sources/UserScript/UserScript.swift +++ b/Sources/UserScript/UserScript.swift @@ -1,6 +1,5 @@ // // UserScript.swift -// Core // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/UserScript/UserScriptEncrypter.swift b/Sources/UserScript/UserScriptEncrypter.swift index d492eb32f..5391662fa 100644 --- a/Sources/UserScript/UserScriptEncrypter.swift +++ b/Sources/UserScript/UserScriptEncrypter.swift @@ -1,6 +1,5 @@ // -// AutofillUserScript+Encryption.swift -// DuckDuckGo +// UserScriptEncrypter.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/UserScript/UserScriptHostProvider.swift b/Sources/UserScript/UserScriptHostProvider.swift index 36b7955f7..a0d23bbec 100644 --- a/Sources/UserScript/UserScriptHostProvider.swift +++ b/Sources/UserScript/UserScriptHostProvider.swift @@ -1,6 +1,5 @@ // // UserScriptHostProvider.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Sources/UserScript/UserScriptMessage.swift b/Sources/UserScript/UserScriptMessage.swift index 29597fcde..01ff79458 100644 --- a/Sources/UserScript/UserScriptMessage.swift +++ b/Sources/UserScript/UserScriptMessage.swift @@ -1,6 +1,5 @@ // // UserScriptMessage.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -31,11 +30,11 @@ extension WKScriptMessage: UserScriptMessage { public var messageName: String { return name } - + public var messageBody: Any { return body } - + public var messageHost: String { return "\(frameInfo.securityOrigin.host)\(messagePort)" } @@ -47,7 +46,7 @@ extension WKScriptMessage: UserScriptMessage { public var isMainFrame: Bool { return frameInfo.isMainFrame } - + public var messageWebView: WKWebView? { return webView } diff --git a/Sources/UserScript/UserScriptMessageEncryption.swift b/Sources/UserScript/UserScriptMessageEncryption.swift index f41f35fc2..5f14ca0cc 100644 --- a/Sources/UserScript/UserScriptMessageEncryption.swift +++ b/Sources/UserScript/UserScriptMessageEncryption.swift @@ -1,6 +1,5 @@ // // UserScriptMessageEncryption.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Sources/UserScript/UserScriptMessaging.swift b/Sources/UserScript/UserScriptMessaging.swift index 8f7b91dba..bef522d0d 100644 --- a/Sources/UserScript/UserScriptMessaging.swift +++ b/Sources/UserScript/UserScriptMessaging.swift @@ -19,7 +19,7 @@ import Foundation import WebKit import Combine -import os.log +import Common /// A protocol to implement if you want to opt-in to centralised messaging. /// diff --git a/Sources/UserScript/UserScriptSourceProvider.swift b/Sources/UserScript/UserScriptSourceProvider.swift index 14d4ec5cb..193c920ca 100644 --- a/Sources/UserScript/UserScriptSourceProvider.swift +++ b/Sources/UserScript/UserScriptSourceProvider.swift @@ -1,6 +1,5 @@ // // UserScriptSourceProvider.swift -// Core // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml index 7c4c97660..bf8a5655d 100644 --- a/Tests/.swiftlint.yml +++ b/Tests/.swiftlint.yml @@ -1,10 +1,17 @@ disabled_rules: + - file_length + - unused_closure_parameter + - type_name - force_cast - force_try - - file_length - function_body_length - - function_parameter_count + - cyclomatic_complexity - identifier_name - - line_length + - blanket_disable_command - type_body_length - - type_name + - explicit_non_final_class + - enforce_os_log_wrapper + +large_tuple: + warning: 6 + error: 10 diff --git a/Tests/BookmarksTests/BookmarkDatabaseCleanerTests.swift b/Tests/BookmarksTests/BookmarkDatabaseCleanerTests.swift index 11e8e1971..2d49d21a9 100644 --- a/Tests/BookmarksTests/BookmarkDatabaseCleanerTests.swift +++ b/Tests/BookmarksTests/BookmarkDatabaseCleanerTests.swift @@ -1,6 +1,5 @@ // // BookmarkDatabaseCleanerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/BookmarkEntityTests.swift b/Tests/BookmarksTests/BookmarkEntityTests.swift index 7583c758a..4bc5ef218 100644 --- a/Tests/BookmarksTests/BookmarkEntityTests.swift +++ b/Tests/BookmarksTests/BookmarkEntityTests.swift @@ -1,6 +1,5 @@ // // BookmarkEntityTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/BookmarkListViewModelTests.swift b/Tests/BookmarksTests/BookmarkListViewModelTests.swift index 19d6ffeb8..3966b53b2 100644 --- a/Tests/BookmarksTests/BookmarkListViewModelTests.swift +++ b/Tests/BookmarksTests/BookmarkListViewModelTests.swift @@ -1,6 +1,5 @@ // // BookmarkListViewModelTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -95,7 +94,7 @@ final class BookmarkListViewModelTests: XCTestCase { context.performAndWait { bookmarkTree.createEntities(in: context) - + try! context.save() let bookmark = BookmarkEntity.fetchBookmark(withUUID: "2", context: context)! diff --git a/Tests/BookmarksTests/BookmarkMigrationTests.swift b/Tests/BookmarksTests/BookmarkMigrationTests.swift index baae0fb03..15b88f1b7 100644 --- a/Tests/BookmarksTests/BookmarkMigrationTests.swift +++ b/Tests/BookmarksTests/BookmarkMigrationTests.swift @@ -1,6 +1,5 @@ // // BookmarkMigrationTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -141,7 +140,7 @@ class BookmarkMigrationTests: XCTestCase { XCTFail("Failed to load model") return } - + let context = bookmarksDatabase.makeContext(concurrencyType: .privateQueueConcurrencyType) context.performAndWait { diff --git a/Tests/BookmarksTests/BookmarkUtilsTests.swift b/Tests/BookmarksTests/BookmarkUtilsTests.swift index 3cd9637cf..37f8397f9 100644 --- a/Tests/BookmarksTests/BookmarkUtilsTests.swift +++ b/Tests/BookmarksTests/BookmarkUtilsTests.swift @@ -1,6 +1,5 @@ // // BookmarkUtilsTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/FaviconsFetcher/BookmarkDomainsTests.swift b/Tests/BookmarksTests/FaviconsFetcher/BookmarkDomainsTests.swift index 02250ca6e..18d74f8a5 100644 --- a/Tests/BookmarksTests/FaviconsFetcher/BookmarkDomainsTests.swift +++ b/Tests/BookmarksTests/FaviconsFetcher/BookmarkDomainsTests.swift @@ -1,6 +1,5 @@ // // BookmarkDomainsTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/FaviconsFetcher/BookmarksFaviconsFetcherTests.swift b/Tests/BookmarksTests/FaviconsFetcher/BookmarksFaviconsFetcherTests.swift index 47c89e74d..e1780a37e 100644 --- a/Tests/BookmarksTests/FaviconsFetcher/BookmarksFaviconsFetcherTests.swift +++ b/Tests/BookmarksTests/FaviconsFetcher/BookmarksFaviconsFetcherTests.swift @@ -1,6 +1,5 @@ // // BookmarksFaviconsFetcherTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetchOperationTests.swift b/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetchOperationTests.swift index 2da747b28..66a0690a5 100644 --- a/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetchOperationTests.swift +++ b/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetchOperationTests.swift @@ -1,6 +1,5 @@ // // FaviconsFetchOperationTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetcherMocks.swift b/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetcherMocks.swift index 7664c95a7..18c9fb2a9 100644 --- a/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetcherMocks.swift +++ b/Tests/BookmarksTests/FaviconsFetcher/FaviconsFetcherMocks.swift @@ -1,6 +1,5 @@ // // FaviconsFetcherMocks.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BookmarksTests/FavoriteListViewModelTests.swift b/Tests/BookmarksTests/FavoriteListViewModelTests.swift index f01cb2e21..a772435b3 100644 --- a/Tests/BookmarksTests/FavoriteListViewModelTests.swift +++ b/Tests/BookmarksTests/FavoriteListViewModelTests.swift @@ -1,6 +1,5 @@ // // FavoriteListViewModelTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift index 03363bafe..b9897c2e0 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift @@ -1,6 +1,5 @@ // // AutofillEmailUserScriptTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -85,11 +84,11 @@ class AutofillEmailUserScriptTests: XCTestCase { func testWhenReceivesStoreTokenMessageThenCallsDelegateMethodWithCorrectTokenAndUsername() { let mock = MockAutofillEmailDelegate() userScript.emailDelegate = mock - + let token = "testToken" let username = "testUsername" let cohort = "testCohort" - + let expect = expectation(description: "testWhenReceivesStoreTokenMessageThenCallsDelegateMethod") mock.requestStoreTokenCallback = { callbackToken, callbackUsername, callbackCohort in XCTAssertEqual(token, callbackToken) @@ -125,11 +124,11 @@ class AutofillEmailUserScriptTests: XCTestCase { waitForExpectations(timeout: 1.0, handler: nil) } - + func testWhenReceivesGetAliasMessageThenCallsDelegateMethod() { let mock = MockAutofillEmailDelegate() userScript.emailDelegate = mock - + let expect = expectation(description: "testWhenReceivesGetAliasMessageThenCallsDelegateMethod") mock.requestAliasCallback = { expect.fulfill() @@ -147,11 +146,11 @@ class AutofillEmailUserScriptTests: XCTestCase { XCTAssertNotNil(mockWebView.javaScriptString) } - + func testWhenReceivesRefreshAliasMessageThenCallsDelegateMethod() { let mock = MockAutofillEmailDelegate() userScript.emailDelegate = mock - + let expect = expectation(description: "testWhenReceivesRefreshAliasMessageThenCallsDelegateMethod") mock.refreshAliasCallback = { expect.fulfill() @@ -207,15 +206,15 @@ class AutofillEmailUserScriptTests: XCTestCase { } class MockWKScriptMessage: WKScriptMessage { - + let mockedName: String let mockedBody: Any let mockedWebView: WKWebView? - + override var name: String { return mockedName } - + override var body: Any { return mockedBody } @@ -223,7 +222,7 @@ class MockWKScriptMessage: WKScriptMessage { override var webView: WKWebView? { return mockedWebView } - + init(name: String, body: Any, webView: WKWebView? = nil) { self.mockedName = name self.mockedBody = body @@ -233,7 +232,7 @@ class MockWKScriptMessage: WKScriptMessage { } class MockUserScriptMessage: UserScriptMessage { - + let mockedName: String let mockedBody: Any let mockedHost: String @@ -243,11 +242,11 @@ class MockUserScriptMessage: UserScriptMessage { var isMainFrame: Bool { return mockedMainFrame } - + var messageName: String { return mockedName } - + var messageBody: Any { return mockedBody } @@ -255,11 +254,11 @@ class MockUserScriptMessage: UserScriptMessage { var messageWebView: WKWebView? { return mockedWebView } - + var messageHost: String { return mockedHost } - + init(name: String, body: Any, host: String, webView: WKWebView? = nil) { self.mockedName = name self.mockedBody = body @@ -298,7 +297,7 @@ class MockAutofillEmailDelegate: AutofillEmailDelegate { signedInCallback?() return false } - + func autofillUserScript(_: AutofillUserScript, didRequestAliasAndRequiresUserPermission requiresUserPermission: Bool, shouldConsumeAliasIfProvided: Bool, @@ -306,11 +305,11 @@ class MockAutofillEmailDelegate: AutofillEmailDelegate { requestAliasCallback?() completionHandler("alias", true, nil) } - + func autofillUserScriptDidRequestRefreshAlias(_: AutofillUserScript) { refreshAliasCallback?() } - + func autofillUserScript(_: AutofillUserScript, didRequestStoreToken token: String, username: String, cohort: String?) { requestStoreTokenCallback!(token, username, cohort) } diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillTestHelper.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillTestHelper.swift index 529a680c9..67e825e89 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillTestHelper.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillTestHelper.swift @@ -1,6 +1,5 @@ // // AutofillTestHelper.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -24,7 +23,7 @@ import BrowserServicesKit import TrackerRadarKit struct AutofillTestHelper { - + static func preparePrivacyConfig(embeddedConfig: Data) -> PrivacyConfigurationManager { let mockEmbeddedData = MockEmbeddedDataProvider(data: embeddedConfig, etag: "embedded") diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift index 5f0fc7e57..0668c9442 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift @@ -1,6 +1,5 @@ // // AutofillUserScriptSourceProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift index 5f1e54f1e..5cdf54bc1 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift @@ -1,6 +1,5 @@ // // AutofillVaultUserScriptTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -215,7 +214,7 @@ class AutofillVaultUserScriptTests: XCTestCase { let randomAccountId = Int.random(in: 0 ..< Int.max) // JS will come through as a Int rather than Int64 hostProvider = MockHostProvider(host: "www.domain1.com") - + let delegate = GetCredentialsDelegate() delegate.tld = tld userScript.vaultDelegate = delegate @@ -460,7 +459,7 @@ class AutofillVaultUserScriptTests: XCTestCase { XCTAssertEqual(delegate.lastDomain, "example.com") } - + func testWhenInitializingAutofillData_WhenCredentialsAreProvidedWithoutAUsername_ThenAutofillDataIsStillInitialized() { let password = "password" let detectedAutofillData = [ @@ -468,34 +467,34 @@ class AutofillVaultUserScriptTests: XCTestCase { "password": password ] ] - + let autofillData = AutofillUserScript.DetectedAutofillData(dictionary: detectedAutofillData) - + XCTAssertNil(autofillData.creditCard) XCTAssertNil(autofillData.identity) XCTAssertNotNil(autofillData.credentials) - + XCTAssertEqual(autofillData.credentials?.username, nil) XCTAssertEqual(autofillData.credentials?.password, password) } - + func testWhenInitializingAutofillData_WhenCredentialsAreProvidedWithAUsername_ThenAutofillDataIsStillInitialized() { let username = "username" let password = "password" - + let detectedAutofillData = [ "credentials": [ "username": username, "password": password ] ] - + let autofillData = AutofillUserScript.DetectedAutofillData(dictionary: detectedAutofillData) - + XCTAssertNil(autofillData.creditCard) XCTAssertNil(autofillData.identity) XCTAssertNotNil(autofillData.credentials) - + XCTAssertEqual(autofillData.credentials?.username, username) XCTAssertEqual(autofillData.credentials?.password, password) } @@ -518,11 +517,11 @@ class AutofillVaultUserScriptTests: XCTestCase { let predicate = NSPredicate(block: { _, _ -> Bool in return !delegate.receivedCallbacks.isEmpty }) - + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: delegate.receivedCallbacks) - + wait(for: [expectation], timeout: 5) - + XCTAssertEqual(delegate.lastSubtype, AutofillUserScript.GetAutofillDataSubType.username) } @@ -574,7 +573,7 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { } var receivedCallbacks: [CallbackType] = [] - + var lastDomain: String? var lastUsername: String? var lastPassword: String? @@ -644,7 +643,7 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { lastSubtype = subType receivedCallbacks.append(.didRequestCredentialsForDomain) let provider = SecureVaultModels.CredentialsProvider(name: .duckduckgo, locked: false) - + completionHandler(nil, provider, .none) } @@ -659,7 +658,7 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { func autofillUserScriptDidOfferGeneratedPassword(_: BrowserServicesKit.AutofillUserScript, password: String, completionHandler: @escaping (Bool) -> Void) { } - + func autofillUserScript(_: AutofillUserScript, didSendPixel pixel: AutofillUserScript.JSPixel) { } } diff --git a/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillDomainNameUrlMatcherTests.swift b/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillDomainNameUrlMatcherTests.swift index d22275dff..ffd7f7d84 100644 --- a/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillDomainNameUrlMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillDomainNameUrlMatcherTests.swift @@ -1,6 +1,5 @@ // // AutofillDomainNameUrlMatcherTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillWebsiteAccountMatcherTests.swift b/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillWebsiteAccountMatcherTests.swift index 035b7238b..b16380a1d 100644 --- a/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillWebsiteAccountMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillWebsiteAccountMatcherTests.swift @@ -1,6 +1,5 @@ // -// AutofillDomainNameUrlGrouperTests.swift -// DuckDuckGo +// AutofillWebsiteAccountMatcherTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Autofill/Sort/AutofillDomainNameUrlSortTests.swift b/Tests/BrowserServicesKitTests/Autofill/Sort/AutofillDomainNameUrlSortTests.swift index 2eab9eb42..a2b51f695 100644 --- a/Tests/BrowserServicesKitTests/Autofill/Sort/AutofillDomainNameUrlSortTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/Sort/AutofillDomainNameUrlSortTests.swift @@ -1,6 +1,5 @@ // // AutofillDomainNameUrlSortTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionCounterTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionCounterTests.swift index f72f0ee93..017050911 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionCounterTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionCounterTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionCounterTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,9 +21,9 @@ import Persistence @testable import BrowserServicesKit class MockKeyValueStore: KeyValueStoring { - + var store = [String: Any?]() - + func object(forKey defaultName: String) -> Any? { return store[defaultName] as Any? } @@ -36,7 +35,7 @@ class MockKeyValueStore: KeyValueStoring { func removeObject(forKey defaultName: String) { store[defaultName] = nil } - + } class AdClickAttributionCounterTests: XCTestCase { @@ -46,62 +45,62 @@ class AdClickAttributionCounterTests: XCTestCase { let counter = AdClickAttributionCounter(store: mockStore, onSendRequest: { _ in XCTFail("Should not send anything") }) - + let date = Date() // First use saves date if not present in store counter.onAttributionActive(currentTime: date) - + // Second use, later, but before sync interval counter.onAttributionActive(currentTime: date + 1) - + let count = mockStore.object(forKey: AdClickAttributionCounter.Constant.pageLoadsCountKey) as? Int XCTAssertEqual(count, 2) - + let storedDate = mockStore.object(forKey: AdClickAttributionCounter.Constant.lastSendAtKey) as? Date XCTAssertEqual(date, storedDate) } - + var onSend: (Int) -> Void = { _ in } - + func testWhenTimeIntervalHasPassedThenDataIsSent() { let interval: Double = 60 * 60 - + let expectation = expectation(description: "Data sent") expectation.expectedFulfillmentCount = 2 - + let mockStore = MockKeyValueStore() let counter = AdClickAttributionCounter(store: mockStore, sendInterval: interval) { count in self.onSend(count) } - + onSend = { _ in XCTFail("Send not expected") } - + counter.onAttributionActive() counter.onAttributionActive() counter.onAttributionActive(currentTime: Date() + interval - 1) - + counter.sendEventsIfNeeded() - + onSend = { count in expectation.fulfill() XCTAssertEqual(count, 3) } - + // timestamp in counter will become now + interval counter.sendEventsIfNeeded(currentTime: Date() + interval + 1) - + onSend = { _ in XCTFail("Send not expected") } - + counter.onAttributionActive(currentTime: Date() + interval + 1) - + onSend = { count in expectation.fulfill() XCTAssertEqual(count, 2) } - + // Add another interval to trigger sync counter.onAttributionActive(currentTime: Date() + 2*interval + 1) - + waitForExpectations(timeout: 1, handler: nil) } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionDetectionTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionDetectionTests.swift index 2aeccf2cf..c3739ddb8 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionDetectionTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionDetectionTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionDetectionTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,42 +21,42 @@ import BrowserServicesKit import Common final class MockAttributing: AdClickAttributing { - + init(onFormatMatching: @escaping (URL) -> Bool = { _ in return true }, onParameterNameQuery: @escaping (URL) -> String? = { _ in return nil }) { self.onFormatMatching = onFormatMatching self.onParameterNameQuery = onParameterNameQuery } - + var isEnabled = true - + var allowlist = [AdClickAttributionFeature.AllowlistEntry]() - + var navigationExpiration: Double = 30 var totalExpiration: Double = 7 * 24 * 60 - + var onFormatMatching: (URL) -> Bool var onParameterNameQuery: (URL) -> String? - + func isMatchingAttributionFormat(_ url: URL) -> Bool { return onFormatMatching(url) } - + func attributionDomainParameterName(for url: URL) -> String? { return onParameterNameQuery(url) } - + var isHeuristicDetectionEnabled: Bool = true var isDomainDetectionEnabled: Bool = true - + } final class MockAdClickAttributionDetectionDelegate: AdClickAttributionDetectionDelegate { - + init(onAttributionDetection: @escaping (String) -> Void) { self.onAttributionDetection = onAttributionDetection } - + var onAttributionDetection: (String) -> Void func attributionDetection(_ detection: AdClickAttributionDetection, didDetectVendor vendorHost: String) { onAttributionDetection(vendorHost) @@ -65,240 +64,240 @@ final class MockAdClickAttributionDetectionDelegate: AdClickAttributionDetection } final class AdClickAttributionDetectionTests: XCTestCase { - + let domainParameterName = "ad_domain_param.com" - + static let tld = TLD() - + func testWhenFeatureIsDisabledThenNothingIsDetected() { let feature = MockAttributing { _ in return true } feature.isEnabled = false - + let delegate = MockAdClickAttributionDetectionDelegate { _ in XCTFail("Nothing should be detected") } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) } - + func testWhenHeuristicOptionIsDisabledThenNothingIsDetected() { let feature = MockAttributing { _ in return true } feature.isHeuristicDetectionEnabled = false - + let delegate = MockAdClickAttributionDetectionDelegate { _ in XCTFail("Nothing should be detected") } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) } - + func testWhenDomainDetectionOptionIsDisabledThenFallbackToHeuristic() { let feature = MockAttributing(onParameterNameQuery: { _ in return self.domainParameterName }) feature.isDomainDetectionEnabled = false feature.isHeuristicDetectionEnabled = true - + var delegate = MockAdClickAttributionDetectionDelegate { _ in XCTFail("Nothing should be detected") } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com?\(domainParameterName)=domain.net")) - + let delegateCalled = expectation(description: "Delegate called") delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "test.com") delegateCalled.fulfill() } detection.delegate = delegate - + detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) - + wait(for: [delegateCalled], timeout: 0.1) } - + func testWhenThereAreNoMatchesThenNothingIsDetected() { - + let feature = MockAttributing { _ in return false } - + let delegate = MockAdClickAttributionDetectionDelegate { _ in XCTFail("Nothing should be detected") } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) } - + func testWhenThereAreMatchesThenVendorIsDetected_Heuristic() { - + let feature = MockAttributing { _ in return true } - + let delegateCalled = expectation(description: "Delegate called") - + let delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "test.com") delegateCalled.fulfill() } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) - + waitForExpectations(timeout: 0.1) } - + func testWhenThereAreMatchesThenVendorIsDetected_Directly() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return self.domainParameterName }) - + let delegateCalled = expectation(description: "Delegate called") - + let delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "domain.net") delegateCalled.fulfill() } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com?\(domainParameterName)=domain.net")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) - + waitForExpectations(timeout: 0.1) } - + func testWhenThereAreMatchesThenVendorIsETLDplus1_Heuristic() { - + let feature = MockAttributing { _ in return true } - + let delegateCalled = expectation(description: "Delegate called") - + let delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "test.com") delegateCalled.fulfill() } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com")) detection.on2XXResponse(url: URL(string: "https://a.sub.test.com")) detection.onDidFinishNavigation(url: URL(string: "https://a.sub.test.com")) - + waitForExpectations(timeout: 0.1) } - + func testWhenThereAreMatchesThenVendorIsETLDplus1_Directly() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return self.domainParameterName }) - + let delegateCalled = expectation(description: "Delegate called") - + let delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "domain.net") delegateCalled.fulfill() } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com?\(domainParameterName)=a.domain.net")) detection.on2XXResponse(url: URL(string: "https://sub.test.com")) detection.onDidFinishNavigation(url: URL(string: "https://sub.test.com")) - + waitForExpectations(timeout: 0.1) } - + func testWhenMatchedAndWrongParameterThenFallbackToHeuristic() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return self.domainParameterName }) - + let delegateCalled = expectation(description: "Delegate called") delegateCalled.expectedFulfillmentCount = 1 - + let delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "test.com") delegateCalled.fulfill() } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://example.com?\(domainParameterName)=com")) detection.on2XXResponse(url: URL(string: "https://sub.test.com")) - + // Should match and notify only once detection.on2XXResponse(url: URL(string: "https://another.test.com")) - + detection.onDidFinishNavigation(url: URL(string: "https://another.test.com")) - + waitForExpectations(timeout: 0.1) } - + func testWhenNavigationFailsThenCorrectVendorIsDetected() { - + let feature = MockAttributing { _ in return true } - + var delegate = MockAdClickAttributionDetectionDelegate { _ in XCTFail("Should not detect in case of an error") } - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld) detection.delegate = delegate - + // First matching requests that fails detection.onStartNavigation(url: URL(string: "https://example.com")) detection.onDidFailNavigation() - + // Simulate non-matching request - nothing should be detected feature.onFormatMatching = { _ in return false } - + detection.onStartNavigation(url: URL(string: "https://other.com")) detection.on2XXResponse(url: URL(string: "https://test.com")) detection.onDidFinishNavigation(url: URL(string: "https://test.com")) - + // Simulate matching request - it should be detected feature.onFormatMatching = { _ in return true } - + let delegateCalled = expectation(description: "Delegate called") delegate = MockAdClickAttributionDetectionDelegate { vendorHost in XCTAssertEqual(vendorHost, "something.com") delegateCalled.fulfill() } detection.delegate = delegate - + detection.onStartNavigation(url: URL(string: "https://domain.com")) detection.on2XXResponse(url: URL(string: "https://a.something.com")) detection.onDidFinishNavigation(url: URL(string: "https://a.something.com")) - + waitForExpectations(timeout: 0.1) } } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionLogicTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionLogicTests.swift index 3deb8cd38..fa5c355fb 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionLogicTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionLogicTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionLogicTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -23,35 +22,35 @@ import Common @testable import BrowserServicesKit final class MockAttributionRulesProvider: AdClickAttributionRulesProviding { - + enum Constants { static let globalAttributionRulesListName = "global" } - + init() async { globalAttributionRules = await ContentBlockingRulesHelper().makeFakeRules(name: Constants.globalAttributionRulesListName, tdsEtag: "tdsEtag", tempListId: "tempEtag", allowListId: nil, unprotectedSitesHash: nil) - + XCTAssertNotNil(globalAttributionRules) } - + var globalAttributionRules: ContentBlockerRulesManager.Rules? - + var onRequestingAttribution: (String, @escaping (ContentBlockerRulesManager.Rules?) -> Void) -> Void = { _, _ in } func requestAttribution(forVendor vendor: String, completion: @escaping (ContentBlockerRulesManager.Rules?) -> Void) { onRequestingAttribution(vendor, completion) } - + } final class MockAdClickAttributionLogicDelegate: AdClickAttributionLogicDelegate { - + var onRequestingRuleApplication: (ContentBlockerRulesManager.Rules?) -> Void = { _ in } - + func attributionLogic(_ logic: AdClickAttributionLogic, didRequestRuleApplication rules: ContentBlockerRulesManager.Rules?, forVendor vendor: String?) { @@ -61,49 +60,49 @@ final class MockAdClickAttributionLogicDelegate: AdClickAttributionLogicDelegate // swiftlint:disable weak_delegate final class AdClickAttributionLogicTests: XCTestCase { - + static let tld = TLD() - + let feature = MockAttributing() let mockDelegate = MockAdClickAttributionLogicDelegate() - + func testWhenInitializedThenGlobalRulesApplied() async { - + let mockRulesProvider = await MockAttributionRulesProvider() - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld) - + logic.delegate = mockDelegate - + let rulesApplied = expectation(description: "Rules Applied") mockDelegate.onRequestingRuleApplication = { rules in XCTAssertNotNil(rules) XCTAssertEqual(rules?.name, MockAttributionRulesProvider.Constants.globalAttributionRulesListName) rulesApplied.fulfill() } - + logic.onRulesChanged(latestRules: [mockRulesProvider.globalAttributionRules!]) await fulfillment(of: [rulesApplied], timeout: 0.1) } - + func testWhenAttributionDetectedThenNewRulesAreRequestedAndApplied() async { - + let mockAttributedRules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed") let mockDetection = AdClickAttributionDetection(feature: feature, tld: Self.tld) - + let mockRulesProvider = await MockAttributionRulesProvider() - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld) - + logic.delegate = mockDelegate logic.onRulesChanged(latestRules: [mockRulesProvider.globalAttributionRules!]) - + // Regular navigation, call handler immediately let navigationAllowed = expectation(description: "Navigation allowed") logic.onProvisionalNavigation { navigationAllowed.fulfill() } @@ -111,23 +110,23 @@ final class AdClickAttributionLogicTests: XCTestCase { // Expect // 1. Call to request attribution for found vendor - + var attributedRulesPrepared: (ContentBlockerRulesManager.Rules?) -> Void = { _ in XCTFail("Expected actual handler") } mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, "example.com") attributedRulesPrepared = completion } - + logic.attributionDetection(mockDetection, didDetectVendor: "example.com") - + // 2. Wait with N requests till rules are ready. var requestCompletedCount = 0 logic.onProvisionalNavigation { requestCompletedCount += 1 } logic.onProvisionalNavigation { requestCompletedCount += 1 } - + // Nothing completed yet... XCTAssertEqual(requestCompletedCount, 0) - + // 3. Apply rules once ready (when callback is called) let rulesApplied = expectation(description: "Rules Applied") mockDelegate.onRequestingRuleApplication = { rules in @@ -135,41 +134,41 @@ final class AdClickAttributionLogicTests: XCTestCase { XCTAssertEqual(rules?.name, mockAttributedRules?.name) rulesApplied.fulfill() } - + // 4. Expect navigation to happen once rules are prepared attributedRulesPrepared(mockAttributedRules) - + // Requests completed now XCTAssertEqual(requestCompletedCount, 2) await fulfillment(of: [rulesApplied], timeout: 0.5) } - + func testWhenAttributionDetectedThenPreviousOneIsReplaced() async { - + let mockDetection = AdClickAttributionDetection(feature: feature, tld: Self.tld) let mockRulesProvider = await MockAttributionRulesProvider() - + let mockAttributedRules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed") - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld) - + logic.delegate = mockDelegate logic.onRulesChanged(latestRules: [mockRulesProvider.globalAttributionRules!]) logic.onProvisionalNavigation { } - + // Expect // 1. Call to request attribution for found vendor - + // - Mock rules creation mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, "example.com") completion(mockAttributedRules) } - + let rulesApplied = expectation(description: "Rules Applied") mockDelegate.onRequestingRuleApplication = { rules in XCTAssertNotNil(rules?.name) @@ -177,27 +176,27 @@ final class AdClickAttributionLogicTests: XCTestCase { rulesApplied.fulfill() } // - - + logic.attributionDetection(mockDetection, didDetectVendor: "example.com") await fulfillment(of: [rulesApplied], timeout: 0.2) - + // 2. These should be executed immediately var requestCompletedCount = 0 logic.onProvisionalNavigation { requestCompletedCount += 1 } logic.onProvisionalNavigation { requestCompletedCount += 1 } - + XCTAssertEqual(requestCompletedCount, 2) - + logic.onDidFinishNavigation(host: "test.com") - + // - Mock new rules creation let mockNewAttributedRules = await ContentBlockingRulesHelper().makeFakeRules(name: "newAttributed") - + mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, "other.com") completion(mockNewAttributedRules) } - + let newRulesApplied = expectation(description: "New Rules Applied") mockDelegate.onRequestingRuleApplication = { rules in XCTAssertNotNil(rules?.name) @@ -205,26 +204,26 @@ final class AdClickAttributionLogicTests: XCTestCase { newRulesApplied.fulfill() } // - - + // 3. Simulate new navigation. logic.onProvisionalNavigation { requestCompletedCount += 1 } // 4. And new attribution detection. logic.attributionDetection(mockDetection, didDetectVendor: "other.com") await fulfillment(of: [newRulesApplied], timeout: 0.2) - + logic.onProvisionalNavigation { requestCompletedCount += 1 } logic.onProvisionalNavigation { requestCompletedCount += 1 } - + // Requests completed now XCTAssertEqual(requestCompletedCount, 5) } } final class AdClickAttributionLogicHelper { - + static let tld = TLD() static let feature = MockAttributing() - + static func prepareLogic(attributedVendorHost: String, eventReporting: EventMapping? = nil) async -> (logic: AdClickAttributionLogic, startOfAttribution: Date) { @@ -232,21 +231,21 @@ final class AdClickAttributionLogicHelper { let mockRulesProvider = await MockAttributionRulesProvider() let mockDetection = AdClickAttributionDetection(feature: feature, tld: tld) - + mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, attributedVendorHost) completion(mockAttributedRules) } - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: tld, eventReporting: eventReporting) - + logic.attributionDetection(mockDetection, didDetectVendor: attributedVendorHost) - + logic.onDidFinishNavigation(host: "sub.\(attributedVendorHost)") - + let startOfAttribution: Date! if case AdClickAttributionLogic.State.activeAttribution(_, let session, let rules) = logic.state { XCTAssertEqual(rules.identifier, mockAttributedRules?.identifier) @@ -255,40 +254,40 @@ final class AdClickAttributionLogicHelper { XCTFail("Attribution should be present") startOfAttribution = Date() } - + return (logic, startOfAttribution) } } final class AdClickAttributionLogicTimeoutTests: XCTestCase { - + func testWhenAttributionIsActiveThenTotalTimeoutApplies() async { let (logic, startOfAttribution) = await AdClickAttributionLogicHelper.prepareLogic(attributedVendorHost: "example.com") let feature = AdClickAttributionLogicHelper.feature - + logic.onProvisionalNavigation(completion: {}, currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration)) if case AdClickAttributionLogic.State.noAttribution = logic.state { } else { XCTFail("Attribution should be forgotten") } } - + func testWhenAttributionIsInactiveThenNavigationalTimeoutApplies() async { let (logic, startOfAttribution) = await AdClickAttributionLogicHelper.prepareLogic(attributedVendorHost: "example.com") let feature = AdClickAttributionLogicHelper.feature - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -296,7 +295,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + var leftAttributionContextAt: Date! = nil logic.onDidFinishNavigation(host: "other.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) @@ -306,30 +305,30 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: leftAttributionContextAt.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: leftAttributionContextAt.addingTimeInterval(feature.navigationExpiration)) if case AdClickAttributionLogic.State.noAttribution = logic.state { } else { XCTFail("Attribution should be forgotten") } } - + func testWhenAttributionIsReappliedThenTotalTimeoutApplies() async { let (logic, startOfAttribution) = await AdClickAttributionLogicHelper.prepareLogic(attributedVendorHost: "example.com") let feature = AdClickAttributionLogicHelper.feature - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "other.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -337,7 +336,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: startOfAttribution.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -345,7 +344,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -353,13 +352,13 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { @@ -372,17 +371,17 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { XCTFail("Attribution should be forgotten") } } - + func testWhenAttributionIsReappliedThenNavigationalTimeoutResetsForNextInactiveState() async { let (logic, startOfAttribution) = await AdClickAttributionLogicHelper.prepareLogic(attributedVendorHost: "example.com") let feature = AdClickAttributionLogicHelper.feature - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + var lastTimeOfLeavingAttributionSite: Date? logic.onDidFinishNavigation(host: "other.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) @@ -392,7 +391,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -400,7 +399,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "other.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -410,7 +409,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onProvisionalNavigation(completion: {}, currentTime: lastTimeOfLeavingAttributionSite!.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -418,7 +417,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } else { XCTFail("Attribution should be present") } - + logic.onDidFinishNavigation(host: "something.com", currentTime: lastTimeOfLeavingAttributionSite!.addingTimeInterval(feature.navigationExpiration - 1)) if case AdClickAttributionLogic.State.activeAttribution(_, let session, _) = logic.state { @@ -437,7 +436,7 @@ final class AdClickAttributionLogicTimeoutTests: XCTestCase { } final class AdClickAttributionLogicStateInheritingTests: XCTestCase { - + static let tld = TLD() let feature = MockAttributing() @@ -445,94 +444,94 @@ final class AdClickAttributionLogicStateInheritingTests: XCTestCase { let (logic, startOfAttribution) = await AdClickAttributionLogicHelper.prepareLogic(attributedVendorHost: "example.com") let feature = AdClickAttributionLogicHelper.feature let rules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed")! - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) - + if case AdClickAttributionLogic.State.activeAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + let inheritedSession = AdClickAttributionLogic.SessionInfo(start: startOfAttribution.addingTimeInterval(-1)) logic.applyInheritedAttribution(state: .activeAttribution(vendor: "example.com", session: inheritedSession, rules: rules)) - + logic.onDidFinishNavigation(host: "example.com", currentTime: startOfAttribution.addingTimeInterval(feature.totalExpiration - 1)) - + if case AdClickAttributionLogic.State.noAttribution = logic.state { } else { XCTFail("Attribution should be forgotten") } } - + func testWhenInactiveAttributionIsInheritedThenItIsIgnored() async { let mockRulesProvider = await MockAttributionRulesProvider() let rules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed")! - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld) - + if case AdClickAttributionLogic.State.noAttribution = logic.state { } else { XCTFail("Attribution should be present") } - + let inactiveSession = AdClickAttributionLogic.SessionInfo(start: Date(), leftContextAt: Date()) logic.applyInheritedAttribution(state: .activeAttribution(vendor: "example.com", session: inactiveSession, rules: rules)) - + if case AdClickAttributionLogic.State.noAttribution = logic.state { } else { XCTFail("Attribution should be forgotten") } } - + } final class AdClickAttributionLogicConfigUpdateTests: XCTestCase { - + static let tld = TLD() - + let feature = MockAttributing() let mockDelegate = MockAdClickAttributionLogicDelegate() - + func testWhenTDSUpdatesThenAttributedRulesAreRefreshed() async { let mockAttributedRules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed") let mockDetection = AdClickAttributionDetection(feature: feature, tld: Self.tld) - + let mockRulesProvider = await MockAttributionRulesProvider() - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld) - + logic.delegate = mockDelegate - + mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, "example.com") completion(mockAttributedRules) } - + let rulesApplied = expectation(description: "Rules Applied") mockDelegate.onRequestingRuleApplication = { rules in XCTAssertNotNil(rules?.name) XCTAssertEqual(rules?.name, mockAttributedRules?.name) rulesApplied.fulfill() } - + logic.attributionDetection(mockDetection, didDetectVendor: "example.com") await fulfillment(of: [rulesApplied], timeout: 0.1) - + // - Prepare callbacks for update let updatedAttributedRules = await ContentBlockingRulesHelper().makeFakeRules(name: "attributed_updated") mockRulesProvider.onRequestingAttribution = { vendor, completion in XCTAssertEqual(vendor, "example.com") completion(updatedAttributedRules) } - + let rulesUpdated = expectation(description: "Rules Updated") mockDelegate.onRequestingRuleApplication = { rules in XCTAssertNotNil(rules?.name) @@ -540,7 +539,7 @@ final class AdClickAttributionLogicConfigUpdateTests: XCTestCase { rulesUpdated.fulfill() } // - - + let updatedTDSRules = await ContentBlockingRulesHelper().makeFakeRules(name: "newTDS", tdsEtag: UUID().uuidString) XCTAssertNotNil(updatedTDSRules) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionPixelTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionPixelTests.swift index 415cf30f0..01e26d73e 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionPixelTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionPixelTests.swift @@ -1,6 +1,5 @@ // -// AdClickAttributionDetectionTests.swift -// DuckDuckGo +// AdClickAttributionPixelTests.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -23,39 +22,39 @@ import ContentBlocking import Common final class AdClickAttributionPixelTests: XCTestCase { - + static let tld = TLD() - + static let noEventExpectedHandler: (AdClickAttributionEvents, [String: String]?) -> Void = { event, _ in XCTFail("Unexpected event: \(event)")} - + static let domainParameterName = "ad_domain_param.com" static let linkUrlWithParameter = URL(string: "https://example.com/test.html?\(domainParameterName)=test.com")! static let linkUrlWithoutParameter = URL(string: "https://example.com/test.html")! - + static let matchedVendorURL = URL(string: "https://test.com/site")! static let mismatchedVendorURL = URL(string: "https://other.com/site")! - + var currentEventHandler: (AdClickAttributionEvents, [String: String]?) -> Void = { _, _ in } - + lazy var mockEventMapping = EventMapping { event, _, params, _ in self.currentEventHandler(event, params) } - + func testWhenSERPAndHeuristicsMatchThenThisMatchIsSent() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = true feature.isHeuristicDetectionEnabled = true - + currentEventHandler = Self.noEventExpectedHandler - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + detection.onStartNavigation(url: Self.linkUrlWithParameter) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -64,25 +63,25 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "1") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "1") } - + detection.on2XXResponse(url: Self.matchedVendorURL) wait(for: [expectation], timeout: 1) } - + func testWhenSERPAndHeuristicsDoNotMatchThenThisMismatchIsSent() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = true feature.isHeuristicDetectionEnabled = true - + currentEventHandler = Self.noEventExpectedHandler - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + detection.onStartNavigation(url: Self.linkUrlWithParameter) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -91,21 +90,21 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "1") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "1") } - + detection.on2XXResponse(url: Self.mismatchedVendorURL) wait(for: [expectation], timeout: 1) } - + func testWhenHeuristicsAreDisabledAndSerpIsPresentThenSerpIsUsed() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = true feature.isHeuristicDetectionEnabled = false - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -114,24 +113,24 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "1") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "0") } - + detection.onStartNavigation(url: Self.linkUrlWithParameter) wait(for: [expectation], timeout: 1) - + currentEventHandler = Self.noEventExpectedHandler detection.on2XXResponse(url: Self.matchedVendorURL) } - + func testWhenHeuristicsAreDisabledAndSerpIsMissingThenNoneIsSent() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = true feature.isHeuristicDetectionEnabled = false - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -140,24 +139,24 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "1") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "0") } - + detection.onStartNavigation(url: Self.linkUrlWithoutParameter) wait(for: [expectation], timeout: 1) - + currentEventHandler = Self.noEventExpectedHandler detection.on2XXResponse(url: Self.matchedVendorURL) } - + func testWhenHeuristicsAndSerpAreDisabledThenNoneIsSent() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = false feature.isHeuristicDetectionEnabled = false - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -166,29 +165,29 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "0") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "0") } - + detection.onStartNavigation(url: Self.linkUrlWithParameter) - + wait(for: [expectation], timeout: 1) - + currentEventHandler = Self.noEventExpectedHandler detection.on2XXResponse(url: Self.matchedVendorURL) } - + func testWhenSerpIsDisabledThenHeuristicsAreUsed() { - + let feature = MockAttributing(onParameterNameQuery: { _ in return Self.domainParameterName }) feature.isDomainDetectionEnabled = false feature.isHeuristicDetectionEnabled = true - + currentEventHandler = Self.noEventExpectedHandler - + let detection = AdClickAttributionDetection(feature: feature, tld: Self.tld, eventReporting: mockEventMapping) - + detection.onStartNavigation(url: Self.linkUrlWithParameter) - + let expectation = expectation(description: "Event fired") currentEventHandler = { event, params in expectation.fulfill() @@ -197,26 +196,26 @@ final class AdClickAttributionPixelTests: XCTestCase { XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.domainDetectionEnabled], "0") XCTAssertEqual(params?[AdClickAttributionEvents.Parameters.heuristicDetectionEnabled], "1") } - + detection.on2XXResponse(url: Self.matchedVendorURL) wait(for: [expectation], timeout: 1) } - + func testWhenAttributionIsInactiveThenNoActivityPixelIsSent() async { currentEventHandler = Self.noEventExpectedHandler - + let feature = MockAttributing() feature.onFormatMatching = { _ in return false } let mockRulesProvider = await MockAttributionRulesProvider() - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld, eventReporting: mockEventMapping) - + logic.onProvisionalNavigation {} logic.onDidFinishNavigation(host: "test.com") - + logic.onRequestDetected(request: DetectedRequest(url: "example.com", eTLDplus1: "example.com", knownTracker: nil, @@ -224,7 +223,7 @@ final class AdClickAttributionPixelTests: XCTestCase { state: .allowed(reason: .adClickAttribution), pageUrl: "test.com")) } - + func testWhenAttributionIsActiveThenActivityPixelIsSentOnce() async { let expectation = expectation(description: "Event fired") expectation.expectedFulfillmentCount = 1 @@ -232,35 +231,35 @@ final class AdClickAttributionPixelTests: XCTestCase { expectation.fulfill() XCTAssertEqual(event, AdClickAttributionEvents.adAttributionActive) } - + let feature = MockAttributing() let mockRulesProvider = await MockAttributionRulesProvider() let mockDetection = AdClickAttributionDetection(feature: feature, tld: Self.tld) - + let logic = AdClickAttributionLogic(featureConfig: feature, rulesProvider: mockRulesProvider, tld: Self.tld, eventReporting: mockEventMapping) - + logic.attributionDetection(mockDetection, didDetectVendor: "vendor.com") logic.onDidFinishNavigation(host: "https://vendor.com") - + logic.onRequestDetected(request: DetectedRequest(url: "example.com", eTLDplus1: "example.com", knownTracker: nil, entity: nil, state: .allowed(reason: .adClickAttribution), pageUrl: "test.com")) - + logic.onRequestDetected(request: DetectedRequest(url: "example.com", eTLDplus1: "example.com", knownTracker: nil, entity: nil, state: .allowed(reason: .adClickAttribution), pageUrl: "test.com")) - + await fulfillment(of: [expectation], timeout: 1) } - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift index 0c56f4fc8..441d08cc6 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionRulesMutatorTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,26 +21,26 @@ import XCTest @testable import TrackerRadarKit final class MockAttributionConfig: AdClickAttributing { - + func isMatchingAttributionFormat(_ url: URL) -> Bool { return true } - + func attributionDomainParameterName(for: URL) -> String? { return nil } - + var isEnabled = true var allowlist = [AdClickAttributionFeature.AllowlistEntry]() var navigationExpiration: Double = 0 var totalExpiration: Double = 0 var isHeuristicDetectionEnabled: Bool = true var isDomainDetectionEnabled: Bool = true - + } final class AdClickAttributionRulesMutatorTests: XCTestCase { - + let exampleTDS = """ { "trackers": { @@ -92,109 +91,109 @@ final class AdClickAttributionRulesMutatorTests: XCTestCase { "cnames": {} } """.data(using: .utf8)! - + func isEqualAsJson(l: T?, r: T?) throws -> Bool { guard let l = l, let r = r else { XCTFail("Could not encode objects") return false } - + let lData = try JSONEncoder().encode(l) let rData = try JSONEncoder().encode(r) - + return String(data: lData, encoding: .utf8) == String(data: rData, encoding: .utf8) } - + func testWhenEntityIsOnAllowlistThenRuleIsApplied() throws { let trackerData = try JSONDecoder().decode(TrackerData.self, from: exampleTDS) - + let mockConfig = MockAttributionConfig() mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "example.com", host: "test.com")) - + let mutator = AdClickAttributionRulesMutator(trackerData: trackerData, config: mockConfig) let attributedRules = mutator.addException(vendorDomain: "vendor.com") - + XCTAssertNotNil(attributedRules.trackers["example.com"]) XCTAssertFalse(try isEqualAsJson(l: attributedRules.trackers["example.com"], r: trackerData.trackers["example.com"])) - + XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.count, 1) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.rule, "test\\.com(:[0-9]+)?/.*") XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.exceptions?.domains, ["vendor.com"]) - + XCTAssert(try isEqualAsJson(l: attributedRules.trackers["examplerules.com"], r: trackerData.trackers["examplerules.com"])) - + XCTAssertEqual(trackerData.domains, attributedRules.domains) XCTAssertEqual(trackerData.entities, attributedRules.entities) XCTAssertEqual(trackerData.cnames, attributedRules.cnames) } - + func testWhenEntityHasMultipleEntriesOnAllowlistThenAllRulesAreApplied() throws { let trackerData = try JSONDecoder().decode(TrackerData.self, from: exampleTDS) - + let mockConfig = MockAttributionConfig() mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "example.com", host: "test.com")) mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "example.com", host: "test.org")) - + let mutator = AdClickAttributionRulesMutator(trackerData: trackerData, config: mockConfig) let attributedRules = mutator.addException(vendorDomain: "vendor.com") - + XCTAssertNotNil(attributedRules.trackers["example.com"]) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.count, 2) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.rule, "test\\.org(:[0-9]+)?/.*") XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.exceptions?.domains, ["vendor.com"]) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.last?.rule, "test\\.com(:[0-9]+)?/.*") XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.last?.exceptions?.domains, ["vendor.com"]) - + XCTAssertEqual(attributedRules.trackers["examplerules.com"], trackerData.trackers["examplerules.com"]) - + XCTAssertEqual(trackerData.domains, attributedRules.domains) XCTAssertEqual(trackerData.entities, attributedRules.entities) XCTAssertEqual(trackerData.cnames, attributedRules.cnames) } - + func testWhenEntityIsNotOnAllowlistThenNothingChanges() throws { let trackerData = try JSONDecoder().decode(TrackerData.self, from: exampleTDS) - + let mockConfig = MockAttributionConfig() mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "other.com", host: "test.com")) - + let mutator = AdClickAttributionRulesMutator(trackerData: trackerData, config: mockConfig) let attributedRules = mutator.addException(vendorDomain: "vendor.com") - + XCTAssert(try isEqualAsJson(l: attributedRules.trackers["example.com"], r: trackerData.trackers["example.com"])) XCTAssert(try isEqualAsJson(l: attributedRules.trackers["examplerules.com"], r: trackerData.trackers["examplerules.com"])) - + XCTAssertEqual(trackerData.trackers, attributedRules.trackers) XCTAssertEqual(trackerData.domains, attributedRules.domains) XCTAssertEqual(trackerData.entities, attributedRules.entities) XCTAssertEqual(trackerData.cnames, attributedRules.cnames) } - + func testWhenEntityExistingRulesThenTheyAreMergedWithAdditonalOnesAndAttributionsAreFirst() throws { let trackerData = try JSONDecoder().decode(TrackerData.self, from: exampleTDS) - + let mockConfig = MockAttributionConfig() mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "example.com", host: "test.com")) mockConfig.allowlist.append(AdClickAttributionFeature.AllowlistEntry(entity: "examplerules.com", host: "test.org")) - + let mutator = AdClickAttributionRulesMutator(trackerData: trackerData, config: mockConfig) let attributedRules = mutator.addException(vendorDomain: "vendor.com") - + XCTAssertFalse(try isEqualAsJson(l: attributedRules.trackers["example.com"], r: trackerData.trackers["example.com"])) XCTAssertFalse(try isEqualAsJson(l: attributedRules.trackers["examplerules.com"], r: trackerData.trackers["examplerules.com"])) - + XCTAssertNotNil(attributedRules.trackers["example.com"]) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.count, 1) XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.rule, "test\\.com(:[0-9]+)?/.*") XCTAssertEqual(attributedRules.trackers["example.com"]?.rules?.first?.exceptions?.domains, ["vendor.com"]) - + XCTAssertNotNil(attributedRules.trackers["examplerules.com"]) XCTAssertEqual(attributedRules.trackers["examplerules.com"]?.rules?.count, 2) XCTAssertEqual(attributedRules.trackers["examplerules.com"]?.rules?.first?.rule, "test\\.org(:[0-9]+)?/.*") XCTAssertEqual(attributedRules.trackers["examplerules.com"]?.rules?.first?.exceptions?.domains, ["vendor.com"]) XCTAssertEqual(attributedRules.trackers["examplerules.com"]?.rules?.last?.rule, "example.com/customrule/1.js") XCTAssertNil(attributedRules.trackers["examplerules.com"]?.rules?.last?.exceptions?.domains) - + XCTAssertEqual(trackerData.domains, attributedRules.domains) XCTAssertEqual(trackerData.entities, attributedRules.entities) XCTAssertEqual(trackerData.cnames, attributedRules.cnames) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesProviderTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesProviderTests.swift index 8525a4ae9..117cd5318 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesProviderTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesProviderTests.swift @@ -1,6 +1,5 @@ // -// AdClickAttributionDetectionTests.swift -// DuckDuckGo +// AdClickAttributionRulesProviderTests.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,13 +21,13 @@ import BrowserServicesKit import os class MockCompiledRuleListSource: CompiledRuleListsSource { - + var currentRules: [ContentBlockerRulesManager.Rules] { [currentMainRules, currentAttributionRules].compactMap { $0 } } - + var currentMainRules: ContentBlockerRulesManager.Rules? - + var onCurrentRulesQueried: () -> Void = { } var _currentAttributionRules: ContentBlockerRulesManager.Rules? @@ -48,23 +47,23 @@ class AdClickAttributionRulesProviderTests: XCTestCase { let feature = MockAttributing() let compiledRulesSource = MockCompiledRuleListSource() let exceptionsSource = MockContentBlockerRulesExceptionsSource() - + var fakeNewRules: ContentBlockerRulesManager.Rules! - + var provider: AdClickAttributionRulesProvider! override func setUp() async throws { try? await super.setUp() - + feature.allowlist = [AdClickAttributionFeature.AllowlistEntry(entity: "tracker.com", host: "sub.test.com")] - + compiledRulesSource.currentMainRules = await ContentBlockingRulesHelper().makeFakeRules(name: DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName, tdsEtag: "tdsEtag", tempListId: "tempEtag", allowListId: nil, unprotectedSitesHash: nil) - + let attributionName = AdClickAttributionRulesSplitter.blockingAttributionRuleListName(forListNamed: compiledRulesSource.currentMainRules!.name) compiledRulesSource.currentAttributionRules = await ContentBlockingRulesHelper().makeFakeRules(name: attributionName, tdsEtag: "tdsEtag", @@ -73,7 +72,7 @@ class AdClickAttributionRulesProviderTests: XCTestCase { unprotectedSitesHash: nil) XCTAssertNotNil(compiledRulesSource.currentMainRules) XCTAssertNotNil(compiledRulesSource.currentAttributionRules) - + fakeNewRules = await ContentBlockingRulesHelper().makeFakeRules(name: compiledRulesSource.currentAttributionRules!.name, tdsEtag: "updatedEtag", tempListId: "updatedEtag", @@ -85,15 +84,15 @@ class AdClickAttributionRulesProviderTests: XCTestCase { exceptionsSource: exceptionsSource, log: log) } - + func testWhenAttributionIsRequestedThenRulesArePrepared() { - + let rulesCompiled = expectation(description: "Rules should be compiled") provider.requestAttribution(forVendor: "vendor.com") { rules in rulesCompiled.fulfill() XCTAssertNotNil(rules) XCTAssertEqual(rules?.name, AdClickAttributionRulesProvider.Constants.attributedTempRuleListName) - + let tracker = rules?.trackerData.trackers["tracker.com"] XCTAssertNotNil(tracker) let rule = tracker?.rules?.first @@ -102,21 +101,21 @@ class AdClickAttributionRulesProviderTests: XCTestCase { XCTAssertEqual(rule?.action, .block) XCTAssertEqual(rule?.exceptions?.domains?.first, "vendor.com") } - + wait(for: [rulesCompiled], timeout: 5) } - + func testWhenAttributionIsRequestedMultipleTimesThenRulesArePreparedOnce() { - + let currentRulesQueried = expectation(description: "Current Rules should be queried") currentRulesQueried.expectedFulfillmentCount = 4 // 3 for set up, 1 for compilation compiledRulesSource.onCurrentRulesQueried = { currentRulesQueried.fulfill() } - + let rulesCompiled = expectation(description: "Rules should be compiled") rulesCompiled.expectedFulfillmentCount = 3 - + var compiledRules: [ContentBlockerRulesManager.Rules] = [] var identifiers: Set = [] provider.requestAttribution(forVendor: "vendor.com") { rules in @@ -134,26 +133,26 @@ class AdClickAttributionRulesProviderTests: XCTestCase { compiledRules.append(rules!) identifiers.insert(rules!.identifier.stringValue) } - + wait(for: [rulesCompiled, currentRulesQueried], timeout: 5) - + XCTAssertEqual(compiledRules.count, 3) XCTAssertEqual(identifiers.count, 1) XCTAssert(compiledRules[0].rulesList === compiledRules[1].rulesList) XCTAssert(compiledRules[0].rulesList === compiledRules[2].rulesList) } - + func testWhenAttributionIsRequestedForMultipleVendorsThenAllRulesArePrepared() { - + let currentRulesQueried = expectation(description: "Current Rules should be queried") currentRulesQueried.expectedFulfillmentCount = 5 // 3 for set up, 2 for compilation compiledRulesSource.onCurrentRulesQueried = { currentRulesQueried.fulfill() } - + let rulesCompiled = expectation(description: "Rules should be compiled") rulesCompiled.expectedFulfillmentCount = 3 - + var compiledRules: [ContentBlockerRulesManager.Rules] = [] var identifiers: Set = [] provider.requestAttribution(forVendor: "vendor.com") { rules in // #1 @@ -171,25 +170,25 @@ class AdClickAttributionRulesProviderTests: XCTestCase { compiledRules.append(rules!) identifiers.insert(rules!.identifier.stringValue) } - + wait(for: [rulesCompiled, currentRulesQueried], timeout: 10) - + XCTAssertEqual(compiledRules.count, 3) XCTAssert(compiledRules[0].rulesList === compiledRules[1].rulesList) // #1 and #3 are returned first XCTAssert(compiledRules[0].rulesList !== compiledRules[2].rulesList) // #2 is compiled afterwards } - + func testWhenAttributionIsRequestedForMultipleVendorsAndRulesChangeThenAllRulesAreCompiled() { - + let currentRulesQueried = expectation(description: "Current Rules should be queried") currentRulesQueried.expectedFulfillmentCount = 6 // 3 for set up, 3 for compilation compiledRulesSource.onCurrentRulesQueried = { currentRulesQueried.fulfill() } - + let rulesCompiled = expectation(description: "Rules should be compiled") rulesCompiled.expectedFulfillmentCount = 3 - + var compiledRules: [ContentBlockerRulesManager.Rules] = [] var identifiers: Set = [] provider.requestAttribution(forVendor: "vendor.com") { rules in // #1 @@ -197,10 +196,10 @@ class AdClickAttributionRulesProviderTests: XCTestCase { compiledRules.append(rules!) identifiers.insert(rules!.identifier.stringValue) } - + // Simulate rule list change self.compiledRulesSource.currentAttributionRules = self.fakeNewRules - + provider.requestAttribution(forVendor: "other.com") { rules in // #2 rulesCompiled.fulfill() compiledRules.append(rules!) @@ -211,12 +210,12 @@ class AdClickAttributionRulesProviderTests: XCTestCase { compiledRules.append(rules!) identifiers.insert(rules!.identifier.stringValue) } - + wait(for: [rulesCompiled, currentRulesQueried], timeout: 10) - + XCTAssertEqual(compiledRules.count, 3) XCTAssert(compiledRules[0].rulesList !== compiledRules[1].rulesList) XCTAssert(compiledRules[0].rulesList !== compiledRules[2].rulesList) } - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesSplitterTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesSplitterTests.swift index a7ae72a27..48687856d 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesSplitterTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesSplitterTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionRulesSplitterTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -22,7 +21,7 @@ import XCTest @testable import TrackerRadarKit final class AdClickAttributionRulesSplitterTests: XCTestCase { - + func testShouldNotSplitIfThereAreNoTrackerNames() { // given let trackerData = TrackerData(trackers: [:], entities: [:], domains: [:], cnames: nil) @@ -71,13 +70,13 @@ final class AdClickAttributionRulesSplitterTests: XCTestCase { // original list XCTAssertEqual(result!.0.name, rulesList.name) - + let attributionNamePrefix = AdClickAttributionRulesSplitter.Constants.attributionRuleListNamePrefix let attributionEtagPrefix = AdClickAttributionRulesSplitter.Constants.attributionRuleListETagPrefix - + XCTAssertEqual(result!.0.trackerData!.etag, attributionEtagPrefix + rulesList.trackerData!.etag) XCTAssertEqual(result!.0.fallbackTrackerData.etag, attributionEtagPrefix + rulesList.fallbackTrackerData.etag) - + XCTAssertTrue(result!.0.trackerData!.tds.trackers.isEmpty) XCTAssertTrue(result!.0.fallbackTrackerData.tds.trackers.isEmpty) @@ -89,9 +88,9 @@ final class AdClickAttributionRulesSplitterTests: XCTestCase { XCTAssertEqual(result!.1.fallbackTrackerData.tds, rulesList.fallbackTrackerData.tds) } - + func testWhenSplittingManyTrackersThenDomainsRelatedToEntitiesArePreserved() { - + // given let allowlistedTrackerNames = ["trackerone.com"] let trackerData = TrackerData(trackers: ["trackerone.com": makeKnownTracker(withName: "trackerone.com", @@ -113,23 +112,23 @@ final class AdClickAttributionRulesSplitterTests: XCTestCase { // when let result = splitter.split() - + // attribution list - + guard let attributionTDS = result!.1.trackerData else { XCTFail("No attribution list found") return } - + let attributionEtagPrefix = AdClickAttributionRulesSplitter.Constants.attributionRuleListETagPrefix XCTAssertEqual(attributionTDS.etag, attributionEtagPrefix + rulesList.trackerData!.etag) - + XCTAssertEqual(attributionTDS.tds.trackers.count, 1) XCTAssertEqual(attributionTDS.tds.trackers.first?.key, "trackerone.com") XCTAssertEqual(attributionTDS.tds.entities.count, 1) XCTAssertEqual(attributionTDS.tds.entities.first?.key, "Tracker Owner") XCTAssertEqual(Set(attributionTDS.tds.domains.keys), Set(["example.com", "trackerone.com"])) - + } private func makeKnownTracker(withName name: String, ownerName: String) -> KnownTracker { @@ -145,5 +144,5 @@ final class AdClickAttributionRulesSplitterTests: XCTestCase { private func makeEntity(withName name: String, domains: [String]) -> Entity { Entity(displayName: name, domains: domains, prevalence: 5.0) } - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerReferenceTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerReferenceTests.swift index 9055a63ad..cd6020298 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerReferenceTests.swift @@ -1,6 +1,5 @@ // // ContentBlockerReferenceTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -205,5 +204,5 @@ class ContentBlockerReferenceTests: XCTestCase { } } // swiftlint:enable function_body_length - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerInitialCompilationTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerInitialCompilationTests.swift index 5c574046a..f197328a1 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerInitialCompilationTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerInitialCompilationTests.swift @@ -1,6 +1,5 @@ // -// ContentBlockerRulesManagerTests.swift -// DuckDuckGo +// ContentBlockerRulesManagerInitialCompilationTests.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -33,18 +32,18 @@ final class CountedFulfillmentTestExpectation: XCTestExpectation { } final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { - + private static let fakeEmbeddedDataSet = ContentBlockerRulesManagerTests.makeDataSet(tds: ContentBlockerRulesManagerTests.validRules, etag: "\"\(UUID().uuidString)\"") private let rulesUpdateListener = RulesUpdateListener() - + func testSuccessfulCompilationStoresLastCompiledRules() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: ContentBlockerRulesManagerTests.makeDataSet(tds: ContentBlockerRulesManagerTests.validRules, etag: ContentBlockerRulesManagerTests.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() let mockLastCompiledRulesStore = MockLastCompiledRulesStore() - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() @@ -54,23 +53,23 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { mockLastCompiledRulesStore.onRulesSet = { expStore.fulfill() } - + let cbrm = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, lastCompiledRulesStore: mockLastCompiledRulesStore, updateListener: rulesUpdateListener) - + wait(for: [exp, expStore], timeout: 15.0) - + XCTAssertNotNil(mockLastCompiledRulesStore.rules) XCTAssertEqual(mockLastCompiledRulesStore.rules.first?.etag, mockRulesSource.trackerData?.etag) XCTAssertEqual(mockLastCompiledRulesStore.rules.first?.name, mockRulesSource.ruleListName) XCTAssertEqual(mockLastCompiledRulesStore.rules.first?.trackerData, mockRulesSource.trackerData?.tds) XCTAssertEqual(mockLastCompiledRulesStore.rules.first?.identifier, cbrm.currentRules.first?.identifier) } - + func testInitialCompilation_WhenNoChangesToTDS_ShouldNotFetchLastCompiled() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: ContentBlockerRulesManagerTests.makeDataSet(tds: ContentBlockerRulesManagerTests.validRules, etag: ContentBlockerRulesManagerTests.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) @@ -90,7 +89,7 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { mockLastCompiledRulesStore.onRulesGet = { XCTFail("Should use rules cached by WebKit") } - + // simulate the rules have been compiled in the past so the WKContentRuleListStore contains it _ = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, @@ -110,7 +109,7 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { } wait(for: [exp], timeout: 15.0) - + func assertRules(_ rules: [ContentBlockerRulesManager.Rules]) { guard let rules = rules.first else { XCTFail("Couldn't get rules"); return } XCTAssertEqual(cachedRules.etag, rules.etag) @@ -183,10 +182,10 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { wait(for: [expCacheLookup, expNext], timeout: 15.0) } - + // swiftlint:disable:next function_body_length func testInitialCompilation_WhenThereAreChangesToTDS_ShouldBuildRulesUsingLastCompiledRulesAndScheduleRecompilationWithNewSource() { - + let oldEtag = ContentBlockerRulesManagerTests.makeEtag() let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: ContentBlockerRulesManagerTests.makeDataSet(tds: ContentBlockerRulesManagerTests.validRules, etag: oldEtag), @@ -211,9 +210,9 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { trackerData: mockRulesSource.trackerData!.tds, etag: mockRulesSource.trackerData!.etag, identifier: oldIdentifier) - + mockLastCompiledRulesStore.rules = [cachedRules] - + // simulate the rules have been compiled in the past so the WKContentRuleListStore contains it _ = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, @@ -257,16 +256,16 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { wait(for: [expLastCompiledFetched, expRecompiled], timeout: 15.0) } - + struct MockLastCompiledRules: LastCompiledRules { - + var name: String var trackerData: TrackerData var etag: String var identifier: ContentBlockerRulesIdentifier - + } - + final class MockLastCompiledRulesStore: LastCompiledRulesStore { var onRulesGet: () -> Void = { } @@ -283,7 +282,7 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { _rules = newValue } } - + func update(with contentBlockerRules: [ContentBlockerRulesManager.Rules]) { rules = contentBlockerRules.map { rules in MockLastCompiledRules(name: rules.name, @@ -292,7 +291,7 @@ final class ContentBlockerRulesManagerInitialCompilationTests: XCTestCase { identifier: rules.identifier) } } - + } - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerMultipleRulesTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerMultipleRulesTests.swift index 6aeb8e22b..caa6b1199 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerMultipleRulesTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerMultipleRulesTests.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesManagerMultipleRulesTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -25,7 +24,7 @@ import Common // swiftlint:disable unused_closure_parameter class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTests { - + let firstRules = """ { "trackers": { @@ -72,7 +71,7 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe } } """ - + let secondRules = """ { "trackers": { @@ -119,10 +118,10 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe } } """ - + class MockContentBlockerRulesListsSource: ContentBlockerRulesListsSource { let contentBlockerRulesLists: [ContentBlockerRulesList] - + init(firstName: String, firstTD: TrackerDataManager.DataSet?, firstFallbackTD: TrackerDataManager.DataSet, secondName: String, secondTD: TrackerDataManager.DataSet?, secondFallbackTD: TrackerDataManager.DataSet) { contentBlockerRulesLists = [ContentBlockerRulesList(name: firstName, @@ -133,9 +132,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe fallbackTrackerData: secondFallbackTD)] } } - + private let rulesUpdateListener = RulesUpdateListener() - + let schemeHandler = TestSchemeHandler() let navigationDelegateMock = MockNavigationDelegate() @@ -145,22 +144,22 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe schemeHandler: TestSchemeHandler) -> WKWebView { XCTAssertFalse(currentRules.isEmpty) - + let configuration = WKWebViewConfiguration() configuration.setURLSchemeHandler(schemeHandler, forURLScheme: schemeHandler.scheme) - + let webView = WKWebView(frame: .init(origin: .zero, size: .init(width: 500, height: 1000)), configuration: configuration) webView.navigationDelegate = self.navigationDelegateMock - + for rule in currentRules { configuration.userContentController.add(rule.rulesList) } return webView } - + func testCompilationOfMultipleRulesListsWithSameETag() { - + let sharedETag = Self.makeEtag() let mockRulesSource = MockContentBlockerRulesListsSource(firstName: "first", firstTD: Self.makeDataSet(tds: firstRules, etag: sharedETag), @@ -181,9 +180,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertFalse(cbrm.currentRules.isEmpty) - + for rules in cbrm.currentRules { if let source = mockRulesSource.contentBlockerRulesLists.first(where: { $0.name == rules.name }) { XCTAssertEqual(source.trackerData?.etag, rules.etag) @@ -191,12 +190,12 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe XCTFail("Missing rules") } } - + XCTAssertNotEqual(cbrm.currentRules[0].identifier.stringValue, cbrm.currentRules[1].identifier.stringValue) } - + func testBrokenTDSRecompilationAndFallback() { - + let invalidRulesETag = Self.makeEtag() let mockRulesSource = MockContentBlockerRulesListsSource(firstName: "first", firstTD: Self.makeDataSet(tds: Self.invalidRules, etag: invalidRulesETag), @@ -211,12 +210,12 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "No error reported") errorExp.expectedFulfillmentCount = 2 var brokenLists = Set() var errorComponents = Set() - let errorHandler = EventMapping.init { event, error, params, onComplete in + let errorHandler = EventMapping { event, error, params, onComplete in if case .contentBlockingCompilationFailed(let listName, let component) = event { brokenLists.insert(listName) errorComponents.insert(component) @@ -230,12 +229,12 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe errorReporting: errorHandler) wait(for: [exp, errorExp], timeout: 15.0) - + XCTAssertEqual(brokenLists, Set(["first", "second"])) XCTAssertEqual(errorComponents, Set([.tds])) - + XCTAssertFalse(cbrm.currentRules.isEmpty) - + for rules in cbrm.currentRules { if let source = mockRulesSource.contentBlockerRulesLists.first(where: { $0.name == rules.name }) { XCTAssertEqual(source.fallbackTrackerData.etag, rules.etag) @@ -244,9 +243,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe } } } - + func testCompilationOfMultipleRulesLists() { - + let mockRulesSource = MockContentBlockerRulesListsSource(firstName: "first", firstTD: Self.makeDataSet(tds: firstRules), firstFallbackTD: Self.makeDataSet(tds: firstRules), @@ -266,9 +265,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertFalse(cbrm.currentRules.isEmpty) - + for rules in cbrm.currentRules { if let source = mockRulesSource.contentBlockerRulesLists.first(where: { $0.name == rules.name }) { XCTAssertEqual(source.trackerData?.etag, rules.etag) @@ -277,9 +276,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe } } } - + func testCompilationOfMultipleFallbackRulesLists() { - + let mockRulesSource = MockContentBlockerRulesListsSource(firstName: "first", firstTD: nil, firstFallbackTD: Self.makeDataSet(tds: firstRules), @@ -299,9 +298,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertFalse(cbrm.currentRules.isEmpty) - + for rules in cbrm.currentRules { if let source = mockRulesSource.contentBlockerRulesLists.first(where: { $0.name == rules.name }) { XCTAssertEqual(source.fallbackTrackerData.etag, rules.etag) @@ -310,9 +309,9 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe } } } - + func testBrokenFallbackTDSFailure() { - + let mockRulesSource = MockContentBlockerRulesListsSource(firstName: "first", firstTD: Self.makeDataSet(tds: Self.invalidRules), firstFallbackTD: Self.makeDataSet(tds: Self.invalidRules), @@ -326,12 +325,12 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "No error reported") errorExp.expectedFulfillmentCount = 2 var brokenLists = Set() var errorComponents = Set() - let errorHandler = EventMapping.init { event, error, params, onComplete in + let errorHandler = EventMapping { event, error, params, onComplete in if case .contentBlockingCompilationFailed(let listName, let component) = event { brokenLists.insert(listName) errorComponents.insert(component) @@ -345,13 +344,13 @@ class ContentBlockerRulesManagerMultipleRulesTests: ContentBlockerRulesManagerTe errorReporting: errorHandler) wait(for: [exp, errorExp], timeout: 15.0) - + XCTAssertEqual(brokenLists, Set(["first"])) XCTAssertEqual(errorComponents, Set([.tds, .fallbackTds])) - + XCTAssertEqual(cbrm.currentRules.count, 1) XCTAssertEqual(cbrm.currentRules.first?.name, "second") } - + } // swiftlint:enable unused_closure_parameter diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerTests.swift index 6ada850f3..dc1e188ce 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesManagerTests.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesManagerTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -25,7 +24,7 @@ import Combine import Common class ContentBlockerRulesManagerTests: XCTestCase { - + static let validRules = """ { "trackers": { @@ -72,7 +71,7 @@ class ContentBlockerRulesManagerTests: XCTestCase { } } """ - + static let invalidRules = """ { "trackers": { @@ -139,35 +138,35 @@ class ContentBlockerRulesManagerTests: XCTestCase { } } """ - + let validTempSites = ["example.com"] let invalidTempSites = ["This is not valid.. ."] let validAllowList = [TrackerException(rule: "tracker.com/", matching: .all)] let invalidAllowList = [TrackerException(rule: "tracker.com/", matching: .domains(["broken site Ltd. . 😉.com"]))] - + static var fakeEmbeddedDataSet: TrackerDataManager.DataSet! - + override class func setUp() { super.setUp() - + fakeEmbeddedDataSet = makeDataSet(tds: validRules, etag: "\"\(UUID().uuidString)\"") } - + static func makeDataSet(tds: String) -> TrackerDataManager.DataSet { return makeDataSet(tds: tds, etag: makeEtag()) } - + static func makeDataSet(tds: String, etag: String) -> TrackerDataManager.DataSet { let data = tds.data(using: .utf8)! let decoded = try? JSONDecoder().decode(TrackerData.self, from: data) return (decoded!, etag) } - + static func makeEtag() -> String { return "\"\(UUID().uuidString)\"" } - + } final class RulesUpdateListener: ContentBlockerRulesUpdating { @@ -185,9 +184,9 @@ final class RulesUpdateListener: ContentBlockerRulesUpdating { class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { private let rulesUpdateListener = RulesUpdateListener() - + func test_ValidTDS_NoTempList_NoAllowList_NoUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: (Self.fakeEmbeddedDataSet.tds, Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() @@ -198,11 +197,11 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "No error reported") errorExp.isInverted = true let compilationTimeExp = expectation(description: "Compilation Time reported") - let errorHandler = EventMapping.init { event, _, params, _ in + let errorHandler = EventMapping { event, _, params, _ in if case .contentBlockingCompilationFailed(let listName, let component) = event { XCTAssertEqual(listName, DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName) switch component { @@ -211,7 +210,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { default: XCTFail("Unexpected component: \(component)") } - + } else if case .contentBlockingCompilationTime = event { XCTAssertNotNil(params?["compilationTime"]) compilationTimeExp.fulfill() @@ -226,7 +225,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { errorReporting: errorHandler) wait(for: [exp, errorExp, compilationTimeExp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.trackerData?.etag) @@ -237,22 +236,22 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_InvalidTDS_NoTempList_NoAllowList_NoUnprotectedSites() { let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.invalidRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "Error reported") - let errorHandler = EventMapping.init { event, _, params, _ in + let errorHandler = EventMapping { event, _, params, _ in if case .contentBlockingCompilationFailed(let listName, let component) = event { XCTAssertEqual(listName, DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName) switch component { @@ -261,7 +260,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { default: XCTFail("Unexpected component: \(component)") } - + } else if case .contentBlockingCompilationTime = event { XCTAssertNotNil(params?["compilationTime"]) } else { @@ -275,7 +274,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { errorReporting: errorHandler) wait(for: [exp, errorExp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.embeddedTrackerData.etag) @@ -286,7 +285,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_ValidTDS_ValidTempList_NoAllowList_NoUnprotectedSites() { let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), @@ -294,9 +293,9 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() @@ -307,7 +306,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules) XCTAssertNotNil(cbrm.currentRules.first?.etag) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.trackerData?.etag) @@ -319,7 +318,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_InvalidTDS_ValidTempList_NoAllowList_NoUnprotectedSites() { let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.invalidRules, etag: Self.makeEtag()), @@ -327,9 +326,9 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() @@ -340,12 +339,12 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules.first?.etag) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier, mockRulesSource.trackerData?.etag) - + XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.embeddedTrackerData.etag) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -355,17 +354,17 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_ValidTDS_InvalidTempList_NoAllowList_NoUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = invalidTempSites - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() @@ -376,14 +375,14 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules.first?.etag) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.embeddedTrackerData.etag) - + // TDS is also marked as invalid to simplify flow XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier, mockRulesSource.trackerData?.etag) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier, mockExceptionsSource.tempListId) @@ -394,18 +393,18 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_ValidTDS_ValidTempList_NoAllowList_ValidUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites mockExceptionsSource.unprotectedSites = ["example.com"] - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() @@ -416,14 +415,14 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { updateListener: rulesUpdateListener) wait(for: [exp], timeout: 15.0) - + XCTAssertNotNil(cbrm.currentRules.first?.etag) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.trackerData?.etag) - + XCTAssertNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier) XCTAssertNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier) XCTAssertNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.unprotectedSitesIdentifier) - + XCTAssertEqual(cbrm.currentRules.first?.identifier, ContentBlockerRulesIdentifier(name: DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName, tdsEtag: mockRulesSource.trackerData?.etag ?? "\"\"", @@ -433,7 +432,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { } func test_ValidTDS_ValidTempList_ValidAllowList_ValidUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() @@ -516,9 +515,9 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: mockExceptionsSource.unprotectedSitesHash)) } - + func test_ValidTDS_ValidTempList_ValidAllowList_BrokenUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() @@ -527,18 +526,18 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { mockExceptionsSource.allowListId = Self.makeEtag() mockExceptionsSource.allowList = validAllowList mockExceptionsSource.unprotectedSites = ["broken site Ltd. . 😉.com"] - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "Error reported") errorExp.expectedFulfillmentCount = 5 var errorEvents = [ContentBlockerDebugEvents.Component]() - let errorHandler = EventMapping.init { event, _, params, _ in + let errorHandler = EventMapping { event, _, params, _ in if case .contentBlockingCompilationFailed(let listName, let component) = event { XCTAssertEqual(listName, DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName) errorEvents.append(component) @@ -548,7 +547,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { default: XCTFail("Unexpected component: \(component)") } - + } else if case .contentBlockingCompilationTime = event { XCTAssertNotNil(params?["compilationTime"]) errorExp.fulfill() @@ -563,20 +562,20 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { errorReporting: errorHandler) wait(for: [exp, errorExp], timeout: 15.0) - + XCTAssertEqual(Set(errorEvents), Set([.tds, .tempUnprotected, .allowlist, .localUnprotected])) - + XCTAssertNotNil(cbrm.currentRules.first?.etag) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.embeddedTrackerData.etag) - + // TDS is also marked as invalid to simplify flow XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier, mockRulesSource.trackerData?.etag) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier, mockExceptionsSource.tempListId) @@ -584,7 +583,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.allowListIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.allowListIdentifier, mockExceptionsSource.allowListId) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.unprotectedSitesIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.unprotectedSitesIdentifier, mockExceptionsSource.unprotectedSitesHash) @@ -596,9 +595,9 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { allowListId: nil, unprotectedSitesHash: nil)) } - + func test_CurrentTDSEqualToFallbackTDS_ValidTempList_ValidAllowList_BrokenUnprotectedSites() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.fakeEmbeddedDataSet, embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() @@ -607,18 +606,18 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { mockExceptionsSource.allowListId = Self.makeEtag() mockExceptionsSource.allowList = validAllowList mockExceptionsSource.unprotectedSites = ["broken site Ltd. . 😉.com"] - + XCTAssertEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let exp = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in exp.fulfill() } - + let errorExp = expectation(description: "Error reported") errorExp.expectedFulfillmentCount = 4 var errorEvents = [ContentBlockerDebugEvents.Component]() - let errorHandler = EventMapping.init { event, _, params, _ in + let errorHandler = EventMapping { event, _, params, _ in if case .contentBlockingCompilationFailed(let listName, let component) = event { XCTAssertEqual(listName, DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName) errorEvents.append(component) @@ -628,7 +627,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { default: XCTFail("Unexpected component: \(component)") } - + } else if case .contentBlockingCompilationTime = event { XCTAssertNotNil(params?["compilationTime"]) errorExp.fulfill() @@ -643,16 +642,16 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { errorReporting: errorHandler) wait(for: [exp, errorExp], timeout: 15.0) - + XCTAssertEqual(Set(errorEvents), Set([.tempUnprotected, .allowlist, .localUnprotected])) - + XCTAssertNotNil(cbrm.currentRules.first?.etag) XCTAssertEqual(cbrm.currentRules.first?.etag, mockRulesSource.embeddedTrackerData.etag) - + XCTAssertNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tdsIdentifier) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.tempListIdentifier, mockExceptionsSource.tempListId) @@ -660,7 +659,7 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.allowListIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.allowListIdentifier, mockExceptionsSource.allowListId) - + XCTAssertNotNil(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.unprotectedSitesIdentifier) XCTAssertEqual(cbrm.sourceManagers[mockRulesSource.ruleListName]?.brokenSources?.unprotectedSitesIdentifier, mockExceptionsSource.unprotectedSitesHash) @@ -677,17 +676,17 @@ class ContentBlockerRulesManagerLoadingTests: ContentBlockerRulesManagerTests { class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { private let rulesUpdateListener = RulesUpdateListener() - + func test_InvalidTDS_BeingFixed() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.invalidRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let initialLoading = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in initialLoading.fulfill() @@ -696,7 +695,7 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { let cbrm = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, updateListener: rulesUpdateListener) - + wait(for: [initialLoading], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -705,9 +704,9 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: mockExceptionsSource.tempListId, allowListId: nil, unprotectedSitesHash: nil)) - + mockRulesSource.trackerData = Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()) - + let identifier = cbrm.currentRules.first?.identifier let updating = expectation(description: "Rules Compiled") @@ -716,7 +715,7 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { } cbrm.scheduleCompilation() - + wait(for: [updating], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier.stringValue, @@ -725,10 +724,10 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: mockExceptionsSource.tempListId, allowListId: nil, unprotectedSitesHash: nil).stringValue) - + if let oldId = identifier, let newId = cbrm.currentRules.first?.identifier { let diff = oldId.compare(with: newId) - + XCTAssert(diff.contains(.tdsEtag)) XCTAssertFalse(diff.contains(.tempListId)) XCTAssertFalse(diff.contains(.unprotectedSites)) @@ -736,17 +735,17 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { XCTFail("Missing identifiers") } } - + func test_InvalidTempList_BeingFixed() { - + let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), embeddedTrackerData: Self.fakeEmbeddedDataSet) let mockExceptionsSource = MockContentBlockerRulesExceptionsSource() mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = invalidTempSites - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let initialLoading = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in initialLoading.fulfill() @@ -755,7 +754,7 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { let cbrm = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, updateListener: rulesUpdateListener) - + wait(for: [initialLoading], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -764,19 +763,19 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: nil, allowListId: nil, unprotectedSitesHash: nil)) - + mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites - + let identifier = cbrm.currentRules.first?.identifier let updating = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in updating.fulfill() } - + cbrm.scheduleCompilation() - + wait(for: [updating], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -785,10 +784,10 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: mockExceptionsSource.tempListId, allowListId: nil, unprotectedSitesHash: nil)) - + if let oldId = identifier, let newId = cbrm.currentRules.first?.identifier { let diff = oldId.compare(with: newId) - + XCTAssert(diff.contains(.tdsEtag)) XCTAssert(diff.contains(.tempListId)) XCTAssertFalse(diff.contains(.unprotectedSites)) @@ -856,7 +855,7 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { XCTFail("Missing identifiers") } } - + func test_InvalidUnprotectedSites_BeingFixed() { let mockRulesSource = MockSimpleContentBlockerRulesListsSource(trackerData: Self.makeDataSet(tds: Self.validRules, etag: Self.makeEtag()), @@ -865,9 +864,9 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { mockExceptionsSource.tempListId = Self.makeEtag() mockExceptionsSource.tempList = validTempSites mockExceptionsSource.unprotectedSites = ["broken site Ltd. . 😉.com"] - + XCTAssertNotEqual(mockRulesSource.trackerData?.etag, mockRulesSource.embeddedTrackerData.etag) - + let initialLoading = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in initialLoading.fulfill() @@ -876,7 +875,7 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { let cbrm = ContentBlockerRulesManager(rulesSource: mockRulesSource, exceptionsSource: mockExceptionsSource, updateListener: rulesUpdateListener) - + wait(for: [initialLoading], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -885,18 +884,18 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: nil, allowListId: nil, unprotectedSitesHash: nil)) - + mockExceptionsSource.unprotectedSites = ["example.com"] - + let identifier = cbrm.currentRules.first?.identifier let updating = expectation(description: "Rules Compiled") rulesUpdateListener.onRulesUpdated = { _ in updating.fulfill() } - + cbrm.scheduleCompilation() - + wait(for: [updating], timeout: 15.0) XCTAssertEqual(cbrm.currentRules.first?.identifier, @@ -905,10 +904,10 @@ class ContentBlockerRulesManagerUpdatingTests: ContentBlockerRulesManagerTests { tempListId: mockExceptionsSource.tempListId, allowListId: nil, unprotectedSitesHash: mockExceptionsSource.unprotectedSitesHash)) - + if let oldId = identifier, let newId = cbrm.currentRules.first?.identifier { let diff = oldId.compare(with: newId) - + XCTAssert(diff.contains(.tdsEtag)) XCTAssert(diff.contains(.tempListId)) XCTAssert(diff.contains(.unprotectedSites)) @@ -1098,7 +1097,7 @@ class ContentBlockerRulesManagerCleanupTests: ContentBlockerRulesManagerTests, C } class MockSimpleContentBlockerRulesListsSource: ContentBlockerRulesListsSource { - + var trackerData: TrackerDataManager.DataSet? { didSet { let trackerData = trackerData @@ -1117,20 +1116,20 @@ class MockSimpleContentBlockerRulesListsSource: ContentBlockerRulesListsSource { fallbackTrackerData: embeddedTrackerData)] } } - + var contentBlockerRulesLists: [ContentBlockerRulesList] - + var ruleListName = DefaultContentBlockerRulesListsSource.Constants.trackerDataSetRulesListName - + init(trackerData: TrackerDataManager.DataSet?, embeddedTrackerData: TrackerDataManager.DataSet) { self.trackerData = trackerData self.embeddedTrackerData = embeddedTrackerData - + contentBlockerRulesLists = [ContentBlockerRulesList(name: ruleListName, trackerData: trackerData, fallbackTrackerData: embeddedTrackerData)] } - + } class MockContentBlockerRulesExceptionsSource: ContentBlockerRulesExceptionsSource { @@ -1140,7 +1139,7 @@ class MockContentBlockerRulesExceptionsSource: ContentBlockerRulesExceptionsSour var allowListId: String = "" var allowList: [TrackerException] = [] var unprotectedSites: [String] = [] - + var unprotectedSitesHash: String { return ContentBlockerRulesIdentifier.hash(domains: unprotectedSites) } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesUserScriptsTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesUserScriptsTests.swift index 6157850f8..670e717ed 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesUserScriptsTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockerRulesUserScriptsTests.swift @@ -1,6 +1,5 @@ // // ContentBlockerRulesUserScriptsTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -76,7 +75,7 @@ class ContentBlockerRulesUserScriptsTests: XCTestCase { let userScriptDelegateMock = MockRulesUserScriptDelegate() let navigationDelegateMock = MockNavigationDelegate() let tld = TLD() - + var webView: WKWebView? let nonTrackerURL = URL(string: "test://nontracker.com/1.png")! @@ -92,7 +91,7 @@ class ContentBlockerRulesUserScriptsTests: XCTestCase { .init(type: .image, url: trackerURL), .init(type: .image, url: subTrackerURL)]) } - + private func setupWebViewForUserScripTests(trackerData: TrackerData, privacyConfig: PrivacyConfiguration, userScriptDelegate: ContentBlockerRulesUserScriptDelegate, @@ -212,11 +211,11 @@ class ContentBlockerRulesUserScriptsTests: XCTestCase { let blockedTrackers = Set(self.userScriptDelegateMock.detectedTrackers.filter { $0.isBlocked }.map { $0.domain }) XCTAssertTrue(blockedTrackers.isEmpty) - + // We don't report first party trackers let detectedTrackers = Set(self.userScriptDelegateMock.detectedTrackers.map { $0.domain }) XCTAssert(detectedTrackers.isEmpty) - + let expected3rdParty: Set = ["nontracker.com"] let detected3rdParty = Set(self.userScriptDelegateMock.detectedThirdPartyRequests.map { $0.domain }) XCTAssertEqual(detected3rdParty, expected3rdParty) @@ -229,7 +228,7 @@ class ContentBlockerRulesUserScriptsTests: XCTestCase { self.wait(for: [websiteLoaded], timeout: 30) } - + func testWhenThereIsFirstPartyRequestThenItIsNotBlocked() { let privacyConfig = WebKitTestHelper.preparePrivacyConfig(locallyUnprotected: [], @@ -247,7 +246,7 @@ class ContentBlockerRulesUserScriptsTests: XCTestCase { let expectedTrackers: Set = ["sub.tracker.com", "tracker.com"] let blockedTrackers = Set(self.userScriptDelegateMock.detectedTrackers.filter { $0.isBlocked }.map { $0.domain }) XCTAssertEqual(blockedTrackers, expectedTrackers) - + let detected3rdParty = Set(self.userScriptDelegateMock.detectedThirdPartyRequests.map { $0.domain }) XCTAssert(detected3rdParty.isEmpty) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockingRulesHelper.swift b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockingRulesHelper.swift index a4553898b..10532b246 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockingRulesHelper.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockingRulesHelper.swift @@ -1,6 +1,5 @@ // // ContentBlockingRulesHelper.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -24,9 +23,9 @@ import WebKit @MainActor final class ContentBlockingRulesHelper { - + func makeFakeTDS() -> TrackerData { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker", displayName: "Tracker"), @@ -34,38 +33,38 @@ final class ContentBlockingRulesHelper { subdomains: nil, categories: nil, rules: nil) - + let entity = Entity(displayName: "Tracker", domains: ["tracker.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker": entity], domains: ["tracker.com": "Tracker"], cnames: nil) - + return tds } - + func makeFakeRules(name: String) async -> ContentBlockerRulesManager.Rules? { return await makeFakeRules(name: name, tdsEtag: UUID().uuidString) } - + func makeFakeRules(name: String, tdsEtag: String, tempListId: String? = nil, allowListId: String? = nil, unprotectedSitesHash: String? = nil) async -> ContentBlockerRulesManager.Rules? { - + let identifier = ContentBlockerRulesIdentifier(name: name, tdsEtag: tdsEtag, tempListId: tempListId, allowListId: allowListId, unprotectedSitesHash: unprotectedSitesHash) let tds = makeFakeTDS() - + let builder = ContentBlockerRulesBuilder(trackerData: tds) let rules = builder.buildRules() - + let data: Data do { data = try JSONEncoder().encode(rules) @@ -74,12 +73,12 @@ final class ContentBlockingRulesHelper { } let ruleList = String(data: data, encoding: .utf8)! - + guard let compiledRules = try? await WKContentRuleListStore.default().compileContentRuleList(forIdentifier: identifier.stringValue, encodedContentRuleList: ruleList) else { return nil } - + return .init(name: name, rulesList: compiledRules, trackerData: tds, diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/DetectedRequestTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/DetectedRequestTests.swift index 6b57273f0..53a0cde7f 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/DetectedRequestTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/DetectedRequestTests.swift @@ -40,7 +40,7 @@ final class DetectedRequestTests: XCTestCase { XCTAssertEqual(tracker1.hashValue, tracker2.hashValue) XCTAssertEqual(tracker1, tracker2) } - + func testWhenTrackerRequestsHaveSameEntityButDifferentBlockedStatusThenHashIsNotEqualAndIsEqualsIsFalse() { let entity = Entity(displayName: "Entity", domains: nil, prevalence: nil) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingReportTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingReportTests.swift index bb24ffa1a..ae9676ab4 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingReportTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingReportTests.swift @@ -1,6 +1,5 @@ // // DomainMatchingReportTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -32,25 +31,25 @@ class DomainMatchingReportTests: XCTestCase { let testJSON = data.fromJsonFile("Resources/privacy-reference-tests/tracker-radar-tests/TR-domain-matching/domain_matching_tests.json") let trackerData = try JSONDecoder().decode(TrackerData.self, from: trackerJSON) - + let refTests = try JSONDecoder().decode(RefTests.self, from: testJSON) let tests = refTests.domainTests.tests - + let resolver = TrackerResolver(tds: trackerData, unprotectedSites: [], tempList: [], tld: TLD()) for test in tests { - + let skip = test.exceptPlatforms?.contains("ios-browser") if skip == true { os_log("!!SKIPPING TEST: %s", test.name) continue } - + let tracker = resolver.trackerFromUrl(test.requestURL, pageUrlString: test.siteURL, resourceType: test.requestType, potentiallyBlocked: true) - + if test.expectAction == "block" { XCTAssertNotNil(tracker) XCTAssert(tracker?.isBlocked ?? false) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingTests.swift index 8dd6006a0..3c1a912b8 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingTests.swift @@ -1,6 +1,5 @@ // // DomainMatchingTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -23,26 +22,26 @@ import Foundation import os.log struct RefTests: Decodable { - + struct Test: Decodable { - + let name: String let siteURL: String let requestURL: String let requestType: String let expectAction, expectExpression: String? let exceptPlatforms: [String]? - + } - + struct DomainTests: Decodable { - + let name: String let desc: String let tests: [Test] - + } - + let domainTests, surrogateTests: DomainTests } @@ -54,7 +53,7 @@ class DomainMatchingTests: XCTestCase { let testJSON = data.fromJsonFile("Resources/privacy-reference-tests/tracker-radar-tests/TR-domain-matching/domain_matching_tests.json") let trackerData = try JSONDecoder().decode(TrackerData.self, from: trackerJSON) - + let refTests = try JSONDecoder().decode(RefTests.self, from: testJSON) let tests = refTests.domainTests.tests @@ -88,7 +87,7 @@ extension Array where Element == ContentBlockerRule { for rule in self where rule.matches(resourceUrl: url, onPageWithUrl: topLevel, ofType: resourceType) { result = rule } - + return result } } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/MockWebsite.swift b/Tests/BrowserServicesKitTests/ContentBlocker/MockWebsite.swift index fff7ad939..f32d5b332 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/MockWebsite.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/MockWebsite.swift @@ -1,6 +1,5 @@ // // MockWebsite.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesReferenceTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesReferenceTests.swift index de5cdf0a8..e9dae8ab7 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesReferenceTests.swift @@ -1,6 +1,5 @@ // -// TrackerAllowlistReferenceTests.swift -// DuckDuckGo +// SurrogatesReferenceTests.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -32,43 +31,43 @@ final class SurrogatesReferenceTests: XCTestCase { private let tld = TLD() private var redirectTests = [RefTests.Test]() private var webView: WKWebView! - + private enum Resource { static let trackerRadar = "Resources/privacy-reference-tests/tracker-radar-tests/TR-domain-matching/tracker_radar_reference.json" static let tests = "Resources/privacy-reference-tests/tracker-radar-tests/TR-domain-matching/domain_matching_tests.json" static let surrogates = "Resources/privacy-reference-tests/tracker-radar-tests/TR-domain-matching/surrogates.txt" } - + func testSurrogates() throws { let dataLoader = JsonTestDataLoader() - + let trackerRadarJSONData = dataLoader.fromJsonFile(Resource.trackerRadar) let testsData = dataLoader.fromJsonFile(Resource.tests) let surrogatesData = dataLoader.fromJsonFile(Resource.surrogates) - + let referenceTests = try JSONDecoder().decode(RefTests.self, from: testsData) let surrogateTests = referenceTests.surrogateTests.tests - + let surrogateString = String(data: surrogatesData, encoding: .utf8)! - + let trackerData = try JSONDecoder().decode(TrackerData.self, from: trackerRadarJSONData) let encodedData = try? JSONEncoder().encode(trackerData) let encodedTrackerData = String(data: encodedData!, encoding: .utf8)! - + let rules = ContentBlockerRulesBuilder(trackerData: trackerData).buildRules(withExceptions: [], andTemporaryUnprotectedDomains: []) - + let privacyConfig = WebKitTestHelper.preparePrivacyConfig(locallyUnprotected: [], tempUnprotected: [], trackerAllowlist: [:], contentBlockingEnabled: true, exceptions: []) - + let platformTests = surrogateTests.filter { let skip = $0.exceptPlatforms?.contains("ios-browser") return skip == false || skip == nil } - + /* We need to split redirect tests from the rest redirect surrogates have to be injected in webview and then validated against an expression @@ -76,11 +75,11 @@ final class SurrogatesReferenceTests: XCTestCase { redirectTests = platformTests.filter { $0.expectAction == "redirect" } - + let notRedirectTests = platformTests.filter { $0.expectAction != "redirect" } - + for test in notRedirectTests { os_log("TEST: %s", test.name) let requestURL = URL(string: test.requestURL)! @@ -88,36 +87,36 @@ final class SurrogatesReferenceTests: XCTestCase { let requestType = ContentBlockerRulesBuilder.resourceMapping[test.requestType] let rule = rules.matchURL(url: requestURL, topLevel: siteURL, resourceType: requestType!) let result = rule?.action - + if test.expectAction == "block" { XCTAssertEqual(result, .block()) } else if test.expectAction == "ignore" { XCTAssertTrue(result == nil || result == .ignorePreviousRules()) } } - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = redirectTests.count - + createWebViewForUserScripTests(trackerData: trackerData, encodedTrackerData: encodedTrackerData, surrogates: surrogateString, privacyConfig: privacyConfig) { webview in - + self.webView = webview self.runTestForRedirect(onTestExecuted: testsExecuted) } - + waitForExpectations(timeout: 30, handler: nil) } - + private func runTestForRedirect(onTestExecuted: XCTestExpectation) { - + guard let test = redirectTests.popLast(), let expectExpression = test.expectExpression else { return } - + os_log("TEST: %s", test.name) let requestURL = URL(string: test.requestURL.testSchemeNormalized)! @@ -134,14 +133,14 @@ final class SurrogatesReferenceTests: XCTestCase { XCTFail("Unknown request type: \(test.requestType) in test \(test.name)") return } - + mockWebsite = MockWebsite(resources: [resource]) - + schemeHandler.reset() schemeHandler.requestHandlers[siteURL] = { _ in return self.mockWebsite.htmlRepresentation.data(using: .utf8)! } - + let request = URLRequest(url: siteURL) WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache, @@ -150,25 +149,25 @@ final class SurrogatesReferenceTests: XCTestCase { completionHandler: { self.webView.load(request) }) - + navigationDelegateMock.onDidFinishNavigation = { - + XCTAssertEqual(self.userScriptDelegateMock.detectedSurrogates.count, 1) - + if let request = self.userScriptDelegateMock.detectedSurrogates.first { XCTAssertTrue(request.isBlocked, "Surrogate should block request \(requestURL)") XCTAssertEqual(request.url, requestURL.absoluteString) } - + self.userScriptDelegateMock.reset() - + self.webView?.evaluateJavaScript(expectExpression, completionHandler: { result, err in XCTAssertNil(err) - + if let result = result as? Bool { XCTAssertTrue(result, "Expression \(expectExpression) should return true") onTestExecuted.fulfill() - + DispatchQueue.main.async { self.runTestForRedirect(onTestExecuted: onTestExecuted) } @@ -176,18 +175,18 @@ final class SurrogatesReferenceTests: XCTestCase { }) } } - + private func createWebViewForUserScripTests(trackerData: TrackerData, encodedTrackerData: String, surrogates: String, privacyConfig: PrivacyConfiguration, completion: @escaping (WKWebView) -> Void) { - + var tempUnprotected = privacyConfig.tempUnprotectedDomains.filter { !$0.trimmingWhitespace().isEmpty } tempUnprotected.append(contentsOf: privacyConfig.exceptionsList(forFeature: .contentBlocking)) - + let exceptions = DefaultContentBlockerRulesExceptionsSource.transform(allowList: privacyConfig.trackerAllowlist.entries) - + WebKitTestHelper.prepareContentBlockingRules(trackerData: trackerData, exceptions: privacyConfig.userUnprotectedDomains, tempUnprotected: tempUnprotected, @@ -196,33 +195,33 @@ final class SurrogatesReferenceTests: XCTestCase { XCTFail("Rules were not compiled properly") return } - + let configuration = WKWebViewConfiguration() configuration.setURLSchemeHandler(self.schemeHandler, forURLScheme: self.schemeHandler.scheme) - + let webView = WKWebView(frame: .init(origin: .zero, size: .init(width: 500, height: 1000)), configuration: configuration) webView.navigationDelegate = self.navigationDelegateMock - + let config = TestSchemeSurrogatesUserScriptConfig(privacyConfig: privacyConfig, surrogates: surrogates, trackerData: trackerData, encodedSurrogateTrackerData: encodedTrackerData, tld: self.tld, isDebugBuild: true) - + let userScript = SurrogatesUserScript(configuration: config) userScript.delegate = self.userScriptDelegateMock - + for messageName in userScript.messageNames { configuration.userContentController.add(userScript, name: messageName) } - + configuration.userContentController.addUserScript(WKUserScript(source: userScript.source, injectionTime: .atDocumentStart, forMainFrameOnly: false)) configuration.userContentController.add(rules) - + completion(webView) } } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesUserScriptTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesUserScriptTests.swift index 5f02c48f4..45194d91e 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/SurrogatesUserScriptTests.swift @@ -1,6 +1,5 @@ // // SurrogatesUserScriptTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/TestSchemeHandler.swift b/Tests/BrowserServicesKitTests/ContentBlocker/TestSchemeHandler.swift index ffa364ba3..d8622cb5f 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/TestSchemeHandler.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/TestSchemeHandler.swift @@ -1,6 +1,5 @@ // // TestSchemeHandler.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -26,7 +25,7 @@ final class TestSchemeHandler: NSObject, WKURLSchemeHandler { public var requestHandlers = [URL: RequestResponse]() public let scheme = "test" - + public var genericHandler: RequestResponse = { _ in return Data() } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerAllowlistReferenceTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerAllowlistReferenceTests.swift index e502c0eff..b7a2f2523 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerAllowlistReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerAllowlistReferenceTests.swift @@ -1,6 +1,5 @@ // // TrackerAllowlistReferenceTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataManagerTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataManagerTests.swift index 5953fcb54..03ad89539 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataManagerTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataManagerTests.swift @@ -1,6 +1,5 @@ // // TrackerDataManagerTests.swift -// Core // // Copyright © 2019 DuckDuckGo. All rights reserved. // @@ -24,7 +23,7 @@ import TrackerRadarKit import WebKit class TrackerDataManagerTests: XCTestCase { - + static let exampleTDS = """ { "trackers": { @@ -71,9 +70,9 @@ class TrackerDataManagerTests: XCTestCase { } } """ - + func testWhenReloadCalledInitiallyThenDataSetIsEmbedded() { - + let exampleData = Self.exampleTDS.data(using: .utf8)! let embeddedDataProvider = MockEmbeddedDataProvider(data: exampleData, etag: "embedded") @@ -89,7 +88,7 @@ class TrackerDataManagerTests: XCTestCase { let exampleData = Self.exampleTDS.data(using: .utf8)! let embeddedDataProvider = MockEmbeddedDataProvider(data: exampleData, etag: "embedded") - + let trackerDataManager = TrackerDataManager(etag: nil, data: nil, embeddedDataProvider: embeddedDataProvider) @@ -97,12 +96,12 @@ class TrackerDataManagerTests: XCTestCase { XCTAssertNotNil(tracker) XCTAssertEqual("Not Real", tracker?.owner?.displayName) } - + func testFindEntityByName() { let exampleData = Self.exampleTDS.data(using: .utf8)! let embeddedDataProvider = MockEmbeddedDataProvider(data: exampleData, etag: "embedded") - + let trackerDataManager = TrackerDataManager(etag: nil, data: nil, embeddedDataProvider: embeddedDataProvider) @@ -110,21 +109,21 @@ class TrackerDataManagerTests: XCTestCase { XCTAssertNotNil(entity) XCTAssertEqual("Not Real", entity?.displayName) } - + func testFindEntityForHost() { let exampleData = Self.exampleTDS.data(using: .utf8)! let embeddedDataProvider = MockEmbeddedDataProvider(data: exampleData, etag: "embedded") - + let trackerDataManager = TrackerDataManager(etag: nil, data: nil, embeddedDataProvider: embeddedDataProvider) - + let entity = trackerDataManager.embeddedData.tds.findEntity(forHost: "www.notreal.io") XCTAssertNotNil(entity) XCTAssertEqual("Not Real", entity?.displayName) } - + // swiftlint:disable function_body_length func testWhenDownloadedDataAvailableThenReloadUsesIt() { @@ -135,11 +134,11 @@ class TrackerDataManagerTests: XCTestCase { let trackerDataManager = TrackerDataManager(etag: nil, data: nil, embeddedDataProvider: embeddedDataProvider) - + XCTAssertEqual(trackerDataManager.embeddedData.etag, "embedded") XCTAssertEqual(trackerDataManager.reload(etag: "new etag", data: exampleData), TrackerDataManager.ReloadResult.downloaded) - + XCTAssertEqual(trackerDataManager.fetchedData?.etag, "new etag") XCTAssertNil(trackerDataManager.fetchedData?.tds.findEntity(byName: "Google LLC")) XCTAssertNotNil(trackerDataManager.fetchedData?.tds.findEntity(byName: "Not Real")) diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerResolverTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerResolverTests.swift index 454a0dd69..977e4df1a 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/TrackerResolverTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/TrackerResolverTests.swift @@ -1,6 +1,5 @@ // // TrackerResolverTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -24,7 +23,7 @@ import ContentBlocking @testable import BrowserServicesKit class TrackerResolverTests: XCTestCase { - + let tld = TLD() func testWhenOptionsAreEmptyThenNothingMatches() { @@ -111,9 +110,9 @@ class TrackerResolverTests: XCTestCase { host: urlThree.host!, resourceType: "image")) } - + func testWhenTrackerIsDetectedThenItIsReported() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -122,20 +121,20 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: ["Advertising"], rules: nil) - + let entity = Entity(displayName: "Trackr Inc company", domains: ["tracker.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": entity], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssert(result?.isBlocked ?? false) XCTAssertEqual(result?.state, .blocked) @@ -144,9 +143,9 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(result?.category, tracker.category) XCTAssertEqual(result?.prevalence, tracker.prevalence) } - + func testWhenTrackerWithBlockActionHasRulesThenTheseAreRespected() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -174,27 +173,27 @@ class TrackerResolverTests: XCTestCase { exceptions: KnownTracker.Rule.Matching(domains: ["other.com"], types: nil)) ]) - + let entity = Entity(displayName: "Trackr Inc company", domains: ["tracker.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": entity], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld, adClickAttributionVendor: "attributed.com") - + let blockedImgUrl = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(blockedImgUrl) XCTAssert(blockedImgUrl?.isBlocked ?? false) XCTAssertEqual(blockedImgUrl?.state, .blocked) @@ -202,12 +201,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(blockedImgUrl?.entityName, entity.displayName) XCTAssertEqual(blockedImgUrl?.category, tracker.category) XCTAssertEqual(blockedImgUrl?.prevalence, tracker.prevalence) - + let ignoredTrackerRuleOption = resolver.trackerFromUrl("https://tracker.com/ignore/s.js", pageUrlString: "https://exception.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(ignoredTrackerRuleOption) XCTAssertFalse(ignoredTrackerRuleOption?.isBlocked ?? false) XCTAssertEqual(ignoredTrackerRuleOption?.state, BlockingState.allowed(reason: .ruleException)) @@ -215,12 +214,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(ignoredTrackerRuleOption?.entityName, entity.displayName) XCTAssertEqual(ignoredTrackerRuleOption?.category, tracker.category) XCTAssertEqual(ignoredTrackerRuleOption?.prevalence, tracker.prevalence) - + let blockTrackerRuleOption = resolver.trackerFromUrl("https://tracker.com/ignore/s.js", pageUrlString: "https://other.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(blockTrackerRuleOption) XCTAssertFalse(blockTrackerRuleOption?.isBlocked ?? false) XCTAssertEqual(blockTrackerRuleOption?.state, BlockingState.allowed(reason: .ruleException)) @@ -228,12 +227,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(blockTrackerRuleOption?.entityName, entity.displayName) XCTAssertEqual(blockTrackerRuleOption?.category, tracker.category) XCTAssertEqual(blockTrackerRuleOption?.prevalence, tracker.prevalence) - + let ignoredTrackerRuleException = resolver.trackerFromUrl("https://tracker.com/nil/s.js", pageUrlString: "https://other.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(ignoredTrackerRuleException) XCTAssertFalse(ignoredTrackerRuleException?.isBlocked ?? false) XCTAssertEqual(ignoredTrackerRuleException?.state, BlockingState.allowed(reason: .ruleException)) @@ -241,12 +240,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(ignoredTrackerRuleException?.entityName, entity.displayName) XCTAssertEqual(ignoredTrackerRuleException?.category, tracker.category) XCTAssertEqual(ignoredTrackerRuleException?.prevalence, tracker.prevalence) - + let blockTrackerRuleException = resolver.trackerFromUrl("https://tracker.com/nil/s.js", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(blockTrackerRuleException) XCTAssert(blockTrackerRuleException?.isBlocked ?? false) XCTAssertEqual(blockTrackerRuleException?.state, BlockingState.blocked) @@ -254,12 +253,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(blockTrackerRuleException?.entityName, entity.displayName) XCTAssertEqual(blockTrackerRuleException?.category, tracker.category) XCTAssertEqual(blockTrackerRuleException?.prevalence, tracker.prevalence) - + let blockTrackerRuleAttributedException = resolver.trackerFromUrl("https://tracker.com/attr/s.js", pageUrlString: "https://attributed.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(blockTrackerRuleAttributedException) XCTAssertFalse(blockTrackerRuleAttributedException?.isBlocked ?? true) XCTAssertEqual(blockTrackerRuleAttributedException?.state, BlockingState.allowed(reason: .adClickAttribution)) @@ -268,9 +267,9 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(blockTrackerRuleAttributedException?.category, tracker.category) XCTAssertEqual(blockTrackerRuleAttributedException?.prevalence, tracker.prevalence) } - + func testWhenTrackerWithIgnoreActionHasRulesThenTheseAreRespected() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .ignore, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -284,23 +283,23 @@ class TrackerResolverTests: XCTestCase { options: nil, exceptions: KnownTracker.Rule.Matching(domains: ["exception.com"], types: nil))]) - + let entity = Entity(displayName: "Trackr Inc company", domains: ["tracker.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": entity], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let resultImgUrl = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(resultImgUrl) XCTAssertFalse(resultImgUrl?.isBlocked ?? false) XCTAssertEqual(resultImgUrl?.state, BlockingState.allowed(reason: .ruleException)) @@ -308,12 +307,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(resultImgUrl?.entityName, entity.displayName) XCTAssertEqual(resultImgUrl?.category, tracker.category) XCTAssertEqual(resultImgUrl?.prevalence, tracker.prevalence) - + let resultScriptURL = resolver.trackerFromUrl("https://tracker.com/script/s.js", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(resultScriptURL) XCTAssert(resultScriptURL?.isBlocked ?? false) XCTAssertEqual(resultScriptURL?.state, BlockingState.blocked) @@ -321,12 +320,12 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(resultScriptURL?.entityName, entity.displayName) XCTAssertEqual(resultScriptURL?.category, tracker.category) XCTAssertEqual(resultScriptURL?.prevalence, tracker.prevalence) - + let resultScriptURLOnExceptionSite = resolver.trackerFromUrl("https://tracker.com/script/s.js", pageUrlString: "https://exception.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(resultScriptURLOnExceptionSite) XCTAssertFalse(resultScriptURLOnExceptionSite?.isBlocked ?? false) XCTAssertEqual(resultScriptURLOnExceptionSite?.state, BlockingState.allowed(reason: .ruleException)) @@ -335,9 +334,9 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(resultScriptURLOnExceptionSite?.category, tracker.category) XCTAssertEqual(resultScriptURLOnExceptionSite?.prevalence, tracker.prevalence) } - + func testWhenTrackerIsOnAssociatedPageThenItIsNotBlocked() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -346,7 +345,7 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com", "example.com"], @@ -354,21 +353,21 @@ class TrackerResolverTests: XCTestCase { domains: ["tracker.com": "Tracker Inc", "example.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssertFalse(result!.isBlocked) XCTAssertEqual(result?.state, BlockingState.allowed(reason: .ownedByFirstParty)) } - + func testWhenTrackerIsACnameThenItIsReportedAsSuch() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -377,20 +376,20 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let entity = Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": entity], domains: ["tracker.com": "Tracker Inc"], cnames: ["cnamed.com": "tracker.com"]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://cnamed.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssert(result?.isBlocked ?? false) XCTAssertEqual(result?.state, BlockingState.blocked) @@ -399,9 +398,9 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(result?.category, tracker.category) XCTAssertEqual(result?.prevalence, tracker.prevalence) } - + func testWhenTrackerIsACnameForAnotherTrackerThenOriginalOneIsReturned() { - + let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Tracker Inc", @@ -410,7 +409,7 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let another = KnownTracker(domain: "another.com", defaultAction: .block, owner: KnownTracker.Owner(name: "Another Inc", @@ -419,15 +418,15 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let trackerEntity = Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1) - + let anotherEntity = Entity(displayName: "Another Inc company", domains: ["another.com"], prevalence: 0.1) - + let tds = TrackerData(trackers: ["tracker.com": tracker, "another.com": another], entities: ["Tracker Inc": trackerEntity, @@ -435,11 +434,11 @@ class TrackerResolverTests: XCTestCase { domains: ["tracker.com": "Tracker Inc", "another.com": "Another Inc."], cnames: ["sub.another.com": "tracker.com"]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://sub.another.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssert(result?.isBlocked ?? false) XCTAssertEqual(result?.state, BlockingState.blocked) @@ -448,7 +447,7 @@ class TrackerResolverTests: XCTestCase { XCTAssertEqual(result?.category, another.category) XCTAssertEqual(result?.prevalence, another.prevalence) } - + func testWhenTrackerIsOnUnprotectedSiteItIsNotBlocked() { let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -458,26 +457,26 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: ["example.com"], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssertFalse(result!.isBlocked) XCTAssertEqual(result?.state, BlockingState.allowed(reason: .protectionDisabled)) } - + func testWhenTrackerIsOnTempListItIsNotBlocked() { let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -487,26 +486,26 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: ["example.com"], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssertFalse(result!.isBlocked) XCTAssertEqual(result?.state, BlockingState.allowed(reason: .protectionDisabled)) } - + // This also covers the scenario when tracker is on domain with disabled contentBlocking feature (through temporaryUnprotectedDomains inside ContentBlockerRulesUserScript) func testWhenTrackerIsOnDomainWithDisabledContentBlockingFeatureItIsNotBlocked() { let tracker = KnownTracker(domain: "tracker.com", @@ -517,26 +516,26 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: ["example.com"], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssertFalse(result!.isBlocked) XCTAssertEqual(result?.state, BlockingState.allowed(reason: .protectionDisabled)) } - + func testWhenTrackerIsFirstPartyThenItIsNotNotBlocked() { // let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -546,26 +545,26 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: ["example.com"], tld: tld) - + let result = resolver.trackerFromUrl("https://tracker.com/img/1.png", pageUrlString: "https://tracker.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNotNil(result) XCTAssertFalse(result!.isBlocked) XCTAssertEqual(result?.state, BlockingState.allowed(reason: .ownedByFirstParty)) } - + func testWhenRequestIsThirdPartyNonTrackerThenItIsIgnored() { // Note: User script has additional logic regarding this case let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -575,24 +574,24 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: ["example.com"], tld: tld) - + let result = resolver.trackerFromUrl("https://other.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNil(result) } - + func testWhenRequestIsFirstPartyNonTrackerThenItIsIgnored() { let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -602,24 +601,24 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], prevalence: 0.1)], domains: ["tracker.com": "Tracker Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://example.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNil(result) } - + func testWhenRequestIsSameEntityNonTrackerThenItIsIgnored() { let tracker = KnownTracker(domain: "tracker.com", defaultAction: .block, @@ -629,7 +628,7 @@ class TrackerResolverTests: XCTestCase { subdomains: nil, categories: nil, rules: nil) - + let tds = TrackerData(trackers: ["tracker.com": tracker], entities: ["Tracker Inc": Entity(displayName: "Tracker Inc company", domains: ["tracker.com"], @@ -641,15 +640,15 @@ class TrackerResolverTests: XCTestCase { "other.com": "Other Inc", "example.com": "Other Inc"], cnames: [:]) - + let resolver = TrackerResolver(tds: tds, unprotectedSites: [], tempList: [], tld: tld) - + let result = resolver.trackerFromUrl("https://other.com/img/1.png", pageUrlString: "https://example.com", resourceType: "image", potentiallyBlocked: true) - + XCTAssertNil(result) } - + } diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/WebViewTestHelper.swift b/Tests/BrowserServicesKitTests/ContentBlocker/WebViewTestHelper.swift index 94e911ecf..126861e54 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/WebViewTestHelper.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/WebViewTestHelper.swift @@ -1,6 +1,5 @@ // // WebViewTestHelper.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -63,7 +62,7 @@ final class MockRulesUserScriptDelegate: NSObject, ContentBlockerRulesUserScript detectedTrackers.insert(tracker) onTrackerDetected?(tracker) } - + func contentBlockerRulesUserScript(_ script: ContentBlockerRulesUserScript, detectedThirdPartyRequest request: DetectedRequest) { detectedThirdPartyRequests.insert(request) diff --git a/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesMocks.swift b/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesMocks.swift index 7bd809979..17790aa22 100644 --- a/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesMocks.swift +++ b/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesMocks.swift @@ -1,10 +1,20 @@ // // ContentScopePropertiesMocks.swift -// // -// Created by Elle Sullivan on 23/05/2022. +// Copyright © 2021 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // - import Foundation @testable import BrowserServicesKit diff --git a/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesTests.swift b/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesTests.swift index fed12a31d..234a45ecc 100644 --- a/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesTests.swift +++ b/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesTests.swift @@ -1,6 +1,5 @@ // // ContentScopePropertiesTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Email/EmailManagerTests.swift b/Tests/BrowserServicesKitTests/Email/EmailManagerTests.swift index 44a23bf87..fdcdaf7c6 100644 --- a/Tests/BrowserServicesKitTests/Email/EmailManagerTests.swift +++ b/Tests/BrowserServicesKitTests/Email/EmailManagerTests.swift @@ -1,6 +1,5 @@ // // EmailManagerTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultFeatureFlaggerTests.swift b/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultFeatureFlaggerTests.swift index 0544a25f0..3408cba96 100644 --- a/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultFeatureFlaggerTests.swift +++ b/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultFeatureFlaggerTests.swift @@ -1,6 +1,5 @@ // -// FeatureFlaggerTests.swift -// DuckDuckGo +// DefaultFeatureFlaggerTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -56,7 +55,7 @@ final class DefaultFeatureFlaggerTests: XCTestCase { func testWhenRemoteDevelopment_isInternalUser_whenFeature_returnsPrivacyConfigValue() { internalUserDeciderStore.isInternalUser = true let sourceProvider = FeatureFlagSource.remoteDevelopment(.feature(.autofill)) - + var embeddedData = Self.embeddedConfig(autofillState: "enabled") assertFeatureFlagger(with: embeddedData, willReturn: true, for: sourceProvider) diff --git a/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultInternalUserDeciderTests.swift b/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultInternalUserDeciderTests.swift index f3e1c30ed..31ae5072f 100644 --- a/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultInternalUserDeciderTests.swift +++ b/Tests/BrowserServicesKitTests/FeatureFlagging/DefaultInternalUserDeciderTests.swift @@ -1,6 +1,5 @@ // -// FeatureFlaggingTests.swift -// DuckDuckGo +// DefaultInternalUserDeciderTests.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,10 +20,10 @@ import XCTest @testable import BrowserServicesKit class DefaultInternalUserDeciderTests: XCTestCase { - + let correctURL = URL(string: "http://use-login.duckduckgo.com")! let correctStatusCode = 200 - + func testShouldMarkUserAsInternalWhenURLAndStatusCodeCorrectThenReturnsTrue() { let featureFlagger = DefaultInternalUserDecider() let result = featureFlagger.shouldMarkUserAsInternal(forUrl: correctURL, statusCode: correctStatusCode) diff --git a/Tests/BrowserServicesKitTests/Fingerprinting/FingerprintingReferenceTests.swift b/Tests/BrowserServicesKitTests/Fingerprinting/FingerprintingReferenceTests.swift index e6cbed6da..982fcd774 100644 --- a/Tests/BrowserServicesKitTests/Fingerprinting/FingerprintingReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/Fingerprinting/FingerprintingReferenceTests.swift @@ -1,6 +1,5 @@ // // FingerprintingReferenceTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -32,23 +31,23 @@ final class FingerprintingReferenceTests: XCTestCase { private let dataLoader = JsonTestDataLoader() private var webView: WKWebView! private var mockWebsite: MockWebsite! - + private enum Resource { static let script = "Resources/privacy-reference-tests/fingerprinting-protections/init.js" static let config = "Resources/privacy-reference-tests/fingerprinting-protections/config_reference.json" static let tests = "Resources/privacy-reference-tests/fingerprinting-protections/tests.json" } - + private lazy var testData: TestData = { let testData = dataLoader.fromJsonFile(Resource.tests) return try! JSONDecoder().decode(TestData.self, from: testData) }() - + private lazy var scriptToInject: String = { let scriptData = dataLoader.fromJsonFile(Resource.script) return String(data: scriptData, encoding: .utf8)! }() - + private lazy var privacyManager: PrivacyConfigurationManager = { let configJSONData = dataLoader.fromJsonFile(Resource.config) let embeddedDataProvider = MockEmbeddedDataProvider(data: configJSONData, @@ -61,57 +60,57 @@ final class FingerprintingReferenceTests: XCTestCase { localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) }() - + override func tearDown() { super.tearDown() referenceTests.removeAll() } - + func testBatteryAPI() throws { let sectionName = testData.batteryAPI.name - + referenceTests = testData.batteryAPI.tests.filter { return $0.exceptPlatforms.contains("ios-browser") == false } - + guard referenceTests.count > 0 else { os_log("NO TESTS FOR SECTION: %s", sectionName) return } - + os_log("TEST SECTION: %s", sectionName) - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = referenceTests.count - + runTests(onTestExecuted: testsExecuted) waitForExpectations(timeout: 30, handler: nil) } - + func testHardwareAPI() throws { let sectionName = testData.hardwareAPIs.name - + referenceTests = testData.hardwareAPIs.tests.filter { return $0.exceptPlatforms.contains("ios-browser") == false } - + guard referenceTests.count > 0 else { os_log("NO TESTS FOR SECTION: %s", sectionName) return } - + os_log("TEST SECTION: %s", sectionName) - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = referenceTests.count - + runTests(onTestExecuted: testsExecuted) waitForExpectations(timeout: 30, handler: nil) } - + func testScreenAPI() throws { let sectionName = testData.screenAPI.name - + referenceTests = testData.screenAPI.tests.filter { return $0.exceptPlatforms.contains("ios-browser") == false } @@ -119,64 +118,64 @@ final class FingerprintingReferenceTests: XCTestCase { os_log("NO TESTS FOR SECTION: %s", sectionName) return } - + os_log("TEST SECTION: %s", sectionName) - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = referenceTests.count - + runTests(onTestExecuted: testsExecuted) waitForExpectations(timeout: 30, handler: nil) } - + func testStorageAPI() throws { let sectionName = testData.temporaryStorageAPI.name - + referenceTests = testData.temporaryStorageAPI.tests.filter { return $0.exceptPlatforms.contains("ios-browser") == false } - + guard referenceTests.count > 0 else { os_log("NO TESTS FOR SECTION: %s", sectionName) return } - + os_log("TEST SECTION: %s", sectionName) - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = referenceTests.count - + runTests(onTestExecuted: testsExecuted) waitForExpectations(timeout: 30, handler: nil) } - + private func runTests(onTestExecuted: XCTestExpectation) { - + guard let test = referenceTests.popLast(), test.exceptPlatforms.contains("macos-browser") == false else { return } - + os_log("TEST: %s", test.name) - + let requestURL = URL(string: test.siteURL.testSchemeNormalized)! - + schemeHandler.reset() schemeHandler.requestHandlers[requestURL] = { _ in return "".data(using: .utf8)! } - + let request = URLRequest(url: requestURL) - + setupWebViewForUserScripTests(schemeHandler: schemeHandler, privacyConfig: privacyManager.privacyConfig) { webView in // Keep webview in memory till test finishes self.webView = webView self.webView.load(request) } - + navigationDelegateMock.onDidFinishNavigation = { [weak self] in - + self!.webView.evaluateJavaScript(test.property) { result, _ in if let result = result as? String { XCTAssertEqual(result, test.expectPropertyValue, "Values should be equal for test: \(test.name)") @@ -188,7 +187,7 @@ final class FingerprintingReferenceTests: XCTestCase { } else { XCTFail("Should not return nil \(test.name)") } - + DispatchQueue.main.async { onTestExecuted.fulfill() self!.runTests(onTestExecuted: onTestExecuted) @@ -196,17 +195,17 @@ final class FingerprintingReferenceTests: XCTestCase { } } } - + private func setupWebViewForUserScripTests(schemeHandler: TestSchemeHandler, privacyConfig: PrivacyConfiguration, completion: @escaping (WKWebView) -> Void) { let configuration = WKWebViewConfiguration() configuration.setURLSchemeHandler(schemeHandler, forURLScheme: schemeHandler.scheme) - + let webView = WKWebView(frame: .init(origin: .zero, size: .init(width: 500, height: 1000)), configuration: configuration) webView.navigationDelegate = self.navigationDelegateMock - + let configFeatureToggle = ContentScopeFeatureToggles(emailProtection: false, emailProtectionIncontextSignup: false, credentialsAutofill: false, @@ -216,28 +215,28 @@ final class FingerprintingReferenceTests: XCTestCase { passwordGeneration: false, inlineIconCredentials: false, thirdPartyCredentialsProvider: false) - + let contentScopeProperties = ContentScopeProperties(gpcEnabled: false, sessionKey: UUID().uuidString, featureToggles: configFeatureToggle) - + let contentScopeScript = ContentScopeUserScript(self.privacyManager, properties: contentScopeProperties) - + configuration.userContentController.addUserScript(WKUserScript(source: "\(scriptToInject) init(window)", injectionTime: .atDocumentStart, forMainFrameOnly: false)) - + configuration.userContentController.addUserScript(WKUserScript(source: contentScopeScript.source, injectionTime: .atDocumentStart, forMainFrameOnly: false)) - + for messageName in contentScopeScript.messageNames { configuration.userContentController.add(contentScopeScript, name: messageName) } - + completion(webView) - + } } diff --git a/Tests/BrowserServicesKitTests/GPC/GPCReferenceTests.swift b/Tests/BrowserServicesKitTests/GPC/GPCReferenceTests.swift index 61b26ec60..8fe83c91f 100644 --- a/Tests/BrowserServicesKitTests/GPC/GPCReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/GPC/GPCReferenceTests.swift @@ -1,6 +1,5 @@ // // GPCReferenceTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -48,44 +47,44 @@ final class GPCReferenceTests: XCTestCase { localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + func testGPCHeader() throws { let dataLoader = JsonTestDataLoader() let testsData = dataLoader.fromJsonFile(Resource.tests) let referenceTests = try JSONDecoder().decode(GPCTestData.self, from: testsData) - + let privacyConfig = privacyManager.privacyConfig for test in referenceTests.gpcHeader.tests { - + if test.exceptPlatforms.contains("ios-browser") || test.exceptPlatforms.contains("macos-browser") { os_log("Skipping test, ignore platform for [%s]", type: .info, test.name) continue } - + os_log("Testing [%s]", type: .info, test.name) - + let factory = GPCRequestFactory() var testRequest = URLRequest(url: URL(string: test.requestURL)!) - + // Simulate request with actual headers testRequest.addValue("DDG-Test", forHTTPHeaderField: "User-Agent") let request = factory.requestForGPC(basedOn: testRequest, config: privacyConfig, gpcEnabled: test.gpcUserSettingOn) - + if !test.gpcUserSettingOn { XCTAssertNil(request, "User opt out, request should not exist \([test.name])") } - + let hasHeader = request?.allHTTPHeaderFields?[GPCRequestFactory.Constants.secGPCHeader] != nil let headerValue = request?.allHTTPHeaderFields?[GPCRequestFactory.Constants.secGPCHeader] if test.expectGPCHeader { XCTAssertNotNil(request, "Request should exist if expectGPCHeader is true [\(test.name)]") XCTAssert(hasHeader, "Couldn't find header for [\(test.requestURL)]") - + if let expectedHeaderValue = test.expectGPCHeaderValue { let headerValue = request?.allHTTPHeaderFields?[GPCRequestFactory.Constants.secGPCHeader] XCTAssertEqual(expectedHeaderValue, headerValue, "Header should be equal [\(test.name)]") @@ -95,41 +94,41 @@ final class GPCReferenceTests: XCTestCase { } } } - + func testGPCJavascriptAPI() throws { let dataLoader = JsonTestDataLoader() let testsData = dataLoader.fromJsonFile(Resource.tests) let referenceTests = try JSONDecoder().decode(GPCTestData.self, from: testsData) - + javascriptTests = referenceTests.gpcJavaScriptAPI.tests.filter { $0.exceptPlatforms.contains("macos-browser") == false } - + let testsExecuted = expectation(description: "tests executed") testsExecuted.expectedFulfillmentCount = javascriptTests.count - + runJavascriptTests(onTestExecuted: testsExecuted) - + waitForExpectations(timeout: 30, handler: nil) } - + private func runJavascriptTests(onTestExecuted: XCTestExpectation) { - + guard let test = javascriptTests.popLast() else { return } - + let siteURL = URL(string: test.siteURL.testSchemeNormalized)! - + schemeHandler.reset() schemeHandler.requestHandlers[siteURL] = { _ in return "".data(using: .utf8)! } - + let request = URLRequest(url: siteURL) let webView = createWebViewForUserScripTests(gpcEnabled: test.gpcUserSettingOn, privacyConfig: privacyManager.privacyConfig) - + WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache, WKWebsiteDataTypeOfflineWebApplicationCache], @@ -137,15 +136,15 @@ final class GPCReferenceTests: XCTestCase { completionHandler: { webView.load(request) }) - + let javascriptToEvaluate = "Navigator.prototype.globalPrivacyControl" - + navigationDelegateMock.onDidFinishNavigation = { - + webView.evaluateJavaScript(javascriptToEvaluate, completionHandler: { result, err in - + XCTAssertNil(err, "Evaluation should not fail") - + if let expectedValue = test.expectGPCAPIValue { switch expectedValue { case "false": @@ -156,7 +155,7 @@ final class GPCReferenceTests: XCTestCase { XCTAssertNil(result, "Test \(test.name) expected value should be nil") } } - + DispatchQueue.main.async { onTestExecuted.fulfill() self.runJavascriptTests(onTestExecuted: onTestExecuted) @@ -164,31 +163,31 @@ final class GPCReferenceTests: XCTestCase { }) } } - + private func createWebViewForUserScripTests(gpcEnabled: Bool, privacyConfig: PrivacyConfiguration) -> WKWebView { - + let properties = ContentScopeProperties(gpcEnabled: gpcEnabled, sessionKey: UUID().uuidString, featureToggles: ContentScopeFeatureToggles.allTogglesOn) - + let contentScopeScript = ContentScopeUserScript(privacyManager, properties: properties) - + let configuration = WKWebViewConfiguration() configuration.setURLSchemeHandler(self.schemeHandler, forURLScheme: self.schemeHandler.scheme) - + let webView = WKWebView(frame: .init(origin: .zero, size: .init(width: 500, height: 1000)), configuration: configuration) webView.navigationDelegate = self.navigationDelegateMock - + for messageName in contentScopeScript.messageNames { configuration.userContentController.add(contentScopeScript, name: messageName) } - + configuration.userContentController.addUserScript(WKUserScript(source: contentScopeScript.source, injectionTime: .atDocumentStart, forMainFrameOnly: false)) - + return webView } } diff --git a/Tests/BrowserServicesKitTests/GPC/GPCTests.swift b/Tests/BrowserServicesKitTests/GPC/GPCTests.swift index 6d07ad500..b83a30e8a 100644 --- a/Tests/BrowserServicesKitTests/GPC/GPCTests.swift +++ b/Tests/BrowserServicesKitTests/GPC/GPCTests.swift @@ -1,6 +1,5 @@ // // GPCTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -25,7 +24,7 @@ final class GPCTests: XCTestCase { override func setUp() { super.setUp() - + let gpcFeature = PrivacyConfigurationData.PrivacyFeature(state: "enabled", exceptions: [], settings: [ @@ -41,32 +40,32 @@ final class GPCTests: XCTestCase { let localProtection = MockDomainsProtectionStore() appConfig = AppPrivacyConfiguration(data: privacyData, identifier: "", localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + func testWhenGPCEnableDomainIsHttpThenISGPCEnabledTrue() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "https://www.washingtonpost.com")!, config: appConfig) XCTAssertTrue(result) } - + func testWhenGPCEnableDomainIsHttpsThenISGPCEnabledTrue() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "http://www.washingtonpost.com")!, config: appConfig) XCTAssertTrue(result) } - + func testWhenGPCEnableDomainHasNoSubDomainThenISGPCEnabledTrue() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "http://washingtonpost.com")!, config: appConfig) XCTAssertTrue(result) } - + func testWhenGPCEnableDomainHasPathThenISGPCEnabledTrue() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "http://www.washingtonpost.com/test/somearticle.html")!, config: appConfig) XCTAssertTrue(result) } - + func testWhenGPCEnableDomainHasCorrectSubdomainThenISGPCEnabledTrue() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "http://global-privacy-control.glitch.me")!, config: appConfig) XCTAssertTrue(result) } - + func testWhenGPCEnableDomainHasWrongSubdomainThenISGPCEnabledFalse() { let result = GPCRequestFactory().isGPCEnabled(url: URL(string: "http://glitch.me")!, config: appConfig) XCTAssertFalse(result) diff --git a/Tests/BrowserServicesKitTests/InternalUserDecider/MockInternalUserStoring.swift b/Tests/BrowserServicesKitTests/InternalUserDecider/MockInternalUserStoring.swift index c232a758a..208160f7e 100644 --- a/Tests/BrowserServicesKitTests/InternalUserDecider/MockInternalUserStoring.swift +++ b/Tests/BrowserServicesKitTests/InternalUserDecider/MockInternalUserStoring.swift @@ -1,6 +1,5 @@ // // MockInternalUserStoring.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/LinkProtection/AmpMatchingTests.swift b/Tests/BrowserServicesKitTests/LinkProtection/AmpMatchingTests.swift index 4b8da9100..e4a2bb3d9 100644 --- a/Tests/BrowserServicesKitTests/LinkProtection/AmpMatchingTests.swift +++ b/Tests/BrowserServicesKitTests/LinkProtection/AmpMatchingTests.swift @@ -1,6 +1,5 @@ // // AmpMatchingTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -28,41 +27,41 @@ struct AmpRefTests: Decodable { let desc: String let tests: [AmpFormatTest] } - + struct AmpFormatTest: Decodable { let name: String let ampURL: String let expectURL: String let exceptPlatforms: [String]? } - + struct AmpKeywordTests: Decodable { let name: String let desc: String let tests: [AmpKeywordTest] } - + struct AmpKeywordTest: Decodable { let name: String let ampURL: String let expectAmpDetected: Bool let exceptPlatforms: [String]? } - + let ampFormats: AmpFormatTests let ampKeywords: AmpKeywordTests } final class AmpMatchingTests: XCTestCase { - + private enum Resource { static let config = "Resources/privacy-reference-tests/amp-protections/config_reference.json" static let tests = "Resources/privacy-reference-tests/amp-protections/tests.json" } - + private static let data = JsonTestDataLoader() private static let config = data.fromJsonFile(Resource.config) - + private var privacyManager: PrivacyConfigurationManager { let embeddedDataProvider = MockEmbeddedDataProvider(data: Self.config, etag: "embedded") @@ -75,58 +74,58 @@ final class AmpMatchingTests: XCTestCase { localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + private var contentBlockingManager: ContentBlockerRulesManager { let listsSource = ContentBlockerRulesListSourceMock() let exceptionsSource = ContentBlockerRulesExceptionsSourceMock() return ContentBlockerRulesManager(rulesSource: listsSource, exceptionsSource: exceptionsSource) } - + private lazy var ampTestSuite: AmpRefTests = { let tests = Self.data.fromJsonFile(Resource.tests) return try! JSONDecoder().decode(AmpRefTests.self, from: tests) }() - + func testAmpFormats() throws { let tests = ampTestSuite.ampFormats.tests let linkCleaner = LinkCleaner(privacyManager: privacyManager) - + for test in tests { let skip = test.exceptPlatforms?.contains("ios-browser") if skip == true { os_log("!!SKIPPING TEST: %s", test.name) continue } - + os_log("TEST: %s", test.name) - + let ampUrl = URL(string: test.ampURL) let resultUrl = linkCleaner.extractCanonicalFromAMPLink(initiator: nil, destination: ampUrl) - + // Empty expectedUrl should be treated as nil let expectedUrl = !test.expectURL.isEmpty ? test.expectURL : nil XCTAssertEqual(resultUrl?.absoluteString, expectedUrl, "\(resultUrl!.absoluteString) not equal to expected: \(expectedUrl ?? "nil")") } } - + func testAmpKeywords() throws { let tests = ampTestSuite.ampKeywords.tests let linkCleaner = LinkCleaner(privacyManager: privacyManager) - + let ampExtractor = AMPCanonicalExtractor(linkCleaner: linkCleaner, privacyManager: privacyManager, contentBlockingManager: contentBlockingManager, errorReporting: nil) - + for test in tests { let skip = test.exceptPlatforms?.contains("ios-browser") if skip == true { os_log("!!SKIPPING TEST: %s", test.name) continue } - + os_log("TEST: %s", test.name) - + let ampUrl = URL(string: test.ampURL) let result = ampExtractor.urlContainsAMPKeyword(ampUrl) XCTAssertEqual(result, test.expectAmpDetected, "\(test.ampURL) not correctly identified. Expected: \(test.expectAmpDetected.description)") diff --git a/Tests/BrowserServicesKitTests/LinkProtection/ContentBlockerManagerMock.swift b/Tests/BrowserServicesKitTests/LinkProtection/ContentBlockerManagerMock.swift index 044a95324..b701b2be3 100644 --- a/Tests/BrowserServicesKitTests/LinkProtection/ContentBlockerManagerMock.swift +++ b/Tests/BrowserServicesKitTests/LinkProtection/ContentBlockerManagerMock.swift @@ -1,6 +1,5 @@ // -// AmpMatchingTests.swift -// DuckDuckGo +// ContentBlockerManagerMock.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/LinkProtection/URLParameterTests.swift b/Tests/BrowserServicesKitTests/LinkProtection/URLParameterTests.swift index 3b32c166b..e6e2a3e73 100644 --- a/Tests/BrowserServicesKitTests/LinkProtection/URLParameterTests.swift +++ b/Tests/BrowserServicesKitTests/LinkProtection/URLParameterTests.swift @@ -1,6 +1,5 @@ // // URLParameterTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -28,7 +27,7 @@ struct URLParamRefTests: Decodable { let desc: String let tests: [URLParamTest] } - + struct URLParamTest: Decodable { let name: String let testURL: String @@ -36,20 +35,20 @@ struct URLParamRefTests: Decodable { let initiatorURL: String? let exceptPlatforms: [String]? } - + let trackingParameters: URLParamTests } final class URLParameterTests: XCTestCase { - + private enum Resource { static let config = "Resources/privacy-reference-tests/url-parameters/config_reference.json" static let tests = "Resources/privacy-reference-tests/url-parameters/tests.json" } - + private static let data = JsonTestDataLoader() private static let config = data.fromJsonFile(Resource.config) - + private var privacyManager: PrivacyConfigurationManager { let embeddedDataProvider = MockEmbeddedDataProvider(data: Self.config, etag: "embedded") @@ -62,35 +61,35 @@ final class URLParameterTests: XCTestCase { localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + private lazy var urlParamTestSuite: URLParamRefTests = { let tests = Self.data.fromJsonFile(Resource.tests) return try! JSONDecoder().decode(URLParamRefTests.self, from: tests) }() - + func testURLParamStripping() throws { let tests = urlParamTestSuite.trackingParameters.tests - + let linkCleaner = LinkCleaner(privacyManager: privacyManager) - + for test in tests { let skip = test.exceptPlatforms?.contains("ios-browser") if skip == true { os_log("!!SKIPPING TEST: %s", test.name) continue } - + os_log("TEST: %s", test.name) - + let testUrl = URL(string: test.testURL) let initiator = test.initiatorURL != nil ? URL(string: test.initiatorURL!) : nil var resultUrl = linkCleaner.cleanTrackingParameters(initiator: initiator, url: testUrl) - + if resultUrl == nil { // Tests expect unchanged URLs to match testURL resultUrl = testUrl } - + XCTAssertEqual(resultUrl?.absoluteString, test.expectURL, "\(resultUrl?.absoluteString ?? "(nil)") not equal to expected: \(test.expectURL)") } diff --git a/Tests/BrowserServicesKitTests/PrivacyConfig/AdClickAttributionFeatureTests.swift b/Tests/BrowserServicesKitTests/PrivacyConfig/AdClickAttributionFeatureTests.swift index fe8a2344b..50530f743 100644 --- a/Tests/BrowserServicesKitTests/PrivacyConfig/AdClickAttributionFeatureTests.swift +++ b/Tests/BrowserServicesKitTests/PrivacyConfig/AdClickAttributionFeatureTests.swift @@ -1,6 +1,5 @@ // // AdClickAttributionFeatureTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import XCTest import BrowserServicesKit class AdClickAttributionFeatureTests: XCTestCase { - + let exampleConfig = """ { "readme": "https://github.com/duckduckgo/privacy-configuration", @@ -66,37 +65,37 @@ class AdClickAttributionFeatureTests: XCTestCase { ] } """.data(using: .utf8)! - + func testDomainMatching() { let dataProvider = MockEmbeddedDataProvider(data: exampleConfig, etag: "empty") - + let config = PrivacyConfigurationManager(fetchedETag: nil, fetchedData: nil, embeddedDataProvider: dataProvider, localProtection: MockDomainsProtectionStore(), internalUserDecider: DefaultInternalUserDecider()) - + let feature = AdClickAttributionFeature(with: config) - + XCTAssertTrue(feature.isEnabled) XCTAssertEqual(Set(feature.allowlist.map { $0.entity }), Set(["bing.com", "ad-site.site", "ad-site.example"])) - + XCTAssertTrue(feature.isMatchingAttributionFormat(URL(string: "https://good.first-party.site/y.js?test_param=test")!)) - + XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://good.first-party.site/y.js")!)) XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://good.first-party.site/y.js?u2=2")!)) XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://good.first-party.site/y.js.gif?u2=2")!)) XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://sub.good.first-party.site/y.js?u3=2")!)) - + // No ad domain param XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://good.first-party.example/y.js?test_param=test.com")!)) - + // Testing for hardcoded value XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://other.first-party.com/m.js?ad_domain=a.com")!)) XCTAssertFalse(feature.isMatchingAttributionFormat(URL(string: "https://other.first-party.com/m.js?test_param=test.com")!)) - + // Dropping parameters tests XCTAssertTrue(feature.isMatchingAttributionFormat(URL(string: "https://different.party.com/y.js?test_param=&foo=&bar=")!)) XCTAssertTrue(feature.isMatchingAttributionFormat(URL(string: "https://different.party.com/y.js?test_param=example.com&foo=&bar=")!)) diff --git a/Tests/BrowserServicesKitTests/PrivacyConfig/AppPrivacyConfigurationTests.swift b/Tests/BrowserServicesKitTests/PrivacyConfig/AppPrivacyConfigurationTests.swift index 97689d3de..1765a015d 100644 --- a/Tests/BrowserServicesKitTests/PrivacyConfig/AppPrivacyConfigurationTests.swift +++ b/Tests/BrowserServicesKitTests/PrivacyConfig/AppPrivacyConfigurationTests.swift @@ -1,6 +1,5 @@ // // AppPrivacyConfigurationTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -651,7 +650,7 @@ class AppPrivacyConfigurationTests: XCTestCase { XCTAssertTrue(config.isEnabled(featureKey: .autofill, versionProvider: currentVersionProvider)) XCTAssertTrue(config.isSubfeatureEnabled(AutofillSubfeature.credentialsSaving, versionProvider: currentVersionProvider, randomizer: Double.random(in:))) } - + let exampleSubfeatureWithRolloutsConfig = """ { @@ -674,17 +673,17 @@ class AppPrivacyConfigurationTests: XCTestCase { "unprotectedTemporary": [] } """.data(using: .utf8)! - + func clearRolloutData(feature: String, subFeature: String) { UserDefaults().set(nil, forKey: "config.\(feature).\(subFeature).enabled") UserDefaults().set(nil, forKey: "config.\(feature).\(subFeature).lastRolloutCount") } - + var mockRandomValue: Double = 0.0 func mockRandom(in range: Range) -> Double { return mockRandomValue } - + func testWhenCheckingSubfeatureState_SubfeatureIsEnabledWithSingleRolloutProbability() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -694,18 +693,18 @@ class AppPrivacyConfigurationTests: XCTestCase { internalUserDecider: DefaultInternalUserDecider()) let config = manager.privacyConfig - + mockRandomValue = 7.0 clearRolloutData(feature: "autofill", subFeature: "credentialsSaving") var enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsSaving, randomizer: mockRandom(in:)) XCTAssertFalse(enabled, "Feature should not be enabled if selected value above rollout") - + mockRandomValue = 2.0 clearRolloutData(feature: "autofill", subFeature: "credentialsSaving") enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsSaving, randomizer: mockRandom(in:)) XCTAssertTrue(enabled, "Feature should be enabled if selected value below rollout") } - + let exampleSubfeatureWithMultipleRolloutsConfig = """ { @@ -758,7 +757,7 @@ class AppPrivacyConfigurationTests: XCTestCase { "unprotectedTemporary": [] } """.data(using: .utf8)! - + func testWhenCheckingSubfeatureState_SubfeatureIsEnabledWithMultipleRolloutProbability() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithMultipleRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -768,28 +767,28 @@ class AppPrivacyConfigurationTests: XCTestCase { internalUserDecider: DefaultInternalUserDecider()) let config = manager.privacyConfig - + mockRandomValue = 37 clearRolloutData(feature: "autofill", subFeature: "credentialsSaving") var enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsSaving, randomizer: mockRandom(in:)) XCTAssertFalse(enabled, "Feature should not be enabled if selected value above rollout") - + mockRandomValue = 0.1 // Effective probability of 10.5% in test config clearRolloutData(feature: "autofill", subFeature: "credentialsSaving") enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsSaving, randomizer: mockRandom(in:)) XCTAssertTrue(enabled, "Feature should not be enabled if selected value above rollout") - + mockRandomValue = 37 clearRolloutData(feature: "autofill", subFeature: "credentialsAutofill") enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsAutofill, randomizer: mockRandom(in:)) XCTAssertFalse(enabled, "Feature should not be enabled if selected value above rollout") - + mockRandomValue = 0.10 // Effective probability of 11.7% in test config clearRolloutData(feature: "autofill", subFeature: "credentialsAutofill") enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsAutofill, randomizer: mockRandom(in:)) XCTAssertTrue(enabled, "Feature should not be enabled if selected value above rollout") } - + func testWhenCheckingSubfeatureStateAndRolloutSizeChanges_SubfeatureIsEnabledWithMultipleRolloutProbability() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithMultipleRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -799,7 +798,7 @@ class AppPrivacyConfigurationTests: XCTestCase { internalUserDecider: DefaultInternalUserDecider()) let config = manager.privacyConfig - + clearRolloutData(feature: "autofill", subFeature: "credentialsAutofill") mockRandomValue = 0.10 // Mock that the user has previously seen the rollout and was not chosen @@ -807,7 +806,7 @@ class AppPrivacyConfigurationTests: XCTestCase { var enabled = config.isSubfeatureEnabled(AutofillSubfeature.credentialsAutofill, randomizer: mockRandom(in:)) XCTAssert(enabled, "Subfeature should be enabled when rollout count changes") - + clearRolloutData(feature: "autofill", subFeature: "credentialsAutofill") // Mock that the user has previously seen the rollout and was not chosen UserDefaults().set(3, forKey: "config.autofill.credentialsAutofill.lastRolloutCount") @@ -815,7 +814,7 @@ class AppPrivacyConfigurationTests: XCTestCase { XCTAssertFalse(enabled, "Subfeature should not be enabled when rollout count does not changes") } - + func testWhenCheckingSubfeatureStateAndUserIsInARollout_SubfeatureIsEnabled() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithMultipleRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -823,14 +822,14 @@ class AppPrivacyConfigurationTests: XCTestCase { embeddedDataProvider: mockEmbeddedData, localProtection: MockDomainsProtectionStore(), internalUserDecider: DefaultInternalUserDecider()) - + let config = manager.privacyConfig - + clearRolloutData(feature: "autofill", subFeature: "credentialsAutofill") UserDefaults().set(true, forKey: "config.autofill.credentialsAutofill.enabled") XCTAssert(config.isSubfeatureEnabled(AutofillSubfeature.credentialsAutofill), "Subfeature should be enabled if the user has already been selected in a rollout") } - + func testWhenCheckingSubfeatureStateAndRolloutsIsEmpty_SubfeatrueIsEnabled() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithMultipleRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -838,13 +837,13 @@ class AppPrivacyConfigurationTests: XCTestCase { embeddedDataProvider: mockEmbeddedData, localProtection: MockDomainsProtectionStore(), internalUserDecider: DefaultInternalUserDecider()) - + let config = manager.privacyConfig - + clearRolloutData(feature: "autofill", subFeature: "inlineIconCredentials") XCTAssert(config.isSubfeatureEnabled(AutofillSubfeature.inlineIconCredentials), "Subfeature should be enabled if rollouts array is empty") } - + func testWhenCheckingSubfeatureStateWithRolloutsAndSubfeatureDisabled_SubfeatureShouldBeDisabled() { let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleSubfeatureWithMultipleRolloutsConfig, etag: "test") let manager = PrivacyConfigurationManager(fetchedETag: nil, @@ -852,9 +851,9 @@ class AppPrivacyConfigurationTests: XCTestCase { embeddedDataProvider: mockEmbeddedData, localProtection: MockDomainsProtectionStore(), internalUserDecider: DefaultInternalUserDecider()) - + let config = manager.privacyConfig - + clearRolloutData(feature: "autofill", subFeature: "accessCredentialManagement") XCTAssertFalse(config.isSubfeatureEnabled(AutofillSubfeature.accessCredentialManagement), "Subfeature should be enabled if rollouts array is empty") } diff --git a/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationDataTests.swift b/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationDataTests.swift index a0025d3c9..94fcd1b6e 100644 --- a/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationDataTests.swift +++ b/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationDataTests.swift @@ -1,6 +1,5 @@ // // PrivacyConfigurationDataTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationReferenceTests.swift b/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationReferenceTests.swift index 3cb8b6e57..2eed811a6 100644 --- a/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationReferenceTests.swift @@ -1,6 +1,5 @@ // // PrivacyConfigurationReferenceTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -27,7 +26,7 @@ final class PrivacyConfigurationReferenceTests: XCTestCase { static let configRootPath = "Resources/privacy-reference-tests/privacy-configuration" static let tests = "Resources/privacy-reference-tests/privacy-configuration/tests.json" } - + func testPrivacyConfiguration() throws { let dataLoader = JsonTestDataLoader() let testsData = dataLoader.fromJsonFile(Resource.tests) @@ -36,10 +35,10 @@ final class PrivacyConfigurationReferenceTests: XCTestCase { for testConfig in referenceTests.testConfigs { let path = "\(Resource.configRootPath)/\(testConfig.referenceConfig)" - + let configData = dataLoader.fromJsonFile(path) let privacyConfigurationData = try PrivacyConfigurationData(data: configData) - + let privacyConfiguration = AppPrivacyConfiguration(data: privacyConfigurationData, identifier: UUID().uuidString, localProtection: MockDomainsProtectionStore(), @@ -49,23 +48,23 @@ final class PrivacyConfigurationReferenceTests: XCTestCase { os_log("Skipping test %@", test.name) continue } - + let testInfo = "\nName: \(test.name)\nFeature: \(test.featureName)\nsiteURL: \(test.siteURL)\nConfig: \(testConfig.referenceConfig)" - + guard let url = URL(string: test.siteURL), let siteDomain = url.host else { XCTFail("Can't get domain \(testInfo)") continue } - + if let feature = PrivacyFeature(rawValue: test.featureName) { let isEnabled = privacyConfiguration.isFeature(feature, enabledForDomain: siteDomain) XCTAssertEqual(isEnabled, test.expectFeatureEnabled, testInfo) - + } else if test.featureName == "trackerAllowlist" { let isEnabled = privacyConfigurationData.trackerAllowlist.state == "enabled" XCTAssertEqual(isEnabled, test.expectFeatureEnabled, testInfo) - + } else { XCTFail("Can't create feature \(testInfo)") continue @@ -78,7 +77,7 @@ final class PrivacyConfigurationReferenceTests: XCTestCase { // MARK: - TestData private struct TestData: Codable { let testConfigs: [TestConfig] - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let dict = try container.decode([String: TestConfig].self) @@ -90,7 +89,7 @@ private struct TestData: Codable { private struct TestConfig: Codable { let name, desc, referenceConfig: String let tests: [Test] - + } // MARK: - Test diff --git a/Tests/BrowserServicesKitTests/ReferrerTrimming/ReferrerTrimmingTests.swift b/Tests/BrowserServicesKitTests/ReferrerTrimming/ReferrerTrimmingTests.swift index 2868757d8..02fffdf3e 100644 --- a/Tests/BrowserServicesKitTests/ReferrerTrimming/ReferrerTrimmingTests.swift +++ b/Tests/BrowserServicesKitTests/ReferrerTrimming/ReferrerTrimmingTests.swift @@ -32,13 +32,13 @@ struct ReferrerTests: Codable { let expectReferrerHeaderValue: String? let exceptPlatforms: [String]? } - + struct ReferrerHeaderTestSuite: Codable { let name: String let desc: String let tests: [ReferrerHeaderTest] } - + let refererHeaderNavigation: ReferrerHeaderTestSuite } @@ -49,10 +49,10 @@ class ReferrerTrimmingTests: XCTestCase { static let tds = "Resources/privacy-reference-tests/referrer-trimming/tracker_radar_reference.json" static let tests = "Resources/privacy-reference-tests/referrer-trimming/tests.json" } - + private static let data = JsonTestDataLoader() private static let config = data.fromJsonFile(Resource.config) - + private var privacyManager: PrivacyConfigurationManager { let embeddedDataProvider = MockEmbeddedDataProvider(data: Self.config, etag: "embedded") @@ -65,43 +65,43 @@ class ReferrerTrimmingTests: XCTestCase { localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + private var contentBlockingManager: ContentBlockerRulesManager { let listsSource = ContentBlockerRulesListSourceMock() let exceptionsSource = ContentBlockerRulesExceptionsSourceMock() return ContentBlockerRulesManager(rulesSource: listsSource, exceptionsSource: exceptionsSource) } - + private lazy var tds: TrackerData = { let trackerJSON = Self.data.fromJsonFile(Resource.tds) return try! JSONDecoder().decode(TrackerData.self, from: trackerJSON) }() - + private lazy var referrerTestSuite: ReferrerTests = { let tests = Self.data.fromJsonFile(Resource.tests) return try! JSONDecoder().decode(ReferrerTests.self, from: tests) }() - + func testReferrerTrimming() throws { let tests = referrerTestSuite.refererHeaderNavigation.tests let referrerTrimming = ReferrerTrimming(privacyManager: privacyManager, contentBlockingManager: contentBlockingManager, tld: TLD()) - + for test in tests { let skip = test.exceptPlatforms?.contains("ios-browser") if skip == true { os_log("!!SKIPPING TEST: %s", test.name) continue } - + os_log("TEST: %s", test.name) - + let referrerResult = referrerTrimming.getTrimmedReferrer(originUrl: URL(string: test.navigatingFromURL)!, destUrl: URL(string: test.navigatingToURL)!, referrerUrl: test.referrerValue != nil ? URL(string: test.referrerValue!) : nil, trackerData: tds) - + // nil result is considered unchanged let resultUrl = referrerResult == nil ? test.referrerValue : referrerResult XCTAssertEqual(resultUrl, test.expectReferrerHeaderValue, "\(test.name) failed") diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapperTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapperTests.swift index 91486cd99..6b0a038c3 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapperTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/Mappers/JsonToRemoteConfigModelMapperTests.swift @@ -1,6 +1,5 @@ // // JsonToRemoteConfigModelMapperTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/AppAttributeMatcherTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/AppAttributeMatcherTests.swift index a488fe438..909ed8263 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/AppAttributeMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/AppAttributeMatcherTests.swift @@ -1,6 +1,5 @@ // // AppAttributeMatcherTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/DeviceAttributeMatcherTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/DeviceAttributeMatcherTests.swift index 00714d1eb..ad6192721 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/DeviceAttributeMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/DeviceAttributeMatcherTests.swift @@ -1,6 +1,5 @@ // // DeviceAttributeMatcherTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/UserAttributeMatcherTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/UserAttributeMatcherTests.swift index 04398c249..d8b63f122 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/UserAttributeMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/Matchers/UserAttributeMatcherTests.swift @@ -1,6 +1,5 @@ // // UserAttributeMatcherTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/Model/RangeStringMatchingAttributeTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/Model/RangeStringMatchingAttributeTests.swift index 1abdfb856..a79bef798 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/Model/RangeStringMatchingAttributeTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/Model/RangeStringMatchingAttributeTests.swift @@ -1,6 +1,5 @@ // // RangeStringMatchingAttributeTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigMatcherTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigMatcherTests.swift index 9d80cc3c2..4b152b088 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigMatcherTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigMatcherTests.swift @@ -1,6 +1,5 @@ // // RemoteMessagingConfigMatcherTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigProcessorTests.swift b/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigProcessorTests.swift index 593e8cf30..24d378591 100644 --- a/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigProcessorTests.swift +++ b/Tests/BrowserServicesKitTests/RemoteMessaging/RemoteMessagingConfigProcessorTests.swift @@ -1,6 +1,5 @@ // -// JsonRemoteMessagingConfigMapperTests.swift -// DuckDuckGo +// RemoteMessagingConfigProcessorTests.swift // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/SecureVault/CredentialsDatabaseCleanerTests.swift b/Tests/BrowserServicesKitTests/SecureVault/CredentialsDatabaseCleanerTests.swift index d99aa8c4b..5110c9dcc 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/CredentialsDatabaseCleanerTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/CredentialsDatabaseCleanerTests.swift @@ -1,6 +1,5 @@ // // CredentialsDatabaseCleanerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift b/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift index c4dd9956d..62e3b13a0 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift @@ -1,5 +1,5 @@ // -// MockProviders.swift +// MockAutofillDatabaseProvider.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -72,7 +72,7 @@ internal class MockAutofillDatabaseProvider: AutofillDatabaseProvider { } func deleteWebsiteCredentialsForAccountId(_ accountId: Int64) throws { - self._credentialsDict.removeValue(forKey: accountId) + self._credentialsDict.removeValue(forKey: accountId) self._accounts = self._accounts.filter { $0.id != String(accountId) } } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift index d5b675fee..5b2679820 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift @@ -23,13 +23,13 @@ import SecureStorageTestsUtils @testable import BrowserServicesKit class SecureVaultManagerTests: XCTestCase { - + private var mockCryptoProvider = NoOpCryptoProvider() private var mockKeystoreProvider = MockKeystoreProvider() private var mockDatabaseProvider: MockAutofillDatabaseProvider = { return try! MockAutofillDatabaseProvider() }() - + private let mockAutofillUserScript: AutofillUserScript = { let embeddedConfig = """ @@ -49,7 +49,7 @@ class SecureVaultManagerTests: XCTestCase { properties: properties) return AutofillUserScript(scriptSourceProvider: sourceProvider, encrypter: MockEncrypter(), hostProvider: SecurityOriginHostProvider()) }() - + private var testVault: (any AutofillSecureVault)! private var secureVaultManagerDelegate: MockSecureVaultManagerDelegate! private var manager: SecureVaultManager! @@ -61,65 +61,65 @@ class SecureVaultManagerTests: XCTestCase { mockKeystoreProvider._encryptedL2Key = "encryptedL2Key".data(using: .utf8) let providers = SecureStorageProviders(crypto: mockCryptoProvider, database: mockDatabaseProvider, keystore: mockKeystoreProvider) - + self.testVault = DefaultAutofillSecureVault(providers: providers) self.secureVaultManagerDelegate = MockSecureVaultManagerDelegate() self.manager = SecureVaultManager(vault: self.testVault) self.manager.delegate = secureVaultManagerDelegate } - + func testWhenGettingExistingEntries_AndNoAutofillDataWasProvided_AndNoEntriesExist_ThenReturnValueIsNil() throws { let autofillData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: nil, creditCard: nil, trigger: nil) let entries = try manager.existingEntries(for: "domain.com", autofillData: autofillData) - + XCTAssertNil(entries.credentials) XCTAssertNil(entries.identity) XCTAssertNil(entries.creditCard) } - + func testWhenGettingExistingEntries_AndAutofillCreditCardWasProvided_AndNoMatchingCreditCardExists_ThenReturnValueIncludesCard() throws { let card = paymentMethod(cardNumber: "5555555555555557", cardholderName: "Name", cvv: "123", month: 1, year: 2022) let autofillData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: nil, creditCard: card, trigger: nil) let entries = try manager.existingEntries(for: "domain.com", autofillData: autofillData) - + XCTAssertNil(entries.credentials) XCTAssertNil(entries.identity) XCTAssertNotNil(entries.creditCard) XCTAssertTrue(entries.creditCard!.hasAutofillEquality(comparedTo: card)) } - + func testWhenGettingExistingEntries_AndAutofillCreditCardWasProvided_AndMatchingCreditCardExists_ThenReturnValueIsNil() throws { let card = paymentMethod(id: 1, cardNumber: "5555555555555557", cardholderName: "Name", cvv: "123", month: 1, year: 2022) try self.testVault.storeCreditCard(card) let autofillData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: nil, creditCard: card, trigger: nil) let entries = try manager.existingEntries(for: "domain.com", autofillData: autofillData) - + XCTAssertNil(entries.credentials) XCTAssertNil(entries.identity) XCTAssertNil(entries.creditCard) } - + func testWhenGettingExistingEntries_AndAutofillIdentityWasProvided_AndNoMatchingIdentityExists_ThenReturnValueIncludesIdentity() throws { let identity = identity(name: ("First", "Middle", "Last"), addressStreet: "Address Street") - + let autofillData = AutofillUserScript.DetectedAutofillData(identity: identity, credentials: nil, creditCard: nil, trigger: nil) let entries = try manager.existingEntries(for: "domain.com", autofillData: autofillData) - + XCTAssertNil(entries.credentials) XCTAssertNil(entries.creditCard) XCTAssertNotNil(entries.identity) XCTAssertTrue(entries.identity!.hasAutofillEquality(comparedTo: identity)) } - + func testWhenGettingExistingEntries_AndAutofillIdentityWasProvided_AndMatchingIdentityExists_ThenReturnValueIsNil() throws { let identity = identity(id: 1, name: ("First", "Middle", "Last"), addressStreet: "Address Street") try self.testVault.storeIdentity(identity) let autofillData = AutofillUserScript.DetectedAutofillData(identity: identity, credentials: nil, creditCard: nil, trigger: nil) let entries = try manager.existingEntries(for: "domain.com", autofillData: autofillData) - + XCTAssertNil(entries.credentials) XCTAssertNil(entries.identity) XCTAssertNil(entries.creditCard) @@ -163,7 +163,7 @@ class SecureVaultManagerTests: XCTestCase { self.secureVaultManagerDelegate = SecureVaultDelegate() self.manager.delegate = self.secureVaultManagerDelegate - + let triggerType = AutofillUserScript.GetTriggerType.userInitiated // account 1 (empty username) @@ -207,7 +207,7 @@ class SecureVaultManagerTests: XCTestCase { self.secureVaultManagerDelegate = SecureVaultDelegate() self.manager.delegate = self.secureVaultManagerDelegate - + let triggerType = AutofillUserScript.GetTriggerType.userInitiated // account 1 (empty username) @@ -383,7 +383,7 @@ class SecureVaultManagerTests: XCTestCase { } - // When generating an email and then changing to personal duck address input, credentials should not be autosaved (prompt should be presented instead) + // When generating an email and then changing to personal duck address input, credentials should not be autosaved (prompt should be presented instead) func testWhenGeneratedUsernameIsChangedToPersonalDuckAddress_ThenDataIsNotAutosaved() { var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "privateemail@duck.com", password: "", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) @@ -392,9 +392,9 @@ class SecureVaultManagerTests: XCTestCase { // Email should be saved let credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "privateemail@duck.com") - XCTAssertEqual(credentials?.password, Data("".utf8)) + XCTAssertEqual(credentials?.password, Data("".utf8)) - // Select Private Email address and submit + // Select Private Email address and submit incomingCredentials = AutofillUserScript.IncomingCredentials(username: "john1@duck.com", password: "", autogenerated: false) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) @@ -402,7 +402,7 @@ class SecureVaultManagerTests: XCTestCase { incomingCredentials = AutofillUserScript.IncomingCredentials(username: "john1@duck.com", password: "QNKs6k4a-axYX@aRQW", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) - + // Confirm autofill entries are present XCTAssertEqual(entries?.credentials?.account.username, "john1@duck.com") XCTAssertEqual(entries?.credentials?.password, Data("QNKs6k4a-axYX@aRQW".utf8)) @@ -416,68 +416,68 @@ class SecureVaultManagerTests: XCTestCase { XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "john1@duck.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("QNKs6k4a-axYX@aRQW".utf8)) - + } - - // When generating an email and then changing to manual input, credentials should not be autosaved (prompt should be presented instead) + + // When generating an email and then changing to manual input, credentials should not be autosaved (prompt should be presented instead) func testWhenGeneratedUsernameIsChangedToManualInput_ThenDataIsNotAutosaved() { var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "akla11@duck.com", password: "", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // Email should be saved let credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "akla11@duck.com") - XCTAssertEqual(credentials?.password, Data("".utf8)) - + XCTAssertEqual(credentials?.password, Data("".utf8)) + // Autofill prompted data tests incomingCredentials = AutofillUserScript.IncomingCredentials(username: "example@duck.com", password: "QNKs6k212aYX@aRQW", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "example@duck.com") XCTAssertEqual(entries?.credentials?.password, Data("QNKs6k212aYX@aRQW".utf8)) - + incomingCredentials = AutofillUserScript.IncomingCredentials(username: "john1@duck.com", password: "QNKs6k4a-axYX@aRQW", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + let creds = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertNil(creds) - + // Prompted data should be there XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "john1@duck.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("QNKs6k4a-axYX@aRQW".utf8)) - + } - - // When generating an email and then changing to manual input, credentials should not be autosaved (prompt should be presented instead) + + // When generating an email and then changing to manual input, credentials should not be autosaved (prompt should be presented instead) func testWhenGeneratedUsernameIsManuallyChanged_ThenDataIsNotAutosaved() { var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "privateemail@duck.com", password: "", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // Autofill prompted data tests incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "email@example.com") XCTAssertEqual(entries?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + // Submit the form incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // No data should be saved let credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertNil(credentials) - + // Prompted data should be there XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "email@example.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + } // When generating and entering a manual password, then deleting the automatically saved login @@ -524,160 +524,159 @@ class SecureVaultManagerTests: XCTestCase { // Create mocked Autofill Data incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: true) - incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) - + incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) + let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "email@example.com") XCTAssertEqual(entries?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) } - + // When the user generates a pasword and there is a username present from the autofill script, it should be automatically saved too func testWhenGeneratingAPassword_ThenUsernameShouldBeSavedIfPresent() { let incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "gener4tedP4sswOrd", autogenerated: true) let incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .passwordGeneration) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + let credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "email@example.com") XCTAssertEqual(credentials?.password, Data("gener4tedP4sswOrd".utf8)) } - + // When submitting a form that never had autogenerated data, a prompt is shown func testWhenEnteringManualUsernameAndPassword_ThenDataIsSaved() { var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: false) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // Create mocked Autofill Data incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: true) - incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) - + incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) + let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "email@example.com") XCTAssertEqual(entries?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + // Prompted data should be there XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "email@example.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) } - + // When autosaving credentials for one site, and the using the same username in other site, data should not be automatically saved func testWhenSavingCredentialsAutomatically_PartialAccountShouldBeCleared() { var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "gener4tedP4sswOrd", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .passwordGeneration) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "gener4tedP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // Credentials should be saved automatically var credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "email@example.com") XCTAssertEqual(credentials?.password, Data("gener4tedP4sswOrd".utf8)) - + incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: false) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "profile.theguardian.com", data: incomingData) - + // Credentials should NOT saved automatically credentials = try? testVault?.websiteCredentialsFor(accountId: 2) XCTAssertNil(credentials) - + // Create mocked Autofill Data incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: false) - incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) - + incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) + let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "email@example.com") XCTAssertEqual(entries?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + // Prompted data should be there XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "email@example.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) } - + // If an account already exists, its data should not be auto-replaced when generating usernames or passwords (on a different session) func testWhenAutosavingCredentialsForAndOldAccount_ThenAccountShouldNotBeUpdatedAutomatically() { - + var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "gener4tedP4sswOrd", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .passwordGeneration) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // A form submission should close the existing session incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "gener4tedP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + var credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "email@example.com") XCTAssertEqual(credentials?.password, Data("gener4tedP4sswOrd".utf8)) - + // The user then goes back to the form and auto generates a password incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "Anoth3rgener4tedP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .passwordGeneration) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // The new password should not be saved credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "email@example.com") XCTAssertEqual(credentials?.password, Data("gener4tedP4sswOrd".utf8)) - + // The user then goes back to the form and auto generates a username incomingCredentials = AutofillUserScript.IncomingCredentials(username: "privateemail@duck.com", password: "", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // The new password should not be saved credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "email@example.com") XCTAssertEqual(credentials?.password, Data("gener4tedP4sswOrd".utf8)) - + } - + // When generating a private email address, and manually typing a password, and typing a manual email // and submitting the form, a prompt to save data should be shown, and no data should be automatically saved func testWhenUsingPrivateAndThenManuallyTypedEmail_ThenDataShouldNotBeAutosaved() { - + // Create a login item via a generated username and manual password var incomingCredentials = AutofillUserScript.IncomingCredentials(username: "privateemail@duck.com", password: "m4nu4lP4sswOrd", autogenerated: true) var incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .emailProtection) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // The new email should not be saved var credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertEqual(credentials?.account.username, "privateemail@duck.com") XCTAssertEqual(credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - - // Change the email to a manual and submit the form + + // Change the email to a manual and submit the form incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: true) incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) manager.autofillUserScript(mockAutofillUserScript, didRequestStoreDataForDomain: "fill.dev", data: incomingData) - + // Credentials should NOT saved automatically credentials = try? testVault?.websiteCredentialsFor(accountId: 1) XCTAssertNil(credentials) - + // Create mocked Autofill Data incomingCredentials = AutofillUserScript.IncomingCredentials(username: "email@example.com", password: "m4nu4lP4sswOrd", autogenerated: false) - incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) - + incomingData = AutofillUserScript.DetectedAutofillData(identity: nil, credentials: incomingCredentials, creditCard: nil, trigger: .formSubmission) + let entries = try? manager.existingEntries(for: "fill.dev", autofillData: incomingData) XCTAssertEqual(entries?.credentials?.account.username, "email@example.com") XCTAssertEqual(entries?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + // Prompted data should be there XCTAssertNotNil(secureVaultManagerDelegate.promptedAutofillData) XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.account.username, "email@example.com") XCTAssertEqual(secureVaultManagerDelegate.promptedAutofillData?.credentials?.password, Data("m4nu4lP4sswOrd".utf8)) - + } - + // MARK: - Test Utilities - // swiftlint:disable:next large_tuple private func identity(id: Int64? = nil, name: (String, String, String), addressStreet: String?) -> SecureVaultModels.Identity { return SecureVaultModels.Identity(id: id, title: nil, @@ -699,7 +698,7 @@ class SecureVaultManagerTests: XCTestCase { mobilePhone: nil, emailAddress: nil) } - + private func paymentMethod(id: Int64? = nil, cardNumber: String, cardholderName: String, @@ -714,7 +713,7 @@ class SecureVaultManagerTests: XCTestCase { expirationMonth: month, expirationYear: year) } - + } private class MockSecureVaultManagerDelegate: SecureVaultManagerDelegate { @@ -732,7 +731,7 @@ private class MockSecureVaultManagerDelegate: SecureVaultManagerDelegate { } func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, isAuthenticatedFor type: BrowserServicesKit.AutofillType, completionHandler: @escaping (Bool) -> Void) {} - + func secureVaultManager(_: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], @@ -740,13 +739,13 @@ private class MockSecureVaultManagerDelegate: SecureVaultManagerDelegate { completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) {} func secureVaultManager(_: BrowserServicesKit.SecureVaultManager, promptUserWithGeneratedPassword password: String, completionHandler: @escaping (Bool) -> Void) {} - + func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: String) {} - + func secureVaultManager(_: SecureVaultManager, didRequestAuthenticationWithCompletionHandler: @escaping (Bool) -> Void) {} - + func secureVaultInitFailed(_ error: SecureStorageError) {} - + func secureVaultManagerShouldSaveData(_: BrowserServicesKit.SecureVaultManager) -> Bool { true } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultModelTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultModelTests.swift index 67e8b52df..642aa5596 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultModelTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultModelTests.swift @@ -14,6 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// import Foundation import XCTest @@ -155,7 +156,6 @@ class SecureVaultModelTests: XCTestCase { // MARK: - Test Utilities - // swiftlint:disable:next large_tuple private func identity(named name: (String, String, String), addressStreet: String?) -> SecureVaultModels.Identity { return SecureVaultModels.Identity(id: nil, title: nil, @@ -331,7 +331,7 @@ class SecureVaultModelTests: XCTestCase { } func testPatternMatchedTitle() { - + let domainTitles: [String] = [ "duck.com", "duck.com (test@duck.com)", @@ -347,7 +347,7 @@ class SecureVaultModelTests: XCTestCase { "https://www.duck.com/section/page.php?test=variable1&b=variable2", "https://WwW.dUck.com/section/page" ] - + let subdomainTitles: [String] = [ "signin.duck.com", "signin.duck.com (test@duck.com.co)", @@ -357,7 +357,7 @@ class SecureVaultModelTests: XCTestCase { "https://signin.duck.com/section/page.php?test=variable1&b=variable2", "https://SiGnIn.dUck.com/section/page" ] - + let tldPlusOneTitles: [String] = [ "signin.duck.com.co", "signin.duck.com.co (test@duck.com.co)", @@ -367,7 +367,7 @@ class SecureVaultModelTests: XCTestCase { "https://signin.duck.com.co/section/page.php?test=variable1&b=variable2", "https://SiGnIn.dUck.com.CO/section/page" ] - + let randomTitles: [String] = [ "John's Work Gmail", "Chase Bank - Main Account", @@ -422,36 +422,36 @@ class SecureVaultModelTests: XCTestCase { "twitter.com my account", "fill.dev personal email" ] - + for title in domainTitles { let account = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "sometestdomain.com", created: Date(), lastUpdated: Date()) XCTAssertEqual("duck.com", account.patternMatchedTitle(), "Failed for title: \(title)") - + let equalDomain = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "duck.com", created: Date(), lastUpdated: Date()) XCTAssertEqual("", equalDomain.patternMatchedTitle(), "Failed for title: \(title)") } - + for title in subdomainTitles { let account = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "sometestdomain.com", created: Date(), lastUpdated: Date()) XCTAssertEqual("signin.duck.com", account.patternMatchedTitle(), "Failed for title: \(title)") - + let equalDomain = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "signin.duck.com", created: Date(), lastUpdated: Date()) XCTAssertEqual("", equalDomain.patternMatchedTitle(), "Failed for title: \(title)") } - + for title in tldPlusOneTitles { let account = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "sometestdomain.com", created: Date(), lastUpdated: Date()) XCTAssertEqual("signin.duck.com.co", account.patternMatchedTitle(), "Failed for title: \(title)") - + let equalDomain = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "signin.duck.com.co", created: Date(), lastUpdated: Date()) XCTAssertEqual("", equalDomain.patternMatchedTitle(), "Failed for title: \(title)") } - + for title in randomTitles { let account = SecureVaultModels.WebsiteAccount(id: "", title: title, username: "", domain: "sometestdomain.com", created: Date(), lastUpdated: Date()) XCTAssertEqual(title, account.patternMatchedTitle(), "Failed for title: \(title)") } - + } - + } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsMigrationPerformanceTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsMigrationPerformanceTests.swift index 3703d10f3..003d6d70d 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsMigrationPerformanceTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsMigrationPerformanceTests.swift @@ -1,6 +1,5 @@ // // SecureVaultSyncableCredentialsMigrationPerformanceTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsTests.swift index af438860f..26c0a649a 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultSyncableCredentialsTests.swift @@ -1,6 +1,5 @@ // // SecureVaultSyncableCredentialsTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift index 42d77442a..53326a8b7 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift @@ -80,7 +80,7 @@ class SecureVaultTests: XCTestCase { mockCryptoProvider._derivedKey = "derived".data(using: .utf8)! mockKeystoreProvider._encryptedL2Key = "encryptedL2Key".data(using: .utf8)! mockCryptoProvider._decryptedData = "decrypted".data(using: .utf8)! - + let account = SecureVaultModels.WebsiteAccount(id: "1", title: "Title", username: "test@duck.com", @@ -88,7 +88,7 @@ class SecureVaultTests: XCTestCase { created: Date(), lastUpdated: Date()) let credentials = SecureVaultModels.WebsiteCredentials(account: account, password: "password".data(using: .utf8)!) - + try testVault.storeWebsiteCredentials(credentials) mockDatabaseProvider._accounts = [account] @@ -166,12 +166,12 @@ class SecureVaultTests: XCTestCase { let account = SecureVaultModels.WebsiteAccount(id: "1", username: "test@duck.com", domain: "example.com", created: Date(), lastUpdated: Date()) let credentials = SecureVaultModels.WebsiteCredentials(account: account, password: password) self.mockDatabaseProvider._accounts = [account] - + mockCryptoProvider._decryptedData = "decrypted".data(using: .utf8) mockKeystoreProvider._generatedPassword = "generated".data(using: .utf8) mockCryptoProvider._derivedKey = "derived".data(using: .utf8) mockKeystoreProvider._encryptedL2Key = "encryptedL2Key".data(using: .utf8) - + try testVault.storeWebsiteCredentials(credentials) let fetchedCredentials = try testVault.websiteCredentialsFor(accountId: 1) @@ -188,11 +188,11 @@ class SecureVaultTests: XCTestCase { let account = SecureVaultModels.WebsiteAccount(id: "1", username: "test@duck.com", domain: "example.com", created: Date(), lastUpdated: Date()) let credentials = SecureVaultModels.WebsiteCredentials(account: account, password: password) self.mockDatabaseProvider._accounts = [account] - + mockCryptoProvider._decryptedData = "decrypted".data(using: .utf8) mockCryptoProvider._derivedKey = "derived".data(using: .utf8) mockKeystoreProvider._encryptedL2Key = "encryptedL2Key".data(using: .utf8) - + _ = try testVault.authWith(password: userPassword) try testVault.storeWebsiteCredentials(credentials) @@ -209,7 +209,7 @@ class SecureVaultTests: XCTestCase { mockCryptoProvider._decryptedData = "decrypted".data(using: .utf8) mockCryptoProvider._derivedKey = "derived".data(using: .utf8) mockKeystoreProvider._encryptedL2Key = "encryptedL2Key".data(using: .utf8) - + _ = try testVault.authWith(password: userPassword) sleep(2) // allow vault to expire password diff --git a/Tests/BrowserServicesKitTests/SmarterEncryption/BloomFilterWrapperTest.swift b/Tests/BrowserServicesKitTests/SmarterEncryption/BloomFilterWrapperTest.swift index 79699b66c..bc058429d 100644 --- a/Tests/BrowserServicesKitTests/SmarterEncryption/BloomFilterWrapperTest.swift +++ b/Tests/BrowserServicesKitTests/SmarterEncryption/BloomFilterWrapperTest.swift @@ -1,6 +1,5 @@ // // BloomFilterWrapperTest.swift -// DuckDuckGo // // Copyright © 2018 DuckDuckGo. All rights reserved. // @@ -23,32 +22,32 @@ import XCTest @testable import BloomFilterWrapper class BloomFilterWrapperTest: XCTestCase { - + struct Constants { static let filterElementCount = 1000 static let additionalTestDataElementCount = 1000 static let targetErrorRate = 0.001 static let acceptableErrorRate = Constants.targetErrorRate * 5 } - + func testWhenBloomFilterEmptyThenContainsIsFalse() { let testee = BloomFilterWrapper(totalItems: Int32(Constants.filterElementCount), errorRate: Constants.targetErrorRate) XCTAssertFalse(testee.contains("abc")) } - + func testWhenBloomFilterContainsElementThenContainsIsTrue() { let testee = BloomFilterWrapper(totalItems: Int32(Constants.filterElementCount), errorRate: Constants.targetErrorRate) testee.add("abc") XCTAssertTrue(testee.contains("abc")) } - + func testWhenBloomFilterContainsItemsThenLookupResultsAreWithinRange() { let bloomData = createRandomStrings(count: Constants.filterElementCount) let testData = bloomData + createRandomStrings(count: Constants.additionalTestDataElementCount) - + let testee = BloomFilterWrapper(totalItems: Int32(bloomData.count), errorRate: Constants.targetErrorRate) bloomData.forEach { testee.add($0) } - + var falsePositives = 0, truePositives = 0, falseNegatives = 0, trueNegatives = 0 for element in testData { let result = testee.contains(element) @@ -57,18 +56,18 @@ class BloomFilterWrapperTest: XCTestCase { if !bloomData.contains(element) && !result { trueNegatives += 1 } if bloomData.contains(element) && result { truePositives += 1 } } - + let errorRate = Double(falsePositives) / Double(testData.count) XCTAssertEqual(0, falseNegatives) XCTAssertEqual(bloomData.count, truePositives) XCTAssertTrue(trueNegatives <= testData.count - bloomData.count) XCTAssertTrue(errorRate <= Constants.acceptableErrorRate) } - + private func createRandomStrings(count: Int) -> [String] { var list = [String]() for _ in 0..(["www.example.com", "example.com", "test.com", "anothertest.com"]), Set(result!)) } - + func testWhenBloomFilterSpecificationJSONIsUnexpectedThenTypeMismatchErrorThrown() { let data = JsonTestDataLoader().unexpected() XCTAssertThrowsError(try HTTPSUpgradeParser.convertBloomFilterSpecification(fromJSONData: data), "") { error in XCTAssertEqual(error.localizedDescription, JsonError.typeMismatch.localizedDescription) } } - + func testWhenBloomFilterSpecificationJSONIsInvalidThenInvalidJsonErrorThrown() { let data = JsonTestDataLoader().invalid() XCTAssertThrowsError(try HTTPSUpgradeParser.convertBloomFilterSpecification(fromJSONData: data)) { error in XCTAssertEqual(error.localizedDescription, JsonError.invalidJson.localizedDescription) } } - + func testWhenBloomFilterSpecificationIsValidThenSpecificationReturned() { let data = JsonTestDataLoader().fromJsonFile("Resources/https_bloom_spec.json") let result = try? HTTPSUpgradeParser.convertBloomFilterSpecification(fromJSONData: data) diff --git a/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeReferenceTests.swift b/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeReferenceTests.swift index 242bea3fa..89641e34c 100644 --- a/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeReferenceTests.swift +++ b/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeReferenceTests.swift @@ -1,6 +1,5 @@ // // HTTPSUpgradeReferenceTests.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -29,7 +28,7 @@ private struct HTTPSUpgradesRefTests: Decodable { let desc: String let tests: [HTTPSUpgradesTest] } - + struct HTTPSUpgradesTest: Decodable { let name: String let siteURL: String @@ -37,16 +36,16 @@ private struct HTTPSUpgradesRefTests: Decodable { let requestType: String let expectURL: String let exceptPlatforms: [String] - + var shouldSkip: Bool { exceptPlatforms.contains("ios-browser") } } - + let navigations: HTTPSUpgradesTests let subrequests: HTTPSUpgradesTests } final class HTTPSUpgradeReferenceTests: XCTestCase { - + private enum Resource { static let config = "Resources/privacy-reference-tests/https-upgrades/config_reference.json" static let tests = "Resources/privacy-reference-tests/https-upgrades/tests.json" @@ -54,9 +53,9 @@ final class HTTPSUpgradeReferenceTests: XCTestCase { static let bloomFilterSpec = "Resources/privacy-reference-tests/https-upgrades/https_bloomfilter_spec_reference.json" static let bloomFilter = "Resources/privacy-reference-tests/https-upgrades/https_bloomfilter_reference" } - + private static let data = JsonTestDataLoader() - + private static let config = data.fromJsonFile(Resource.config) private static let emptyConfig = """ @@ -68,51 +67,51 @@ final class HTTPSUpgradeReferenceTests: XCTestCase { } } """.data(using: .utf8)! - + private func makePrivacyManager(config: Data? = config, unprotectedDomains: [String] = []) -> PrivacyConfigurationManager { let embeddedDataProvider = MockEmbeddedDataProvider(data: config ?? Self.emptyConfig, etag: "embedded") let localProtection = MockDomainsProtectionStore() localProtection.unprotectedDomains = Set(unprotectedDomains) - + return PrivacyConfigurationManager(fetchedETag: nil, fetchedData: nil, embeddedDataProvider: embeddedDataProvider, localProtection: localProtection, internalUserDecider: DefaultInternalUserDecider()) } - + private lazy var httpsUpgradesTestSuite: HTTPSUpgradesRefTests = { let tests = Self.data.fromJsonFile(Resource.tests) return try! JSONDecoder().decode(HTTPSUpgradesRefTests.self, from: tests) }() - + private lazy var excludedDomains: [String] = { let allowListData = Self.data.fromJsonFile(Resource.allowList) return try! HTTPSUpgradeParser.convertExcludedDomainsData(allowListData) }() - + private lazy var bloomFilterSpecification: HTTPSBloomFilterSpecification = { let data = Self.data.fromJsonFile(Resource.bloomFilterSpec) return try! HTTPSUpgradeParser.convertBloomFilterSpecification(fromJSONData: data) }() - + private lazy var bloomFilter: BloomFilterWrapper? = { let path = Bundle.module.path(forResource: Resource.bloomFilter, ofType: "bin")! return BloomFilterWrapper(fromPath: path, withBitCount: Int32(bloomFilterSpecification.bitCount), andTotalItems: Int32(bloomFilterSpecification.totalEntries)) }() - + private lazy var mockStore: HTTPSUpgradeStore = { HTTPSUpgradeStoreMock(bloomFilter: bloomFilter, bloomFilterSpecification: bloomFilterSpecification, excludedDomains: excludedDomains) }() - + func testHTTPSUpgradesNavigations() async { let tests = httpsUpgradesTestSuite.navigations.tests let httpsUpgrade = HTTPSUpgrade(store: mockStore, privacyManager: makePrivacyManager()) await httpsUpgrade.loadData() - + for test in tests { os_log("TEST: %s", test.name) @@ -120,12 +119,12 @@ final class HTTPSUpgradeReferenceTests: XCTestCase { os_log("SKIPPING TEST: \(test.name)") return } - + guard let url = URL(string: test.requestURL) else { XCTFail("BROKEN INPUT: \(Resource.tests)") return } - + var resultURL = url let result = await httpsUpgrade.upgrade(url: url) if case let .success(upgradedURL) = result { @@ -134,28 +133,28 @@ final class HTTPSUpgradeReferenceTests: XCTestCase { XCTAssertEqual(resultURL.absoluteString, test.expectURL, "FAILED: \(test.name)") } } - + func testLocalUnprotectedDomainShouldNotUpgradeToHTTPS() async { let httpsUpgrade = HTTPSUpgrade(store: mockStore, privacyManager: makePrivacyManager(config: nil, unprotectedDomains: ["secure.thirdtest.com"])) await httpsUpgrade.loadData() - + let url = URL(string: "http://secure.thirdtest.com")! - + var resultURL = url let result = await httpsUpgrade.upgrade(url: url) if case let .success(upgradedURL) = result { resultURL = upgradedURL } - + XCTAssertEqual(resultURL.absoluteString, url.absoluteString, "FAILED: \(resultURL)") } - + func testLocalUnprotectedDomainShouldUpgradeSubdomainToHTTPS() async { let httpsUpgrade = HTTPSUpgrade(store: mockStore, privacyManager: makePrivacyManager(config: nil, unprotectedDomains: ["thirdtest.com"])) await httpsUpgrade.loadData() - + let url = URL(string: "http://secure.thirdtest.com")! - + var resultURL = url let result = await httpsUpgrade.upgrade(url: url) if case let .success(upgradedURL) = result { diff --git a/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeStoreMock.swift b/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeStoreMock.swift index f14339161..43e834b19 100644 --- a/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeStoreMock.swift +++ b/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeStoreMock.swift @@ -1,6 +1,5 @@ // // HTTPSUpgradeStoreMock.swift -// DuckDuckGo // // Copyright © 2022 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Statistics/MockStatisticsStore.swift b/Tests/BrowserServicesKitTests/Statistics/MockStatisticsStore.swift index aba39aced..729d7d3e0 100644 --- a/Tests/BrowserServicesKitTests/Statistics/MockStatisticsStore.swift +++ b/Tests/BrowserServicesKitTests/Statistics/MockStatisticsStore.swift @@ -1,6 +1,5 @@ // // MockStatisticsStore.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/Statistics/MockVariantManager.swift b/Tests/BrowserServicesKitTests/Statistics/MockVariantManager.swift index 3ed76d171..ba302c936 100644 --- a/Tests/BrowserServicesKitTests/Statistics/MockVariantManager.swift +++ b/Tests/BrowserServicesKitTests/Statistics/MockVariantManager.swift @@ -1,6 +1,5 @@ // // MockVariantManager.swift -// DuckDuckGo // // Copyright © 2018 DuckDuckGo. All rights reserved. // @@ -28,7 +27,7 @@ struct MockVariantManager: VariantManager { isSupportedBlock = { _ in return newValue } } } - + var isSupportedBlock: (FeatureName) -> Bool var currentVariant: Variant? @@ -41,7 +40,7 @@ struct MockVariantManager: VariantManager { func assignVariantIfNeeded(_ newInstallCompletion: (VariantManager) -> Void) { } - + func isSupported(feature: FeatureName) -> Bool { return isSupportedBlock(feature) } diff --git a/Tests/BrowserServicesKitTests/Suggestions/ScoreTests.swift b/Tests/BrowserServicesKitTests/Suggestions/ScoreTests.swift index b9d013dad..0a10c12a2 100644 --- a/Tests/BrowserServicesKitTests/Suggestions/ScoreTests.swift +++ b/Tests/BrowserServicesKitTests/Suggestions/ScoreTests.swift @@ -95,5 +95,5 @@ final class ScoreTests: XCTestCase { XCTAssert(score1 < score2) } - + } diff --git a/Tests/BrowserServicesKitTests/Utils/JsonTestDataLoader.swift b/Tests/BrowserServicesKitTests/Utils/JsonTestDataLoader.swift index 05df51d26..89f10b726 100644 --- a/Tests/BrowserServicesKitTests/Utils/JsonTestDataLoader.swift +++ b/Tests/BrowserServicesKitTests/Utils/JsonTestDataLoader.swift @@ -1,6 +1,5 @@ // // JsonTestDataLoader.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -27,18 +26,18 @@ enum FileError: Error { final class FileLoader { func load(filePath: String, fromBundle bundle: Bundle) throws -> Data { - + guard let resourceUrl = bundle.resourceURL else { throw FileError.unknownFile } - + let url = resourceUrl.appendingPathComponent(filePath) - + let finalURL: URL if FileManager.default.fileExists(atPath: url.path) { finalURL = url } else { // Workaround for resource bundle having a different structure when running tests from command line. let url = resourceUrl.deletingLastPathComponent().appendingPathComponent(filePath) - + if FileManager.default.fileExists(atPath: url.path) { finalURL = url } else { diff --git a/Tests/BrowserServicesKitTests/Utils/StringExtension.swift b/Tests/BrowserServicesKitTests/Utils/StringExtension.swift index d15912f60..40463f0a3 100644 --- a/Tests/BrowserServicesKitTests/Utils/StringExtension.swift +++ b/Tests/BrowserServicesKitTests/Utils/StringExtension.swift @@ -1,6 +1,5 @@ // // StringExtension.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/BrowserServicesKitTests/XCTestManifests.swift b/Tests/BrowserServicesKitTests/XCTestManifests.swift index 3c1ee008d..b03615fab 100644 --- a/Tests/BrowserServicesKitTests/XCTestManifests.swift +++ b/Tests/BrowserServicesKitTests/XCTestManifests.swift @@ -1,3 +1,21 @@ +// +// XCTestManifests.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + import XCTest #if !canImport(ObjectiveC) diff --git a/Tests/CommonTests/AppVersionExtensionTests.swift b/Tests/CommonTests/AppVersionExtensionTests.swift index dbd5bb4c2..b4c13820f 100644 --- a/Tests/CommonTests/AppVersionExtensionTests.swift +++ b/Tests/CommonTests/AppVersionExtensionTests.swift @@ -1,6 +1,5 @@ // // AppVersionExtensionTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -33,7 +32,7 @@ final class AppVersionExtensionTests: XCTestCase { override func setUp() { super.setUp() - + mockBundle = MockBundle() testee = AppVersion(bundle: mockBundle) } @@ -44,7 +43,7 @@ final class AppVersionExtensionTests: XCTestCase { mockBundle.add(name: Bundle.Key.buildNumber, value: Constants.build) XCTAssertEqual("2.0.4.14", testee.versionAndBuildNumber) } - + func testLocalisedTextContainsNameVersionAndBuild() { mockBundle.add(name: Bundle.Key.name, value: Constants.name) mockBundle.add(name: Bundle.Key.versionNumber, value: Constants.version) diff --git a/Tests/CommonTests/AppVersionTests.swift b/Tests/CommonTests/AppVersionTests.swift index ca8209495..8fa66d69a 100644 --- a/Tests/CommonTests/AppVersionTests.swift +++ b/Tests/CommonTests/AppVersionTests.swift @@ -1,6 +1,5 @@ // // AppVersionTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -34,7 +33,7 @@ final class AppVersionTests: XCTestCase { override func setUp() { super.setUp() - + mockBundle = MockBundle() testee = AppVersion(bundle: mockBundle) } @@ -48,7 +47,7 @@ final class AppVersionTests: XCTestCase { mockBundle.add(name: Bundle.Key.versionNumber, value: Constants.version) XCTAssertEqual("2", testee.majorVersionNumber) } - + func testVersionNumber() { mockBundle.add(name: Bundle.Key.versionNumber, value: Constants.version) XCTAssertEqual(Constants.version, testee.versionNumber) diff --git a/Tests/CommonTests/Concurrency/TaskTimeoutTests.swift b/Tests/CommonTests/Concurrency/TaskTimeoutTests.swift index 8a5778f6e..0ddb153ef 100644 --- a/Tests/CommonTests/Concurrency/TaskTimeoutTests.swift +++ b/Tests/CommonTests/Concurrency/TaskTimeoutTests.swift @@ -1,6 +1,5 @@ // // TaskTimeoutTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -35,7 +34,7 @@ final class TaskTimeoutTests: XCTestCase { XCTAssertEqual(result, 1) } - + func testWithTimeoutThrowsTimeoutError() async { do { try await withTimeout(0.0001) { diff --git a/Tests/CommonTests/Extensions/StringExtensionTests.swift b/Tests/CommonTests/Extensions/StringExtensionTests.swift index 4b005b71e..cc341fdfa 100644 --- a/Tests/CommonTests/Extensions/StringExtensionTests.swift +++ b/Tests/CommonTests/Extensions/StringExtensionTests.swift @@ -25,47 +25,47 @@ final class StringExtensionTests: XCTestCase { func testWhenNormalizingStringsForAutofill_ThenDiacriticsAreRemoved() { let stringToNormalize = "Dáx Thê Dûck" let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "daxtheduck") } - + func testWhenNormalizingStringsForAutofill_ThenWhitespaceIsRemoved() { let stringToNormalize = "Dax The Duck" let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "daxtheduck") } - + func testWhenNormalizingStringsForAutofill_ThenPunctuationIsRemoved() { let stringToNormalize = ",Dax+The_Duck." let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "daxtheduck") } - + func testWhenNormalizingStringsForAutofill_ThenNumbersAreRetained() { let stringToNormalize = "Dax123" let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "dax123") } - + func testWhenNormalizingStringsForAutofill_ThenStringsThatDoNotNeedNormalizationAreUntouched() { let stringToNormalize = "firstmiddlelast" let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "firstmiddlelast") } - + func testWhenNormalizingStringsForAutofill_ThenEmojiAreRemoved() { let stringToNormalize = "Dax 🤔" let normalizedString = stringToNormalize.autofillNormalized() - + XCTAssertEqual(normalizedString, "dax") } - + func testWhenEmojisArePresentInDomains_ThenTheseCanBePunycoded() { - + XCTAssertEqual("example.com".punycodeEncodedHostname, "example.com") XCTAssertEqual("Dax🤔.com".punycodeEncodedHostname, "xn--dax-v153b.com") XCTAssertEqual("🤔.com".punycodeEncodedHostname, "xn--wp9h.com") diff --git a/Tests/CommonTests/Mocks/MockBundle.swift b/Tests/CommonTests/Mocks/MockBundle.swift index bdb784d1e..23aed843f 100644 --- a/Tests/CommonTests/Mocks/MockBundle.swift +++ b/Tests/CommonTests/Mocks/MockBundle.swift @@ -1,6 +1,5 @@ // // MockBundle.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // diff --git a/Tests/CommonTests/TLD/TLDTests.swift b/Tests/CommonTests/TLD/TLDTests.swift index 8860a2362..9e2d78960 100644 --- a/Tests/CommonTests/TLD/TLDTests.swift +++ b/Tests/CommonTests/TLD/TLDTests.swift @@ -1,6 +1,5 @@ // // TLDTests.swift -// DuckDuckGo // // Copyright © 2017 DuckDuckGo. All rights reserved. // @@ -46,11 +45,11 @@ final class TLDTests: XCTestCase { func testWhenHostIsTopLevelDotComThenDomainIsSame() { XCTAssertEqual("example.com", tld.domain("example.com")) } - + func testWhenHostIsMalformedThenDomainIsFixed() { XCTAssertEqual("example.com", tld.domain(".example.com")) } - + func testWhenHostMultiPartTopLevelWithSubdomainThenETLDp1Correct() { XCTAssertEqual("bbc.co.uk", tld.eTLDplus1("www.bbc.co.uk")) XCTAssertEqual("bbc.co.uk", tld.eTLDplus1("other.bbc.co.uk")) @@ -67,25 +66,25 @@ final class TLDTests: XCTestCase { XCTAssertEqual(nil, tld.eTLDplus1("com")) XCTAssertEqual(nil, tld.eTLDplus1("co.uk")) } - + func testWhenHostIsIncorrectThenETLDp1IsNotFound() { XCTAssertEqual(nil, tld.eTLDplus1("abcderfg")) } - + func testWhenHostIsNilDomainIsNil() { XCTAssertNil(tld.domain(nil)) } - + func testWhenHostIsTLDThenDomainIsFound() { XCTAssertEqual("com", tld.domain("com")) XCTAssertEqual("co.uk", tld.domain("co.uk")) } - + func testWhenHostIsMultiPartTLDThenDomainIsFound() { XCTAssertEqual(nil, tld.domain("za")) XCTAssertEqual("co.za", tld.domain("co.za")) } - + func testWhenHostIsIncorrectThenDomainIsNil() { XCTAssertNil(tld.domain("abcdefgh")) } diff --git a/Tests/ConfigurationTests/ConfigurationFetcherTests.swift b/Tests/ConfigurationTests/ConfigurationFetcherTests.swift index 77449768e..7fbfe1aae 100644 --- a/Tests/ConfigurationTests/ConfigurationFetcherTests.swift +++ b/Tests/ConfigurationTests/ConfigurationFetcherTests.swift @@ -1,6 +1,5 @@ // // ConfigurationFetcherTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -23,16 +22,16 @@ import XCTest @testable import TestUtils final class ConfigurationFetcherTests: XCTestCase { - + enum MockError: Error { case someError } - + override class func setUp() { APIRequest.Headers.setUserAgent("") Configuration.setURLProvider(MockConfigurationURLProvider()) } - + func makeConfigurationFetcher(store: ConfigurationStoring = MockStore(), validator: ConfigurationValidating = MockValidator()) -> ConfigurationFetcher { let testConfiguration = URLSessionConfiguration.default @@ -41,7 +40,7 @@ final class ConfigurationFetcherTests: XCTestCase { validator: validator, urlSession: URLSession(configuration: testConfiguration)) } - + let privacyConfigurationData = Data("Privacy Config".utf8) // MARK: - Tests for fetch(_:) @@ -55,11 +54,11 @@ final class ConfigurationFetcherTests: XCTestCase { let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertEqual(store.loadData(for: .privacyConfiguration), self.privacyConfigurationData) XCTAssertEqual(store.loadEtag(for: .privacyConfiguration), HTTPURLResponse.testEtag) } - + func testFetchConfigurationWhenNoEtagIsStoredThenResponseIsStored() async throws { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() @@ -68,18 +67,18 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertEqual(store.loadData(for: .privacyConfiguration), self.privacyConfigurationData) XCTAssertEqual(store.loadEtag(for: .privacyConfiguration), HTTPURLResponse.testEtag) } - + func testFetchConfigurationWhenEtagIsStoredButStoreHasNoDataThenResponseIsStored() async throws { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (HTTPURLResponse.testEtag, nil) - + let fetcher = makeConfigurationFetcher(store: store) try await fetcher.fetch(.privacyConfiguration) - + XCTAssertNotNil(store.loadData(for: .privacyConfiguration)) } - + func testFetchConfigurationWhenStoringDataFailsThenEtagIsNotStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() @@ -91,76 +90,76 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertNil(store.loadData(for: .privacyConfiguration)) XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) } - + func testFetchConfigurationWhenStoringEtagFailsThenEtagIsNotStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() store.defaultSaveEtag = { _, _ in throw MockError.someError } - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) } - + func testFetchConfigurationWhenResponseIsNotModifiedThenNoDataStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.notModified, nil) } let store = MockStore() - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) XCTAssertNil(store.loadData(for: .privacyConfiguration)) } - + func testFetchConfigurationWhenEtagAndDataStoredThenEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + func testFetchConfigurationWhenNoEtagStoredThenNoEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (nil, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertNil(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch)) } - + func testFetchConfigurationWhenNoDataStoredThenNoEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, nil) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertNil(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch)) } - + func testFetchConfigurationWhenEtagProvidedThenItIsAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + func testFetchConfigurationWhenEmbeddedEtagAndExternalEtagProvidedThenExternalAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString @@ -168,15 +167,15 @@ final class ConfigurationFetcherTests: XCTestCase { let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) store.configToEmbeddedEtag[.privacyConfiguration] = embeddedEtag - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(.privacyConfiguration) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + // MARK: - Tests for fetch(all:) - + func testFetchAllWhenOneAssetFailsToFetchThenOtherIsNotStoredAndErrorIsThrown() async { MockURLProtocol.requestHandler = { request in if let url = request.url, url == Configuration.bloomFilterBinary.url { @@ -200,13 +199,13 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertNil(store.loadData(for: .bloomFilterBinary)) XCTAssertNil(store.loadData(for: .bloomFilterSpec)) } - + func testFetchAllWhenOneAssetFailsToValidateThenOtherIsNotStoredAndErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let validatorMock = MockValidator() validatorMock.shouldThrowErrorPerConfiguration[.privacyConfiguration] = true validatorMock.shouldThrowErrorPerConfiguration[.trackerDataSet] = false - + let store = MockStore() let fetcher = makeConfigurationFetcher(store: store, validator: validatorMock) do { @@ -223,7 +222,7 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertNil(store.loadData(for: .privacyConfiguration)) XCTAssertNil(store.loadData(for: .trackerDataSet)) } - + func testFetchAllWhenEtagAndDataAreStoredThenResponseIsStored() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.ok, self.privacyConfigurationData) } let oldEtag = UUID().uuidString @@ -233,11 +232,11 @@ final class ConfigurationFetcherTests: XCTestCase { let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertEqual(store.loadData(for: .privacyConfiguration), self.privacyConfigurationData) XCTAssertEqual(store.loadEtag(for: .privacyConfiguration), HTTPURLResponse.testEtag) } - + func testFetchAllWhenNoEtagIsStoredThenResponseIsStored() async throws { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() @@ -246,18 +245,18 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertEqual(store.loadData(for: .privacyConfiguration), self.privacyConfigurationData) XCTAssertEqual(store.loadEtag(for: .privacyConfiguration), HTTPURLResponse.testEtag) } - + func testFetchAllWhenEtagIsStoredButStoreHasNoDataThenResponseIsStored() async throws { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (HTTPURLResponse.testEtag, nil) - + let fetcher = makeConfigurationFetcher(store: store) try await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertNotNil(store.loadData(for: .privacyConfiguration)) } - + func testFetchAllWhenStoringDataFailsThenEtagIsNotStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() @@ -269,76 +268,76 @@ final class ConfigurationFetcherTests: XCTestCase { XCTAssertNil(store.loadData(for: .privacyConfiguration)) XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) } - + func testFetchAllWhenStoringEtagFailsThenEtagIsNotStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.ok, self.privacyConfigurationData) } let store = MockStore() store.defaultSaveEtag = { _, _ in throw MockError.someError } - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) } - + func testFetchAllWhenResponseIsNotModifiedThenNoDataStored() async { MockURLProtocol.requestHandler = { _ in ( HTTPURLResponse.notModified, nil) } let store = MockStore() - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertNil(store.loadEtag(for: .privacyConfiguration)) XCTAssertNil(store.loadData(for: .privacyConfiguration)) } - + func testFetchAllWhenEtagAndDataStoredThenEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + func testFetchAllWhenNoEtagStoredThenNoEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (nil, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertNil(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch)) } - + func testFetchAllWhenNoDataStoredThenNoEtagAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, nil) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertNil(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch)) } - + func testFetchAllWhenEtagProvidedThenItIsAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + func testFetchAllWhenEmbeddedEtagAndExternalEtagProvidedThenExternalAddedToRequest() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let etag = UUID().uuidString @@ -346,11 +345,11 @@ final class ConfigurationFetcherTests: XCTestCase { let store = MockStore() store.configToStoredEtagAndData[.privacyConfiguration] = (etag, Data()) store.configToEmbeddedEtag[.privacyConfiguration] = embeddedEtag - + let fetcher = makeConfigurationFetcher(store: store) try? await fetcher.fetch(all: [.privacyConfiguration]) - + XCTAssertEqual(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: APIRequest.HTTPHeaderField.ifNoneMatch), etag) } - + } diff --git a/Tests/ConfigurationTests/Mocks/MockConfigurationURLProvider.swift b/Tests/ConfigurationTests/Mocks/MockConfigurationURLProvider.swift index 84efe1ee4..460a4a52a 100644 --- a/Tests/ConfigurationTests/Mocks/MockConfigurationURLProvider.swift +++ b/Tests/ConfigurationTests/Mocks/MockConfigurationURLProvider.swift @@ -1,6 +1,5 @@ // // MockConfigurationURLProvider.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,7 +20,7 @@ import Foundation import Configuration struct MockConfigurationURLProvider: ConfigurationURLProviding { - + func url(for configuration: Configuration) -> URL { switch configuration { case .bloomFilterBinary: @@ -40,5 +39,5 @@ struct MockConfigurationURLProvider: ConfigurationURLProviding { return URL(string: "g")! } } - + } diff --git a/Tests/ConfigurationTests/Mocks/MockStore.swift b/Tests/ConfigurationTests/Mocks/MockStore.swift index ef63bacc8..da1f9b4cf 100644 --- a/Tests/ConfigurationTests/Mocks/MockStore.swift +++ b/Tests/ConfigurationTests/Mocks/MockStore.swift @@ -1,6 +1,5 @@ // // MockStore.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -21,34 +20,34 @@ import Foundation @testable import Configuration final class MockStore: ConfigurationStoring { - + var configToEmbeddedEtag = [Configuration: String?]() var configToStoredEtagAndData = [Configuration: (etag: String?, data: Data?)]() var defaultSaveData: ((_ data: Data, _ configuration: Configuration) throws -> Void)? var defaultSaveEtag: ((_ etag: String, _ configuration: Configuration) throws -> Void)? - + init() { defaultSaveData = { data, configuration in let (currentEtag, _) = self.configToStoredEtagAndData[configuration] ?? (nil, nil) self.configToStoredEtagAndData[configuration] = (currentEtag, data) } - + defaultSaveEtag = { etag, configuration in let (_, currentData) = self.configToStoredEtagAndData[configuration] ?? (nil, nil) self.configToStoredEtagAndData[configuration] = (etag, currentData) } } - + func loadData(for configuration: Configuration) -> Data? { configToStoredEtagAndData[configuration]?.data } func loadEtag(for configuration: Configuration) -> String? { configToStoredEtagAndData[configuration]?.etag } func loadEmbeddedEtag(for configuration: Configuration) -> String? { configToEmbeddedEtag[configuration] ?? nil } - + func saveData(_ data: Data, for configuration: Configuration) throws { try defaultSaveData?(data, configuration) } - + func saveEtag(_ etag: String, for configuration: Configuration) throws { try defaultSaveEtag?(etag, configuration) } - + } diff --git a/Tests/ConfigurationTests/Mocks/MockValidator.swift b/Tests/ConfigurationTests/Mocks/MockValidator.swift index 064ebd2af..400215061 100644 --- a/Tests/ConfigurationTests/Mocks/MockValidator.swift +++ b/Tests/ConfigurationTests/Mocks/MockValidator.swift @@ -1,6 +1,5 @@ // // MockValidator.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -29,5 +28,5 @@ final class MockValidator: ConfigurationValidating { throw ConfigurationFetcher.Error.invalidPayload } } - + } diff --git a/Tests/DDGSyncTests/CrypterTests.swift b/Tests/DDGSyncTests/CrypterTests.swift index bfea21663..58619c91f 100644 --- a/Tests/DDGSyncTests/CrypterTests.swift +++ b/Tests/DDGSyncTests/CrypterTests.swift @@ -28,32 +28,32 @@ class CrypterTests: XCTestCase { let crypter = Crypter(secureStore: storage) let userId = "Simple User Name" - + let account = try crypter.createAccountCreationKeys(userId: userId, password: "password") let recoveryKey = SyncCode.RecoveryKey(userId: userId, primaryKey: account.primaryKey) let login = try crypter.extractLoginInfo(recoveryKey: recoveryKey) XCTAssertEqual(account.passwordHash, login.passwordHash) // The login flow calls the server to retreve the protected secret key, but we already have it so check we can decrypt it. - + let secretKey = try crypter.extractSecretKey(protectedSecretKey: account.protectedSecretKey, stretchedPrimaryKey: login.stretchedPrimaryKey) XCTAssertEqual(account.secretKey, secretKey) } - + func testWhenGivenRecoveryKeyThenCanExtractUserIdAndPrimaryKey() throws { let storage = SecureStorageStub() let crypter = Crypter(secureStore: storage) - + let userId = "Simple User Name" let primaryKey = Data([UInt8](repeating: 1, count: Int(DDGSYNCCRYPTO_PRIMARY_KEY_SIZE.rawValue))) - + let recoveryKey = SyncCode.RecoveryKey(userId: userId, primaryKey: primaryKey) let loginInfo = try crypter.extractLoginInfo(recoveryKey: recoveryKey) - + XCTAssertEqual(loginInfo.userId, userId) XCTAssertEqual(loginInfo.primaryKey, primaryKey) } - + func testWhenDecryptingNoneBase64ThenErrorIsThrown() throws { let storage = SecureStorageStub() let primaryKey = Data([UInt8]((0 ..< DDGSYNCCRYPTO_PRIMARY_KEY_SIZE.rawValue).map { _ in UInt8.random(in: 0 ..< UInt8.max )})) diff --git a/Tests/DDGSyncTests/DDGSyncLifecycleTests.swift b/Tests/DDGSyncTests/DDGSyncLifecycleTests.swift index 88690cb25..55066e886 100644 --- a/Tests/DDGSyncTests/DDGSyncLifecycleTests.swift +++ b/Tests/DDGSyncTests/DDGSyncLifecycleTests.swift @@ -1,6 +1,5 @@ // // DDGSyncLifecycleTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/DDGSyncTests/DDGSyncTests.swift b/Tests/DDGSyncTests/DDGSyncTests.swift index 0e17aa2af..3044d7f79 100644 --- a/Tests/DDGSyncTests/DDGSyncTests.swift +++ b/Tests/DDGSyncTests/DDGSyncTests.swift @@ -1,6 +1,5 @@ // // DDGSyncTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/DDGSyncTests/Mocks/Mocks.swift b/Tests/DDGSyncTests/Mocks/Mocks.swift index c2c3e62b5..d0f8d290e 100644 --- a/Tests/DDGSyncTests/Mocks/Mocks.swift +++ b/Tests/DDGSyncTests/Mocks/Mocks.swift @@ -1,6 +1,5 @@ // // Mocks.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/DDGSyncTests/SecureStorageStub.swift b/Tests/DDGSyncTests/SecureStorageStub.swift index 12afb4f9e..52da9b605 100644 --- a/Tests/DDGSyncTests/SecureStorageStub.swift +++ b/Tests/DDGSyncTests/SecureStorageStub.swift @@ -20,12 +20,12 @@ import Foundation @testable import DDGSync class SecureStorageStub: SecureStoring { - + var theAccount: SyncAccount? var mockReadError: SyncError? var mockWriteError: SyncError? - + func persistAccount(_ account: SyncAccount) throws { if let mockWriteError { throw mockWriteError @@ -44,5 +44,5 @@ class SecureStorageStub: SecureStoring { func removeAccount() throws { theAccount = nil } - + } diff --git a/Tests/DDGSyncTests/SyncOperationTests.swift b/Tests/DDGSyncTests/SyncOperationTests.swift index 37a94ac4e..724435157 100644 --- a/Tests/DDGSyncTests/SyncOperationTests.swift +++ b/Tests/DDGSyncTests/SyncOperationTests.swift @@ -1,6 +1,5 @@ // // SyncOperationTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 3c94d4e24..49350f0e6 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,3 +1,21 @@ +// +// LinuxMain.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + import XCTest import BrowserServicesKitTests diff --git a/Tests/NavigationTests/ClosureNavigationResponderTests.swift b/Tests/NavigationTests/ClosureNavigationResponderTests.swift index f33ac280e..50ae712a4 100644 --- a/Tests/NavigationTests/ClosureNavigationResponderTests.swift +++ b/Tests/NavigationTests/ClosureNavigationResponderTests.swift @@ -1,5 +1,5 @@ // -// NavigationValuesTests.swift +// ClosureNavigationResponderTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/NavigationTests/DistributedNavigationDelegateTests.swift b/Tests/NavigationTests/DistributedNavigationDelegateTests.swift index 3375ff14e..449d1f6b3 100644 --- a/Tests/NavigationTests/DistributedNavigationDelegateTests.swift +++ b/Tests/NavigationTests/DistributedNavigationDelegateTests.swift @@ -24,8 +24,6 @@ import XCTest @testable import Navigation // swiftlint:disable unused_closure_parameter -// swiftlint:disable trailing_comma -// swiftlint:disable opening_brace @available(macOS 12.0, iOS 15.0, *) class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase { @@ -51,18 +49,18 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase XCTAssertEqual(nav.state, .finished) eDidFinish.fulfill() } - + server.middleware = [{ [data] request in return .ok(.data(data.html)) }] - + // regular navigation from an empty state try server.start(8084) withWebView { webView in _=webView.load(req(urls.local)) } waitForExpectations(timeout: 5) - + XCTAssertFalse(navAct(1).navigationAction.isTargetingNewWindow) assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local), .other, src: main()), @@ -73,7 +71,7 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase .didFinish(Nav(action: navAct(1), .finished, resp: resp(0), .committed)) ]) } - + func testWhenResponderCancelsNavigationAction_followingRespondersNotCalled() { navigationDelegate.setResponders( .strong(NavigationResponderMock(defaultHandler: { _ in })), @@ -92,9 +90,9 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase withWebView { webView in _=webView.load(req(urls.local1)) } - + waitForExpectations(timeout: 5) - + assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local1), .other, src: main()), .didCancel(navAct(1)) @@ -104,14 +102,14 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase .didCancel(navAct(1)) ]) } - + func testWhenResponderCancelsNavigationResponse_followingRespondersNotCalled() throws { navigationDelegate.setResponders( .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })) ) - + responder(at: 0).onNavigationResponse = { resp in XCTAssertEqual(resp.isSuccessful, false) XCTAssertEqual(resp.httpResponse?.statusCode, 404) @@ -120,7 +118,7 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase } responder(at: 1).onNavigationResponse = { _ in .cancel } responder(at: 2).onNavigationResponse = { _ in XCTFail("Unexpected decidePolicyForNavigationAction:"); return .next } - + let eDidFail = expectation(description: "onDidFail") responder(at: 2).onDidFail = { @MainActor [urls] nav, error in XCTAssertEqual(error._nsError.domain, WKError.WebKitErrorDomain) @@ -129,13 +127,13 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase XCTAssertEqual(error.failingUrl?.matches(urls.local1), true) eDidFail.fulfill() } - + try server.start(8084) withWebView { webView in _=webView.load(req(urls.local1)) } waitForExpectations(timeout: 5) - + assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local1), .other, src: main()), .willStart(Nav(action: navAct(1), .approved, isCurrent: false)), @@ -151,18 +149,18 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase .didFail(Nav(action: navAct(1), .failed(WKError(.frameLoadInterruptedByPolicyChange)), resp: resp(0)), WKError.Code.frameLoadInterruptedByPolicyChange.rawValue) ]) } - + func testWhenNavigationFails_didFailIsCalled() { navigationDelegate.setResponders(.strong(NavigationResponderMock(defaultHandler: { _ in }))) let eDidFail = expectation(description: "onDidFail") responder(at: 0).onDidFail = { _, _ in eDidFail.fulfill() } - + // not calling server.start withWebView { webView in _=webView.load(req(urls.local)) } waitForExpectations(timeout: 5) - + assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local), .other, src: main()), .willStart(Nav(action: navAct(1), .approved, isCurrent: false)), @@ -170,14 +168,14 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase .didFail( Nav(action: navAct(1), .failed(WKError(NSURLErrorCannotConnectToHost))), NSURLErrorCannotConnectToHost) ]) } - + func testWhenNavigationActionIsAllowed_followingRespondersNotCalled() throws { navigationDelegate.setResponders( .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })) ) - + // Regular navigation without redirects // 1st: .next let eOnNavigationAction1 = expectation(description: "onNavigationAction 1") @@ -187,20 +185,20 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase responder(at: 1).onNavigationAction = { _, _ in eOnNavigationAction2.fulfill(); return .allow } // 3rd: not called responder(at: 2).onNavigationAction = { _, _ in XCTFail("Unexpected navAction"); return .cancel } - + let eDidFinish = expectation(description: "onDidFinish") responder(at: 2).onDidFinish = { _ in eDidFinish.fulfill() } - + server.middleware = [{ [data] request in return .ok(.data(data.html)) }] - + try server.start(8084) withWebView { webView in _=webView.load(req(urls.local)) } waitForExpectations(timeout: 5) - + assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local), .other, src: main()), .willStart(Nav(action: navAct(1), .approved, isCurrent: false)), @@ -218,30 +216,30 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase .didFinish(Nav(action: navAct(1), .finished, resp: resp(0), .committed)) ]) } - + func testWhenNavigationResponseAllowed_followingRespondersNotCalled() throws { navigationDelegate.setResponders( .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })), .strong(NavigationResponderMock(defaultHandler: { _ in })) ) - + responder(at: 1).onNavigationResponse = { _ in return .allow } responder(at: 2).onNavigationResponse = { _ in XCTFail("Unexpected decidePolicyForNavigationAction:"); return .next } - + let eDidFinish = expectation(description: "onDidFinish") responder(at: 2).onDidFinish = { _ in eDidFinish.fulfill() } - + server.middleware = [{ [data] request in return .ok(.data(data.html)) }] - + try server.start(8084) withWebView { webView in _=webView.load(req(urls.local)) } waitForExpectations(timeout: 5) - + assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(req(urls.local), .other, src: main()), .willStart(Nav(action: navAct(1), .approved, isCurrent: false)), @@ -1663,5 +1661,4 @@ class DistributedNavigationDelegateTests: DistributedNavigationDelegateTestsBase } // swiftlint:enable unused_closure_parameter -// swiftlint:enable trailing_comma // swiftlint:enable opening_brace diff --git a/Tests/NavigationTests/Helpers/DistributedNavigationDelegateTestsHelpers.swift b/Tests/NavigationTests/Helpers/DistributedNavigationDelegateTestsHelpers.swift index d1142d729..48d7a15d2 100644 --- a/Tests/NavigationTests/Helpers/DistributedNavigationDelegateTestsHelpers.swift +++ b/Tests/NavigationTests/Helpers/DistributedNavigationDelegateTestsHelpers.swift @@ -69,7 +69,7 @@ class DistributedNavigationDelegateTestsBase: XCTestCase { self.usedDelegates.append(navigationDelegateProxy) navigationDelegateProxy = DistributedNavigationDelegateTests.makeNavigationDelegateProxy() } - + } @available(macOS 12.0, iOS 15.0, *) diff --git a/Tests/NavigationTests/Helpers/NavigationResponderMock.swift b/Tests/NavigationTests/Helpers/NavigationResponderMock.swift index db7650594..c4945e886 100644 --- a/Tests/NavigationTests/Helpers/NavigationResponderMock.swift +++ b/Tests/NavigationTests/Helpers/NavigationResponderMock.swift @@ -181,7 +181,7 @@ class NavigationResponderMock: NavigationResponder { func reset() { clear() - + onNavigationAction = nil onWillStart = nil onDidStart = nil @@ -199,7 +199,7 @@ class NavigationResponderMock: NavigationResponder { onNavResponseWillBecomeDownload = nil onNavResponseBecameDownload = nil - defaultHandler = { + defaultHandler = { fatalError("event received after test completed: \($0)") } } diff --git a/Tests/NavigationTests/Helpers/NavigationTestHelpers.swift b/Tests/NavigationTests/Helpers/NavigationTestHelpers.swift index ce28ab16b..034996702 100644 --- a/Tests/NavigationTests/Helpers/NavigationTestHelpers.swift +++ b/Tests/NavigationTests/Helpers/NavigationTestHelpers.swift @@ -36,7 +36,7 @@ extension TestsNavigationEvent { static func navigationAction(_ request: URLRequest, _ navigationType: NavigationType, from currentHistoryItemIdentity: HistoryItemIdentity? = nil, redirects: [NavAction]? = nil, _ isUserInitiated: NavigationAction.UserInitiated? = nil, src: FrameInfo, _ shouldDownload: NavigationAction.ShouldDownload? = nil, line: UInt = #line) -> TestsNavigationEvent { .navigationAction(.init(request, navigationType, from: currentHistoryItemIdentity, redirects: redirects, isUserInitiated, src: src, targ: src, shouldDownload), line: line) } - + static func response(_ nav: Nav, line: UInt = #line) -> TestsNavigationEvent { .navigationResponse(.navigation(nav), line: line) } diff --git a/Tests/NavigationTests/Helpers/TestNavigationSchemeHandler.swift b/Tests/NavigationTests/Helpers/TestNavigationSchemeHandler.swift index cfab8c7de..9293af19b 100644 --- a/Tests/NavigationTests/Helpers/TestNavigationSchemeHandler.swift +++ b/Tests/NavigationTests/Helpers/TestNavigationSchemeHandler.swift @@ -1,6 +1,5 @@ // // TestNavigationSchemeHandler.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/NavigationTests/NavigationRedirectsTests.swift b/Tests/NavigationTests/NavigationRedirectsTests.swift index a76570812..0e6c173b0 100644 --- a/Tests/NavigationTests/NavigationRedirectsTests.swift +++ b/Tests/NavigationTests/NavigationRedirectsTests.swift @@ -1034,7 +1034,7 @@ class NavigationRedirectsTests: DistributedNavigationDelegateTestsBase { assertHistory(ofResponderAt: 0, equalsTo: [ .navigationAction(NavAction(req(urls.local), .other, from: history[2], src: main(urls.local2))), .didCancel(navAct(3), expected: 2), - + // .navigationAction(NavAction(req(urls.local4, defaultHeaders.allowingExtraKeys), .redirect(.developer), from: history[2], src: main(urls.local2))), // .willStart(Nav(action: navAct(4), redirects: [navAct(3)], .approved, isCurrent: false)), // .didFail(Nav(action: NavAction(req(urls.local4, defaultHeaders.allowingExtraKeys), .redirect(.developer), from: history[2], src: main(urls.local2)), redirects: [navAct(3)], .failed(WKError(NSURLErrorCancelled)), isCurrent: false), NSURLErrorCancelled), diff --git a/Tests/NavigationTests/NavigationValuesTests.swift b/Tests/NavigationTests/NavigationValuesTests.swift index bd11a6df4..b973f27bf 100644 --- a/Tests/NavigationTests/NavigationValuesTests.swift +++ b/Tests/NavigationTests/NavigationValuesTests.swift @@ -113,7 +113,7 @@ class NavigationValuesTests: DistributedNavigationDelegateTestsBase { @MainActor func testNavigationTypes() { navigationDelegate.setResponders(.strong(NavigationResponderMock(defaultHandler: { _ in }))) - + let webView = withWebView { $0 } var navAction = WKNavigationActionMock(sourceFrame: .mock(for: webView, isMain: false), targetFrame: nil, navigationType: .formSubmitted, request: req(urls.local)).navigationAction var e = expectation(description: "decisionHandler 1 called") diff --git a/Tests/NetworkProtectionTests/EndpointTests.swift b/Tests/NetworkProtectionTests/EndpointTests.swift index b732948c8..5d22a405c 100644 --- a/Tests/NetworkProtectionTests/EndpointTests.swift +++ b/Tests/NetworkProtectionTests/EndpointTests.swift @@ -1,6 +1,5 @@ // -// File.swift -// DuckDuckGo +// EndpointTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift b/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift index 4971045f7..a01043439 100644 --- a/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift +++ b/Tests/NetworkProtectionTests/Mocks/NetworkProtectionServerMocks.swift @@ -32,7 +32,7 @@ extension NetworkProtectionServerInfo { static let mock = NetworkProtectionServerInfo(name: "Mock Server", publicKey: "ovn9RpzUuvQ4XLQt6B3RKuEXGIxa5QpTnehjduZlcSE=", hostNames: ["duckduckgo.com"], - ips: ["192.168.1.1"], + ips: ["192.168.1.1"], internalIP: "10.11.12.1", port: 443, attributes: .init(city: "City", country: "Country", state: "State", timezoneOffset: 0)) diff --git a/Tests/NetworkProtectionTests/NWConnectionExtensionTests.swift b/Tests/NetworkProtectionTests/NWConnectionExtensionTests.swift index f2d6f1698..c2113af73 100644 --- a/Tests/NetworkProtectionTests/NWConnectionExtensionTests.swift +++ b/Tests/NetworkProtectionTests/NWConnectionExtensionTests.swift @@ -1,6 +1,5 @@ // // NWConnectionExtensionTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/NetworkProtectionTests/Notifications/DistributedNotificationObjectCodersTests.swift b/Tests/NetworkProtectionTests/Notifications/DistributedNotificationObjectCodersTests.swift index b271123da..90533910b 100644 --- a/Tests/NetworkProtectionTests/Notifications/DistributedNotificationObjectCodersTests.swift +++ b/Tests/NetworkProtectionTests/Notifications/DistributedNotificationObjectCodersTests.swift @@ -1,5 +1,5 @@ // -// DistributedNotificationObjectCoders.swift +// DistributedNotificationObjectCodersTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/NetworkProtectionTests/Repositories/NetworkProtectionLocationListCompositeRepositoryTests.swift b/Tests/NetworkProtectionTests/Repositories/NetworkProtectionLocationListCompositeRepositoryTests.swift index 71a392d26..87960edd7 100644 --- a/Tests/NetworkProtectionTests/Repositories/NetworkProtectionLocationListCompositeRepositoryTests.swift +++ b/Tests/NetworkProtectionTests/Repositories/NetworkProtectionLocationListCompositeRepositoryTests.swift @@ -1,6 +1,5 @@ // // NetworkProtectionLocationListCompositeRepositoryTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -41,7 +40,7 @@ class NetworkProtectionLocationListCompositeRepositoryTests: XCTestCase { }) } - @MainActor + @MainActor override func tearDown() { NetworkProtectionLocationListCompositeRepository.clearCache() client = nil @@ -143,12 +142,12 @@ class NetworkProtectionLocationListCompositeRepositoryTests: XCTestCase { private extension NetworkProtectionLocation { static func testData(country: String = "", cities: [City] = []) -> NetworkProtectionLocation { - return Self.init(country: country, cities: cities) + return Self(country: country, cities: cities) } } private extension NetworkProtectionLocation.City { static func testData(name: String = "") -> NetworkProtectionLocation.City { - Self.init(name: name) + Self(name: name) } } diff --git a/Tests/NetworkProtectionTests/StartupOptionTests.swift b/Tests/NetworkProtectionTests/StartupOptionTests.swift index 1ade73fb2..e516de5b8 100644 --- a/Tests/NetworkProtectionTests/StartupOptionTests.swift +++ b/Tests/NetworkProtectionTests/StartupOptionTests.swift @@ -1,5 +1,5 @@ // -// StartupOptionsTests.swift +// StartupOptionTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/NetworkProtectionTests/XCTestCase+TemporaryFileURL.swift b/Tests/NetworkProtectionTests/XCTestCase+TemporaryFileURL.swift index c343b11af..1fa81c6bc 100644 --- a/Tests/NetworkProtectionTests/XCTestCase+TemporaryFileURL.swift +++ b/Tests/NetworkProtectionTests/XCTestCase+TemporaryFileURL.swift @@ -1,5 +1,5 @@ // -// TestUtilities.swift +// XCTestCase+TemporaryFileURL.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/NetworkingTests/APIRequestTests.swift b/Tests/NetworkingTests/APIRequestTests.swift index e9c0ef88f..21de657d5 100644 --- a/Tests/NetworkingTests/APIRequestTests.swift +++ b/Tests/NetworkingTests/APIRequestTests.swift @@ -1,6 +1,5 @@ // // APIRequestTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -22,21 +21,21 @@ import XCTest @testable import TestUtils final class APIRequestTests: XCTestCase { - + enum MockError: Error { case someError } - + override class func setUp() { APIRequest.Headers.setUserAgent("") } - + private var mockURLSession: URLSession { let testConfiguration = URLSessionConfiguration.default testConfiguration.protocolClasses = [MockURLProtocol.self] return URLSession(configuration: testConfiguration) } - + func testWhenUrlSessionThrowsErrorThenWrappedUrlSessionErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in throw MockError.someError } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -52,7 +51,7 @@ final class APIRequestTests: XCTestCase { } } } - + func testWhenThereIsNoDataInResponseThenEmptyDataIsReturned() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.ok, nil) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -64,7 +63,7 @@ final class APIRequestTests: XCTestCase { XCTFail("Unexpected error thrown: \(error).") } } - + func testWhenThereIsNoDataInResponseButItIsRequiredThenEmptyDataErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.ok, nil) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -79,9 +78,9 @@ final class APIRequestTests: XCTestCase { } } } - + let privacyConfigurationData = Data("Privacy Config".utf8) - + func testWhenEtagIsMissingInResponseThenResponseIsReturned() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.okNoEtag, self.privacyConfigurationData) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -94,7 +93,7 @@ final class APIRequestTests: XCTestCase { XCTFail("Unexpected error thrown: \(error).") } } - + func testWhenEtagIsMissingInResponseButItIsRequiredThenMissingEtagErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.okNoEtag, self.privacyConfigurationData) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -109,7 +108,7 @@ final class APIRequestTests: XCTestCase { } } } - + func testWhenInternalServerErrorThenInvalidStatusCodeErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.internalServerError, nil) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -124,7 +123,7 @@ final class APIRequestTests: XCTestCase { } } } - + func testWhenNotModifiedResponseThenInvalidResponseErrorIsThrown() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.notModified, nil) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -139,7 +138,7 @@ final class APIRequestTests: XCTestCase { } } } - + func testWhenNotModifiedResponseButItIsAllowedThenResponseWithNilDataIsReturned() async { MockURLProtocol.requestHandler = { _ in (HTTPURLResponse.notModified, nil) } let configuration = APIRequest.Configuration(url: HTTPURLResponse.testUrl) @@ -152,5 +151,5 @@ final class APIRequestTests: XCTestCase { XCTFail("Unexpected error thrown: \(error).") } } - + } diff --git a/Tests/PersistenceTests/CoreDataErrorsParserTests.swift b/Tests/PersistenceTests/CoreDataErrorsParserTests.swift index 01b42245e..ce06dd146 100644 --- a/Tests/PersistenceTests/CoreDataErrorsParserTests.swift +++ b/Tests/PersistenceTests/CoreDataErrorsParserTests.swift @@ -1,6 +1,6 @@ // // CoreDataErrorsParserTests.swift -// +// // Copyright © 2022 DuckDuckGo. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,29 +22,29 @@ import Persistence @objc(TestEntity) class TestEntity: NSManagedObject { - + static let name = "TestEntity" - + public class func entity(in context: NSManagedObjectContext) -> NSEntityDescription { return NSEntityDescription.entity(forEntityName: "TestEntity", in: context)! } - + @NSManaged public var attribute: String? @NSManaged public var relationTo: TestEntity? @NSManaged public var relationFrom: TestEntity? } class CoreDataErrorsParserTests: XCTestCase { - + func tempDBDir() -> URL { FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) } - + var db: CoreDataDatabase! - + func testModel() -> NSManagedObjectModel { let model = NSManagedObjectModel() - + let entity = NSEntityDescription() entity.name = "TestEntity" entity.managedObjectClassName = TestEntity.name @@ -56,10 +56,10 @@ class CoreDataErrorsParserTests: XCTestCase { attribute.attributeType = .stringAttributeType attribute.isOptional = false properties.append(attribute) - + let relationTo = NSRelationshipDescription() let relationFrom = NSRelationshipDescription() - + relationTo.name = "relationTo" relationFrom.isOptional = false relationTo.destinationEntity = entity @@ -67,7 +67,7 @@ class CoreDataErrorsParserTests: XCTestCase { relationTo.maxCount = 1 relationTo.deleteRule = .nullifyDeleteRule relationTo.inverseRelationship = relationFrom - + relationFrom.name = "relationFrom" relationFrom.isOptional = false relationFrom.destinationEntity = entity @@ -75,7 +75,7 @@ class CoreDataErrorsParserTests: XCTestCase { relationFrom.maxCount = 1 relationFrom.deleteRule = .nullifyDeleteRule relationFrom.inverseRelationship = relationTo - + properties.append(relationTo) properties.append(relationFrom) @@ -83,83 +83,83 @@ class CoreDataErrorsParserTests: XCTestCase { model.entities = [entity] return model } - + override func setUp() { super.setUp() - + db = CoreDataDatabase(name: "Test", containerLocation: tempDBDir(), model: testModel()) db.loadStore() } - + override func tearDown() async throws { - + try db.tearDown(deleteStores: true) try await super.tearDown() } - + func testWhenObjectsAreValidThenTheyAreSaved() throws { - + let context = db.makeContext(concurrencyType: .mainQueueConcurrencyType) - + let e1 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) let e2 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) - + e1.attribute = "e1" e2.attribute = "e2" e1.relationTo = e2 e2.relationTo = e1 - + try context.save() } - + func testWhenOneAttributesAreMissingThenErrorIsIdentified() { - + let context = db.makeContext(concurrencyType: .mainQueueConcurrencyType) - + let e1 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) let e2 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) - + e2.attribute = "e2" e1.relationTo = e2 e2.relationTo = e1 - + do { try context.save() XCTFail("This must fail") } catch { let error = error as NSError - + let info = CoreDataErrorsParser.parse(error: error) XCTAssertEqual(info.first?.entity, TestEntity.name) XCTAssertEqual(info.first?.property, "attribute") } } - + func testWhenMoreAttributesAreMissingThenErrorIsIdentified() { - + let context = db.makeContext(concurrencyType: .mainQueueConcurrencyType) - + _ = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) _ = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) - + do { try context.save() XCTFail("This must fail") } catch { let error = error as NSError - + let info = CoreDataErrorsParser.parse(error: error) XCTAssertEqual(info.count, 4) - + let uniqueSet = Set(info.map { $0.property }) XCTAssertEqual(uniqueSet, ["attribute", "relationFrom"]) } } - + func testWhenStoreIsReadOnlyThenErrorIsIdentified() { - + guard let url = db.coordinator.persistentStores.first?.url else { XCTFail("Failed to get persistent store URL") return @@ -172,58 +172,58 @@ class CoreDataErrorsParserTests: XCTestCase { XCTAssertNil(error) } let context = ro.makeContext(concurrencyType: .mainQueueConcurrencyType) - + let e1 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) let e2 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) - + e1.attribute = "e1" e2.attribute = "e2" e1.relationTo = e2 e2.relationTo = e1 - + do { try context.save() XCTFail("This must fail") } catch { let error = error as NSError - + let info = CoreDataErrorsParser.parse(error: error) XCTAssertEqual(info.first?.domain, NSCocoaErrorDomain) XCTAssertEqual(info.first?.code, 513) } } - + func testWhenThereIsMergeConflictThenErrorIsIdentified() throws { - + let context = db.makeContext(concurrencyType: .mainQueueConcurrencyType) - + let e1 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) let e2 = TestEntity(entity: TestEntity.entity(in: context), insertInto: context) - + e1.attribute = "e1" e2.attribute = "e2" e1.relationTo = e2 e2.relationTo = e1 - + try context.save() - + let anotherContext = db.makeContext(concurrencyType: .mainQueueConcurrencyType) guard let anotherE1 = try anotherContext.existingObject(with: e1.objectID) as? TestEntity else { XCTFail("Expected object") return } - + e1.attribute = "e1updated" try context.save() - + anotherE1.attribute = "e1ConflictingUpdate" - + do { try anotherContext.save() XCTFail("This must fail") } catch { let error = error as NSError - + let info = CoreDataErrorsParser.parse(error: error) XCTAssertEqual(info.first?.domain, NSCocoaErrorDomain) XCTAssertEqual(info.first?.entity, TestEntity.name) diff --git a/Tests/SecureStorageTests/GRDBSecureStorageDatabaseProviderTests.swift b/Tests/SecureStorageTests/GRDBSecureStorageDatabaseProviderTests.swift index 23b6c7afc..709607220 100644 --- a/Tests/SecureStorageTests/GRDBSecureStorageDatabaseProviderTests.swift +++ b/Tests/SecureStorageTests/GRDBSecureStorageDatabaseProviderTests.swift @@ -1,6 +1,5 @@ // // GRDBSecureStorageDatabaseProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SecureStorageTests/SecureStorageCryptoProviderTests.swift b/Tests/SecureStorageTests/SecureStorageCryptoProviderTests.swift index d04947591..ff1135fbe 100644 --- a/Tests/SecureStorageTests/SecureStorageCryptoProviderTests.swift +++ b/Tests/SecureStorageTests/SecureStorageCryptoProviderTests.swift @@ -1,6 +1,5 @@ // // SecureStorageCryptoProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SecureStorageTests/SecureVaultFactoryTests.swift b/Tests/SecureStorageTests/SecureVaultFactoryTests.swift index 16200eb45..03c00aa5f 100644 --- a/Tests/SecureStorageTests/SecureVaultFactoryTests.swift +++ b/Tests/SecureStorageTests/SecureVaultFactoryTests.swift @@ -1,5 +1,5 @@ // -// MockVault.swift +// SecureVaultFactoryTests.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SecureStorageTests/TestMocks.swift b/Tests/SecureStorageTests/TestMocks.swift index 3869b3c9c..88f187fc6 100644 --- a/Tests/SecureStorageTests/TestMocks.swift +++ b/Tests/SecureStorageTests/TestMocks.swift @@ -1,6 +1,5 @@ // -// MockProviders.swift -// DuckDuckGo +// TestMocks.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksInitialSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksInitialSyncResponseHandlerTests.swift index bf33e3fc6..a07339303 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksInitialSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksInitialSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // BookmarksInitialSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksProviderTests.swift b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksProviderTests.swift index 82c80bd3a..ceb3f2f0c 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksProviderTests.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksProviderTests.swift @@ -1,6 +1,5 @@ // // BookmarksProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksRegularSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksRegularSyncResponseHandlerTests.swift index 752af2fb4..9cf55fc04 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/BookmarksRegularSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/BookmarksRegularSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // BookmarksRegularSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/SyncableBookmarkAdapterTests.swift b/Tests/SyncDataProvidersTests/Bookmarks/SyncableBookmarkAdapterTests.swift index 741b808ab..2ad67bd52 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/SyncableBookmarkAdapterTests.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/SyncableBookmarkAdapterTests.swift @@ -1,6 +1,5 @@ // // SyncableBookmarkAdapterTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/helpers/BookmarksProviderTestsBase.swift b/Tests/SyncDataProvidersTests/Bookmarks/helpers/BookmarksProviderTestsBase.swift index 9b2c5ccf5..08f535486 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/helpers/BookmarksProviderTestsBase.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/helpers/BookmarksProviderTestsBase.swift @@ -1,6 +1,5 @@ // // BookmarksProviderTestsBase.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Bookmarks/helpers/SyncableBookmarksExtension.swift b/Tests/SyncDataProvidersTests/Bookmarks/helpers/SyncableBookmarksExtension.swift index bf30700f4..172be7401 100644 --- a/Tests/SyncDataProvidersTests/Bookmarks/helpers/SyncableBookmarksExtension.swift +++ b/Tests/SyncDataProvidersTests/Bookmarks/helpers/SyncableBookmarksExtension.swift @@ -1,6 +1,5 @@ // // SyncableBookmarksExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/CredentialsInitialSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Credentials/CredentialsInitialSyncResponseHandlerTests.swift index 09b463653..5d64af593 100644 --- a/Tests/SyncDataProvidersTests/Credentials/CredentialsInitialSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Credentials/CredentialsInitialSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // CredentialsInitialSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/CredentialsProviderTests.swift b/Tests/SyncDataProvidersTests/Credentials/CredentialsProviderTests.swift index 8a84055f2..1b98b7bd3 100644 --- a/Tests/SyncDataProvidersTests/Credentials/CredentialsProviderTests.swift +++ b/Tests/SyncDataProvidersTests/Credentials/CredentialsProviderTests.swift @@ -1,6 +1,5 @@ // // CredentialsProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/CredentialsRegularSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Credentials/CredentialsRegularSyncResponseHandlerTests.swift index 0f5271a31..a5a41367a 100644 --- a/Tests/SyncDataProvidersTests/Credentials/CredentialsRegularSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Credentials/CredentialsRegularSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // CredentialsRegularSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/helpers/CredentialsProviderTestsBase.swift b/Tests/SyncDataProvidersTests/Credentials/helpers/CredentialsProviderTestsBase.swift index 93ec6dec3..d58676d1a 100644 --- a/Tests/SyncDataProvidersTests/Credentials/helpers/CredentialsProviderTestsBase.swift +++ b/Tests/SyncDataProvidersTests/Credentials/helpers/CredentialsProviderTestsBase.swift @@ -1,6 +1,5 @@ // // CredentialsProviderTestsBase.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/helpers/SyncableCredentialsExtension.swift b/Tests/SyncDataProvidersTests/Credentials/helpers/SyncableCredentialsExtension.swift index ab15e5540..a18f95df5 100644 --- a/Tests/SyncDataProvidersTests/Credentials/helpers/SyncableCredentialsExtension.swift +++ b/Tests/SyncDataProvidersTests/Credentials/helpers/SyncableCredentialsExtension.swift @@ -1,6 +1,5 @@ // // SyncableCredentialsExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Credentials/helpers/TestAutofillSecureVaultFactory.swift b/Tests/SyncDataProvidersTests/Credentials/helpers/TestAutofillSecureVaultFactory.swift index 9fd29ab75..0b33e3439 100644 --- a/Tests/SyncDataProvidersTests/Credentials/helpers/TestAutofillSecureVaultFactory.swift +++ b/Tests/SyncDataProvidersTests/Credentials/helpers/TestAutofillSecureVaultFactory.swift @@ -1,6 +1,5 @@ // -// TestSecureVaultFactory.swift -// DuckDuckGo +// TestAutofillSecureVaultFactory.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/CryptingMock.swift b/Tests/SyncDataProvidersTests/CryptingMock.swift index 32810c95e..a5ab06128 100644 --- a/Tests/SyncDataProvidersTests/CryptingMock.swift +++ b/Tests/SyncDataProvidersTests/CryptingMock.swift @@ -1,6 +1,5 @@ // // CryptingMock.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/SettingsInitialSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Settings/SettingsInitialSyncResponseHandlerTests.swift index 7ef3b9007..95546c68f 100644 --- a/Tests/SyncDataProvidersTests/Settings/SettingsInitialSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Settings/SettingsInitialSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // SettingsInitialSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/SettingsProviderTests.swift b/Tests/SyncDataProvidersTests/Settings/SettingsProviderTests.swift index 902726e38..f35c1f720 100644 --- a/Tests/SyncDataProvidersTests/Settings/SettingsProviderTests.swift +++ b/Tests/SyncDataProvidersTests/Settings/SettingsProviderTests.swift @@ -1,6 +1,5 @@ // // SettingsProviderTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/SettingsRegularSyncResponseHandlerTests.swift b/Tests/SyncDataProvidersTests/Settings/SettingsRegularSyncResponseHandlerTests.swift index 8d6219617..cb7619a65 100644 --- a/Tests/SyncDataProvidersTests/Settings/SettingsRegularSyncResponseHandlerTests.swift +++ b/Tests/SyncDataProvidersTests/Settings/SettingsRegularSyncResponseHandlerTests.swift @@ -1,6 +1,5 @@ // // SettingsRegularSyncResponseHandlerTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/helpers/SettingsProviderTestsBase.swift b/Tests/SyncDataProvidersTests/Settings/helpers/SettingsProviderTestsBase.swift index 081e6e494..ef15a1ef2 100644 --- a/Tests/SyncDataProvidersTests/Settings/helpers/SettingsProviderTestsBase.swift +++ b/Tests/SyncDataProvidersTests/Settings/helpers/SettingsProviderTestsBase.swift @@ -1,6 +1,5 @@ // // SettingsProviderTestsBase.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/helpers/SyncableSettingsExtension.swift b/Tests/SyncDataProvidersTests/Settings/helpers/SyncableSettingsExtension.swift index 57efdae4b..09b49a851 100644 --- a/Tests/SyncDataProvidersTests/Settings/helpers/SyncableSettingsExtension.swift +++ b/Tests/SyncDataProvidersTests/Settings/helpers/SyncableSettingsExtension.swift @@ -1,6 +1,5 @@ // // SyncableSettingsExtension.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/SyncDataProvidersTests/Settings/helpers/TestSettingSyncHandler.swift b/Tests/SyncDataProvidersTests/Settings/helpers/TestSettingSyncHandler.swift index bc3457fb5..8735bc12d 100644 --- a/Tests/SyncDataProvidersTests/Settings/helpers/TestSettingSyncHandler.swift +++ b/Tests/SyncDataProvidersTests/Settings/helpers/TestSettingSyncHandler.swift @@ -1,6 +1,5 @@ // -// TestSettingHandler.swift -// DuckDuckGo +// TestSettingSyncHandler.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Tests/UserScriptTests/StaticUserScriptTests.swift b/Tests/UserScriptTests/StaticUserScriptTests.swift index b065dd496..580280108 100644 --- a/Tests/UserScriptTests/StaticUserScriptTests.swift +++ b/Tests/UserScriptTests/StaticUserScriptTests.swift @@ -1,6 +1,5 @@ // // StaticUserScriptTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/UserScriptTests/UserScriptEncrypterTests.swift b/Tests/UserScriptTests/UserScriptEncrypterTests.swift index dc7058c72..7ea993faf 100644 --- a/Tests/UserScriptTests/UserScriptEncrypterTests.swift +++ b/Tests/UserScriptTests/UserScriptEncrypterTests.swift @@ -1,6 +1,5 @@ // // UserScriptEncrypterTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. // diff --git a/Tests/UserScriptTests/UserScriptMessagingTests.swift b/Tests/UserScriptTests/UserScriptMessagingTests.swift index 60ef4868b..3bed9c37b 100644 --- a/Tests/UserScriptTests/UserScriptMessagingTests.swift +++ b/Tests/UserScriptTests/UserScriptMessagingTests.swift @@ -1,6 +1,5 @@ // -// ContentScopeMessagingTests.swift -// DuckDuckGo +// UserScriptMessagingTests.swift // // Copyright © 2021 DuckDuckGo. All rights reserved. // @@ -60,7 +59,7 @@ class UserScriptMessagingTests: XCTestCase { } } catch {} - wait(for: [expectation], timeout: 2.0) + await fulfillment(of: [expectation], timeout: 2.0) } /// This test verifies that the replyHandler is called for notifications, @@ -90,7 +89,7 @@ class UserScriptMessagingTests: XCTestCase { expectation.fulfill() } catch {} - wait(for: [expectation], timeout: 2.0) + await fulfillment(of: [expectation], timeout: 2.0) } /// This test verifies that errors from handlers are reflected back to the JS side. @@ -128,7 +127,7 @@ class UserScriptMessagingTests: XCTestCase { } } catch {} - wait(for: [expectation], timeout: 2.0) + await fulfillment(of: [expectation], timeout: 2.0) } /// Ensure that an error is thrown if the feature was not registered @@ -188,8 +187,6 @@ class UserScriptMessagingTests: XCTestCase { } } -// swiftlint:disable large_tuple - /// A helper for registering a test delegate and creating a MockMsg based on the /// incoming dictionary (which represents a message coming from a webview) /// diff --git a/Tests/UserScriptTests/UserScriptTests.swift b/Tests/UserScriptTests/UserScriptTests.swift index 873509155..9751fac2b 100644 --- a/Tests/UserScriptTests/UserScriptTests.swift +++ b/Tests/UserScriptTests/UserScriptTests.swift @@ -1,6 +1,5 @@ // // UserScriptTests.swift -// DuckDuckGo // // Copyright © 2021 DuckDuckGo. All rights reserved. //