From ebd0e6bc34816e838da07a36f89ce1720a118fd4 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 14 Dec 2023 10:25:08 +0000 Subject: [PATCH 01/22] tap close enabled in privacy dashboard for macOS --- Package.resolved | 4 ++-- Package.swift | 2 +- Sources/PrivacyDashboard/PrivacyDashboardController.swift | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Package.resolved b/Package.resolved index 8482cdadb..32a2960f5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "38336a574e13090764ba09a6b877d15ee514e371", - "version" : "3.1.1" + "branch" : "12-14-feat_macos_allow_direct_navigation_to_breakage_form", + "revision" : "97f6acbf9318045fbfb9b0ff098e307450d7457b" } }, { diff --git a/Package.swift b/Package.swift index fa4283873..300e02ce2 100644 --- a/Package.swift +++ b/Package.swift @@ -37,7 +37,7 @@ let package = Package( .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.2"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), .package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"), - .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.1.1" ), + .package(url: "https://github.com/duckduckgo/privacy-dashboard", .branch("12-14-feat_macos_allow_direct_navigation_to_breakage_form")), //exact: "3.1.1" ), .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"), diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index 9c715d5ef..f5a1fd78b 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -28,10 +28,8 @@ public enum PrivacyDashboardOpenSettingsTarget: String { /// Navigation delegate for the pages provided by the PrivacyDashboardController public protocol PrivacyDashboardNavigationDelegate: AnyObject { -#if os(iOS) - func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) -#endif + func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) } @@ -280,9 +278,7 @@ extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate { } func userScriptDidRequestClosing(_ userScript: PrivacyDashboardUserScript) { -#if os(iOS) privacyDashboardNavigationDelegate?.privacyDashboardControllerDidTapClose(self) -#endif } func userScriptDidRequestShowReportBrokenSite(_ userScript: PrivacyDashboardUserScript) { From c1313329a38703076aaa1eb2e6f0557bf4a3dc3b Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 20 Dec 2023 15:07:29 +0000 Subject: [PATCH 02/22] privacy dashboard code cleanup and model shared --- .../{Model => Models}/AllowedPermission.swift | 0 .../{Model => Models}/CookieConsentInfo.swift | 0 .../PermissionAuthorizationState.swift | 0 .../{Model => Models}/ProtectionStatus.swift | 0 .../{Model => Models}/TrackerInfo.swift | 0 .../Models/WebsiteBreakage.swift | 105 ++++++++++++++++++ .../PrivacyDashboardUserScript.swift | 0 .../ServerTrustViewModel.swift | 0 Sources/PrivacyDashboard/URL+Trimming.swift | 40 +++++++ 9 files changed, 145 insertions(+) rename Sources/PrivacyDashboard/{Model => Models}/AllowedPermission.swift (100%) rename Sources/PrivacyDashboard/{Model => Models}/CookieConsentInfo.swift (100%) rename Sources/PrivacyDashboard/{Model => Models}/PermissionAuthorizationState.swift (100%) rename Sources/PrivacyDashboard/{Model => Models}/ProtectionStatus.swift (100%) rename Sources/PrivacyDashboard/{Model => Models}/TrackerInfo.swift (100%) create mode 100644 Sources/PrivacyDashboard/Models/WebsiteBreakage.swift rename Sources/PrivacyDashboard/{UserScript => }/PrivacyDashboardUserScript.swift (100%) rename Sources/PrivacyDashboard/{ViewModel => }/ServerTrustViewModel.swift (100%) create mode 100644 Sources/PrivacyDashboard/URL+Trimming.swift diff --git a/Sources/PrivacyDashboard/Model/AllowedPermission.swift b/Sources/PrivacyDashboard/Models/AllowedPermission.swift similarity index 100% rename from Sources/PrivacyDashboard/Model/AllowedPermission.swift rename to Sources/PrivacyDashboard/Models/AllowedPermission.swift diff --git a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift b/Sources/PrivacyDashboard/Models/CookieConsentInfo.swift similarity index 100% rename from Sources/PrivacyDashboard/Model/CookieConsentInfo.swift rename to Sources/PrivacyDashboard/Models/CookieConsentInfo.swift diff --git a/Sources/PrivacyDashboard/Model/PermissionAuthorizationState.swift b/Sources/PrivacyDashboard/Models/PermissionAuthorizationState.swift similarity index 100% rename from Sources/PrivacyDashboard/Model/PermissionAuthorizationState.swift rename to Sources/PrivacyDashboard/Models/PermissionAuthorizationState.swift diff --git a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift b/Sources/PrivacyDashboard/Models/ProtectionStatus.swift similarity index 100% rename from Sources/PrivacyDashboard/Model/ProtectionStatus.swift rename to Sources/PrivacyDashboard/Models/ProtectionStatus.swift diff --git a/Sources/PrivacyDashboard/Model/TrackerInfo.swift b/Sources/PrivacyDashboard/Models/TrackerInfo.swift similarity index 100% rename from Sources/PrivacyDashboard/Model/TrackerInfo.swift rename to Sources/PrivacyDashboard/Models/TrackerInfo.swift diff --git a/Sources/PrivacyDashboard/Models/WebsiteBreakage.swift b/Sources/PrivacyDashboard/Models/WebsiteBreakage.swift new file mode 100644 index 000000000..c4bfcc0b5 --- /dev/null +++ b/Sources/PrivacyDashboard/Models/WebsiteBreakage.swift @@ -0,0 +1,105 @@ +// +// WebsiteBreakage.swift +// +// Copyright © 2022 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 WebsiteBreakage { + + public enum Source: String { + case appMenu = "menu" + case dashboard + } + + let category: String + let description: String? + let siteUrlString: String + let osVersion: String + let upgradedHttps: Bool + let tdsETag: String? + let blockedTrackerDomains: [String] + let installedSurrogates: [String] + let isGPCEnabled: Bool + let ampURL: String + let urlParametersRemoved: Bool + let reportFlow: Source + let protectionsState: Bool + //iOS Only + let isDesktop: Bool + let atb: String + let model: String + + init( + category: String, + description: String?, + siteUrlString: String, + osVersion: String, + upgradedHttps: Bool, + tdsETag: String?, + blockedTrackerDomains: [String], + installedSurrogates: [String], + isGPCEnabled: Bool, + ampURL: String, + urlParametersRemoved: Bool, + protectionsState: Bool, + reportFlow: Source, + isDesktop: Bool, + atb: String, + model: String + ) { + self.category = category + self.description = description + self.siteUrlString = siteUrlString + self.osVersion = osVersion + self.upgradedHttps = upgradedHttps + self.tdsETag = tdsETag + self.blockedTrackerDomains = blockedTrackerDomains + self.installedSurrogates = installedSurrogates + self.isGPCEnabled = isGPCEnabled + self.ampURL = ampURL + self.protectionsState = protectionsState + self.urlParametersRemoved = urlParametersRemoved + self.reportFlow = reportFlow + //iOS Only + self.isDesktop = isDesktop + self.atb = atb + self.model = model + } + + var requestParameters: [String: String] { + [ + "category": category, + "description": description ?? "", + "siteUrl": siteUrlString, + "upgradedHttps": upgradedHttps ? "true" : "false", + "tds": tdsETag?.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) ?? "", + "blockedTrackers": blockedTrackerDomains.joined(separator: ","), + "surrogates": installedSurrogates.joined(separator: ","), + "gpc": isGPCEnabled ? "true" : "false", + "ampUrl": ampURL, + "urlParametersRemoved": urlParametersRemoved ? "true" : "false", + "os": osVersion, + "manufacturer": "Apple", + "reportFlow": reportFlow.rawValue, + "protectionsState": protectionsState ? "true" : "false", + //iOS only + "siteType": isDesktop ? "desktop" : "mobile", + "atb": atb, + "model": model + ] + } +} diff --git a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift b/Sources/PrivacyDashboard/PrivacyDashboardUserScript.swift similarity index 100% rename from Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift rename to Sources/PrivacyDashboard/PrivacyDashboardUserScript.swift diff --git a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift b/Sources/PrivacyDashboard/ServerTrustViewModel.swift similarity index 100% rename from Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift rename to Sources/PrivacyDashboard/ServerTrustViewModel.swift diff --git a/Sources/PrivacyDashboard/URL+Trimming.swift b/Sources/PrivacyDashboard/URL+Trimming.swift new file mode 100644 index 000000000..be8475e7d --- /dev/null +++ b/Sources/PrivacyDashboard/URL+Trimming.swift @@ -0,0 +1,40 @@ +// +// File.swift +// DuckDuckGo +// +// 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 URL { + + /// To limit privacy risk, site URL is trimmed to not include query and fragment + /// - Returns: A privacy-sanitised URL + public func trimmingQueryItemsAndFragment() -> URL { + + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { + return self + } + components.queryItems = nil + components.fragment = nil + + guard let result = components.url else { + return self + } + return result + } + +} From 12e535fcd2a8c3020c0718caccda3df629d1e02e Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Tue, 9 Jan 2024 11:00:28 +0000 Subject: [PATCH 03/22] updated to latest dashboard --- Package.resolved | 6 +++--- Package.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Package.resolved b/Package.resolved index 9c2192175..dc88f8887 100644 --- a/Package.resolved +++ b/Package.resolved @@ -42,7 +42,7 @@ "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { "branch" : "12-14-feat_macos_allow_direct_navigation_to_breakage_form", - "revision" : "97f6acbf9318045fbfb9b0ff098e307450d7457b" + "revision" : "24b6260043a888827998124bc12104f4779e010f" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version" : "1.2.3" + "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", + "version" : "1.3.0" } }, { diff --git a/Package.swift b/Package.swift index edab8aaaf..629d64d17 100644 --- a/Package.swift +++ b/Package.swift @@ -39,7 +39,7 @@ let package = Package( .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.2"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), .package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"), - .package(url: "https://github.com/duckduckgo/privacy-dashboard", .branch("12-14-feat_macos_allow_direct_navigation_to_breakage_form")), //exact: "3.1.1" ), + .package(url: "https://github.com/duckduckgo/privacy-dashboard", branch: "12-14-feat_macos_allow_direct_navigation_to_breakage_form"), .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"), From c49e72d45b9e2580f3e9549fdf062a30f6e6d60c Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 9 Jan 2024 14:54:54 +0100 Subject: [PATCH 04/22] Report broken site refactored, all business logic and models are now in BSK https://app.asana.com/0/1205842942115003/1205860617092609/f --- .../xcschemes/BloomFilterWrapper.xcscheme | 66 ++ .../xcshareddata/xcschemes/Bookmarks.xcscheme | 66 ++ .../xcschemes/BookmarksTestDBBuilder.xcscheme | 151 ++++ .../BrowserServicesKit-Package.xcscheme | 665 ++++++++++++++++++ .../xcshareddata/xcschemes/Common.xcscheme | 66 ++ .../xcschemes/Configuration.xcscheme | 66 ++ .../xcschemes/ContentBlocking.xcscheme | 66 ++ .../xcshareddata/xcschemes/Crashes.xcscheme | 66 ++ .../xcshareddata/xcschemes/DDGSync.xcscheme | 66 ++ .../xcschemes/Navigation.xcscheme | 66 ++ .../xcschemes/NetworkProtection.xcscheme | 66 ++ .../NetworkProtectionTestUtils.xcscheme | 66 ++ .../xcschemes/Networking.xcscheme | 66 ++ .../xcschemes/Persistence.xcscheme | 66 ++ .../xcschemes/PrivacyDashboard.xcscheme | 66 ++ .../xcschemes/RemoteMessaging.xcscheme | 66 ++ .../xcschemes/SecureStorage.xcscheme | 66 ++ .../xcschemes/SyncDataProviders.xcscheme | 66 ++ .../xcshareddata/xcschemes/TestUtils.xcscheme | 66 ++ .../xcschemes/UserScript.xcscheme | 66 ++ Package.swift | 9 + .../.xccurrentversion | 5 +- .../Persistence/DictionaryRepresentable.swift | 30 + .../PrivacyDashboardController.swift | 4 + .../WebsiteBreakage/ExpiryStorage.swift | 89 +++ .../WebsiteBreakage.swift | 67 +- .../WebsiteBreakageHistoryEntry.swift | 86 +++ .../WebsiteBreakageReporter.swift | 105 +++ Sources/TestUtils/MockKeyValueStore.swift | 9 +- .../ExpiryStorageTests.swift | 52 ++ .../WebsiteBreakageHistoryEntryTests.swift | 43 ++ .../WebsiteBreakageMoks.swift | 62 ++ .../WebsiteBreakageReporterTests.swift | 54 ++ 33 files changed, 2592 insertions(+), 27 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme create mode 100644 Sources/Persistence/DictionaryRepresentable.swift create mode 100644 Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift rename Sources/PrivacyDashboard/{Models => WebsiteBreakage}/WebsiteBreakage.swift (67%) create mode 100644 Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift create mode 100644 Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift create mode 100644 Tests/PrivacyDashboardTests/ExpiryStorageTests.swift create mode 100644 Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift create mode 100644 Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift create mode 100644 Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme new file mode 100644 index 000000000..9b1131c8c --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme new file mode 100644 index 000000000..23aee3a67 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme new file mode 100644 index 000000000..5a69bb3dc --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme new file mode 100644 index 000000000..92e405f22 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme @@ -0,0 +1,665 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme new file mode 100644 index 000000000..95a441fd8 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme new file mode 100644 index 000000000..6b51c3346 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme new file mode 100644 index 000000000..367fc91c6 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme new file mode 100644 index 000000000..4e42a52a4 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme new file mode 100644 index 000000000..1c11a9671 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme new file mode 100644 index 000000000..cc48561b8 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme new file mode 100644 index 000000000..cb8778d4e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme new file mode 100644 index 000000000..b78ba8135 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme new file mode 100644 index 000000000..691f9d89c --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme new file mode 100644 index 000000000..48755a2cc --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme new file mode 100644 index 000000000..d8e67c057 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme new file mode 100644 index 000000000..83b1c4c89 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme new file mode 100644 index 000000000..78243413b --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme new file mode 100644 index 000000000..80e0d3b12 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme new file mode 100644 index 000000000..d2421c529 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme new file mode 100644 index 000000000..3a0272146 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index edab8aaaf..e68daed54 100644 --- a/Package.swift +++ b/Package.swift @@ -197,6 +197,7 @@ let package = Package( "TrackerRadarKit", "UserScript", "ContentBlocking", + "Persistence", .product(name: "PrivacyDashboardResources", package: "privacy-dashboard") ], path: "Sources/PrivacyDashboard", @@ -451,6 +452,14 @@ let package = Package( ], plugins: [.plugin(name: "SwiftLintPlugin")] ), + .testTarget( + name: "PrivacyDashboardTests", + dependencies: [ + "PrivacyDashboard", + "TestUtils" + ], + plugins: [.plugin(name: "SwiftLintPlugin")] + ), ], cxxLanguageStandard: .cxx11 ) diff --git a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgrade.xcdatamodeld/.xccurrentversion b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgrade.xcdatamodeld/.xccurrentversion index 0c67376eb..7ae5865f4 100644 --- a/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgrade.xcdatamodeld/.xccurrentversion +++ b/Sources/BrowserServicesKit/SmarterEncryption/Store/HTTPSUpgrade.xcdatamodeld/.xccurrentversion @@ -1,5 +1,8 @@ - + + _XCCurrentVersionName + HTTPSUpgrade 3.xcdatamodel + diff --git a/Sources/Persistence/DictionaryRepresentable.swift b/Sources/Persistence/DictionaryRepresentable.swift new file mode 100644 index 000000000..85d2c2fb1 --- /dev/null +++ b/Sources/Persistence/DictionaryRepresentable.swift @@ -0,0 +1,30 @@ +// +// File.swift +// DuckDuckGo +// +// Copyright © 2024 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 + +/// Something that can be represented as a Dictionary +public protocol DictionaryRepresentable { + + /// Convenience method to return a Dictionary representation of this metadata. + /// - Returns: A Dictionary object containing the object representation + func dictionaryRepresentation() -> [String : Any] +} + +extension UserDefaults: DictionaryRepresentable { } diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index f5a1fd78b..4adcb9b0e 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -146,6 +146,8 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject { } } +//MARK: - WKNavigationDelegate + extension PrivacyDashboardController: WKNavigationDelegate { public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { @@ -255,6 +257,8 @@ extension PrivacyDashboardController: WKNavigationDelegate { } } +//MARK: - PrivacyDashboardUserScriptDelegate + extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate { func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenSettings target: String) { diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift new file mode 100644 index 000000000..02cf52b50 --- /dev/null +++ b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift @@ -0,0 +1,89 @@ +// +// UserDefaultExpiryStorage.swift +// DuckDuckGo +// +// 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 Persistence + +public typealias KeyValueStoringDictionaryRepresentable = KeyValueStoring & DictionaryRepresentable + +/// A storage solution were each entry has an expiry date and a function for removing all expired entries is provided. +/// Any persistency solution implementing `KeyValueStoringDictionaryRepresentable` can be used. +public class ExpiryStorage { + + enum Keys: String { + case expiryDatesStorage = "com.duckduckgo.UserDefaultExpiryStorage" + case valueExpiryDate = "com.duckduckgo.UserDefaultExpiryStorage.valueExpiryDate" + case valueData = "com.duckduckgo.UserDefaultExpiryStorage.valueData" + } + + let localStorage: KeyValueStoringDictionaryRepresentable + + /// Default initialiser + /// - Parameter keyValueStoring: An object managing the persistency of the key-value pairs that implements `KeyValueStoringDictionaryRepresentable` + public init(keyValueStoring: KeyValueStoringDictionaryRepresentable) { + self.localStorage = keyValueStoring + } + + /// Store a value and the desired expiry date (or removes the value if nil is passed as the value) for the provided key + /// - Parameters: + /// - value: The value to store, must be + /// - key: The value key + /// - expiryDate: A date stored alongside the value, used by `removeExpiredItems(...)` for removing expired values. + public func set(value: Any?, forKey key: String, expiryDate: Date) { + + let valueDic = [Keys.valueExpiryDate.rawValue: expiryDate, Keys.valueData.rawValue: value] + localStorage.set(valueDic, forKey: key) + } + + /// - Returns: The stored value assiciated to the key, nil if not existent + public func value(forKey key: String) -> Any? { + + return entry(forKey: key)?.value + } + + /// - Returns: The tuple expiryDate+value associated to the key, nil if they don't exist + public func entry(forKey key: String) -> (expiryDate: Date, value: Any)? { + guard let valueDic = localStorage.object(forKey: key) as? [String : Any], + let expiryDate = valueDic[Keys.valueExpiryDate.rawValue] as? Date, + let value = valueDic[Keys.valueData.rawValue] + else { + return nil + } + return (expiryDate, value) + } + + /// Search the entire storage for values that a re a dictionary containing 2 keys: `ExpiryStorage.Keys.valueExpiryDate` and `ExpiryStorage.Keys.valueData`, if found compares the `valueExpiryDate` with the `currentDate`, if the `currentDate` > `valueExpiryDate` then the value is removed form the storage + /// - Parameter currentDate: The date used in the comparison with the values `valueExpiryDate` + /// - Returns: The number of values removed + public func removeExpiredItems(currentDate: Date) -> Int { + + var removedCount = 0 + let allKeys = localStorage.dictionaryRepresentation().keys + for key in allKeys { + if let entry = entry(forKey: key) { + if currentDate > entry.expiryDate { + localStorage.removeObject(forKey: key) + removedCount += 1 + } + } + } + + return removedCount + } +} diff --git a/Sources/PrivacyDashboard/Models/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift similarity index 67% rename from Sources/PrivacyDashboard/Models/WebsiteBreakage.swift rename to Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index c4bfcc0b5..fb418aabf 100644 --- a/Sources/PrivacyDashboard/Models/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -15,19 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. // +// Implementation guidelines: https://app.asana.com/0/1198207348643509/1200202563872939/f import Foundation -struct WebsiteBreakage { - +/// Model containing all the info required for a report broken site submission +public struct WebsiteBreakage { + + /// The source of the broken site report public enum Source: String { + /// The app menu case appMenu = "menu" + /// From the privacy dashboard case dashboard } - + + let siteUrl: URL let category: String let description: String? - let siteUrlString: String let osVersion: String let upgradedHttps: Bool let tdsETag: String? @@ -37,21 +42,23 @@ struct WebsiteBreakage { let ampURL: String let urlParametersRemoved: Bool let reportFlow: Source - let protectionsState: Bool - //iOS Only - let isDesktop: Bool + let protectionsState: Bool + var lastSentDay: String? = nil +#if os(iOS) + let isDesktop: Bool //?? not in documentation let atb: String let model: String - - init( +#endif + + public init( + siteUrl: URL, category: String, description: String?, - siteUrlString: String, osVersion: String, upgradedHttps: Bool, tdsETag: String?, - blockedTrackerDomains: [String], - installedSurrogates: [String], + blockedTrackerDomains: [String]?, + installedSurrogates: [String]?, isGPCEnabled: Bool, ampURL: String, urlParametersRemoved: Bool, @@ -61,30 +68,33 @@ struct WebsiteBreakage { atb: String, model: String ) { + self.siteUrl = siteUrl self.category = category self.description = description - self.siteUrlString = siteUrlString self.osVersion = osVersion self.upgradedHttps = upgradedHttps self.tdsETag = tdsETag - self.blockedTrackerDomains = blockedTrackerDomains - self.installedSurrogates = installedSurrogates + self.blockedTrackerDomains = blockedTrackerDomains ?? [] + self.installedSurrogates = installedSurrogates ?? [] self.isGPCEnabled = isGPCEnabled self.ampURL = ampURL self.protectionsState = protectionsState self.urlParametersRemoved = urlParametersRemoved self.reportFlow = reportFlow - //iOS Only + +#if os(iOS) self.isDesktop = isDesktop self.atb = atb self.model = model +#endif } - - var requestParameters: [String: String] { - [ + + /// A dictionary containing all the parameters needed from the Report Broken Site Pixel + public var requestParameters: [String: String] { + var result = [ + "siteUrl": siteUrl.trimmingQueryItemsAndFragment().absoluteString, "category": category, "description": description ?? "", - "siteUrl": siteUrlString, "upgradedHttps": upgradedHttps ? "true" : "false", "tds": tdsETag?.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) ?? "", "blockedTrackers": blockedTrackerDomains.joined(separator: ","), @@ -95,11 +105,18 @@ struct WebsiteBreakage { "os": osVersion, "manufacturer": "Apple", "reportFlow": reportFlow.rawValue, - "protectionsState": protectionsState ? "true" : "false", - //iOS only - "siteType": isDesktop ? "desktop" : "mobile", - "atb": atb, - "model": model + "protectionsState": protectionsState ? "true" : "false" ] + + if let lastSentDay = lastSentDay { + result["lastSentDay"] = lastSentDay + } + +#if os(iOS) + result["siteType"] = isDesktop ? "desktop" : "mobile" + result["atb"] = atb + result["model"] = model +#endif + return result } } diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift new file mode 100644 index 000000000..2eca2cd05 --- /dev/null +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift @@ -0,0 +1,86 @@ +// +// WebsiteBreakageHistoryEntry.swift +// DuckDuckGo +// +// 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 CryptoKit + +//public typealias WebsiteBreakageDomainIdentifier = String + +/// Storage for the last time a WebsiteBreakage has been sent +public struct WebsiteBreakageHistoryEntry { + + /// first 6 chars of the sha256 hash of www.example.com + let identifier: String + /// yyyy-mm-dd + let lastSentDayString: String + /// Object Creation + 30 days + let expiryDate: Date? + + public init?(withBreakage breakage: WebsiteBreakage, currentDate: Date) { + + guard let domainIdentifier = breakage.siteUrl.privacySafeDomainIdentifier, + let expiryDate = Calendar.current.date(byAdding: .day, value: 30, to: currentDate) else { + return nil + } + self.identifier = domainIdentifier + self.lastSentDayString = currentDate.websiteBreakageFormattedString + self.expiryDate = expiryDate + } + + /// To be used when the entry is created from a UserDefault key value pair + public init(safeDomainIdentifier: String, lastSentDayString: String) { + + self.identifier = safeDomainIdentifier + self.lastSentDayString = lastSentDayString + self.expiryDate = nil + } +} + +fileprivate extension URL { + + /// A string containing the first 6 chars of the sha256 hash of the URL's domain part + var privacySafeDomainIdentifier: String? { + guard let domain = self.host else { + return nil + } + + guard let utf8Data = domain.data(using: .utf8) else { + return nil + } + + let sha256Digest = SHA256.hash(data: utf8Data) + let sha256Hex = sha256Digest.compactMap { String(format: "%02x", $0) }.joined() + + let startIndex = sha256Hex.startIndex + let endIndex = sha256Hex.index(startIndex, offsetBy: 6) + let firstSixCharacters = sha256Hex[startIndex.. WebsiteBreakageHistoryEntry? + func persist(breakegeHistory: WebsiteBreakageHistoryEntry) throws +} + +public enum WebsiteBreakageReporterError: Error { + + case failedToGenerateHistoryEntry + case missingExpiryDate +} + +/// Class responsible of reporting broken sites +/// The actual report is sent via Pixel from the main app, this class only persists the reports' history and prepares the models. +public class WebsiteBreakageReporter { + + /// A closure that receives the Pixel's parameters + public typealias PixelHandler = (_ parameters: [String: String]) -> () + /// Pixels are sent by the main apps not by BSK, this is the closure called by the class when a pixel need to be sent + let pixelHandler: PixelHandler + let persistencyManager: ExpiryStorage + + public init(pixelHandler: @escaping PixelHandler, keyValueStoring: KeyValueStoringDictionaryRepresentable) { + self.pixelHandler = pixelHandler + self.persistencyManager = ExpiryStorage(keyValueStoring: keyValueStoring) + } + + /// Report the site breakage + public func report(breakage: WebsiteBreakage) throws { + + let now = Date() + let removedCount = persistencyManager.removeExpiredItems(currentDate: now) + if removedCount > 0 { + os_log(.debug, "\(removedCount) breakage history record removed") + } + + var breakage = breakage + + //Create history entry + guard let historyEntry = WebsiteBreakageHistoryEntry(withBreakage: breakage, currentDate: now) else { + os_log(.error, "Failed to create a history entry for breakage report") + throw WebsiteBreakageReporterError.failedToGenerateHistoryEntry + } + + os_log(.debug, "Reporting website breakage for \(breakage.siteUrl.absoluteString)") + + //Check if the report has been sent before + if let storedHistoryEntry = try persistencyManager.getBreakageHistory(forDomainIdentifier: historyEntry.identifier) { + breakage.lastSentDay = storedHistoryEntry.lastSentDayString + os_log(.debug, "Breakage report sent on the \(breakage.lastSentDay ?? "?") for \(breakage.siteUrl.absoluteString) ID:\(historyEntry.identifier)") + } + + let pixelParams = breakage.requestParameters + + //report the breakage + pixelHandler(pixelParams) + + //persist history entry + try persistencyManager.persist(breakegeHistory: historyEntry) //this overrides the previously stored entry if existed + + os_log(.debug, "Website breakage reported for \(breakage.siteUrl.absoluteString)") + } +} + +extension ExpiryStorage: WebsiteBreakagePersistencyManaging { + + public func getBreakageHistory(forDomainIdentifier domainIdentifier: String) throws -> WebsiteBreakageHistoryEntry? { + + if let savedData = value(forKey: domainIdentifier) as? String { + return WebsiteBreakageHistoryEntry(safeDomainIdentifier: domainIdentifier, lastSentDayString: savedData) + } + return nil + } + + public func persist(breakegeHistory: WebsiteBreakageHistoryEntry) throws { + + guard let expirydate = breakegeHistory.expiryDate else { + throw WebsiteBreakageReporterError.missingExpiryDate + } + set(value: breakegeHistory.lastSentDayString, forKey: breakegeHistory.identifier, expiryDate: expirydate) + } +} diff --git a/Sources/TestUtils/MockKeyValueStore.swift b/Sources/TestUtils/MockKeyValueStore.swift index 47b81a5c3..3cd868535 100644 --- a/Sources/TestUtils/MockKeyValueStore.swift +++ b/Sources/TestUtils/MockKeyValueStore.swift @@ -21,7 +21,7 @@ import Persistence public class MockKeyValueStore: KeyValueStoring { - var store = [String: Any?]() + public var store = [String: Any?]() public init() { } @@ -38,3 +38,10 @@ public class MockKeyValueStore: KeyValueStoring { } } + +extension MockKeyValueStore: DictionaryRepresentable { + + public func dictionaryRepresentation() -> [String : Any] { + return store as [String : Any] + } +} diff --git a/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift new file mode 100644 index 000000000..e9f6f968e --- /dev/null +++ b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift @@ -0,0 +1,52 @@ +// +// UserDefaultsExpiryStorageTests.swift +// DuckDuckGo +// +// Copyright © 2024 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 TestUtils +@testable import PrivacyDashboard + +final class ExpiryStorageTests: XCTestCase { + + func testAddAndRetrieveValue() throws { + let expiryStorage = ExpiryStorage(keyValueStoring: MockKeyValueStore()) + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(86400)) //+1 day + + let value = expiryStorage.value(forKey: "key1") as! String + XCTAssertEqual(value, "value1") + } + + func testExpiry() throws { + let expiryStorage = ExpiryStorage(keyValueStoring: MockKeyValueStore()) + + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) //-1 day + XCTAssertEqual(expiryStorage.value(forKey: "key1") as! String, "value1") + + var removedCount = expiryStorage.removeExpiredItems(currentDate: Date()) + XCTAssertEqual(removedCount, 1) + + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) //-1 day + expiryStorage.set(value: "value2", forKey: "key2", expiryDate: Date().addingTimeInterval(+86400)) //+1 day + + removedCount = expiryStorage.removeExpiredItems(currentDate: Date()) + XCTAssertEqual(removedCount, 1) + + XCTAssertNil(expiryStorage.value(forKey: "key1")) + XCTAssertEqual(expiryStorage.value(forKey: "key2") as! String, "value2") + } +} diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift new file mode 100644 index 000000000..19391eef0 --- /dev/null +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift @@ -0,0 +1,43 @@ +// +// WebsiteBreakageHistoryEntryTests.swift +// DuckDuckGo +// +// Copyright © 2024 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 +@testable import PrivacyDashboard + +final class WebsiteBreakageHistoryEntryTests: XCTestCase { + + func testDates() throws { + let testDate = Date(timeIntervalSince1970: 1704795829) + let breakageHistory = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) + + XCTAssertNotNil(breakageHistory) + XCTAssertEqual("2024-01-09", breakageHistory?.lastSentDayString) + XCTAssertEqual(1704795829 + (86400*30), breakageHistory?.expiryDate?.timeIntervalSince1970) + } + + func testUniqueIdentifier() throws { + let testDate = Date(timeIntervalSince1970: 1704795829) + let breakageHistory = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) + let breakageHistory2 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage2, currentDate: testDate) + let breakageHistory3 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) + + XCTAssertEqual(breakageHistory?.identifier, breakageHistory3?.identifier) + XCTAssertNotEqual(breakageHistory?.identifier, breakageHistory2?.identifier) + } +} diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift new file mode 100644 index 000000000..a1728e0f7 --- /dev/null +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -0,0 +1,62 @@ +// +// WebsiteBreakageMoks.swift +// DuckDuckGo +// +// Copyright © 2024 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 PrivacyDashboard + +struct WebsiteBreakageMoks { + + static var testBreakage: WebsiteBreakage { + WebsiteBreakage(siteUrl: URL(string: "https://duckduckgo.com")!, + category: "test", + description: "test", + osVersion: "test", + upgradedHttps: true, + tdsETag: "test", + blockedTrackerDomains: [], + installedSurrogates: [], + isGPCEnabled: true, + ampURL: "test", + urlParametersRemoved: true, + protectionsState: true, + reportFlow: .appMenu, + isDesktop: true, + atb: "test", + model: "test") + } + + static var testBreakage2: WebsiteBreakage { + WebsiteBreakage(siteUrl: URL(string: "https://somethingelse.zz")!, + category: "test", + description: "test", + osVersion: "test", + upgradedHttps: true, + tdsETag: "test", + blockedTrackerDomains: [], + installedSurrogates: [], + isGPCEnabled: true, + ampURL: "test", + urlParametersRemoved: true, + protectionsState: true, + reportFlow: .appMenu, + isDesktop: true, + atb: "test", + model: "test") + } +} diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift new file mode 100644 index 000000000..420d38fa2 --- /dev/null +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift @@ -0,0 +1,54 @@ +// +// WebsiteBreakageReporterTests.swift +// DuckDuckGo +// +// 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 +@testable import PrivacyDashboard +import TestUtils + +final class WebsiteBreakageReporterTests: XCTestCase { + + func testReport() throws { + + let expctation1 = expectation(description: "Pixel sent without lastSentDay") + let expctation2 = expectation(description: "Pixel sent with lastSentDay ") + var pixelCount = 0 + + let keyValueStore = MockKeyValueStore() + let reporter = WebsiteBreakageReporter(pixelHandler: { parameters in + //Send pixel + print("PIXEL SENT: \n\(parameters)") + pixelCount += 1 + + if pixelCount == 1, parameters["lastSentDay"] == nil { + expctation1.fulfill() + } else if pixelCount == 2, parameters["lastSentDay"] != nil { + expctation2.fulfill() + } + }, keyValueStoring: keyValueStore) + + try reporter.report(breakage: WebsiteBreakageMoks.testBreakage) + + //test second report, the pixel must have `lastSeenDate` param + try reporter.report(breakage: WebsiteBreakageMoks.testBreakage) + + waitForExpectations(timeout: 3) + } + + +} From f1e796a4f09cb1f740e1afbb7e8b4438cb8db842 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 09:40:31 +0100 Subject: [PATCH 05/22] swiftlint --fix --- .../Persistence/DictionaryRepresentable.swift | 4 +- .../PrivacyDashboardController.swift | 118 +++++++++--------- Sources/PrivacyDashboard/URL+Trimming.swift | 4 +- .../WebsiteBreakage/ExpiryStorage.swift | 24 ++-- .../WebsiteBreakage/WebsiteBreakage.swift | 20 +-- .../WebsiteBreakageHistoryEntry.swift | 24 ++-- .../WebsiteBreakageReporter.swift | 48 +++---- Sources/TestUtils/MockKeyValueStore.swift | 6 +- .../ExpiryStorageTests.swift | 22 ++-- .../WebsiteBreakageHistoryEntryTests.swift | 8 +- .../WebsiteBreakageMoks.swift | 4 +- .../WebsiteBreakageReporterTests.swift | 19 ++- 12 files changed, 150 insertions(+), 151 deletions(-) diff --git a/Sources/Persistence/DictionaryRepresentable.swift b/Sources/Persistence/DictionaryRepresentable.swift index 85d2c2fb1..d35c1cbfd 100644 --- a/Sources/Persistence/DictionaryRepresentable.swift +++ b/Sources/Persistence/DictionaryRepresentable.swift @@ -21,10 +21,10 @@ import Foundation /// Something that can be represented as a Dictionary public protocol DictionaryRepresentable { - + /// Convenience method to return a Dictionary representation of this metadata. /// - Returns: A Dictionary object containing the object representation - func dictionaryRepresentation() -> [String : Any] + func dictionaryRepresentation() -> [String: Any] } extension UserDefaults: DictionaryRepresentable { } diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index 4adcb9b0e..8d5114601 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -28,35 +28,35 @@ public enum PrivacyDashboardOpenSettingsTarget: String { /// Navigation delegate for the pages provided by the PrivacyDashboardController public protocol PrivacyDashboardNavigationDelegate: AnyObject { - + func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) 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 } @@ -66,25 +66,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 @@ -92,75 +92,75 @@ 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()) } } -//MARK: - WKNavigationDelegate +// MARK: - WKNavigationDelegate 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) } } -//MARK: - PrivacyDashboardUserScriptDelegate +// MARK: - PrivacyDashboardUserScriptDelegate 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) { privacyDashboardNavigationDelegate?.privacyDashboardControllerDidTapClose(self) } - + 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/URL+Trimming.swift b/Sources/PrivacyDashboard/URL+Trimming.swift index be8475e7d..59807b09e 100644 --- a/Sources/PrivacyDashboard/URL+Trimming.swift +++ b/Sources/PrivacyDashboard/URL+Trimming.swift @@ -24,13 +24,13 @@ extension URL { /// To limit privacy risk, site URL is trimmed to not include query and fragment /// - Returns: A privacy-sanitised URL public func trimmingQueryItemsAndFragment() -> URL { - + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return self } components.queryItems = nil components.fragment = nil - + guard let result = components.url else { return self } diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift index 02cf52b50..c0c265355 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift @@ -25,41 +25,41 @@ public typealias KeyValueStoringDictionaryRepresentable = KeyValueStoring & Dict /// A storage solution were each entry has an expiry date and a function for removing all expired entries is provided. /// Any persistency solution implementing `KeyValueStoringDictionaryRepresentable` can be used. public class ExpiryStorage { - + enum Keys: String { case expiryDatesStorage = "com.duckduckgo.UserDefaultExpiryStorage" case valueExpiryDate = "com.duckduckgo.UserDefaultExpiryStorage.valueExpiryDate" case valueData = "com.duckduckgo.UserDefaultExpiryStorage.valueData" } - + let localStorage: KeyValueStoringDictionaryRepresentable - + /// Default initialiser /// - Parameter keyValueStoring: An object managing the persistency of the key-value pairs that implements `KeyValueStoringDictionaryRepresentable` public init(keyValueStoring: KeyValueStoringDictionaryRepresentable) { self.localStorage = keyValueStoring } - + /// Store a value and the desired expiry date (or removes the value if nil is passed as the value) for the provided key /// - Parameters: /// - value: The value to store, must be /// - key: The value key /// - expiryDate: A date stored alongside the value, used by `removeExpiredItems(...)` for removing expired values. public func set(value: Any?, forKey key: String, expiryDate: Date) { - + let valueDic = [Keys.valueExpiryDate.rawValue: expiryDate, Keys.valueData.rawValue: value] localStorage.set(valueDic, forKey: key) } - + /// - Returns: The stored value assiciated to the key, nil if not existent public func value(forKey key: String) -> Any? { - + return entry(forKey: key)?.value } - + /// - Returns: The tuple expiryDate+value associated to the key, nil if they don't exist public func entry(forKey key: String) -> (expiryDate: Date, value: Any)? { - guard let valueDic = localStorage.object(forKey: key) as? [String : Any], + guard let valueDic = localStorage.object(forKey: key) as? [String: Any], let expiryDate = valueDic[Keys.valueExpiryDate.rawValue] as? Date, let value = valueDic[Keys.valueData.rawValue] else { @@ -67,12 +67,12 @@ public class ExpiryStorage { } return (expiryDate, value) } - + /// Search the entire storage for values that a re a dictionary containing 2 keys: `ExpiryStorage.Keys.valueExpiryDate` and `ExpiryStorage.Keys.valueData`, if found compares the `valueExpiryDate` with the `currentDate`, if the `currentDate` > `valueExpiryDate` then the value is removed form the storage /// - Parameter currentDate: The date used in the comparison with the values `valueExpiryDate` /// - Returns: The number of values removed public func removeExpiredItems(currentDate: Date) -> Int { - + var removedCount = 0 let allKeys = localStorage.dictionaryRepresentation().keys for key in allKeys { @@ -83,7 +83,7 @@ public class ExpiryStorage { } } } - + return removedCount } } diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index fb418aabf..ca8fc4219 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -21,7 +21,7 @@ import Foundation /// Model containing all the info required for a report broken site submission public struct WebsiteBreakage { - + /// The source of the broken site report public enum Source: String { /// The app menu @@ -29,7 +29,7 @@ public struct WebsiteBreakage { /// From the privacy dashboard case dashboard } - + let siteUrl: URL let category: String let description: String? @@ -42,14 +42,14 @@ public struct WebsiteBreakage { let ampURL: String let urlParametersRemoved: Bool let reportFlow: Source - let protectionsState: Bool - var lastSentDay: String? = nil + let protectionsState: Bool + var lastSentDay: String? #if os(iOS) - let isDesktop: Bool //?? not in documentation + let isDesktop: Bool // ?? not in documentation let atb: String let model: String #endif - + public init( siteUrl: URL, category: String, @@ -81,14 +81,14 @@ public struct WebsiteBreakage { self.protectionsState = protectionsState self.urlParametersRemoved = urlParametersRemoved self.reportFlow = reportFlow - + #if os(iOS) self.isDesktop = isDesktop self.atb = atb self.model = model #endif } - + /// A dictionary containing all the parameters needed from the Report Broken Site Pixel public var requestParameters: [String: String] { var result = [ @@ -107,11 +107,11 @@ public struct WebsiteBreakage { "reportFlow": reportFlow.rawValue, "protectionsState": protectionsState ? "true" : "false" ] - + if let lastSentDay = lastSentDay { result["lastSentDay"] = lastSentDay } - + #if os(iOS) result["siteType"] = isDesktop ? "desktop" : "mobile" result["atb"] = atb diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift index 2eca2cd05..f9572b1b2 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift @@ -20,20 +20,20 @@ import Foundation import CryptoKit -//public typealias WebsiteBreakageDomainIdentifier = String +// public typealias WebsiteBreakageDomainIdentifier = String /// Storage for the last time a WebsiteBreakage has been sent public struct WebsiteBreakageHistoryEntry { - + /// first 6 chars of the sha256 hash of www.example.com let identifier: String /// yyyy-mm-dd let lastSentDayString: String /// Object Creation + 30 days let expiryDate: Date? - + public init?(withBreakage breakage: WebsiteBreakage, currentDate: Date) { - + guard let domainIdentifier = breakage.siteUrl.privacySafeDomainIdentifier, let expiryDate = Calendar.current.date(byAdding: .day, value: 30, to: currentDate) else { return nil @@ -42,10 +42,10 @@ public struct WebsiteBreakageHistoryEntry { self.lastSentDayString = currentDate.websiteBreakageFormattedString self.expiryDate = expiryDate } - + /// To be used when the entry is created from a UserDefault key value pair public init(safeDomainIdentifier: String, lastSentDayString: String) { - + self.identifier = safeDomainIdentifier self.lastSentDayString = lastSentDayString self.expiryDate = nil @@ -53,30 +53,30 @@ public struct WebsiteBreakageHistoryEntry { } fileprivate extension URL { - + /// A string containing the first 6 chars of the sha256 hash of the URL's domain part var privacySafeDomainIdentifier: String? { guard let domain = self.host else { return nil } - + guard let utf8Data = domain.data(using: .utf8) else { return nil } - + let sha256Digest = SHA256.hash(data: utf8Data) let sha256Hex = sha256Digest.compactMap { String(format: "%02x", $0) }.joined() - + let startIndex = sha256Hex.startIndex let endIndex = sha256Hex.index(startIndex, offsetBy: 6) let firstSixCharacters = sha256Hex[startIndex.. WebsiteBreakageHistoryEntry? func persist(breakegeHistory: WebsiteBreakageHistoryEntry) throws } public enum WebsiteBreakageReporterError: Error { - + case failedToGenerateHistoryEntry case missingExpiryDate } @@ -36,67 +36,67 @@ public enum WebsiteBreakageReporterError: Error { /// Class responsible of reporting broken sites /// The actual report is sent via Pixel from the main app, this class only persists the reports' history and prepares the models. public class WebsiteBreakageReporter { - + /// A closure that receives the Pixel's parameters - public typealias PixelHandler = (_ parameters: [String: String]) -> () + public typealias PixelHandler = (_ parameters: [String: String]) -> Void /// Pixels are sent by the main apps not by BSK, this is the closure called by the class when a pixel need to be sent let pixelHandler: PixelHandler let persistencyManager: ExpiryStorage - + public init(pixelHandler: @escaping PixelHandler, keyValueStoring: KeyValueStoringDictionaryRepresentable) { self.pixelHandler = pixelHandler self.persistencyManager = ExpiryStorage(keyValueStoring: keyValueStoring) } - + /// Report the site breakage public func report(breakage: WebsiteBreakage) throws { - + let now = Date() let removedCount = persistencyManager.removeExpiredItems(currentDate: now) if removedCount > 0 { os_log(.debug, "\(removedCount) breakage history record removed") } - + var breakage = breakage - - //Create history entry + + // Create history entry guard let historyEntry = WebsiteBreakageHistoryEntry(withBreakage: breakage, currentDate: now) else { os_log(.error, "Failed to create a history entry for breakage report") throw WebsiteBreakageReporterError.failedToGenerateHistoryEntry } - + os_log(.debug, "Reporting website breakage for \(breakage.siteUrl.absoluteString)") - - //Check if the report has been sent before + + // Check if the report has been sent before if let storedHistoryEntry = try persistencyManager.getBreakageHistory(forDomainIdentifier: historyEntry.identifier) { breakage.lastSentDay = storedHistoryEntry.lastSentDayString os_log(.debug, "Breakage report sent on the \(breakage.lastSentDay ?? "?") for \(breakage.siteUrl.absoluteString) ID:\(historyEntry.identifier)") } - + let pixelParams = breakage.requestParameters - - //report the breakage + + // report the breakage pixelHandler(pixelParams) - - //persist history entry - try persistencyManager.persist(breakegeHistory: historyEntry) //this overrides the previously stored entry if existed - + + // persist history entry + try persistencyManager.persist(breakegeHistory: historyEntry) // this overrides the previously stored entry if existed + os_log(.debug, "Website breakage reported for \(breakage.siteUrl.absoluteString)") } } extension ExpiryStorage: WebsiteBreakagePersistencyManaging { - + public func getBreakageHistory(forDomainIdentifier domainIdentifier: String) throws -> WebsiteBreakageHistoryEntry? { - + if let savedData = value(forKey: domainIdentifier) as? String { return WebsiteBreakageHistoryEntry(safeDomainIdentifier: domainIdentifier, lastSentDayString: savedData) } return nil } - + public func persist(breakegeHistory: WebsiteBreakageHistoryEntry) throws { - + guard let expirydate = breakegeHistory.expiryDate else { throw WebsiteBreakageReporterError.missingExpiryDate } diff --git a/Sources/TestUtils/MockKeyValueStore.swift b/Sources/TestUtils/MockKeyValueStore.swift index 3cd868535..7d0773f08 100644 --- a/Sources/TestUtils/MockKeyValueStore.swift +++ b/Sources/TestUtils/MockKeyValueStore.swift @@ -40,8 +40,8 @@ public class MockKeyValueStore: KeyValueStoring { } extension MockKeyValueStore: DictionaryRepresentable { - - public func dictionaryRepresentation() -> [String : Any] { - return store as [String : Any] + + public func dictionaryRepresentation() -> [String: Any] { + return store as [String: Any] } } diff --git a/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift index e9f6f968e..30bf8828b 100644 --- a/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift +++ b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift @@ -25,27 +25,27 @@ final class ExpiryStorageTests: XCTestCase { func testAddAndRetrieveValue() throws { let expiryStorage = ExpiryStorage(keyValueStoring: MockKeyValueStore()) - expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(86400)) //+1 day - + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(86400)) // +1 day + let value = expiryStorage.value(forKey: "key1") as! String XCTAssertEqual(value, "value1") } - + func testExpiry() throws { let expiryStorage = ExpiryStorage(keyValueStoring: MockKeyValueStore()) - - expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) //-1 day + + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) // -1 day XCTAssertEqual(expiryStorage.value(forKey: "key1") as! String, "value1") - + var removedCount = expiryStorage.removeExpiredItems(currentDate: Date()) XCTAssertEqual(removedCount, 1) - - expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) //-1 day - expiryStorage.set(value: "value2", forKey: "key2", expiryDate: Date().addingTimeInterval(+86400)) //+1 day - + + expiryStorage.set(value: "value1", forKey: "key1", expiryDate: Date().addingTimeInterval(-86400)) // -1 day + expiryStorage.set(value: "value2", forKey: "key2", expiryDate: Date().addingTimeInterval(+86400)) // +1 day + removedCount = expiryStorage.removeExpiredItems(currentDate: Date()) XCTAssertEqual(removedCount, 1) - + XCTAssertNil(expiryStorage.value(forKey: "key1")) XCTAssertEqual(expiryStorage.value(forKey: "key2") as! String, "value2") } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift index 19391eef0..1da969b8f 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift @@ -21,22 +21,22 @@ import XCTest @testable import PrivacyDashboard final class WebsiteBreakageHistoryEntryTests: XCTestCase { - + func testDates() throws { let testDate = Date(timeIntervalSince1970: 1704795829) let breakageHistory = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) - + XCTAssertNotNil(breakageHistory) XCTAssertEqual("2024-01-09", breakageHistory?.lastSentDayString) XCTAssertEqual(1704795829 + (86400*30), breakageHistory?.expiryDate?.timeIntervalSince1970) } - + func testUniqueIdentifier() throws { let testDate = Date(timeIntervalSince1970: 1704795829) let breakageHistory = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) let breakageHistory2 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage2, currentDate: testDate) let breakageHistory3 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) - + XCTAssertEqual(breakageHistory?.identifier, breakageHistory3?.identifier) XCTAssertNotEqual(breakageHistory?.identifier, breakageHistory2?.identifier) } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index a1728e0f7..3219b9597 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -21,7 +21,7 @@ import Foundation import PrivacyDashboard struct WebsiteBreakageMoks { - + static var testBreakage: WebsiteBreakage { WebsiteBreakage(siteUrl: URL(string: "https://duckduckgo.com")!, category: "test", @@ -40,7 +40,7 @@ struct WebsiteBreakageMoks { atb: "test", model: "test") } - + static var testBreakage2: WebsiteBreakage { WebsiteBreakage(siteUrl: URL(string: "https://somethingelse.zz")!, category: "test", diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift index 420d38fa2..0ef878461 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift @@ -24,31 +24,30 @@ import TestUtils final class WebsiteBreakageReporterTests: XCTestCase { func testReport() throws { - + let expctation1 = expectation(description: "Pixel sent without lastSentDay") let expctation2 = expectation(description: "Pixel sent with lastSentDay ") var pixelCount = 0 - + let keyValueStore = MockKeyValueStore() let reporter = WebsiteBreakageReporter(pixelHandler: { parameters in - //Send pixel + // Send pixel print("PIXEL SENT: \n\(parameters)") pixelCount += 1 - + if pixelCount == 1, parameters["lastSentDay"] == nil { expctation1.fulfill() } else if pixelCount == 2, parameters["lastSentDay"] != nil { expctation2.fulfill() } }, keyValueStoring: keyValueStore) - + try reporter.report(breakage: WebsiteBreakageMoks.testBreakage) - - //test second report, the pixel must have `lastSeenDate` param + + // test second report, the pixel must have `lastSeenDate` param try reporter.report(breakage: WebsiteBreakageMoks.testBreakage) - + waitForExpectations(timeout: 3) } - - + } From f4560fc16e7a3041ece21a41a22d6edf0c65b431 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 09:50:55 +0100 Subject: [PATCH 06/22] submodule updated --- Tests/BrowserServicesKitTests/Resources/privacy-reference-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BrowserServicesKitTests/Resources/privacy-reference-tests b/Tests/BrowserServicesKitTests/Resources/privacy-reference-tests index a3acc2194..6b7ad1e7f 160000 --- a/Tests/BrowserServicesKitTests/Resources/privacy-reference-tests +++ b/Tests/BrowserServicesKitTests/Resources/privacy-reference-tests @@ -1 +1 @@ -Subproject commit a3acc2194758bec0f01f57dd0c5f106de01a354e +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 From a9551e1c6ca980c1f46bb739a32faad5f7cb8d97 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 10:13:39 +0100 Subject: [PATCH 07/22] swiftlint violations fixed --- Sources/Persistence/DictionaryRepresentable.swift | 3 +-- Sources/PrivacyDashboard/URL+Trimming.swift | 3 +-- Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift | 5 ++--- .../WebsiteBreakage/WebsiteBreakageHistoryEntry.swift | 1 - .../WebsiteBreakage/WebsiteBreakageReporter.swift | 5 ++--- Tests/PrivacyDashboardTests/ExpiryStorageTests.swift | 3 +-- .../WebsiteBreakageHistoryEntryTests.swift | 1 - Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift | 1 - .../PrivacyDashboardTests/WebsiteBreakageReporterTests.swift | 1 - 9 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Sources/Persistence/DictionaryRepresentable.swift b/Sources/Persistence/DictionaryRepresentable.swift index d35c1cbfd..d84b5c9ba 100644 --- a/Sources/Persistence/DictionaryRepresentable.swift +++ b/Sources/Persistence/DictionaryRepresentable.swift @@ -1,6 +1,5 @@ // -// File.swift -// DuckDuckGo +// DictionaryRepresentable.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/Sources/PrivacyDashboard/URL+Trimming.swift b/Sources/PrivacyDashboard/URL+Trimming.swift index 59807b09e..95a06a155 100644 --- a/Sources/PrivacyDashboard/URL+Trimming.swift +++ b/Sources/PrivacyDashboard/URL+Trimming.swift @@ -1,6 +1,5 @@ // -// File.swift -// DuckDuckGo +// URL+Trimming.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift index c0c265355..39d5dfb7f 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/ExpiryStorage.swift @@ -1,8 +1,7 @@ // -// UserDefaultExpiryStorage.swift -// DuckDuckGo +// ExpiryStorage.swift // -// Copyright © 2023 DuckDuckGo. All rights reserved. +// Copyright © 2024 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. diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift index f9572b1b2..41305b19f 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageHistoryEntry.swift @@ -1,6 +1,5 @@ // // WebsiteBreakageHistoryEntry.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift index dba1bc98d..60746b919 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift @@ -1,6 +1,5 @@ // -// File.swift -// DuckDuckGo +// WebsiteBreakageReporter.swift // // Copyright © 2023 DuckDuckGo. All rights reserved. // @@ -18,7 +17,7 @@ // import Foundation -import OSLog +import Common import Persistence public protocol WebsiteBreakagePersistencyManaging { diff --git a/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift index 30bf8828b..3315329ac 100644 --- a/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift +++ b/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift @@ -1,6 +1,5 @@ // -// UserDefaultsExpiryStorageTests.swift -// DuckDuckGo +// ExpiryStorageTests.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift index 1da969b8f..fdbec8b70 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift @@ -1,6 +1,5 @@ // // WebsiteBreakageHistoryEntryTests.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index 3219b9597..90a8cb57b 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -1,6 +1,5 @@ // // WebsiteBreakageMoks.swift -// DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. // diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift index 0ef878461..0577ab56a 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift @@ -1,6 +1,5 @@ // // WebsiteBreakageReporterTests.swift -// DuckDuckGo // // Copyright © 2023 DuckDuckGo. All rights reserved. // From 3b8ac065860b46307c16c7b56f150514e4e2e8e5 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 10:47:05 +0100 Subject: [PATCH 08/22] Website breakage init differentiated for iOS and macOS --- .../WebsiteBreakage/WebsiteBreakage.swift | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index ca8fc4219..42147ca96 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -29,6 +29,11 @@ public struct WebsiteBreakage { /// From the privacy dashboard case dashboard } + + public enum SiteType: String { + case desktop + case mobile + } let siteUrl: URL let category: String @@ -45,11 +50,44 @@ public struct WebsiteBreakage { let protectionsState: Bool var lastSentDay: String? #if os(iOS) - let isDesktop: Bool // ?? not in documentation + let siteType: SiteType // ?? not in documentation let atb: String let model: String #endif +#if os(macOS) + public init( + siteUrl: URL, + category: String, + description: String?, + osVersion: String, + upgradedHttps: Bool, + tdsETag: String?, + blockedTrackerDomains: [String]?, + installedSurrogates: [String]?, + isGPCEnabled: Bool, + ampURL: String, + urlParametersRemoved: Bool, + protectionsState: Bool, + reportFlow: Source + ) { + self.siteUrl = siteUrl + self.category = category + self.description = description + self.osVersion = osVersion + self.upgradedHttps = upgradedHttps + self.tdsETag = tdsETag + self.blockedTrackerDomains = blockedTrackerDomains ?? [] + self.installedSurrogates = installedSurrogates ?? [] + self.isGPCEnabled = isGPCEnabled + self.ampURL = ampURL + self.protectionsState = protectionsState + self.urlParametersRemoved = urlParametersRemoved + self.reportFlow = reportFlow + } +#endif + +#if os(iOS) public init( siteUrl: URL, category: String, @@ -64,7 +102,7 @@ public struct WebsiteBreakage { urlParametersRemoved: Bool, protectionsState: Bool, reportFlow: Source, - isDesktop: Bool, + siteType: SiteType, atb: String, model: String ) { @@ -81,13 +119,11 @@ public struct WebsiteBreakage { self.protectionsState = protectionsState self.urlParametersRemoved = urlParametersRemoved self.reportFlow = reportFlow - -#if os(iOS) - self.isDesktop = isDesktop + self.siteType = siteType self.atb = atb self.model = model -#endif } +#endif /// A dictionary containing all the parameters needed from the Report Broken Site Pixel public var requestParameters: [String: String] { @@ -113,7 +149,7 @@ public struct WebsiteBreakage { } #if os(iOS) - result["siteType"] = isDesktop ? "desktop" : "mobile" + result["siteType"] = siteType.rawValue result["atb"] = atb result["model"] = model #endif From e3d2ddd18d5bd4fc8d3db8edaca31921eafaed97 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 10:49:24 +0100 Subject: [PATCH 09/22] swiftlint fix --- .../PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index 42147ca96..75792cd99 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -29,7 +29,7 @@ public struct WebsiteBreakage { /// From the privacy dashboard case dashboard } - + public enum SiteType: String { case desktop case mobile @@ -86,7 +86,7 @@ public struct WebsiteBreakage { self.reportFlow = reportFlow } #endif - + #if os(iOS) public init( siteUrl: URL, From b2354082ea0fc697bb35ab8ab7de9db0e1b42665 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 11:55:34 +0100 Subject: [PATCH 10/22] debug logs sanithised --- .../WebsiteBreakage/WebsiteBreakageReporter.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift index 60746b919..c53c8cc30 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakageReporter.swift @@ -64,12 +64,12 @@ public class WebsiteBreakageReporter { throw WebsiteBreakageReporterError.failedToGenerateHistoryEntry } - os_log(.debug, "Reporting website breakage for \(breakage.siteUrl.absoluteString)") + os_log(.debug, "Reporting website breakage for \(historyEntry.identifier)") // Check if the report has been sent before if let storedHistoryEntry = try persistencyManager.getBreakageHistory(forDomainIdentifier: historyEntry.identifier) { breakage.lastSentDay = storedHistoryEntry.lastSentDayString - os_log(.debug, "Breakage report sent on the \(breakage.lastSentDay ?? "?") for \(breakage.siteUrl.absoluteString) ID:\(historyEntry.identifier)") + os_log(.debug, "Breakage report sent on the \(breakage.lastSentDay ?? "?") for \(historyEntry.identifier)") } let pixelParams = breakage.requestParameters @@ -80,7 +80,7 @@ public class WebsiteBreakageReporter { // persist history entry try persistencyManager.persist(breakegeHistory: historyEntry) // this overrides the previously stored entry if existed - os_log(.debug, "Website breakage reported for \(breakage.siteUrl.absoluteString)") + os_log(.debug, "Website breakage reported for \(historyEntry.identifier)") } } From dc60cf47d5b325e2270978fd3f1b2c903a329d1f Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 12:00:52 +0100 Subject: [PATCH 11/22] unit tests fixed --- Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index 90a8cb57b..81d5dfa9a 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -34,10 +34,7 @@ struct WebsiteBreakageMoks { ampURL: "test", urlParametersRemoved: true, protectionsState: true, - reportFlow: .appMenu, - isDesktop: true, - atb: "test", - model: "test") + reportFlow: .appMenu) } static var testBreakage2: WebsiteBreakage { @@ -53,9 +50,6 @@ struct WebsiteBreakageMoks { ampURL: "test", urlParametersRemoved: true, protectionsState: true, - reportFlow: .appMenu, - isDesktop: true, - atb: "test", - model: "test") + reportFlow: .appMenu) } } From 1f8b8df3925158378f493c2e64eac1a749de1f58 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 13:15:11 +0100 Subject: [PATCH 12/22] folder name reverted --- .../PrivacyDashboard/{Models => Model}/AllowedPermission.swift | 0 .../PrivacyDashboard/{Models => Model}/CookieConsentInfo.swift | 0 .../{Models => Model}/PermissionAuthorizationState.swift | 0 Sources/PrivacyDashboard/{Models => Model}/ProtectionStatus.swift | 0 Sources/PrivacyDashboard/{Models => Model}/TrackerInfo.swift | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename Sources/PrivacyDashboard/{Models => Model}/AllowedPermission.swift (100%) rename Sources/PrivacyDashboard/{Models => Model}/CookieConsentInfo.swift (100%) rename Sources/PrivacyDashboard/{Models => Model}/PermissionAuthorizationState.swift (100%) rename Sources/PrivacyDashboard/{Models => Model}/ProtectionStatus.swift (100%) rename Sources/PrivacyDashboard/{Models => Model}/TrackerInfo.swift (100%) diff --git a/Sources/PrivacyDashboard/Models/AllowedPermission.swift b/Sources/PrivacyDashboard/Model/AllowedPermission.swift similarity index 100% rename from Sources/PrivacyDashboard/Models/AllowedPermission.swift rename to Sources/PrivacyDashboard/Model/AllowedPermission.swift diff --git a/Sources/PrivacyDashboard/Models/CookieConsentInfo.swift b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift similarity index 100% rename from Sources/PrivacyDashboard/Models/CookieConsentInfo.swift rename to Sources/PrivacyDashboard/Model/CookieConsentInfo.swift diff --git a/Sources/PrivacyDashboard/Models/PermissionAuthorizationState.swift b/Sources/PrivacyDashboard/Model/PermissionAuthorizationState.swift similarity index 100% rename from Sources/PrivacyDashboard/Models/PermissionAuthorizationState.swift rename to Sources/PrivacyDashboard/Model/PermissionAuthorizationState.swift diff --git a/Sources/PrivacyDashboard/Models/ProtectionStatus.swift b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift similarity index 100% rename from Sources/PrivacyDashboard/Models/ProtectionStatus.swift rename to Sources/PrivacyDashboard/Model/ProtectionStatus.swift diff --git a/Sources/PrivacyDashboard/Models/TrackerInfo.swift b/Sources/PrivacyDashboard/Model/TrackerInfo.swift similarity index 100% rename from Sources/PrivacyDashboard/Models/TrackerInfo.swift rename to Sources/PrivacyDashboard/Model/TrackerInfo.swift From 6f7f38003101a1b851c4204289b86c75b227fbab Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 13:20:14 +0100 Subject: [PATCH 13/22] schemes removed --- .../xcschemes/BloomFilterWrapper.xcscheme | 66 -- .../xcshareddata/xcschemes/Bookmarks.xcscheme | 66 -- .../xcschemes/BookmarksTestDBBuilder.xcscheme | 151 ---- .../BrowserServicesKit-Package.xcscheme | 665 ------------------ .../xcschemes/BrowserServicesKit.xcscheme | 203 ------ .../xcshareddata/xcschemes/Common.xcscheme | 66 -- .../xcschemes/Configuration.xcscheme | 66 -- .../xcschemes/ContentBlocking.xcscheme | 66 -- .../xcshareddata/xcschemes/Crashes.xcscheme | 66 -- .../xcshareddata/xcschemes/DDGSync.xcscheme | 66 -- .../xcschemes/Navigation.xcscheme | 66 -- .../xcschemes/NetworkProtection.xcscheme | 66 -- .../NetworkProtectionTestUtils.xcscheme | 66 -- .../xcschemes/Networking.xcscheme | 66 -- .../xcschemes/Persistence.xcscheme | 66 -- .../xcschemes/PrivacyDashboard.xcscheme | 66 -- .../xcschemes/RemoteMessaging.xcscheme | 66 -- .../xcschemes/SecureStorage.xcscheme | 66 -- .../xcschemes/SyncDataProviders.xcscheme | 66 -- .../xcshareddata/xcschemes/TestUtils.xcscheme | 66 -- .../xcschemes/UserScript.xcscheme | 66 -- 21 files changed, 2207 deletions(-) delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme deleted file mode 100644 index 9b1131c8c..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/BloomFilterWrapper.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme deleted file mode 100644 index 23aee3a67..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Bookmarks.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme deleted file mode 100644 index 5a69bb3dc..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/BookmarksTestDBBuilder.xcscheme +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme deleted file mode 100644 index 92e405f22..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit-Package.xcscheme +++ /dev/null @@ -1,665 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme deleted file mode 100644 index 0264ce576..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme deleted file mode 100644 index 95a441fd8..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Common.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme deleted file mode 100644 index 6b51c3346..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Configuration.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme deleted file mode 100644 index 367fc91c6..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ContentBlocking.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme deleted file mode 100644 index 4e42a52a4..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Crashes.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme deleted file mode 100644 index 1c11a9671..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/DDGSync.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme deleted file mode 100644 index cc48561b8..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Navigation.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme deleted file mode 100644 index cb8778d4e..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtection.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme deleted file mode 100644 index b78ba8135..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/NetworkProtectionTestUtils.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme deleted file mode 100644 index 691f9d89c..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Networking.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme deleted file mode 100644 index 48755a2cc..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Persistence.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme deleted file mode 100644 index d8e67c057..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/PrivacyDashboard.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme deleted file mode 100644 index 83b1c4c89..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/RemoteMessaging.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme deleted file mode 100644 index 78243413b..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SecureStorage.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme deleted file mode 100644 index 80e0d3b12..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SyncDataProviders.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme deleted file mode 100644 index d2421c529..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/TestUtils.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme deleted file mode 100644 index 3a0272146..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/UserScript.xcscheme +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - From 37c369a7a07b808067ab439ecf37f046d2ffb6e2 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 13:22:35 +0100 Subject: [PATCH 14/22] bsk scheme --- .../xcschemes/BrowserServicesKit.xcscheme | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme new file mode 100644 index 000000000..ba6340ef5 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fe51d88fb793a552040bd03891380a004e92a9b0 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 13:24:54 +0100 Subject: [PATCH 15/22] .swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme reverted --- .../xcschemes/BrowserServicesKit.xcscheme | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme index ba6340ef5..0264ce576 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/BrowserServicesKit.xcscheme @@ -76,6 +76,20 @@ ReferencedContainer = "container:"> + + + + + + + + + + + + Date: Wed, 10 Jan 2024 15:42:24 +0100 Subject: [PATCH 16/22] allowedQueryReservedCharacters moved --- Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index 75792cd99..0778d44d5 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -34,6 +34,8 @@ public struct WebsiteBreakage { case desktop case mobile } + + static let allowedQueryReservedCharacters = CharacterSet(charactersIn: ",") let siteUrl: URL let category: String From 252ebde2517f703e6fe5e98d2e04f79f1504f202 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 15:43:16 +0100 Subject: [PATCH 17/22] accessibility fix --- Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index 0778d44d5..73503dc14 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -35,7 +35,7 @@ public struct WebsiteBreakage { case mobile } - static let allowedQueryReservedCharacters = CharacterSet(charactersIn: ",") + public static let allowedQueryReservedCharacters = CharacterSet(charactersIn: ",") let siteUrl: URL let category: String From 7dd2e929883e02915f554167341b4500339a01d1 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 15:47:53 +0100 Subject: [PATCH 18/22] swiftlint fix --- Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index 73503dc14..c964418a5 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -34,7 +34,7 @@ public struct WebsiteBreakage { case desktop case mobile } - + public static let allowedQueryReservedCharacters = CharacterSet(charactersIn: ",") let siteUrl: URL From 690024ca0c4dffd1ef72799eb2cbd1962fc5adef Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 10 Jan 2024 18:42:09 +0100 Subject: [PATCH 19/22] manufacturer: "Apple" added --- .../PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift | 7 ++++++- Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index c964418a5..a8977054f 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -41,6 +41,7 @@ public struct WebsiteBreakage { let category: String let description: String? let osVersion: String + let manufacturer: String let upgradedHttps: Bool let tdsETag: String? let blockedTrackerDomains: [String] @@ -63,6 +64,7 @@ public struct WebsiteBreakage { category: String, description: String?, osVersion: String, + manufacturer: String, upgradedHttps: Bool, tdsETag: String?, blockedTrackerDomains: [String]?, @@ -77,6 +79,7 @@ public struct WebsiteBreakage { self.category = category self.description = description self.osVersion = osVersion + self.manufacturer = manufacturer self.upgradedHttps = upgradedHttps self.tdsETag = tdsETag self.blockedTrackerDomains = blockedTrackerDomains ?? [] @@ -95,6 +98,7 @@ public struct WebsiteBreakage { category: String, description: String?, osVersion: String, + manufacturer: String, upgradedHttps: Bool, tdsETag: String?, blockedTrackerDomains: [String]?, @@ -112,6 +116,7 @@ public struct WebsiteBreakage { self.category = category self.description = description self.osVersion = osVersion + self.manufacturer = manufacturer self.upgradedHttps = upgradedHttps self.tdsETag = tdsETag self.blockedTrackerDomains = blockedTrackerDomains ?? [] @@ -141,7 +146,7 @@ public struct WebsiteBreakage { "ampUrl": ampURL, "urlParametersRemoved": urlParametersRemoved ? "true" : "false", "os": osVersion, - "manufacturer": "Apple", + "manufacturer": manufacturer, "reportFlow": reportFlow.rawValue, "protectionsState": protectionsState ? "true" : "false" ] diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index 81d5dfa9a..3866e45d4 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -26,6 +26,7 @@ struct WebsiteBreakageMoks { category: "test", description: "test", osVersion: "test", + manufacturer: "Apple", upgradedHttps: true, tdsETag: "test", blockedTrackerDomains: [], @@ -42,6 +43,7 @@ struct WebsiteBreakageMoks { category: "test", description: "test", osVersion: "test", + manufacturer: "Apple", upgradedHttps: true, tdsETag: "test", blockedTrackerDomains: [], From 18b7151dccf74097a3403a3d1a5d14ae4b3921f4 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 11 Jan 2024 11:04:30 +0100 Subject: [PATCH 20/22] report broken site history url privacy sanitisation improved --- Sources/PrivacyDashboard/URL+Trimming.swift | 5 +++-- .../WebsiteBreakage/WebsiteBreakage.swift | 2 +- .../WebsiteBreakageHistoryEntryTests.swift | 8 ++++++-- .../WebsiteBreakageMoks.swift | 17 +++++++++++++++++ .../WebsiteBreakageReporterTests.swift | 1 - 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Sources/PrivacyDashboard/URL+Trimming.swift b/Sources/PrivacyDashboard/URL+Trimming.swift index 95a06a155..721484b8e 100644 --- a/Sources/PrivacyDashboard/URL+Trimming.swift +++ b/Sources/PrivacyDashboard/URL+Trimming.swift @@ -20,15 +20,16 @@ import Foundation extension URL { - /// To limit privacy risk, site URL is trimmed to not include query and fragment + /// To limit privacy risk, site URL is trimmed to not include, path, query and fragments /// - Returns: A privacy-sanitised URL - public func trimmingQueryItemsAndFragment() -> URL { + public func privacySanitised() -> URL { guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return self } components.queryItems = nil components.fragment = nil + components.path = "" guard let result = components.url else { return self diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index a8977054f..1fc15bfe1 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -135,7 +135,7 @@ public struct WebsiteBreakage { /// A dictionary containing all the parameters needed from the Report Broken Site Pixel public var requestParameters: [String: String] { var result = [ - "siteUrl": siteUrl.trimmingQueryItemsAndFragment().absoluteString, + "siteUrl": siteUrl.privacySanitised().absoluteString, "category": category, "description": description ?? "", "upgradedHttps": upgradedHttps ? "true" : "false", diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift index fdbec8b70..76889ae31 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift @@ -34,9 +34,13 @@ final class WebsiteBreakageHistoryEntryTests: XCTestCase { let testDate = Date(timeIntervalSince1970: 1704795829) let breakageHistory = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) let breakageHistory2 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage2, currentDate: testDate) - let breakageHistory3 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) + XCTAssertNotEqual(breakageHistory?.identifier, breakageHistory2?.identifier) + let breakageHistory3 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) XCTAssertEqual(breakageHistory?.identifier, breakageHistory3?.identifier) - XCTAssertNotEqual(breakageHistory?.identifier, breakageHistory2?.identifier) + + let breakage = WebsiteBreakageMoks.testBreakage3 + let trimmedURL = breakage.siteUrl.privacySanitised() + XCTAssertEqual(trimmedURL.absoluteString, "https://www.subdomain.example.com") } } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index 3866e45d4..2736f5c7b 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -54,4 +54,21 @@ struct WebsiteBreakageMoks { protectionsState: true, reportFlow: .appMenu) } + + static var testBreakage3: WebsiteBreakage { + WebsiteBreakage(siteUrl: URL(string: "https://www.subdomain.example.com/some/pathname?t=param")!, + category: "test", + description: "test", + osVersion: "test", + manufacturer: "Apple", + upgradedHttps: true, + tdsETag: "test", + blockedTrackerDomains: [], + installedSurrogates: [], + isGPCEnabled: true, + ampURL: "test", + urlParametersRemoved: true, + protectionsState: true, + reportFlow: .appMenu) + } } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift index 0577ab56a..b0fc676b1 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageReporterTests.swift @@ -48,5 +48,4 @@ final class WebsiteBreakageReporterTests: XCTestCase { waitForExpectations(timeout: 3) } - } From d10308aa31ab81149bc8773140de3b08ba9707dd Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 11 Jan 2024 12:23:20 +0100 Subject: [PATCH 21/22] url sanitation and unit test improved --- Sources/PrivacyDashboard/URL+Trimming.swift | 20 +++++++++++++++++-- .../WebsiteBreakage/WebsiteBreakage.swift | 2 +- .../AdClickAttributionRulesMutatorTests.swift | 7 +++++-- .../Extensions/URLExtensionTests.swift | 1 + .../WebsiteBreakageHistoryEntryTests.swift | 10 ++++++++-- .../WebsiteBreakageMoks.swift | 2 +- 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Sources/PrivacyDashboard/URL+Trimming.swift b/Sources/PrivacyDashboard/URL+Trimming.swift index 721484b8e..4703714b2 100644 --- a/Sources/PrivacyDashboard/URL+Trimming.swift +++ b/Sources/PrivacyDashboard/URL+Trimming.swift @@ -20,8 +20,24 @@ import Foundation extension URL { - /// To limit privacy risk, site URL is trimmed to not include, path, query and fragments - /// - Returns: A privacy-sanitised URL + /// To limit privacy risk, site URL is trimmed to not include query and fragment + /// - Returns: The original URL without query items and fragment + public func trimmingQueryItemsAndFragment() -> URL { + + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { + return self + } + components.queryItems = nil + components.fragment = nil + + guard let result = components.url else { + return self + } + return result + } + + /// To limit privacy risk, site URL is trimmed to include only schema, subdomain and domain + /// - Returns: The original URL without query items, fragment and path public func privacySanitised() -> URL { guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { diff --git a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift index 1fc15bfe1..a8977054f 100644 --- a/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift +++ b/Sources/PrivacyDashboard/WebsiteBreakage/WebsiteBreakage.swift @@ -135,7 +135,7 @@ public struct WebsiteBreakage { /// A dictionary containing all the parameters needed from the Report Broken Site Pixel public var requestParameters: [String: String] { var result = [ - "siteUrl": siteUrl.privacySanitised().absoluteString, + "siteUrl": siteUrl.trimmingQueryItemsAndFragment().absoluteString, "category": category, "description": description ?? "", "upgradedHttps": upgradedHttps ? "true" : "false", diff --git a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift index 441d08cc6..535ba2101 100644 --- a/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift +++ b/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesMutatorTests.swift @@ -98,8 +98,11 @@ final class AdClickAttributionRulesMutatorTests: XCTestCase { return false } - let lData = try JSONEncoder().encode(l) - let rData = try JSONEncoder().encode(r) + let jsonEncoder = JSONEncoder() + jsonEncoder.outputFormatting = .sortedKeys + + let lData = try jsonEncoder.encode(l) + let rData = try jsonEncoder.encode(r) return String(data: lData, encoding: .utf8) == String(data: rData, encoding: .utf8) } diff --git a/Tests/CommonTests/Extensions/URLExtensionTests.swift b/Tests/CommonTests/Extensions/URLExtensionTests.swift index bd42c7cf8..972c21947 100644 --- a/Tests/CommonTests/Extensions/URLExtensionTests.swift +++ b/Tests/CommonTests/Extensions/URLExtensionTests.swift @@ -68,6 +68,7 @@ final class URLExtensionTests: XCTestCase { .init("http://[::]:8080") ] for item in urls { + XCTAssertNotNil(item.url, "URL is nil: \(item.rawValue)") XCTAssertTrue(item.url!.isValid, item.rawValue, line: item.line) } } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift index 76889ae31..ee8658b38 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageHistoryEntryTests.swift @@ -38,9 +38,15 @@ final class WebsiteBreakageHistoryEntryTests: XCTestCase { let breakageHistory3 = WebsiteBreakageHistoryEntry(withBreakage: WebsiteBreakageMoks.testBreakage, currentDate: testDate) XCTAssertEqual(breakageHistory?.identifier, breakageHistory3?.identifier) + } + func testURLSanitation() { let breakage = WebsiteBreakageMoks.testBreakage3 - let trimmedURL = breakage.siteUrl.privacySanitised() - XCTAssertEqual(trimmedURL.absoluteString, "https://www.subdomain.example.com") + + let trimmedURL = breakage.siteUrl.trimmingQueryItemsAndFragment() + XCTAssertEqual(trimmedURL.absoluteString, "https://www.subdomain.example.com/some/pathname") + + let privacySanitisedURL = breakage.siteUrl.privacySanitised() + XCTAssertEqual(privacySanitisedURL.absoluteString, "https://www.subdomain.example.com") } } diff --git a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift index 2736f5c7b..f4eedd248 100644 --- a/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift +++ b/Tests/PrivacyDashboardTests/WebsiteBreakageMoks.swift @@ -56,7 +56,7 @@ struct WebsiteBreakageMoks { } static var testBreakage3: WebsiteBreakage { - WebsiteBreakage(siteUrl: URL(string: "https://www.subdomain.example.com/some/pathname?t=param")!, + WebsiteBreakage(siteUrl: URL(string: "https://www.subdomain.example.com/some/pathname?t=param#aaa")!, category: "test", description: "test", osVersion: "test", From e4d8dca3e4a7ad56065c75363303624a2af3ab4a Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 29 Jan 2024 10:17:40 +0000 Subject: [PATCH 22/22] privacy dashboard v 3.2.0 --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index c4b126419..854453711 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "branch" : "12-14-feat_macos_allow_direct_navigation_to_breakage_form", - "revision" : "24b6260043a888827998124bc12104f4779e010f" + "revision" : "c67d268bf234760f49034a0fe7a6137a1b216b05", + "version" : "3.2.0" } }, { diff --git a/Package.swift b/Package.swift index 1d251dcbb..0e9fa2421 100644 --- a/Package.swift +++ b/Package.swift @@ -39,7 +39,7 @@ let package = Package( .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.2"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), .package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"), - .package(url: "https://github.com/duckduckgo/privacy-dashboard", branch: "12-14-feat_macos_allow_direct_navigation_to_breakage_form"), + .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.2.0"), .package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "4.59.1"), .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"),