Fix insecure HTTP proceed to be one-time unless saved
This commit is contained in:
parent
d1bcb17b0d
commit
3ffceb7e8e
3 changed files with 58 additions and 42 deletions
|
|
@ -194,27 +194,35 @@ enum BrowserInsecureHTTPSettings {
|
|||
|
||||
func browserShouldBlockInsecureHTTPURL(
|
||||
_ url: URL,
|
||||
defaults: UserDefaults = .standard,
|
||||
runtimeAllowedHosts: Set<String> = []
|
||||
defaults: UserDefaults = .standard
|
||||
) -> Bool {
|
||||
browserShouldBlockInsecureHTTPURL(
|
||||
url,
|
||||
rawAllowlist: defaults.string(forKey: BrowserInsecureHTTPSettings.allowlistKey),
|
||||
runtimeAllowedHosts: runtimeAllowedHosts
|
||||
rawAllowlist: defaults.string(forKey: BrowserInsecureHTTPSettings.allowlistKey)
|
||||
)
|
||||
}
|
||||
|
||||
func browserShouldBlockInsecureHTTPURL(
|
||||
_ url: URL,
|
||||
rawAllowlist: String?,
|
||||
runtimeAllowedHosts: Set<String> = []
|
||||
rawAllowlist: String?
|
||||
) -> Bool {
|
||||
guard url.scheme?.lowercased() == "http" else { return false }
|
||||
guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return true }
|
||||
if runtimeAllowedHosts.contains(host) {
|
||||
return !BrowserInsecureHTTPSettings.isHostAllowed(host, rawAllowlist: rawAllowlist)
|
||||
}
|
||||
|
||||
func browserShouldConsumeOneTimeInsecureHTTPBypass(
|
||||
_ url: URL,
|
||||
bypassHostOnce: inout String?
|
||||
) -> Bool {
|
||||
guard let bypassHost = bypassHostOnce else { return false }
|
||||
guard url.scheme?.lowercased() == "http",
|
||||
let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else {
|
||||
return false
|
||||
}
|
||||
return !BrowserInsecureHTTPSettings.isHostAllowed(host, rawAllowlist: rawAllowlist)
|
||||
guard host == bypassHost else { return false }
|
||||
bypassHostOnce = nil
|
||||
return true
|
||||
}
|
||||
|
||||
func browserShouldPersistInsecureHTTPAllowlistSelection(
|
||||
|
|
@ -225,23 +233,6 @@ func browserShouldPersistInsecureHTTPAllowlistSelection(
|
|||
return response == .alertFirstButtonReturn || response == .alertSecondButtonReturn
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum BrowserInsecureHTTPRuntimeAllowlist {
|
||||
private static var hosts = Set<String>()
|
||||
|
||||
static func contains(_ host: String) -> Bool {
|
||||
hosts.contains(host)
|
||||
}
|
||||
|
||||
static func allow(_ host: String) {
|
||||
hosts.insert(host)
|
||||
}
|
||||
|
||||
static func snapshot() -> Set<String> {
|
||||
hosts
|
||||
}
|
||||
}
|
||||
|
||||
enum BrowserUserAgentSettings {
|
||||
// Force a Safari UA. Some WebKit builds return a minimal UA without Version/Safari tokens,
|
||||
// and some installs may have legacy Chrome UA overrides. Both can cause Google to serve
|
||||
|
|
@ -1009,6 +1000,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
private let minPageZoom: CGFloat = 0.25
|
||||
private let maxPageZoom: CGFloat = 5.0
|
||||
private let pageZoomStep: CGFloat = 0.1
|
||||
private var insecureHTTPBypassHostOnce: String?
|
||||
|
||||
var displayTitle: String {
|
||||
if !pageTitle.isEmpty {
|
||||
|
|
@ -1028,9 +1020,10 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
false
|
||||
}
|
||||
|
||||
init(workspaceId: UUID, initialURL: URL? = nil) {
|
||||
init(workspaceId: UUID, initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil) {
|
||||
self.id = UUID()
|
||||
self.workspaceId = workspaceId
|
||||
self.insecureHTTPBypassHostOnce = BrowserInsecureHTTPSettings.normalizeHost(bypassInsecureHTTPHostOnce ?? "")
|
||||
|
||||
// Configure web view
|
||||
let config = WKWebViewConfiguration()
|
||||
|
|
@ -1432,10 +1425,10 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
}
|
||||
|
||||
private func shouldBlockInsecureHTTPNavigation(to url: URL) -> Bool {
|
||||
browserShouldBlockInsecureHTTPURL(
|
||||
url,
|
||||
runtimeAllowedHosts: BrowserInsecureHTTPRuntimeAllowlist.snapshot()
|
||||
)
|
||||
if browserShouldConsumeOneTimeInsecureHTTPBypass(url, bypassHostOnce: &insecureHTTPBypassHostOnce) {
|
||||
return false
|
||||
}
|
||||
return browserShouldBlockInsecureHTTPURL(url)
|
||||
}
|
||||
|
||||
private func requestNavigation(_ url: URL, intent: BrowserInsecureHTTPNavigationIntent) {
|
||||
|
|
@ -1483,12 +1476,12 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
case .alertFirstButtonReturn:
|
||||
NSWorkspace.shared.open(url)
|
||||
case .alertSecondButtonReturn:
|
||||
BrowserInsecureHTTPRuntimeAllowlist.allow(host)
|
||||
switch intent {
|
||||
case .currentTab:
|
||||
insecureHTTPBypassHostOnce = host
|
||||
navigateWithoutInsecureHTTPPrompt(to: url, recordTypedNavigation: recordTypedNavigation)
|
||||
case .newTab:
|
||||
openLinkInNewTab(url: url)
|
||||
openLinkInNewTab(url: url, bypassInsecureHTTPHostOnce: host)
|
||||
}
|
||||
default:
|
||||
return
|
||||
|
|
@ -1545,11 +1538,16 @@ extension BrowserPanel {
|
|||
}
|
||||
|
||||
/// Open a link in a new browser surface in the same pane
|
||||
func openLinkInNewTab(url: URL) {
|
||||
func openLinkInNewTab(url: URL, bypassInsecureHTTPHostOnce: String? = nil) {
|
||||
guard let tabManager = AppDelegate.shared?.tabManager,
|
||||
let workspace = tabManager.tabs.first(where: { $0.id == workspaceId }),
|
||||
let paneId = workspace.paneId(forPanelId: id) else { return }
|
||||
workspace.newBrowserSurface(inPane: paneId, url: url, focus: true)
|
||||
workspace.newBrowserSurface(
|
||||
inPane: paneId,
|
||||
url: url,
|
||||
focus: true,
|
||||
bypassInsecureHTTPHostOnce: bypassInsecureHTTPHostOnce
|
||||
)
|
||||
}
|
||||
|
||||
/// Reload the current page
|
||||
|
|
|
|||
|
|
@ -571,11 +571,16 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
inPane paneId: PaneID,
|
||||
url: URL? = nil,
|
||||
focus: Bool? = nil,
|
||||
insertAtEnd: Bool = false
|
||||
insertAtEnd: Bool = false,
|
||||
bypassInsecureHTTPHostOnce: String? = nil
|
||||
) -> BrowserPanel? {
|
||||
let shouldFocusNewTab = focus ?? (bonsplitController.focusedPaneId == paneId)
|
||||
|
||||
let browserPanel = BrowserPanel(workspaceId: id, initialURL: url)
|
||||
let browserPanel = BrowserPanel(
|
||||
workspaceId: id,
|
||||
initialURL: url,
|
||||
bypassInsecureHTTPHostOnce: bypassInsecureHTTPHostOnce
|
||||
)
|
||||
panels[browserPanel.id] = browserPanel
|
||||
|
||||
guard let newTabId = bonsplitController.createTab(
|
||||
|
|
|
|||
|
|
@ -130,22 +130,35 @@ final class BrowserInsecureHTTPSettingsTests: XCTestCase {
|
|||
XCTAssertFalse(BrowserInsecureHTTPSettings.isHostAllowed("example.net", rawAllowlist: raw))
|
||||
}
|
||||
|
||||
func testBlockDecisionUsesAllowlistAndRuntimeProceedCache() throws {
|
||||
func testBlockDecisionUsesAllowlistAndSchemeRules() throws {
|
||||
let localURL = try XCTUnwrap(URL(string: "http://foo.localtest.me:3000"))
|
||||
XCTAssertFalse(browserShouldBlockInsecureHTTPURL(localURL, rawAllowlist: nil))
|
||||
|
||||
let insecureURL = try XCTUnwrap(URL(string: "http://neverssl.com"))
|
||||
XCTAssertTrue(browserShouldBlockInsecureHTTPURL(insecureURL, rawAllowlist: nil))
|
||||
XCTAssertFalse(browserShouldBlockInsecureHTTPURL(
|
||||
insecureURL,
|
||||
rawAllowlist: nil,
|
||||
runtimeAllowedHosts: ["neverssl.com"]
|
||||
))
|
||||
|
||||
let httpsURL = try XCTUnwrap(URL(string: "https://neverssl.com"))
|
||||
XCTAssertFalse(browserShouldBlockInsecureHTTPURL(httpsURL, rawAllowlist: nil))
|
||||
}
|
||||
|
||||
func testOneTimeBypassIsConsumedAfterFirstNavigation() throws {
|
||||
let insecureURL = try XCTUnwrap(URL(string: "http://neverssl.com"))
|
||||
var bypassHostOnce: String? = "neverssl.com"
|
||||
|
||||
XCTAssertTrue(browserShouldConsumeOneTimeInsecureHTTPBypass(
|
||||
insecureURL,
|
||||
bypassHostOnce: &bypassHostOnce
|
||||
))
|
||||
XCTAssertNil(bypassHostOnce)
|
||||
|
||||
// Subsequent visits should prompt again unless host was saved.
|
||||
XCTAssertFalse(browserShouldConsumeOneTimeInsecureHTTPBypass(
|
||||
insecureURL,
|
||||
bypassHostOnce: &bypassHostOnce
|
||||
))
|
||||
XCTAssertTrue(browserShouldBlockInsecureHTTPURL(insecureURL, rawAllowlist: nil))
|
||||
}
|
||||
|
||||
func testAddAllowedHostPersistsToDefaultsAndUnblocksHTTP() throws {
|
||||
let suiteName = "BrowserInsecureHTTPSettingsTests.Persist.\(UUID().uuidString)"
|
||||
guard let defaults = UserDefaults(suiteName: suiteName) else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue