+
+
+
+
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift
index 616c17312133..add931f469e5 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift
@@ -59,6 +59,8 @@ extension UTType {
// MARK: WKNavigationDelegate
extension BrowserViewController: WKNavigationDelegate {
+ private static let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "navigation")
+
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
{
if tabManager.selectedTab?.webView !== webView {
@@ -314,8 +316,8 @@ extension BrowserViewController: WKNavigationDelegate {
tab.loadRequest(modifiedRequest)
if let url = modifiedRequest.url {
- ContentBlockerManager.log.debug(
- "Redirected user to `\(url.absoluteString, privacy: .private)`"
+ Self.log.debug(
+ "Redirected to `\(url.absoluteString, privacy: .private)`"
)
}
@@ -606,6 +608,26 @@ extension BrowserViewController: WKNavigationDelegate {
let response = navigationResponse.response
let responseURL = response.url
let tab = tab(for: webView)
+ let isInvalid: Bool
+ if let httpResponse = response as? HTTPURLResponse {
+ isInvalid = httpResponse.statusCode >= 400
+ } else {
+ isInvalid = true
+ }
+
+ // Handle invalid upgrade to https
+ if isInvalid,
+ navigationResponse.isForMainFrame,
+ let responseURL = responseURL,
+ let tab = tab,
+ let originalResponse = handleInvalidHTTPSUpgrade(
+ tab: tab,
+ responseURL: responseURL
+ )
+ {
+ tab.loadRequest(originalResponse)
+ return .cancel
+ }
// Store the response in the tab
if let responseURL = responseURL {
@@ -1001,6 +1023,17 @@ extension BrowserViewController: WKNavigationDelegate {
) {
guard let tab = tab(for: webView) else { return }
+ // Handle invalid upgrade to https
+ if let responseURL = webView.url,
+ let response = handleInvalidHTTPSUpgrade(
+ tab: tab,
+ responseURL: responseURL
+ )
+ {
+ tab.loadRequest(response)
+ return
+ }
+
// Ignore the "Frame load interrupted" error that is triggered when we cancel a request
// to open an external application and hand it over to UIApplication.openURL(). The result
// will be that we switch to the external app, for example the app store, while keeping the
@@ -1708,16 +1741,74 @@ extension BrowserViewController: WKUIDelegate {
modifiedRequest.setValue(headerValue, forHTTPHeaderField: headerKey)
}
+ Self.log.debug(
+ "Debouncing `\(requestURL.absoluteString)`"
+ )
+
return modifiedRequest
}
}
// Handle query param stripping
- return navigationAction.request.stripQueryParams(
+ if let request = navigationAction.request.stripQueryParams(
initiatorURL: tab.committedURL,
redirectSourceURL: tab.redirectSourceURL,
isInternalRedirect: tab.isInternalRedirect
- )
+ ) {
+ Self.log.debug(
+ "Stripping query params for `\(requestURL.absoluteString)`"
+ )
+ return request
+ }
+
+ // Attempt to upgrade to HTTPS
+ if ShieldPreferences.httpsUpgradeLevel.isEnabled,
+ let upgradedURL = braveCore.httpsUpgradeExceptionsService.upgradeToHTTPS(for: requestURL)
+ {
+ Self.log.debug(
+ "Upgrading `\(requestURL.absoluteString)` to HTTPS"
+ )
+
+ tab.upgradedHTTPSRequest = navigationAction.request
+ var request = navigationAction.request
+ request.url = upgradedURL
+ return request
+ }
+
+ return nil
+ }
+
+ /// Upon an invalid response, check that we need to roll back any HTTPS upgrade
+ /// or show the interstitial page
+ private func handleInvalidHTTPSUpgrade(tab: Tab, responseURL: URL) -> URLRequest? {
+ // Handle invalid upgrade to https
+ guard responseURL.scheme == "https",
+ let originalRequest = tab.upgradedHTTPSRequest,
+ let originalURL = originalRequest.url,
+ responseURL.baseDomain == originalURL.baseDomain
+ else {
+ braveCore.httpsUpgradeExceptionsService.addException(for: responseURL)
+ return nil
+ }
+
+ if ShieldPreferences.httpsUpgradeLevel.isStrict,
+ let url = originalURL.encodeEmbeddedInternalURL(for: .httpBlocked)
+ {
+ Self.log.debug(
+ "Show http blocked interstitial for `\(originalURL.absoluteString)`"
+ )
+
+ let request = PrivilegedRequest(url: url) as URLRequest
+ return request
+ } else {
+ Self.log.debug(
+ "Revert HTTPS upgrade for `\(originalURL.absoluteString)`"
+ )
+
+ tab.upgradedHTTPSRequest = nil
+ braveCore.httpsUpgradeExceptionsService.addException(for: originalURL)
+ return originalRequest
+ }
}
}
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift
index eeca31ae7de7..86ffa7bd17f5 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift
@@ -502,6 +502,7 @@ public class BrowserViewController: UIViewController {
Preferences.Playlist.syncSharedFoldersAutomatically.observe(from: self)
Preferences.NewTabPage.backgroundSponsoredImages.observe(from: self)
ShieldPreferences.blockAdsAndTrackingLevelRaw.observe(from: self)
+ ShieldPreferences.httpsUpgradeLevelRaw.observe(from: self)
Preferences.Privacy.screenTimeEnabled.observe(from: self)
pageZoomListener = NotificationCenter.default.addObserver(
@@ -2658,6 +2659,7 @@ extension BrowserViewController: TabDelegate {
ErrorPageHelper(certStore: profile.certStore),
SessionRestoreScriptHandler(tab: tab),
BlockedDomainScriptHandler(tab: tab),
+ HTTPBlockedScriptHandler(tab: tab, exceptionService: braveCore.httpsUpgradeExceptionsService),
PrintScriptHandler(browserController: self, tab: tab),
CustomSearchScriptHandler(tab: tab),
NightModeScriptHandler(tab: tab),
@@ -3313,7 +3315,7 @@ extension BrowserViewController: PreferencesObserver {
?? Preferences.General.defaultPageZoomLevel.value
$0.webView?.setValue(zoomLevel, forKey: PageZoomHandler.propertyName)
})
- case Preferences.Shields.httpsEverywhere.key:
+ case ShieldPreferences.httpsUpgradeLevelRaw.key:
tabManager.reset()
tabManager.reloadSelectedTab()
case Preferences.Privacy.blockAllCookies.key,
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/Handlers/HTTPBlockedHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/Handlers/HTTPBlockedHandler.swift
new file mode 100644
index 000000000000..f0cb1820a74e
--- /dev/null
+++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/Handlers/HTTPBlockedHandler.swift
@@ -0,0 +1,72 @@
+// Copyright 2024 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+import BraveShared
+import BraveShields
+import Foundation
+import Shared
+import WebKit
+
+public class HTTPBlockedHandler: InternalSchemeResponse {
+ public static let path = InternalURL.Path.httpBlocked.rawValue
+
+ public init() {}
+
+ public func response(forRequest request: URLRequest) -> (URLResponse, Data)? {
+ guard let url = request.url, let internalURL = InternalURL(url),
+ let originalURL = internalURL.extractedUrlParam
+ else { return nil }
+ let response = InternalSchemeHandler.response(forUrl: internalURL.url)
+
+ guard let asset = Bundle.module.path(forResource: "HTTPBlocked", ofType: "html") else {
+ assert(false)
+ return nil
+ }
+
+ var html = try? String(contentsOfFile: asset)
+ .replacingOccurrences(
+ of: "%page_title%",
+ with: Strings.Shields.siteIsNotSecure
+ )
+ .replacingOccurrences(
+ of: "%blocked_title%",
+ with: String.localizedStringWithFormat(
+ Strings.Shields.theConnectionIsNotSecure,
+ "\(originalURL.domainURL.absoluteDisplayString)"
+ )
+ )
+ .replacingOccurrences(
+ of: "%blocked_description%",
+ with: Strings.Shields.httpBlockedDescription
+ )
+ .replacingOccurrences(
+ of: "%learn_more%",
+ with: Strings.learnMore
+ )
+ .replacingOccurrences(
+ of: "%proceed_action%",
+ with: Strings.Shields.domainBlockedProceedAction
+ )
+ .replacingOccurrences(of: "%go_back_action%", with: Strings.Shields.domainBlockedGoBackAction)
+ .replacingOccurrences(
+ of: "%message_handler%",
+ with: HTTPBlockedScriptHandler.messageHandlerName
+ )
+ .replacingOccurrences(of: "%security_token%", with: UserScriptManager.securityToken)
+
+ if #available(iOS 16.0, *) {
+ html = html?.replacingOccurrences(
+ of: "",
+ with: ""
+ )
+ }
+
+ guard let data = html?.data(using: .utf8) else {
+ return nil
+ }
+
+ return (response, data)
+ }
+}
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift
index d537beccdea3..55a44f19a51a 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift
@@ -3,6 +3,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
import BraveCore
+import BraveShields
import BraveWallet
import CertificateUtilities
import Data
@@ -302,6 +303,10 @@ class Tab: NSObject {
var playlistItem: PlaylistInfo?
var playlistItemState: PlaylistItemAddedState = .none
+ /// This is the request that was upgraded to HTTPS
+ /// This allows us to rollback the upgrade when we encounter a 4xx+
+ var upgradedHTTPSRequest: URLRequest?
+
/// The tabs new tab page controller.
///
/// Should be setup in BVC then assigned here for future use.
@@ -459,7 +464,7 @@ class Tab: NSObject {
configuration!.allowsInlineMediaPlayback = true
// Enables Zoom in website by ignoring their javascript based viewport Scale limits.
configuration!.ignoresViewportScaleLimits = true
- configuration!.upgradeKnownHostsToHTTPS = Preferences.Shields.httpsEverywhere.value
+ configuration!.upgradeKnownHostsToHTTPS = ShieldPreferences.httpsUpgradeLevel.isEnabled
configuration!.enablePageTopColorSampling()
if configuration!.urlSchemeHandler(forURLScheme: InternalURL.scheme) == nil {
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift
index 210a30bd0f0f..563be5edf3b7 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift
@@ -77,6 +77,11 @@ import os
}
}
}
+ @Published var httpsUpgradeLevel: HTTPSUpgradeLevel {
+ didSet {
+ ShieldPreferences.httpsUpgradeLevel = httpsUpgradeLevel
+ }
+ }
typealias ClearDataCallback = @MainActor (Bool, Bool) -> Void
@Published var clearableSettings: [ClearableSetting]
@@ -105,6 +110,7 @@ import os
self.isP3AEnabled = p3aUtilities.isP3AEnabled
self.clearDataCallback = clearDataCallback
self.adBlockAndTrackingPreventionLevel = ShieldPreferences.blockAdsAndTrackingLevel
+ self.httpsUpgradeLevel = ShieldPreferences.httpsUpgradeLevel
self.isDeAmpEnabled = deAmpPrefs.isDeAmpEnabled
self.isDebounceEnabled = debounceService?.isEnabled ?? false
diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift
index cda63f9f983e..8604f0e5460a 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift
@@ -38,11 +38,20 @@ struct DefaultShieldsViewView: View {
}
.listRowBackground(Color(.secondaryBraveGroupedBackground))
- OptionToggleView(
- title: Strings.HTTPSEverywhere,
- subtitle: Strings.HTTPSEverywhereDescription,
- option: Preferences.Shields.httpsEverywhere
- )
+ Picker(selection: $settings.httpsUpgradeLevel) {
+ ForEach(HTTPSUpgradeLevel.allCases) { level in
+ Text(level.localizedTitle)
+ .foregroundColor(Color(.secondaryBraveLabel))
+ .tag(level)
+ }
+ } label: {
+ LabelView(
+ title: Strings.Shields.upgradeConnectionsToHTTPS,
+ subtitle: nil
+ )
+ }
+ .listRowBackground(Color(.secondaryBraveGroupedBackground))
+
ToggleView(
title: Strings.autoRedirectAMPPages,
subtitle: Strings.autoRedirectAMPPagesDescription,
@@ -169,3 +178,17 @@ extension ShieldLevel: Identifiable {
}
}
}
+
+extension HTTPSUpgradeLevel: Identifiable {
+ public var id: String {
+ return rawValue
+ }
+
+ public var localizedTitle: String {
+ switch self {
+ case .strict: return Strings.Shields.httpsUpgradeLevelStrict
+ case .disabled: return Strings.Shields.trackersAndAdsBlockingDisabled
+ case .standard: return Strings.Shields.trackersAndAdsBlockingStandard
+ }
+ }
+}
diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift
index d8f7d3d5b82c..bfdac0f56e41 100644
--- a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift
+++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift
@@ -75,6 +75,7 @@ class BlockedDomainScriptHandler: TabContentScript {
// All testing indicates no, so we will not handle.
// If we find it is, then we need to disable or hide the "Go Back" button in these cases.
// But this would require heavy changes or ugly mechanisms to InternalSchemeHandler.
+ tab?.goBack()
return
}
diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/HTTPBlockedScriptHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/HTTPBlockedScriptHandler.swift
new file mode 100644
index 000000000000..694460466c79
--- /dev/null
+++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/HTTPBlockedScriptHandler.swift
@@ -0,0 +1,88 @@
+// Copyright 2023 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+import BraveCore
+import Foundation
+import Shared
+import WebKit
+
+class HTTPBlockedScriptHandler: TabContentScript {
+ private weak var tab: Tab?
+ private weak var exceptionService: HTTPSUpgradeExceptionsService?
+
+ required init(tab: Tab, exceptionService: HTTPSUpgradeExceptionsService) {
+ self.tab = tab
+ self.exceptionService = exceptionService
+ }
+
+ static let scriptName = "HTTPBlockedScript"
+ static let scriptId = UUID().uuidString
+ static let messageHandlerName = "\(scriptName)_\(messageUUID)"
+ static let scriptSandbox: WKContentWorld = .page
+ static let userScript: WKUserScript? = nil
+
+ func userContentController(
+ _ userContentController: WKUserContentController,
+ didReceiveScriptMessage message: WKScriptMessage,
+ replyHandler: (Any?, String?) -> Void
+ ) {
+ defer { replyHandler(nil, nil) }
+
+ if !verifyMessage(message: message, securityToken: UserScriptManager.securityToken) {
+ assertionFailure("Missing required security token.")
+ return
+ }
+
+ guard let params = message.body as? [String: AnyObject],
+ let action = params["action"] as? String
+ else {
+ assertionFailure("Missing required params.")
+ return
+ }
+
+ switch action {
+ case "didProceed":
+ didProceed()
+ case "didGoBack":
+ didGoBack()
+ default:
+ assertionFailure("Unhandled action `\(action)`")
+ }
+ }
+
+ private func didProceed() {
+ guard let url = tab?.upgradedHTTPSRequest?.url ?? tab?.url?.strippedInternalURL else {
+ // assertionFailure(
+ // "There should be no way this method can be triggered if the tab is not on an internal url"
+ // )
+ return
+ }
+
+ // When restoring the page, `upgradedHTTPSRequest` will be nil
+ // So we default to the embedded internal page URL
+ let request = tab?.upgradedHTTPSRequest ?? URLRequest(url: url)
+ exceptionService?.addException(for: url)
+ tab?.loadRequest(request)
+ }
+
+ private func didGoBack() {
+ let etldP1 =
+ tab?.upgradedHTTPSRequest?.url?.baseDomain
+ ?? tab?.url?.strippedInternalURL?.baseDomain
+
+ guard
+ let listItem = tab?.backList?.reversed().first(where: {
+ // It is not the blocked page or the internal page
+ $0.url.baseDomain != etldP1 && $0.url != tab?.webView?.url
+ })
+ else {
+ tab?.goBack()
+ return
+ }
+
+ tab?.upgradedHTTPSRequest = nil
+ tab?.goToBackForwardListItem(listItem)
+ }
+}
diff --git a/ios/brave-ios/Sources/Brave/Migration/Migration.swift b/ios/brave-ios/Sources/Brave/Migration/Migration.swift
index 895b3b40f9c4..084fa85ab4fb 100644
--- a/ios/brave-ios/Sources/Brave/Migration/Migration.swift
+++ b/ios/brave-ios/Sources/Brave/Migration/Migration.swift
@@ -24,6 +24,7 @@ public class Migration {
Preferences.migratePreferences(keyPrefix: keyPrefix)
Preferences.migrateWalletPreferences()
Preferences.migrateAdAndTrackingProtection()
+ Preferences.migrateHTTPSUpgradeLevel()
if Preferences.General.isFirstLaunch.value {
if UIDevice.current.userInterfaceIdiom == .phone {
@@ -169,6 +170,12 @@ extension Preferences {
key: "shields.block-ads-and-tracking",
default: true
)
+
+ /// Websites will be upgraded to HTTPS if a loaded page attempts to use HTTP
+ public static let httpsEverywhere = Option(
+ key: "shields.https-everywhere",
+ default: true
+ )
}
/// Migration preferences
@@ -198,6 +205,13 @@ extension Preferences {
default: false
)
+ /// A more complicated https upgrades preference
+ /// allows a user to select between `standard`, `strict` and `disabled` instead of a simple on/off `Bool`
+ static let httpsUpgradesLivelCompleted = Option(
+ key: "migration.https-upgrades-level-completed",
+ default: false
+ )
+
static let lostTabsWindowIDMigration = Option(
key: "migration.lost-tabs-window-id-two",
default: !UIApplication.shared.supportsMultipleScenes
@@ -271,7 +285,7 @@ extension Preferences {
// Shields
migrate(key: "braveBlockAdsAndTracking", to: DeprecatedPreferences.blockAdsAndTracking)
- migrate(key: "braveHttpsEverywhere", to: Preferences.Shields.httpsEverywhere)
+ migrate(key: "braveHttpsEverywhere", to: DeprecatedPreferences.httpsEverywhere)
migrate(key: "noscript_on", to: Preferences.Shields.blockScripts)
migrate(key: "fingerprintprotection_on", to: Preferences.Shields.fingerprintingProtection)
migrate(key: "braveAdblockUseRegional", to: Preferences.Shields.useRegionAdBlock)
@@ -313,6 +327,17 @@ extension Preferences {
Migration.adBlockAndTrackingProtectionShieldLevelCompleted.value = true
}
+ fileprivate class func migrateHTTPSUpgradeLevel() {
+ guard !Migration.httpsUpgradesLivelCompleted.value else { return }
+
+ // Migrate old tracking protection setting to new BraveShields setting
+ DeprecatedPreferences.httpsEverywhere.migrate { isEnabled in
+ ShieldPreferences.httpsUpgradeLevel = isEnabled ? .standard : .disabled
+ }
+
+ Migration.adBlockAndTrackingProtectionShieldLevelCompleted.value = true
+ }
+
/// Migrate Wallet Preferences from version <1.43
fileprivate class func migrateWalletPreferences() {
guard Preferences.Migration.walletProviderAccountRequestCompleted.value != true else { return }
diff --git a/ios/brave-ios/Sources/BraveShared/Extensions/URLExtensions.swift b/ios/brave-ios/Sources/BraveShared/Extensions/URLExtensions.swift
index 9836e3d35ee0..4809de2b7dc8 100644
--- a/ios/brave-ios/Sources/BraveShared/Extensions/URLExtensions.swift
+++ b/ios/brave-ios/Sources/BraveShared/Extensions/URLExtensions.swift
@@ -28,10 +28,7 @@ extension URL {
///
/// Returns the original url without internal parameters
public var strippedInternalURL: URL? {
- if InternalURL.isValid(url: self),
- let internalURL = InternalURL(self)
- {
-
+ if let internalURL = InternalURL(self) {
switch internalURL.urlType {
case .errorPage:
return internalURL.originalURLFromErrorPage
@@ -39,6 +36,8 @@ extension URL {
return internalURL.extractedUrlParam
case .blockedPage:
return decodeEmbeddedInternalURL(for: .blocked)
+ case .httpBlockedPage:
+ return decodeEmbeddedInternalURL(for: .httpBlocked)
case .readerModePage:
return decodeEmbeddedInternalURL(for: .readermode)
default:
@@ -69,7 +68,8 @@ extension URL {
}
if let internalUrl = InternalURL(self),
- internalUrl.isSessionRestore || internalUrl.isWeb3URL || internalUrl.isBlockedPage
+ internalUrl.isSessionRestore || internalUrl.isWeb3URL || internalUrl.isHTTPBlockedPage
+ || internalUrl.isBlockedPage
{
return internalUrl.extractedUrlParam?.displayURL
}
@@ -138,6 +138,7 @@ extension InternalURL {
enum URLType {
case blockedPage
+ case httpBlockedPage
case sessionRestorePage
case errorPage
case readerModePage
@@ -147,6 +148,13 @@ extension InternalURL {
}
var urlType: URLType {
+ // This needs to be before `isBlockedPage`
+ // because http-blocked has the word "blocked" in it
+ // We should refactor this code because its really iffy.
+ if isHTTPBlockedPage {
+ return .httpBlockedPage
+ }
+
if isBlockedPage {
return .blockedPage
}
diff --git a/ios/brave-ios/Sources/BraveShields/HTTPSUpgradeLevel.swift b/ios/brave-ios/Sources/BraveShields/HTTPSUpgradeLevel.swift
new file mode 100644
index 000000000000..c26470e2d1f8
--- /dev/null
+++ b/ios/brave-ios/Sources/BraveShields/HTTPSUpgradeLevel.swift
@@ -0,0 +1,32 @@
+// Copyright 2024 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+import Foundation
+
+/// A 3 part option for shield levels varying in strength of blocking content
+public enum HTTPSUpgradeLevel: String, CaseIterable, Hashable {
+ /// Always upgrade websites and block websites that cannot be upgraded
+ case strict
+ /// Always upgrade websites but allow http when it cannot be upgraded
+ case standard
+ /// Mode indicating this setting is disabled
+ case disabled
+
+ /// Wether this setting indicates that the shields are enabled or not
+ public var isEnabled: Bool {
+ switch self {
+ case .strict, .standard: return true
+ case .disabled: return false
+ }
+ }
+
+ /// Wether this setting indicates that the shields are aggressive or not
+ public var isStrict: Bool {
+ switch self {
+ case .strict: return true
+ case .disabled, .standard: return false
+ }
+ }
+}
diff --git a/ios/brave-ios/Sources/BraveShields/ShieldPreferences.swift b/ios/brave-ios/Sources/BraveShields/ShieldPreferences.swift
index 92b52d37b965..ba7c86576047 100644
--- a/ios/brave-ios/Sources/BraveShields/ShieldPreferences.swift
+++ b/ios/brave-ios/Sources/BraveShields/ShieldPreferences.swift
@@ -6,8 +6,10 @@
import Foundation
import Preferences
+/// All the settings pertaining to shields and privacy
public class ShieldPreferences {
private static let defaultBlockAdsAndTrackingLevel: ShieldLevel = .standard
+ private static let defaultHTTPsUpgradeLevel: HTTPSUpgradeLevel = .standard
/// Get the level of the adblock and tracking protection as a stored preference
/// - Warning: You should not access this directly but through ``blockAdsAndTrackingLevel``
@@ -16,6 +18,13 @@ public class ShieldPreferences {
default: defaultBlockAdsAndTrackingLevel.rawValue
)
+ /// Get the level of the https upgrade setting as a stored preference
+ /// - Warning: You should not access this directly but through ``httpsUpgradeLevel``
+ public static var httpsUpgradeLevelRaw = Preferences.Option(
+ key: "shields.https-upgrade-level",
+ default: defaultHTTPsUpgradeLevel.rawValue
+ )
+
/// Get the level of the adblock and tracking protection
public static var blockAdsAndTrackingLevel: ShieldLevel {
get {
@@ -24,6 +33,14 @@ public class ShieldPreferences {
set { blockAdsAndTrackingLevelRaw.value = newValue.rawValue }
}
+ /// Get the level of HTTPS upgrades
+ public static var httpsUpgradeLevel: HTTPSUpgradeLevel {
+ get {
+ HTTPSUpgradeLevel(rawValue: httpsUpgradeLevelRaw.value) ?? defaultHTTPsUpgradeLevel
+ }
+ set { httpsUpgradeLevelRaw.value = newValue.rawValue }
+ }
+
/// A boolean value inidicating if GPC is enabled
public static var enableGPC = Preferences.Option(
key: "shields.enable-gpc",
diff --git a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift
index 87190c56a8f8..59a3f38f0104 100644
--- a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift
+++ b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift
@@ -317,3 +317,51 @@ extension Strings.Shields {
"Text for a button in a blocked page info screen that takes you back where you came from"
)
}
+
+// MARK: - HTTPS Upgrades
+
+extension Strings.Shields {
+ /// The option the user can select to do aggressive ad and tracker blocking
+ public static let httpsUpgradeLevelStrict = NSLocalizedString(
+ "HttpsUpgradeLevelStrict",
+ tableName: "BraveShared",
+ bundle: .module,
+ value: "Strict",
+ comment: "The option the user can select to do strict https upgrading"
+ )
+ /// The option the user can select for the type of https upgrading
+ public static let upgradeConnectionsToHTTPS = NSLocalizedString(
+ "UpgradeConnectionsToHTTPS",
+ tableName: "BraveShared",
+ bundle: .module,
+ value: "Upgrade Connections to HTTPS",
+ comment: "The option the user can select for the type of https upgrading"
+ )
+
+ /// A page title for the warning page that appears when http was blocked
+ public static let siteIsNotSecure = NSLocalizedString(
+ "SiteIsNotSecure",
+ tableName: "BraveShared",
+ bundle: .module,
+ value: "Site is not secure",
+ comment: "A page title for the warning page that appears when http was blocked"
+ )
+
+ /// A page title for the warning page that appears when http was blocked
+ public static let theConnectionIsNotSecure = NSLocalizedString(
+ "TheConnectionIsNotSecure",
+ tableName: "BraveShared",
+ bundle: .module,
+ value: "The connection to %@ is not secure",
+ comment: "A page title for the warning page that appears when http was blocked"
+ )
+
+ /// A tab title that appears when a page was blocked
+ public static let httpBlockedDescription = NSLocalizedString(
+ "YourConnectionIsNotPrivate",
+ tableName: "BraveShared",
+ bundle: .module,
+ value: "You are seeing this warning because this site does not support HTTPS.",
+ comment: "A description shown an a page where the http page was blocked"
+ )
+}
diff --git a/ios/brave-ios/Sources/Preferences/GlobalPreferences.swift b/ios/brave-ios/Sources/Preferences/GlobalPreferences.swift
index 59df5f968ef2..a4dbe461279d 100644
--- a/ios/brave-ios/Sources/Preferences/GlobalPreferences.swift
+++ b/ios/brave-ios/Sources/Preferences/GlobalPreferences.swift
@@ -40,10 +40,8 @@ extension Preferences {
public final class Shields {
public static let allShields = [
- httpsEverywhere, googleSafeBrowsing, blockScripts, fingerprintingProtection, blockImages,
+ googleSafeBrowsing, blockScripts, fingerprintingProtection, blockImages,
]
- /// Websites will be upgraded to HTTPS if a loaded page attempts to use HTTP
- public static let httpsEverywhere = Option(key: "shields.https-everywhere", default: true)
/// Enable Google Safe Browsing
public static let googleSafeBrowsing = Option(
key: "shields.google-safe-browsing",
diff --git a/ios/brave-ios/Sources/Shared/Extensions/URLExtensions.swift b/ios/brave-ios/Sources/Shared/Extensions/URLExtensions.swift
index f7ff8f1dd23f..b87f9e06d585 100644
--- a/ios/brave-ios/Sources/Shared/Extensions/URLExtensions.swift
+++ b/ios/brave-ios/Sources/Shared/Extensions/URLExtensions.swift
@@ -467,6 +467,7 @@ public struct InternalURL {
case sessionrestore
case readermode = "reader-mode"
case blocked
+ case httpBlocked = "http-blocked"
func matches(_ string: String) -> Bool {
return string.range(
@@ -547,6 +548,10 @@ public struct InternalURL {
return InternalURL.Path.blocked.matches(url.path)
}
+ public var isHTTPBlockedPage: Bool {
+ return InternalURL.Path.httpBlocked.matches(url.path)
+ }
+
public var isReaderModePage: Bool {
return InternalURL.Path.readermode.matches(url.path)
}
diff --git a/ios/brave-ios/Tests/BraveSharedTests/URLExtensionTests.swift b/ios/brave-ios/Tests/BraveSharedTests/URLExtensionTests.swift
index 34f337f03d18..7ed457576c2f 100644
--- a/ios/brave-ios/Tests/BraveSharedTests/URLExtensionTests.swift
+++ b/ios/brave-ios/Tests/BraveSharedTests/URLExtensionTests.swift
@@ -49,11 +49,15 @@ class URLExtensionTests: XCTestCase {
XCTAssertEqual(
embeddedURL.encodeEmbeddedInternalURL(for: .readermode)?.strippedInternalURL,
- URL(string: "https://en.m.wikipedia.org/wiki/Main_Page?somequery=abc-123")
+ embeddedURL
)
XCTAssertEqual(
embeddedURL.encodeEmbeddedInternalURL(for: .blocked)?.strippedInternalURL,
- URL(string: "https://en.m.wikipedia.org/wiki/Main_Page?somequery=abc-123")
+ embeddedURL
+ )
+ XCTAssertEqual(
+ embeddedURL.encodeEmbeddedInternalURL(for: .httpBlocked)?.strippedInternalURL,
+ embeddedURL
)
XCTAssertNil(embeddedURL.strippedInternalURL)
}
@@ -116,15 +120,19 @@ class URLExtensionTests: XCTestCase {
XCTAssertEqual(
embeddedURL.encodeEmbeddedInternalURL(for: .readermode)?.displayURL,
- URL(string: "https://en.m.wikipedia.org/wiki/Main_Page?somequery=abc-123")
+ embeddedURL
)
XCTAssertEqual(
embeddedURL.encodeEmbeddedInternalURL(for: .blocked)?.displayURL,
- URL(string: "https://en.m.wikipedia.org/wiki/Main_Page?somequery=abc-123")
+ embeddedURL
+ )
+ XCTAssertEqual(
+ embeddedURL.encodeEmbeddedInternalURL(for: .httpBlocked)?.displayURL,
+ embeddedURL
)
XCTAssertEqual(
embeddedURL.displayURL,
- URL(string: "https://en.m.wikipedia.org/wiki/Main_Page?somequery=abc-123")
+ embeddedURL
)
}
}
diff --git a/ios/browser/api/https_upgrade_exceptions/BUILD.gn b/ios/browser/api/https_upgrade_exceptions/BUILD.gn
new file mode 100644
index 000000000000..230408dc90a9
--- /dev/null
+++ b/ios/browser/api/https_upgrade_exceptions/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright (c) 2024 The Brave Authors. All rights reserved.
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at https://mozilla.org/MPL/2.0/.
+
+source_set("https_upgrade_exceptions") {
+ sources = [
+ "https_upgrade_exceptions_service+private.h",
+ "https_upgrade_exceptions_service.h",
+ "https_upgrade_exceptions_service.mm",
+ ]
+ deps = [
+ "//base",
+ "//brave/components/https_upgrade_exceptions/browser",
+ "//brave/ios/browser/application_context",
+ "//ios/chrome/browser/shared/model/application_context",
+ "//net",
+ "//url",
+ ]
+ frameworks = [ "Foundation.framework" ]
+}
diff --git a/ios/browser/api/https_upgrade_exceptions/headers.gni b/ios/browser/api/https_upgrade_exceptions/headers.gni
new file mode 100644
index 000000000000..b075c1ae253a
--- /dev/null
+++ b/ios/browser/api/https_upgrade_exceptions/headers.gni
@@ -0,0 +1,6 @@
+# Copyright (c) 2024 The Brave Authors. All rights reserved.
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at https://mozilla.org/MPL/2.0/.
+
+browser_api_https_upgrade_exceptions_service_public_headers = [ "//brave/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.h" ]
diff --git a/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service+private.h b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service+private.h
new file mode 100644
index 000000000000..e2b33eb35574
--- /dev/null
+++ b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service+private.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2024 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#ifndef BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_PRIVATE_H_
+#define BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_PRIVATE_H_
+
+#include "brave/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.h"
+
+@interface HTTPSUpgradeExceptionsService (Private)
+@end
+
+#endif // BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_PRIVATE_H_
diff --git a/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.h b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.h
new file mode 100644
index 000000000000..57b12a6f4a58
--- /dev/null
+++ b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2024 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#ifndef BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_H_
+#define BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_H_
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+OBJC_EXPORT
+@interface HTTPSUpgradeExceptionsService : NSObject
+/// Tells us if the "HTTPS by default" feature is enabled
+@property(readonly) bool isHttpsByDefaultFeatureEnabled;
+
+- (instancetype)init;
+/// This returns a new URL with HTTPS if the url can be upgraded to HTTPS
+- (nullable NSURL*)upgradeToHTTPSForURL:(NSURL*)url;
+/// Add an exception for the URL so it will no longer upgrade it to HTTPS
+/// It will use the host of the URL to add the exception
+- (void)addExceptionForURL:(NSURL*)url;
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // BRAVE_IOS_BROWSER_API_HTTPS_UPGRADE_EXCEPTIONS_HTTPS_UPGRADE_EXCEPTIONS_SERVICE_H_
diff --git a/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.mm b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.mm
new file mode 100644
index 000000000000..eedac8fcb539
--- /dev/null
+++ b/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service.mm
@@ -0,0 +1,82 @@
+// Copyright (c) 2024 The Brave Authors. All rights reserved.
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#include "brave/ios/browser/api/https_upgrade_exceptions/https_upgrade_exceptions_service+private.h"
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/memory/raw_ptr.h"
+#include "brave/components/https_upgrade_exceptions/browser/https_upgrade_exceptions_service.h"
+#include "brave/ios/browser/application_context/brave_application_context_impl.h"
+#include "ios/chrome/browser/shared/model/application_context/application_context.h"
+#include "net/base/apple/url_conversions.h"
+#include "net/base/features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+@interface HTTPSUpgradeExceptionsService () {
+ /// set of domain exceptions
+ std::set exceptional_domains_;
+}
+
+@property(nonatomic) bool isHttpsByDefaultFeatureEnabled;
+@end
+
+@implementation HTTPSUpgradeExceptionsService
+- (instancetype)init {
+ if ((self = [super init])) {
+ }
+ return self;
+}
+
+- (bool)isHttpsByDefaultFeatureEnabled {
+ return base::FeatureList::IsEnabled(net::features::kBraveHttpsByDefault);
+}
+
+- (NSURL*)upgradeToHTTPSForURL:(NSURL*)url {
+ GURL gurl = net::GURLWithNSURL(url);
+ if (![self isHttpsByDefaultFeatureEnabled]) {
+ return nil;
+ }
+
+ // Check url validity
+ if (!gurl.SchemeIs("http") || !gurl.is_valid()) {
+ return nil;
+ }
+
+ // Ensure we didn't add an exception for this domain
+ if (base::Contains(exceptional_domains_, gurl.host())) {
+ return nil;
+ }
+
+ // Finally ask the service if we should upgrade
+ BraveApplicationContextImpl* braveContext =
+ static_cast(GetApplicationContext());
+ if (!braveContext->https_upgrade_exceptions_service()->CanUpgradeToHTTPS(
+ gurl)) {
+ return nil;
+ }
+
+ GURL::Replacements replacements;
+ replacements.SetSchemeStr("https");
+ GURL final_url = gurl.ReplaceComponents(replacements);
+
+ if (final_url.is_valid()) {
+ return net::NSURLWithGURL(final_url);
+ } else {
+ return nil;
+ }
+}
+
+- (void)addExceptionForURL:(NSURL*)url {
+ GURL gurl = net::GURLWithNSURL(url);
+
+ if (gurl.is_empty()) {
+ return;
+ }
+
+ exceptional_domains_.insert(gurl.host());
+}
+@end
diff --git a/ios/browser/application_context/BUILD.gn b/ios/browser/application_context/BUILD.gn
index c62ff763033d..79d4859cf074 100644
--- a/ios/browser/application_context/BUILD.gn
+++ b/ios/browser/application_context/BUILD.gn
@@ -16,9 +16,11 @@ source_set("application_context") {
"//brave/components/brave_component_updater/browser",
"//brave/components/brave_wallet/browser",
"//brave/components/debounce/core/browser",
+ "//brave/components/https_upgrade_exceptions/browser",
"//brave/components/url_sanitizer/browser",
"//brave/ios/browser/brave_wallet",
"//ios/chrome/browser/application_context/model",
"//ios/chrome/browser/shared/model/application_context",
+ "//net",
]
}
diff --git a/ios/browser/application_context/brave_application_context_impl.h b/ios/browser/application_context/brave_application_context_impl.h
index 254527661f99..de137628a564 100644
--- a/ios/browser/application_context/brave_application_context_impl.h
+++ b/ios/browser/application_context/brave_application_context_impl.h
@@ -11,6 +11,7 @@
#include "brave/components/brave_component_updater/browser/brave_component.h"
#include "brave/components/debounce/core/browser/debounce_component_installer.h"
+#include "brave/components/https_upgrade_exceptions/browser/https_upgrade_exceptions_service.h"
#include "brave/components/url_sanitizer/browser/url_sanitizer_component_installer.h"
#include "ios/chrome/browser/application_context/model/application_context_impl.h"
@@ -42,6 +43,8 @@ class BraveApplicationContextImpl : public ApplicationContextImpl {
// BraveApplicationContextImpl
brave::URLSanitizerComponentInstaller* url_sanitizer_component_installer();
debounce::DebounceComponentInstaller* debounce_component_installer();
+ https_upgrade_exceptions::HttpsUpgradeExceptionsService*
+ https_upgrade_exceptions_service();
// Start any services that we may need later
void StartBraveServices();
@@ -59,6 +62,8 @@ class BraveApplicationContextImpl : public ApplicationContextImpl {
url_sanitizer_component_installer_;
std::unique_ptr
debounce_component_installer_;
+ std::unique_ptr
+ https_upgrade_exceptions_service_;
};
#endif // BRAVE_IOS_BROWSER_APPLICATION_CONTEXT_BRAVE_APPLICATION_CONTEXT_IMPL_H_
diff --git a/ios/browser/application_context/brave_application_context_impl.mm b/ios/browser/application_context/brave_application_context_impl.mm
index 54762ce98bbc..be632c6a56a4 100644
--- a/ios/browser/application_context/brave_application_context_impl.mm
+++ b/ios/browser/application_context/brave_application_context_impl.mm
@@ -14,9 +14,11 @@
#include "brave/components/brave_component_updater/browser/local_data_files_service.h"
#include "brave/components/brave_wallet/browser/wallet_data_files_installer.h"
#include "brave/components/debounce/core/browser/debounce_component_installer.h"
+#include "brave/components/https_upgrade_exceptions/browser/https_upgrade_exceptions_service.h"
#include "brave/components/url_sanitizer/browser/url_sanitizer_component_installer.h"
#include "brave/ios/browser/brave_wallet/wallet_data_files_installer_delegate_impl.h"
#include "ios/chrome/browser/shared/model/application_context/application_context.h"
+#include "net/base/features.h"
BraveApplicationContextImpl::BraveApplicationContextImpl(
base::SequencedTaskRunner* local_state_task_runner,
@@ -84,12 +86,26 @@
return debounce_component_installer_.get();
}
+https_upgrade_exceptions::HttpsUpgradeExceptionsService*
+BraveApplicationContextImpl::https_upgrade_exceptions_service() {
+ if (!https_upgrade_exceptions_service_) {
+ https_upgrade_exceptions_service_ =
+ https_upgrade_exceptions::HttpsUpgradeExceptionsServiceFactory(
+ local_data_files_service());
+ }
+ return https_upgrade_exceptions_service_.get();
+}
+
void BraveApplicationContextImpl::StartBraveServices() {
// We need to Initialize the component installers
// before calling Start on the local_data_files_service
url_sanitizer_component_installer();
debounce_component_installer();
+ if (base::FeatureList::IsEnabled(net::features::kBraveHttpsByDefault)) {
+ https_upgrade_exceptions_service();
+ }
+
// Start the local data file service
local_data_files_service()->Start();