Files

154 lines
7.0 KiB
Swift

import XCTest
@testable import ShadowsocksX_NG2
final class OfflineRuleMatcherTests: XCTestCase {
func testURLAndLiteralIPNormalizationSelectOnlyTheHostBranch() throws {
let rules = [
CustomRule(action: .proxy, match: try RuleMatch(domainSuffix: "example.com")),
CustomRule(action: .proxy, match: try RuleMatch(ipv4CIDR: "203.0.113.0/24")),
CustomRule(action: .direct, match: try RuleMatch(ipv6CIDR: "2001:db8::/32")),
]
let collection = RulesCollection.load(
preset: .gfwlist, custom: { rules }, builtin: { rulesFixture($0) })
for input in [" A.Example.COM. \n", "https://A.Example.COM.:8443/path?q=203.0.113.8#fragment"] {
let result = try OfflineRuleMatcher.test(collection: collection, address: input)
XCTAssertEqual(result.target, "a.example.com")
XCTAssertEqual(result.outcome, .proxy)
XCTAssertTrue(result.domainWithoutDNS)
}
for input in ["203.0.113.8", "https://203.0.113.8:443/path"] {
let result = try OfflineRuleMatcher.test(collection: collection, address: input)
XCTAssertEqual(result.target, "203.0.113.8")
XCTAssertEqual(result.outcome, .proxy)
XCTAssertFalse(result.domainWithoutDNS)
}
let ipv6 = try OfflineRuleMatcher.test(
collection: collection, address: "https://[2001:DB8::8]:8443/path")
XCTAssertEqual(ipv6.target, "2001:db8::8")
XCTAssertEqual(ipv6.outcome, .direct)
XCTAssertFalse(ipv6.domainWithoutDNS)
}
func testDomainOverlapAndSuffixBoundariesKeepAllCommonEvidence() throws {
let suffix = CustomRule(action: .direct, match: try RuleMatch(domainSuffix: "example.com"))
let exact = CustomRule(action: .proxy, match: try RuleMatch(domainExact: "a.example.com"))
let secondProxy = CustomRule(action: .proxy, match: try RuleMatch(domainSuffix: "example.com"))
let rules = [suffix, exact, secondProxy]
for entries in [rules, Array(rules.reversed())] {
let collection = RulesCollection.load(
preset: .gfwlist, custom: { entries }, builtin: { rulesFixture($0) })
let result = try OfflineRuleMatcher.test(collection: collection, address: "a.example.com")
XCTAssertEqual(result.outcome, .proxy)
XCTAssertEqual(result.explanation, .singleAction)
XCTAssertEqual(
Set(result.deciding.compactMap(\.identity)), [exact.identity, secondProxy.identity])
XCTAssertEqual(result.otherMatches.isEmpty, true)
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "notexample.com").outcome,
.direct)
}
let collection = RulesCollection.load(
preset: .gfwlist,
custom: { [suffix, exact] }, builtin: { rulesFixture($0) })
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "example.com").outcome, .direct)
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "b.example.com").outcome, .direct
)
let exactOnly = RulesCollection.load(
preset: .gfwlist, custom: { [exact] }, builtin: { rulesFixture($0) })
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: exactOnly, address: "sub.a.example.com").outcome,
.direct)
}
func testIPOverlapAndMappedAddressesUseOnlySchemeAction() throws {
for reverseActions in [false, true] {
let broad = CustomRule(
action: reverseActions ? .direct : .proxy,
match: try RuleMatch(ipv4CIDR: "203.0.113.0/24"))
let narrow = CustomRule(
action: reverseActions ? .proxy : .direct,
match: try RuleMatch(ipv6CIDR: "::ffff:203.0.113.8/128"))
let collection = RulesCollection.load(
preset: .gfwlist,
custom: { [broad, narrow] }, builtin: { rulesFixture($0) })
for address in ["203.0.113.8", "::ffff:203.0.113.8"] {
let result = try OfflineRuleMatcher.test(collection: collection, address: address)
XCTAssertEqual(result.outcome, .proxy)
XCTAssertEqual(result.explanation, .singleAction)
XCTAssertEqual(
result.deciding.map(\.identity), [reverseActions ? narrow.identity : broad.identity])
XCTAssertEqual(
result.otherMatches.map(\.identity), [])
}
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "203.0.113.9").outcome,
reverseActions ? .direct : .proxy)
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "198.51.100.8").outcome,
.direct)
XCTAssertEqual(
try OfflineRuleMatcher.test(collection: collection, address: "unresolved.example").outcome,
.direct)
}
}
func testFixedPolicyWinsAndSourceMembershipsRemainComplete() throws {
let proxy = ProxyRule(action: .proxy, match: .localSuffix)
let collection = RulesCollection.load(
preset: .gfwlist,
custom: { [] },
builtin: {
rulesFixture($0, rules: $0 == .gfwlist ? [proxy] : [])
})
for address in [
"printer", "localhost", "a.localhost", "host.local", "127.0.0.1", "192.168.1.2",
"::1", "::ffff:192.168.1.2", "fe80::1",
] {
let result = try OfflineRuleMatcher.test(collection: collection, address: address)
XCTAssertEqual(result.outcome, .direct, address)
XCTAssertEqual(result.explanation, .fixedLocal, address)
XCTAssertTrue(result.deciding.allSatisfy(\.isFixed))
}
let local = try OfflineRuleMatcher.test(collection: collection, address: "host.local")
XCTAssertEqual(local.otherMatches.map(\.identity), [proxy])
let match = try RuleMatch(domainExact: "example.com")
let custom = CustomRule(action: .proxy, match: match)
let duplicate = ProxyRule(action: .proxy, match: match)
let merged = RulesCollection.load(
preset: .gfwlist,
custom: { [custom] },
builtin: {
rulesFixture($0, rules: $0 == .gfwlist ? [duplicate] : [])
})
let result = try OfflineRuleMatcher.test(collection: merged, address: "example.com")
XCTAssertEqual(result.deciding.count, 1)
XCTAssertEqual(result.deciding[0].sources, [.custom, .gfwlist])
XCTAssertEqual(result.deciding[0].customIDs, [custom.id])
}
func testInvalidInputsAreOperationErrorsRatherThanUnmatched() throws {
let collection = RulesCollection.load(
preset: .gfwlist, custom: { [] }, builtin: { rulesFixture($0) })
for input in [
"", " ", "example..com", "example.com..", "-example.com", "example-.com",
"https:///path", "https://", "file:///tmp/test", "203.0.113.999", "203.0.113.8/32",
"2001:db8::xyz", "example.com:443", "a example.com", "*.example.com",
"https://example.com:99999",
"https://ex%61mple.com", "例子.com", "https://例子.com",
] {
XCTAssertThrowsError(
try OfflineRuleMatcher.test(collection: collection, address: input), input
) {
XCTAssertEqual($0 as? OfflineRuleMatcher.Failure, .invalidTarget, input)
}
}
let valid = try OfflineRuleMatcher.test(
collection: collection, address: "XN--BCHER-KVA.example.")
XCTAssertEqual(valid.target, "xn--bcher-kva.example")
XCTAssertEqual(valid.outcome, .direct)
}
}