Merge pull request #206 from manaflow-ai/issue-180-http-nonsecure-hosts
Allow HTTP in built-in browser for local dev hosts
This commit is contained in:
commit
ea0593475c
7 changed files with 554 additions and 10 deletions
|
|
@ -1425,6 +1425,39 @@ final class OmnibarSuggestionRankingTests: XCTestCase {
|
|||
XCTAssertEqual(results.first?.completion, "https://gmail.com/")
|
||||
}
|
||||
|
||||
func testNavigateSuggestionRanksAheadOfSwitchToTabForSameResolvedURL() throws {
|
||||
let targetURL = try XCTUnwrap(URL(string: "http://http.badssl.com/"))
|
||||
|
||||
let results = buildOmnibarSuggestions(
|
||||
query: targetURL.absoluteString,
|
||||
engineName: "Google",
|
||||
historyEntries: [],
|
||||
openTabMatches: [
|
||||
.init(
|
||||
tabId: UUID(),
|
||||
panelId: UUID(),
|
||||
url: targetURL.absoluteString,
|
||||
title: "http.badssl.com",
|
||||
isKnownOpenTab: true
|
||||
),
|
||||
],
|
||||
remoteQueries: [],
|
||||
resolvedURL: targetURL,
|
||||
limit: 8,
|
||||
now: fixedNow
|
||||
)
|
||||
|
||||
guard let first = results.first else {
|
||||
XCTFail("Expected at least one suggestion")
|
||||
return
|
||||
}
|
||||
guard case .navigate(let navigateURL) = first.kind else {
|
||||
XCTFail("Expected first suggestion to be navigate, got \(first.kind)")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(navigateURL, targetURL.absoluteString)
|
||||
}
|
||||
|
||||
func testSuggestionSelectionPrefersAutocompletionCandidateAfterSuggestionsUpdate() {
|
||||
let entries: [BrowserHistoryStore.Entry] = [
|
||||
.init(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import XCTest
|
||||
import Foundation
|
||||
import AppKit
|
||||
@testable import cmux_DEV
|
||||
|
||||
/// Regression test: ensures UpdatePill is never gated behind #if DEBUG in production code paths.
|
||||
/// This prevents accidentally hiding the update UI in Release builds.
|
||||
|
|
@ -64,3 +66,133 @@ final class UpdatePillReleaseVisibilityTests: XCTestCase {
|
|||
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression test: ensure WKWebView can load HTTP development URLs (e.g. *.localtest.me).
|
||||
final class AppTransportSecurityTests: XCTestCase {
|
||||
func testInfoPlistAllowsArbitraryLoadsInWebContent() throws {
|
||||
let projectRoot = findProjectRoot()
|
||||
let infoPlistURL = projectRoot.appendingPathComponent("Resources/Info.plist")
|
||||
let data = try Data(contentsOf: infoPlistURL)
|
||||
var format = PropertyListSerialization.PropertyListFormat.xml
|
||||
let plist = try XCTUnwrap(
|
||||
PropertyListSerialization.propertyList(from: data, options: [], format: &format) as? [String: Any]
|
||||
)
|
||||
let ats = try XCTUnwrap(plist["NSAppTransportSecurity"] as? [String: Any])
|
||||
XCTAssertEqual(
|
||||
ats["NSAllowsArbitraryLoadsInWebContent"] as? Bool,
|
||||
true,
|
||||
"Resources/Info.plist must allow HTTP loads in WKWebView for local dev hostnames."
|
||||
)
|
||||
}
|
||||
|
||||
private func findProjectRoot() -> URL {
|
||||
var dir = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent()
|
||||
for _ in 0..<10 {
|
||||
let marker = dir.appendingPathComponent("GhosttyTabs.xcodeproj")
|
||||
if FileManager.default.fileExists(atPath: marker.path) {
|
||||
return dir
|
||||
}
|
||||
dir = dir.deletingLastPathComponent()
|
||||
}
|
||||
return URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
final class BrowserInsecureHTTPSettingsTests: XCTestCase {
|
||||
func testDefaultAllowlistPatternsArePresent() {
|
||||
XCTAssertEqual(
|
||||
BrowserInsecureHTTPSettings.normalizedAllowlistPatterns(rawValue: nil),
|
||||
["127.0.0.1", "localhost", "*.localtest.me"]
|
||||
)
|
||||
}
|
||||
|
||||
func testWildcardAndExactHostMatching() {
|
||||
XCTAssertTrue(BrowserInsecureHTTPSettings.isHostAllowed("localhost", rawAllowlist: nil))
|
||||
XCTAssertTrue(BrowserInsecureHTTPSettings.isHostAllowed("api.localtest.me", rawAllowlist: nil))
|
||||
XCTAssertFalse(BrowserInsecureHTTPSettings.isHostAllowed("neverssl.com", rawAllowlist: nil))
|
||||
}
|
||||
|
||||
func testCustomAllowlistNormalizesAndDeduplicatesEntries() {
|
||||
let raw = """
|
||||
localhost
|
||||
*.example.com
|
||||
127.0.0.1
|
||||
https://dev.internal:8080/path
|
||||
*.example.com
|
||||
"""
|
||||
|
||||
XCTAssertEqual(
|
||||
BrowserInsecureHTTPSettings.normalizedAllowlistPatterns(rawValue: raw),
|
||||
["localhost", "*.example.com", "127.0.0.1", "dev.internal"]
|
||||
)
|
||||
XCTAssertTrue(BrowserInsecureHTTPSettings.isHostAllowed("foo.example.com", rawAllowlist: raw))
|
||||
XCTAssertTrue(BrowserInsecureHTTPSettings.isHostAllowed("dev.internal", rawAllowlist: raw))
|
||||
XCTAssertFalse(BrowserInsecureHTTPSettings.isHostAllowed("example.net", rawAllowlist: raw))
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
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 {
|
||||
XCTFail("Failed to create isolated UserDefaults suite")
|
||||
return
|
||||
}
|
||||
defer { defaults.removePersistentDomain(forName: suiteName) }
|
||||
|
||||
let url = try XCTUnwrap(URL(string: "http://persist-me.test"))
|
||||
XCTAssertTrue(browserShouldBlockInsecureHTTPURL(url, defaults: defaults))
|
||||
|
||||
BrowserInsecureHTTPSettings.addAllowedHost("persist-me.test", defaults: defaults)
|
||||
let persisted = defaults.string(forKey: BrowserInsecureHTTPSettings.allowlistKey)
|
||||
XCTAssertNotNil(persisted)
|
||||
XCTAssertTrue(BrowserInsecureHTTPSettings.isHostAllowed("persist-me.test", defaults: defaults))
|
||||
XCTAssertFalse(browserShouldBlockInsecureHTTPURL(url, defaults: defaults))
|
||||
}
|
||||
|
||||
func testAllowlistSelectionPersistsForProceedAndOpenExternal() {
|
||||
XCTAssertTrue(browserShouldPersistInsecureHTTPAllowlistSelection(
|
||||
response: .alertFirstButtonReturn,
|
||||
suppressionEnabled: true
|
||||
))
|
||||
XCTAssertTrue(browserShouldPersistInsecureHTTPAllowlistSelection(
|
||||
response: .alertSecondButtonReturn,
|
||||
suppressionEnabled: true
|
||||
))
|
||||
XCTAssertFalse(browserShouldPersistInsecureHTTPAllowlistSelection(
|
||||
response: .alertThirdButtonReturn,
|
||||
suppressionEnabled: true
|
||||
))
|
||||
XCTAssertFalse(browserShouldPersistInsecureHTTPAllowlistSelection(
|
||||
response: .alertSecondButtonReturn,
|
||||
suppressionEnabled: false
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue