From 031f9408a02b2348f6ec77a4810d3b9d08c977d8 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:56:57 +0900 Subject: [PATCH 1/3] fix: Apply with excluded domains --- ...EvaluateConnectionRuleAction+Codable.swift | 10 +++ DNSecure/Models/OnDemandRule.swift | 86 +++++++++++++++++++ DNSecure/Models/Resolver.swift | 71 --------------- DNSecure/Views/RuleView.swift | 53 ++++++++++++ 4 files changed, 149 insertions(+), 71 deletions(-) create mode 100644 DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift create mode 100644 DNSecure/Models/OnDemandRule.swift diff --git a/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift b/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift new file mode 100644 index 0000000..db52e16 --- /dev/null +++ b/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift @@ -0,0 +1,10 @@ +// +// NEEvaluateConnectionRuleAction+Codable.swift +// DNSecure +// +// Created by Kenta Kubo on 8/31/25. +// + +import NetworkExtension + +extension NEEvaluateConnectionRuleAction: @retroactive Codable {} diff --git a/DNSecure/Models/OnDemandRule.swift b/DNSecure/Models/OnDemandRule.swift new file mode 100644 index 0000000..868c6fc --- /dev/null +++ b/DNSecure/Models/OnDemandRule.swift @@ -0,0 +1,86 @@ +// +// OnDemandRule.swift +// DNSecure +// +// Created by Kenta Kubo on 8/31/25. +// + +import Foundation +import NetworkExtension + +struct OnDemandRule { + var id = UUID() + var name: String + var action: NEOnDemandRuleAction = .connect + var interfaceType: NEOnDemandRuleInterfaceType = .any + var ssidMatch: [String] = [] + var dnsSearchDomainMatch: [String] = [] + var dnsServerAddressMatch: [String] = [] + var probeURL: URL? + var excludedDomains: [String]? +} + +extension OnDemandRule: Identifiable {} + +extension OnDemandRule: Equatable {} + +extension OnDemandRule: Hashable {} + +extension OnDemandRule: Codable {} + +extension [OnDemandRule] { + func toNEOnDemandRules() -> [NEOnDemandRule] { + self.map { rule in + switch rule.action { + case .connect: + let newRule = NEOnDemandRuleConnect() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + case .disconnect: + let newRule = NEOnDemandRuleDisconnect() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + case .evaluateConnection: + let newRule = NEOnDemandRuleEvaluateConnection() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + newRule.connectionRules = + switch rule.excludedDomains { + case let domains? where !domains.isEmpty: + [.init(matchDomains: domains, andAction: .neverConnect)] + case _: [] + } + return newRule + case .ignore: + let newRule = NEOnDemandRuleIgnore() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + @unknown case _: + preconditionFailure("Unexpected NEOnDemandRuleAction") + } + } + } +} diff --git a/DNSecure/Models/Resolver.swift b/DNSecure/Models/Resolver.swift index 06510d3..082bfcd 100644 --- a/DNSecure/Models/Resolver.swift +++ b/DNSecure/Models/Resolver.swift @@ -106,77 +106,6 @@ extension Configuration: CustomStringConvertible { } } -struct OnDemandRule { - var id = UUID() - var name: String - var action: NEOnDemandRuleAction = .connect - var interfaceType: NEOnDemandRuleInterfaceType = .any - var ssidMatch: [String] = [] - var dnsSearchDomainMatch: [String] = [] - var dnsServerAddressMatch: [String] = [] - var probeURL: URL? -} - -extension OnDemandRule: Identifiable {} - -extension OnDemandRule: Equatable {} - -extension OnDemandRule: Hashable {} - -extension OnDemandRule: Codable {} - -extension Array where Self.Element == OnDemandRule { - func toNEOnDemandRules() -> [NEOnDemandRule] { - self.lazy - .map { rule in - switch rule.action { - case .connect: - let newRule = NEOnDemandRuleConnect() - newRule.interfaceTypeMatch = rule.interfaceType - if rule.interfaceType.isSSIDUsed { - newRule.ssidMatch = rule.ssidMatch - } - newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch - newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch - newRule.probeURL = rule.probeURL - return newRule - case .disconnect: - let newRule = NEOnDemandRuleDisconnect() - newRule.interfaceTypeMatch = rule.interfaceType - if rule.interfaceType.isSSIDUsed { - newRule.ssidMatch = rule.ssidMatch - } - newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch - newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch - newRule.probeURL = rule.probeURL - return newRule - case .evaluateConnection: - let newRule = NEOnDemandRuleEvaluateConnection() - newRule.interfaceTypeMatch = rule.interfaceType - if rule.interfaceType.isSSIDUsed { - newRule.ssidMatch = rule.ssidMatch - } - newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch - newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch - newRule.probeURL = rule.probeURL - return newRule - case .ignore: - let newRule = NEOnDemandRuleIgnore() - newRule.interfaceTypeMatch = rule.interfaceType - if rule.interfaceType.isSSIDUsed { - newRule.ssidMatch = rule.ssidMatch - } - newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch - newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch - newRule.probeURL = rule.probeURL - return newRule - default: - preconditionFailure("Unexpected NEOnDemandRuleAction") - } - } - } -} - struct Resolver { var id = UUID() var name: String diff --git a/DNSecure/Views/RuleView.swift b/DNSecure/Views/RuleView.swift index feeb244..8e3bdc5 100644 --- a/DNSecure/Views/RuleView.swift +++ b/DNSecure/Views/RuleView.swift @@ -8,6 +8,42 @@ import NetworkExtension import SwiftUI +struct ExcludedDomainsView { + @Binding var domains: [String] +} + +extension ExcludedDomainsView: View { + var body: some View { + Form { + Section { + ForEach(0.. Date: Wed, 3 Sep 2025 03:01:28 +0900 Subject: [PATCH 2/3] refactor: Split `ExcludedDomainsView` into a separate file --- DNSecure/Views/ExcludedDomainsView.swift | 45 ++++++++++++++++++++++++ DNSecure/Views/RuleView.swift | 36 ------------------- 2 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 DNSecure/Views/ExcludedDomainsView.swift diff --git a/DNSecure/Views/ExcludedDomainsView.swift b/DNSecure/Views/ExcludedDomainsView.swift new file mode 100644 index 0000000..b8c116f --- /dev/null +++ b/DNSecure/Views/ExcludedDomainsView.swift @@ -0,0 +1,45 @@ +import SwiftUI + +struct ExcludedDomainsView { + @Binding var domains: [String] +} + +extension ExcludedDomainsView: View { + var body: some View { + Form { + Section { + ForEach(0.. Date: Wed, 3 Sep 2025 23:15:40 +0900 Subject: [PATCH 3/3] fix: improve text field UX in `ExcludedDomainsView` --- DNSecure/Views/ExcludedDomainsView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DNSecure/Views/ExcludedDomainsView.swift b/DNSecure/Views/ExcludedDomainsView.swift index b8c116f..2582ae6 100644 --- a/DNSecure/Views/ExcludedDomainsView.swift +++ b/DNSecure/Views/ExcludedDomainsView.swift @@ -17,6 +17,10 @@ extension ExcludedDomainsView: View { set: { self.domains[i] = $0 } ) ) + .textContentType(.URL) + .keyboardType(.URL) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() } .onDelete { self.domains.remove(atOffsets: $0) } .onMove { self.domains.move(fromOffsets: $0, toOffset: $1) }