diff --git a/Core/FingerprintUserScript.swift b/Core/FingerprintUserScript.swift new file mode 100644 index 0000000000..c00c5e6266 --- /dev/null +++ b/Core/FingerprintUserScript.swift @@ -0,0 +1,37 @@ +// +// FingerprintUserScript.swift +// DuckDuckGo +// +// Copyright © 2020 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 UIKit +import WebKit + +public class FingerprintUserScript: NSObject, UserScript { + public var source: String { + return loadJS("fingerprint") + } + + public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart + + public var forMainFrameOnly: Bool = false + + public var messageNames: [String] = [] + + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + + } +} diff --git a/Core/fingerprint.js b/Core/fingerprint.js new file mode 100644 index 0000000000..27a6263cdb --- /dev/null +++ b/Core/fingerprint.js @@ -0,0 +1,201 @@ +// +// fingerprint.js +// DuckDuckGo +// +// Copyright © 2017 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. +// + +(function protect () { + // Property values to be set and their original values. + const fingerprintPropertyValues = { + 'screen': { + 'availTop': { + 'object': 'screen', + 'origValue': screen.availTop, + 'targetValue': 0 + }, + 'availLeft': { + 'object': 'screen', + 'origValue': screen.availLeft, + 'targetValue': 0 + }, + 'availWidth': { + 'object': 'screen', + 'origValue': screen.availWidth, + 'targetValue': screen.width + }, + 'availHeight': { + 'object': 'screen', + 'origValue': screen.availHeight, + 'targetValue': screen.height + }, + 'screenY': { + 'object': 'window', + 'origValue': window.screenY, + 'targetValue': 0 + }, + 'screenLeft': { + 'object': 'window', + 'origValue': window.screenLeft, + 'targetValue': 0 + }, + 'colorDepth': { + 'object': 'screen', + 'origValue': screen.colorDepth, + 'targetValue': 24 + }, + 'pixelDepth': { + 'object': 'screen', + 'origValue': screen.pixelDepth, + 'targetValue': 24 + } + }, + 'options': { + 'doNotTrack': { + 'object': 'navigator', + 'origValue': navigator.doNotTrack, + 'targetValue': false + } + } + } + + /* + * Return device specific battery value that prevents fingerprinting. + * On Desktop/Laptop - fully charged and plugged in. + * On Mobile, should not plugged in with random battery values every load. + * Property event functions are also defined, for setting later. + */ + function getBattery () { + let battery = {} + battery.value = { + charging: true, + chargingTime: 0, + dischargingTime: Infinity, + level: 1 + } + battery.properties = ['onchargingchange', 'onchargingtimechange', 'ondischargingtimechange', 'onlevelchange'] + return battery + } + + /** + * For each property defined on the object, update it with the target value. + */ + function buildScriptProperties () { + let script = '' + for (const category in fingerprintPropertyValues) { + for (const [name, prop] of Object.entries(fingerprintPropertyValues[category])) { + // Don't update if existing value is undefined or null + if (!(prop.origValue === undefined)) { + script += `Object.defineProperty(${prop.object}, "${name}", { value: ${prop.targetValue} });\n` + } + } + } + return script + } + + /** + * Build a script that overwrites the Battery API if present in the browser. + * It will return the values defined in the getBattery function to the client, + * as well as prevent any script from listening to events. + */ + function buildBatteryScript () { + if (navigator.getBattery) { + const battery = getBattery() + let batteryScript = ` + navigator.getBattery = function getBattery () { + let battery = ${JSON.stringify(battery.value)} + ` + for (const prop of battery.properties) { + // Prevent setting events via event handlers + batteryScript += ` + Object.defineProperty(battery, '${prop}', { + enumerable: true, + configurable: false, + writable: false, + value: undefined + }) + ` + } + + // Wrap event listener functions so handlers aren't added + for (const handler of ['addEventListener']) { + batteryScript += ` + battery.${handler} = function ${handler} () { + return + } + ` + } + batteryScript += ` + return Promise.resolve(battery) + } + ` + return batteryScript + } else { + return '' + } + } + + /** + * Temporary storage can be used to determine hard disk usage and size. + * This will limit the max storage to 4GB without completely disabling the + * feature. + */ + function modifyTemporaryStorage () { + const script = ` + if (navigator.webkitTemporaryStorage) { + try { + const org = navigator.webkitTemporaryStorage.queryUsageAndQuota + navigator.webkitTemporaryStorage.queryUsageAndQuota = function queryUsageAndQuota (callback, err) { + const modifiedCallback = function (usedBytes, grantedBytes) { + const maxBytesGranted = 4 * 1024 * 1024 * 1024 + const spoofedGrantedBytes = Math.min(grantedBytes, maxBytesGranted) + callback(usedBytes, spoofedGrantedBytes) + } + org.call(navigator.webkitTemporaryStorage, modifiedCallback, err) + } + } + catch(e) {} + } + ` + return script + } + + /** + * All the steps for building the injection script. Should only be done at initial page load. + */ + function buildInjectionScript () { + let script = buildScriptProperties() + script += modifyTemporaryStorage() + script += buildBatteryScript() + return script + } + + /** + * Inject all the overwrites into the page. + */ + function inject (scriptToInject, removeAfterExec) { + // Inject into main page + let e = document.createElement('script') + e.textContent = scriptToInject; + (document.head || document.documentElement).appendChild(e) + + if (removeAfterExec) { + e.remove() + } + } + + const injectionScript = buildInjectionScript() + inject(injectionScript) +})() diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5dea59aba1..241537bcf4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12,12 +12,15 @@ 0254AC2024ED8B82004855DF /* ContentBlockerRulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC1F24ED8B82004855DF /* ContentBlockerRulesManager.swift */; }; 0254AC2224EDAC41004855DF /* ContentBlockerRulesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */; }; 0254AC2424EDACDE004855DF /* contentblockerrules.js in Resources */ = {isa = PBXBuildFile; fileRef = 0254AC2324EDACDE004855DF /* contentblockerrules.js */; }; + 025CD01025826035001CD5BB /* FingerprintUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */; }; 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */; }; 02C57C5525153330009E5129 /* DoNotSellUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */; }; 02C57C5F251533CE009E5129 /* donotsell.js in Resources */ = {isa = PBXBuildFile; fileRef = 02C57C5E251533CE009E5129 /* donotsell.js */; }; 02CA904924F6BFE700D41DDF /* navigatorsharepatch.js in Resources */ = {isa = PBXBuildFile; fileRef = 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */; }; 02CA904B24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */; }; 02CA904D24FD2DB000D41DDF /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; }; + 02DC6A52255D8C0A00B03BC2 /* fingerprint.js in Resources */ = {isa = PBXBuildFile; fileRef = 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */; }; + 02DC6A64255D8C5000B03BC2 /* FingerprintUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */; }; 0A6CC0EF23904D5400E4F627 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0A6CC0EE23904D5400E4F627 /* Settings.bundle */; }; 1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */; }; 1CB7B82323CEA28300AA24EA /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */; }; @@ -577,6 +580,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 025CCFE72582601C001CD5BB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84E341911E2F7EFB00BDBA6F; + remoteInfo = DuckDuckGo; + }; 83491190217F491200610F35 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; @@ -697,12 +707,17 @@ 0254AC1F24ED8B82004855DF /* ContentBlockerRulesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesManager.swift; sourceTree = ""; }; 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesUserScript.swift; sourceTree = ""; }; 0254AC2324EDACDE004855DF /* contentblockerrules.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = contentblockerrules.js; sourceTree = ""; }; + 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintUITest.swift; sourceTree = ""; }; + 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FingerprintingUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 025CCFE62582601C001CD5BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellSettingsViewController.swift; sourceTree = ""; }; 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellUserScript.swift; sourceTree = ""; }; 02C57C5E251533CE009E5129 /* donotsell.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = donotsell.js; sourceTree = ""; }; 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = navigatorsharepatch.js; sourceTree = ""; }; 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSharePatchUserScript.swift; sourceTree = ""; }; 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockingRulesTests.swift; sourceTree = ""; }; + 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fingerprint.js; sourceTree = ""; }; + 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintUserScript.swift; sourceTree = ""; }; 0A6CC0EE23904D5400E4F627 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; @@ -2002,6 +2017,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 025CCFDF2582601C001CD5BB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491184217F491200610F35 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2099,6 +2121,23 @@ name = ContentBlockerRules; sourceTree = ""; }; + 025CCFE32582601C001CD5BB /* FingerprintingUITests */ = { + isa = PBXGroup; + children = ( + 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */, + 025CCFE62582601C001CD5BB /* Info.plist */, + ); + path = FingerprintingUITests; + sourceTree = ""; + }; + 02DC6A50255D8BE700B03BC2 /* fingerprinting */ = { + isa = PBXGroup; + children = ( + 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */, + ); + name = fingerprinting; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -2343,6 +2382,7 @@ 85F21DAE210F5E32002631A6 /* IntegrationTests */, 85482D892462DCD100EDEDD1 /* OpenAction */, 8512EA5224ED30D20073EE19 /* Widgets */, + 025CCFE32582601C001CD5BB /* FingerprintingUITests */, F1AA545F1E48D90700223211 /* Frameworks */, 84E341931E2F7EFB00BDBA6F /* Products */, 83ED3B8D1FA8E63700B47556 /* README.md */, @@ -2364,6 +2404,7 @@ 98A54A8122AFCB2C00E541F4 /* Instruments.instrdst */, 85482D882462DCD100EDEDD1 /* OpenAction.appex */, 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */, + 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */, ); name = Products; sourceTree = ""; @@ -3287,6 +3328,7 @@ 836A941C247F23C600BF8EF5 /* UserAgentManager.swift */, 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */, 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */, + 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */, ); name = Web; sourceTree = ""; @@ -3433,6 +3475,7 @@ F18608DE1E5E648100361C30 /* Javascript */ = { isa = PBXGroup; children = ( + 02DC6A50255D8BE700B03BC2 /* fingerprinting */, 835750931F8E9A610059E07B /* contentblocking */, F18608DF1E5E649400361C30 /* document.js */, ); @@ -3743,6 +3786,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 025CCFE12582601C001CD5BB /* FingerprintingUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 025CCFEB2582601C001CD5BB /* Build configuration list for PBXNativeTarget "FingerprintingUITests" */; + buildPhases = ( + 025CCFDE2582601C001CD5BB /* Sources */, + 025CCFDF2582601C001CD5BB /* Frameworks */, + 025CCFE02582601C001CD5BB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 025CCFE82582601C001CD5BB /* PBXTargetDependency */, + ); + name = FingerprintingUITests; + productName = FingerprintingUITests; + productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 83491186217F491200610F35 /* BookmarksTodayExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 83491193217F491300610F35 /* Build configuration list for PBXNativeTarget "BookmarksTodayExtension" */; @@ -3955,10 +4016,14 @@ 84E3418A1E2F7EFB00BDBA6F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1220; LastUpgradeCheck = 1020; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { + 025CCFE12582601C001CD5BB = { + CreatedOnToolsVersion = 12.2; + TestTargetID = 84E341911E2F7EFB00BDBA6F; + }; 83491186217F491200610F35 = { CreatedOnToolsVersion = 10.0; DevelopmentTeam = HKE973VLUW; @@ -4098,11 +4163,19 @@ 98A54A8022AFCB2C00E541F4 /* Instruments */, 85F21DAC210F5E32002631A6 /* IntegrationTests */, 84E341A51E2F7EFB00BDBA6F /* UnitTests */, + 025CCFE12582601C001CD5BB /* FingerprintingUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 025CCFE02582601C001CD5BB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491185217F491200610F35 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4289,6 +4362,7 @@ buildActionMask = 2147483647; files = ( 83E2D2B4253CC16B005605F5 /* httpsMobileV2BloomSpec.json in Resources */, + 02DC6A52255D8C0A00B03BC2 /* fingerprint.js in Resources */, 987AFB6C22AE83C2001B84CF /* debug-messaging-enabled.js in Resources */, 98B001B0251EABB40090EC07 /* InfoPlist.strings in Resources */, F18608E01E5E649400361C30 /* document.js in Resources */, @@ -4433,6 +4507,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 025CCFDE2582601C001CD5BB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 025CD01025826035001CD5BB /* FingerprintUITest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491183217F491200610F35 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4817,6 +4899,7 @@ 85C271DD1FD04459007216B4 /* HTTPSUpgrade.swift in Sources */, 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */, 853A717620F62FE800FE60BC /* Pixel.swift in Sources */, + 02DC6A64255D8C5000B03BC2 /* FingerprintUserScript.swift in Sources */, 85BDC31224339E080053DB07 /* DocumentUserScript.swift in Sources */, 0201A45124ED821E00C6641C /* ContentBlockerRule.swift in Sources */, 9876B75E2232B36900D81D9F /* TabInstrumentation.swift in Sources */, @@ -4903,6 +4986,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 025CCFE82582601C001CD5BB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84E341911E2F7EFB00BDBA6F /* DuckDuckGo */; + targetProxy = 025CCFE72582601C001CD5BB /* PBXContainerItemProxy */; + }; 83491191217F491200610F35 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 83491186217F491200610F35 /* BookmarksTodayExtension */; @@ -5946,6 +6034,61 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 025CCFE92582601C001CD5BB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = Debug; + }; + 025CCFEA2582601C001CD5BB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = Release; + }; 83491194217F491300610F35 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6585,6 +6728,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 025CCFEB2582601C001CD5BB /* Build configuration list for PBXNativeTarget "FingerprintingUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 025CCFE92582601C001CD5BB /* Debug */, + 025CCFEA2582601C001CD5BB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 83491193217F491300610F35 /* Build configuration list for PBXNativeTarget "BookmarksTodayExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme index ffb26fd890..9b178ff66a 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme @@ -54,6 +54,16 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + - - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme index cb7d427c02..4f662a0cc7 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme @@ -11,15 +11,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - @@ -31,6 +22,16 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + - - - - diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme index f1e63c8353..a0cd760595 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme @@ -43,6 +43,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + + + + + + + + + Bool { + #if targetEnvironment(simulator) + if ProcessInfo.processInfo.environment["UITESTING"] == "true" { + // Disable hardware keyboards. + let setHardwareLayout = NSSelectorFromString("setHardwareLayout:") + UITextInputMode.activeInputModes + // Filter `UIKeyboardInputMode`s. + .filter({ $0.responds(to: setHardwareLayout) }) + .forEach { $0.perform(setHardwareLayout, with: nil) } + } + #endif + _ = UserAgentManager.shared testing = ProcessInfo().arguments.contains("testing") if testing { diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index feb1143ccf..22473ffd2d 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -154,6 +154,7 @@ class TabViewController: UIViewController { private var loginFormDetectionScript = LoginFormDetectionUserScript() private var contentBlockerScript = ContentBlockerUserScript() private var contentBlockerRulesScript = ContentBlockerRulesUserScript() + private var fingerprintScript = FingerprintUserScript() private var navigatorPatchScript = NavigatorSharePatchUserScript() private var doNotSellScript = DoNotSellUserScript() private var documentScript = DocumentUserScript() @@ -228,6 +229,7 @@ class TabViewController: UIViewController { navigatorPatchScript, contentBlockerScript, contentBlockerRulesScript, + fingerprintScript, faviconScript, fullScreenVideoScript ] diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift new file mode 100644 index 0000000000..7da0a0cc3a --- /dev/null +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -0,0 +1,117 @@ +// +// FingerprintUITest.swift +// DuckDuckGo +// +// Copyright © 2020 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. +// + +// swiftlint:disable line_length + +import XCTest + +class FingerprintUITest: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + + let app = XCUIApplication() + + app.launchEnvironment = [ + "DAXDIALOGS": "false", + "ONBOARDING": "false", + "VARIANT": "sc", + "UITESTING": "true" + ] + + app.launch() + + // Add a bookmark to edit to a bookmarklet later + app.searchFields["searchEntry"].tap() + app + .searchFields["searchEntry"] + .typeText("https://duckduckgo.com\n") + + sleep(5) // let site load + + app.buttons["Browsing Menu"].tap() + if app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].waitForExistence(timeout: 2) { + app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() + } else { + app.sheets.scrollViews.otherElements.buttons["Cancel"].tap() + } + } + + override func tearDownWithError() throws { + // Remove the bookmark we added + let app = XCUIApplication() + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + let tablesQuery = app.tables + tablesQuery.staticTexts["DuckDuckGo — Privacy, simplified."].swipeLeft() + tablesQuery.buttons["Delete"].tap() + app.navigationBars["Bookmarks"].buttons["Done"].tap() + } + + func test() throws { + let app = XCUIApplication() + + if app.toolbars["Toolbar"].buttons["Bookmarks"].waitForExistence(timeout: 2) { + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + } else { + XCTFail("Bookmarks button missing") + } + + // Edit bookmark into bookmarklet to verify fingerprinting test + let bookmarksNavigationBar = app.navigationBars["Bookmarks"] + _ = bookmarksNavigationBar.buttons["Edit"].waitForExistence(timeout: 25) + bookmarksNavigationBar.buttons["Edit"].tap() + if app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: 25) { + app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + } else { + XCTFail("Could not find bookmark") + } + app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"].tap(withNumberOfTaps: 3, numberOfTouches: 1) + app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"] + .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") + app.alerts["Edit Bookmark"].scrollViews.otherElements.buttons["Save"].tap() + bookmarksNavigationBar.buttons["Done"].tap() + bookmarksNavigationBar.buttons["Done"].tap() + + // Clear all tabs and data + app.toolbars["Toolbar"].buttons["Fire"].tap() + app.sheets.scrollViews.otherElements.buttons["Close Tabs and Clear Data"].tap() + + sleep(2) + + // Go to fingerprinting test page + app + .searchFields["searchEntry"] + .tap() + app + .searchFields["searchEntry"] + .typeText("https://privacy-test-pages.glitch.me/privacy-protections/fingerprinting/?run\n") + let webview = app.webViews.firstMatch + XCTAssertTrue(webview.staticTexts["⚠️ Please note that:"].firstMatch.waitForExistence(timeout: 25), "Page not loaded") + + // Run the new bookmarklet + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + + // Verify the test passed + XCTAssertTrue(webview.staticTexts["TEST PASSED"].waitForExistence(timeout: 25), "Test not run") + } + +} + +// swiftlint:enable line_length diff --git a/FingerprintingUITests/Info.plist b/FingerprintingUITests/Info.plist new file mode 100644 index 0000000000..64d65ca495 --- /dev/null +++ b/FingerprintingUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + +