Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 9be2437

Browse files
authored
1 parent 9f37e33 commit 9be2437

36 files changed

+385
-508
lines changed

Core/AppConfigurationURLProvider.swift

+6-8
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,13 @@ import Core
2424
struct AppConfigurationURLProvider: ConfigurationURLProviding {
2525

2626
func url(for configuration: Configuration) -> URL {
27-
let appUrls = AppUrls()
28-
2927
switch configuration {
30-
case .bloomFilterSpec: return appUrls.httpsBloomFilterSpec
31-
case .bloomFilterBinary: return appUrls.httpsBloomFilter
32-
case .bloomFilterExcludedDomains: return appUrls.httpsExcludedDomains
33-
case .privacyConfiguration: return appUrls.privacyConfig
34-
case .trackerDataSet: return appUrls.trackerDataSet
35-
case .surrogates: return appUrls.surrogates
28+
case .bloomFilterSpec: return URL.bloomFilterSpec
29+
case .bloomFilterBinary: return URL.bloomFilter
30+
case .bloomFilterExcludedDomains: return URL.bloomFilterExcludedDomains
31+
case .privacyConfiguration: return URL.privacyConfig
32+
case .trackerDataSet: return URL.trackerDataSet
33+
case .surrogates: return URL.surrogates
3634
case .FBConfig: fatalError("This feature is not supported on iOS")
3735
}
3836
}

Core/AppURLs.swift

+259
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
//
2+
// AppURLs.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2017 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import BrowserServicesKit
22+
23+
public extension URL {
24+
25+
private static let base: String = ProcessInfo.processInfo.environment["BASE_URL", default: "https://duckduckgo.com"]
26+
private static let staticBase: String = "https://staticcdn.duckduckgo.com"
27+
28+
static let ddg = URL(string: URL.base)!
29+
30+
static let autocomplete = URL(string: "\(base)/ac/")!
31+
static let emailProtection = URL(string: "\(base)/email")!
32+
static let emailProtectionQuickLink = URL(string: "ddgQuickLink://\(base)/email")!
33+
34+
static let surrogates = URL(string: "\(staticBase)/surrogates.txt")!
35+
static let privacyConfig = URL(string: "\(staticBase)/trackerblocking/config/v2/ios-config.json")!
36+
static let trackerDataSet = URL(string: "\(staticBase)/trackerblocking/v3/apple-tds.json")!
37+
static let bloomFilter = URL(string: "\(staticBase)/https/https-mobile-v2-bloom.bin")!
38+
static let bloomFilterSpec = URL(string: "\(staticBase)/https/https-mobile-v2-bloom-spec.json")!
39+
static let bloomFilterExcludedDomains = URL(string: "\(staticBase)/https/https-mobile-v2-false-positives.json")!
40+
41+
private static var devMode: String { isDebugBuild ? "?test=1" : "" }
42+
static let atb = URL(string: "\(base)/atb.js\(devMode)")!
43+
static let exti = URL(string: "\(base)/exti/\(devMode)")!
44+
static let feedback = URL(string: "\(base)/feedback.js?type=app-feedback")!
45+
46+
static let appStore = URL(string: "https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361")!
47+
48+
static let mac = URL(string: "\(base)/mac")!
49+
static let windows = URL(string: "\(base)/windows")!
50+
51+
static func makeExtiURL(atb: String) -> URL { URL.exti.appendingParameter(name: Param.atb, value: atb) }
52+
53+
static func makeAutocompleteURL(for text: String) throws -> URL {
54+
URL.autocomplete.appendingParameters([
55+
Param.search: text,
56+
Param.enableNavSuggestions: ParamValue.enableNavSuggestions
57+
])
58+
}
59+
60+
static func isDuckDuckGo(domain: String?) -> Bool {
61+
guard let domain = domain, let url = URL(string: "https://\(domain)") else { return false }
62+
return url.isDuckDuckGo
63+
}
64+
65+
var isDuckDuckGo: Bool { isPart(ofDomain: URL.ddg.host!) }
66+
67+
var isDuckDuckGoStatic: Bool {
68+
let staticPaths = ["/settings", "/params"]
69+
guard isDuckDuckGo, staticPaths.contains(path) else { return false }
70+
return true
71+
}
72+
73+
var isDuckDuckGoSearch: Bool {
74+
guard isDuckDuckGo, getParameter(named: Param.search) != nil else { return false }
75+
return true
76+
}
77+
78+
var isDuckDuckGoEmailProtection: Bool { absoluteString.starts(with: URL.emailProtection.absoluteString) }
79+
80+
var searchQuery: String? {
81+
guard isDuckDuckGo else { return nil }
82+
return getParameter(named: Param.search)
83+
}
84+
85+
func applyingSearchHeaderParams() -> URL {
86+
removingParameters(named: [Param.searchHeader]).appendingParameter(name: Param.searchHeader, value: ParamValue.searchHeader)
87+
}
88+
89+
var hasCorrectSearchHeaderParams: Bool {
90+
guard let header = getParameter(named: Param.searchHeader) else { return false }
91+
return header == ParamValue.searchHeader
92+
}
93+
94+
func removingInternalSearchParameters() -> URL {
95+
guard isDuckDuckGoSearch else { return self }
96+
return removingParameters(named: [Param.atb, Param.source, Param.searchHeader])
97+
}
98+
99+
fileprivate enum Param {
100+
101+
static let search = "q"
102+
static let source = "t"
103+
static let atb = "atb"
104+
static let setAtb = "set_atb"
105+
static let activityType = "at"
106+
static let partialHost = "pv1"
107+
static let searchHeader = "ko"
108+
static let vertical = "ia"
109+
static let verticalRewrite = "iar"
110+
static let verticalMaps = "iaxm"
111+
static let enableNavSuggestions = "is_nav"
112+
static let email = "email"
113+
114+
}
115+
116+
fileprivate enum ParamValue {
117+
118+
static let source = "ddg_ios"
119+
static let appUsage = "app_use"
120+
static let searchHeader = "-1"
121+
static let enableNavSuggestions = "1"
122+
static let emailEnabled = "1"
123+
static let emailDisabled = "0"
124+
static let majorVerticals: Set<String> = ["images", "videos", "news"]
125+
126+
}
127+
128+
// MARK: - StatisticsDependentURLs
129+
130+
private static let defaultStatisticsDependentURLFactory = StatisticsDependentURLFactory()
131+
132+
static func makeSearchURL(text: String) -> URL? { defaultStatisticsDependentURLFactory.makeSearchURL(text: text) }
133+
134+
static func makeSearchURL(query: String, queryContext: URL? = nil) -> URL? {
135+
defaultStatisticsDependentURLFactory.makeSearchURL(query: query, queryContext: queryContext)
136+
}
137+
138+
func applyingStatsParams() -> URL { URL.defaultStatisticsDependentURLFactory.applyingStatsParams(to: self) }
139+
140+
static var searchAtb: URL? { defaultStatisticsDependentURLFactory.makeSearchAtbURL() }
141+
142+
static var appAtb: URL? { defaultStatisticsDependentURLFactory.makeAppAtbURL() }
143+
144+
var hasCorrectMobileStatsParams: Bool { URL.defaultStatisticsDependentURLFactory.hasCorrectMobileStatsParams(url: self) }
145+
146+
static func makePixelURL(pixelName: String, formFactor: String? = nil, includeATB: Bool = true) -> URL {
147+
defaultStatisticsDependentURLFactory.makePixelURL(pixelName: pixelName, formFactor: formFactor, includeATB: includeATB)
148+
}
149+
150+
}
151+
152+
public final class StatisticsDependentURLFactory {
153+
154+
private let statisticsStore: StatisticsStore
155+
156+
init(statisticsStore: StatisticsStore = StatisticsUserDefaults()) {
157+
self.statisticsStore = statisticsStore
158+
}
159+
160+
// MARK: Search
161+
162+
func makeSearchURL(text: String) -> URL? {
163+
makeSearchURL(text: text, additionalParameters: [])
164+
}
165+
166+
func makeSearchURL(query: String, queryContext: URL? = nil) -> URL? {
167+
if let url = URL.webUrl(from: query) {
168+
return url
169+
}
170+
171+
var parameters = [String: String]()
172+
if let queryContext = queryContext,
173+
queryContext.isDuckDuckGoSearch,
174+
queryContext.getParameter(named: URL.Param.verticalMaps) == nil,
175+
let vertical = queryContext.getParameter(named: URL.Param.vertical),
176+
URL.ParamValue.majorVerticals.contains(vertical) {
177+
178+
parameters[URL.Param.verticalRewrite] = vertical
179+
}
180+
181+
return makeSearchURL(text: query, additionalParameters: parameters)
182+
}
183+
184+
/**
185+
Generates a search url with the source (t) https://duck.co/help/privacy/t
186+
and cohort (atb) https://duck.co/help/privacy/atb
187+
*/
188+
private func makeSearchURL<C: Collection>(text: String, additionalParameters: C) -> URL
189+
where C.Element == (key: String, value: String) {
190+
let searchURL = URL.ddg
191+
.appendingParameter(name: URL.Param.search, value: text)
192+
.appendingParameters(additionalParameters)
193+
return applyingStatsParams(to: searchURL)
194+
}
195+
196+
func applyingStatsParams(to url: URL) -> URL {
197+
var searchURL = url.removingParameters(named: [URL.Param.source, URL.Param.atb])
198+
.appendingParameter(name: URL.Param.source,
199+
value: URL.ParamValue.source)
200+
201+
if let atbWithVariant = statisticsStore.atbWithVariant {
202+
searchURL = searchURL.appendingParameter(name: URL.Param.atb, value: atbWithVariant)
203+
}
204+
return searchURL
205+
}
206+
207+
// MARK: ATB
208+
209+
func makeSearchAtbURL() -> URL? {
210+
guard let atbWithVariant = statisticsStore.atbWithVariant, let setAtb = statisticsStore.searchRetentionAtb else {
211+
return nil
212+
}
213+
return URL.atb.appendingParameters([
214+
URL.Param.atb: atbWithVariant,
215+
URL.Param.setAtb: setAtb,
216+
URL.Param.email: EmailManager().isSignedIn ? URL.ParamValue.emailEnabled : URL.ParamValue.emailDisabled
217+
])
218+
}
219+
220+
func makeAppAtbURL() -> URL? {
221+
guard let atbWithVariant = statisticsStore.atbWithVariant, let setAtb = statisticsStore.appRetentionAtb else {
222+
return nil
223+
}
224+
return URL.atb.appendingParameters([
225+
URL.Param.activityType: URL.ParamValue.appUsage,
226+
URL.Param.atb: atbWithVariant,
227+
URL.Param.setAtb: setAtb,
228+
URL.Param.email: EmailManager().isSignedIn ? URL.ParamValue.emailEnabled : URL.ParamValue.emailDisabled
229+
])
230+
}
231+
232+
func hasCorrectMobileStatsParams(url: URL) -> Bool {
233+
guard let source = url.getParameter(named: URL.Param.source),
234+
source == URL.ParamValue.source
235+
else { return false }
236+
if let atbWithVariant = statisticsStore.atbWithVariant {
237+
return atbWithVariant == url.getParameter(named: URL.Param.atb)
238+
}
239+
return true
240+
}
241+
242+
// MARK: Pixel
243+
244+
private static let pixelBase: String = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"]
245+
func makePixelURL(pixelName: String, formFactor: String? = nil, includeATB: Bool = true) -> URL {
246+
var urlString = "\(Self.pixelBase)/t/\(pixelName)"
247+
if let formFactor = formFactor {
248+
urlString.append("_ios_\(formFactor)")
249+
}
250+
var url = URL(string: urlString)!
251+
252+
if includeATB {
253+
url = url.appendingParameter(name: URL.Param.atb, value: statisticsStore.atbWithVariant ?? "")
254+
}
255+
256+
return url
257+
}
258+
259+
}

0 commit comments

Comments
 (0)