Compare commits

..

No commits in common. "main" and "1.5.1" have entirely different histories.
main ... 1.5.1

61 changed files with 896 additions and 1092 deletions

1
.github/CODEOWNERS vendored
View file

@ -1 +0,0 @@
* @kkebo

View file

@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
assignees:
- "kkebo"
commit-message:
prefix: "ci"

View file

@ -4,24 +4,16 @@ on:
branches: ["main"] branches: ["main"]
pull_request: pull_request:
branches: ["main"] branches: ["main"]
permissions: {}
defaults:
run:
shell: bash -euo pipefail {0}
jobs: jobs:
lint: lint:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
container: swift:6.2 container: swift:6.1
permissions:
contents: read
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v4
- run: swift format lint -rsp . - run: swift format lint -rsp .
yamllint: yamllint:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
permissions:
contents: read
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v4
- run: yamllint --version - run: yamllint --version
- run: yamllint --strict --config-file .yamllint.yml . - run: yamllint --strict --config-file .yamllint.yml .

View file

@ -1,89 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: '40 19 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: swift
build-mode: manual
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
if: matrix.build-mode == 'manual'
shell: bash
env:
DEVELOPER_DIR: /Applications/Xcode_26.1.1.app
run: xcodebuild build -project DNSecure.xcodeproj -scheme DNSecure -destination "platform=iOS Simulator,name=iPad Pro 13-inch (M5),OS=26.1" -quiet -showBuildTimingSummary
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
category: "/language:${{matrix.language}}"

View file

@ -21,7 +21,11 @@
] ]
}, },
"prioritizeKeepingFunctionOutputTogether" : true, "prioritizeKeepingFunctionOutputTogether" : true,
"reflowMultilineStringLiterals" : "never", "reflowMultilineStringLiterals" : {
"never" : {
}
},
"respectsExistingLineBreaks" : true, "respectsExistingLineBreaks" : true,
"rules" : { "rules" : {
"AllPublicDeclarationsHaveDocumentation" : false, "AllPublicDeclarationsHaveDocumentation" : false,

View file

@ -3,11 +3,34 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 77; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80D4251DC3A20046BAA0 /* DetailView.swift */; };
890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80DE251DC6B50046BAA0 /* Presets.swift */; };
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */; };
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */; };
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */; };
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */; };
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */; };
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */; };
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */; };
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023B24ACBD2700EBE74B /* DNSecureApp.swift */; };
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023D24ACBD2700EBE74B /* ContentView.swift */; };
8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940023F24ACBD2800EBE74B /* Assets.xcassets */; };
8940024324ACBD2800EBE74B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940024224ACBD2800EBE74B /* Preview Assets.xcassets */; };
8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940024D24ACBD2800EBE74B /* DNSecureTests.swift */; };
8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* DNSecureUITests.swift */; };
8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; }; 8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; };
894958AD2548405E009691D5 /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894958AC2548405E009691D5 /* RuleView.swift */; };
894F33652C46D2F00060F385 /* RestorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894F33642C46D2A20060F385 /* RestorationView.swift */; };
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */; };
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; };
8998041628DCDED800C8B421 /* DoTSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998041528DCDED800C8B421 /* DoTSections.swift */; };
8998041828DCDEEF00C8B421 /* DoHSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998041728DCDEEF00C8B421 /* DoHSections.swift */; };
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CB922025209DD100B6983C /* HowToActivateView.swift */; };
89E8B71A29F80164002C2AEF /* LazyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E8B71929F80164002C2AEF /* LazyTextField.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -28,44 +51,40 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
890B80D4251DC3A20046BAA0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = "<group>"; };
890B80DE251DC6B50046BAA0 /* Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presets.swift; sourceTree = "<group>"; };
8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; 8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CaseIterable.swift"; sourceTree = "<group>"; };
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CustomStringConvertible.swift"; sourceTree = "<group>"; };
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+Codable.swift"; sourceTree = "<group>"; };
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+ssidIsUsed.swift"; sourceTree = "<group>"; };
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CaseIterable.swift"; sourceTree = "<group>"; };
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CustomStringConvertible.swift"; sourceTree = "<group>"; };
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+Codable.swift"; sourceTree = "<group>"; };
8940023824ACBD2700EBE74B /* DNSecure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSecure.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8940023824ACBD2700EBE74B /* DNSecure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSecure.app; sourceTree = BUILT_PRODUCTS_DIR; };
8940023B24ACBD2700EBE74B /* DNSecureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureApp.swift; sourceTree = "<group>"; };
8940023D24ACBD2700EBE74B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
8940023F24ACBD2800EBE74B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8940024224ACBD2800EBE74B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
8940024424ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940024924ACBD2800EBE74B /* DNSecureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8940024924ACBD2800EBE74B /* DNSecureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8940024D24ACBD2800EBE74B /* DNSecureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureTests.swift; sourceTree = "<group>"; };
8940024F24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8940025824ACBD2800EBE74B /* DNSecureUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureUITests.swift; sourceTree = "<group>"; };
8940025A24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940026624ACBE4900EBE74B /* DNSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNSecure.entitlements; sourceTree = "<group>"; };
8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
894958AC2548405E009691D5 /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = "<group>"; };
894F33642C46D2A20060F385 /* RestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationView.swift; sourceTree = "<group>"; };
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = "<group>"; };
8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
8998041528DCDED800C8B421 /* DoTSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoTSections.swift; sourceTree = "<group>"; };
8998041728DCDEEF00C8B421 /* DoHSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoHSections.swift; sourceTree = "<group>"; };
89CB922025209DD100B6983C /* HowToActivateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowToActivateView.swift; sourceTree = "<group>"; };
89E8B71929F80164002C2AEF /* LazyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyTextField.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
899AD03F2E66100500449710 /* Exceptions for "DNSecure" folder in "DNSecure" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 8940023724ACBD2700EBE74B /* DNSecure */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
899AD0232E66100500449710 /* DNSecure */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
899AD03F2E66100500449710 /* Exceptions for "DNSecure" folder in "DNSecure" target */,
);
path = DNSecure;
sourceTree = "<group>";
};
899AD0422E66100E00449710 /* DNSecureTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = DNSecureTests;
sourceTree = "<group>";
};
899AD0472E66101100449710 /* DNSecureUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = DNSecureUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
8940023524ACBD2700EBE74B /* Frameworks */ = { 8940023524ACBD2700EBE74B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
@ -92,13 +111,52 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
893AA816258F790D0060B022 /* Views */ = {
isa = PBXGroup;
children = (
894F33642C46D2A20060F385 /* RestorationView.swift */,
8940023D24ACBD2700EBE74B /* ContentView.swift */,
89CB922025209DD100B6983C /* HowToActivateView.swift */,
890B80D4251DC3A20046BAA0 /* DetailView.swift */,
894958AC2548405E009691D5 /* RuleView.swift */,
8998041528DCDED800C8B421 /* DoTSections.swift */,
8998041728DCDEEF00C8B421 /* DoHSections.swift */,
89E8B71929F80164002C2AEF /* LazyTextField.swift */,
);
path = Views;
sourceTree = "<group>";
};
893AA817258F79F70060B022 /* Models */ = {
isa = PBXGroup;
children = (
8986CDCE251D9B3400D947CD /* Resolver.swift */,
890B80DE251DC6B50046BAA0 /* Presets.swift */,
);
path = Models;
sourceTree = "<group>";
};
893AA818258F7A0C0060B022 /* Extensions */ = {
isa = PBXGroup;
children = (
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */,
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */,
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */,
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */,
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */,
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */,
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */,
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
8940022F24ACBD2700EBE74B = { 8940022F24ACBD2700EBE74B = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8924EDF324C9CDF1004AF871 /* README.md */, 8924EDF324C9CDF1004AF871 /* README.md */,
899AD0232E66100500449710 /* DNSecure */, 8940023A24ACBD2700EBE74B /* DNSecure */,
899AD0422E66100E00449710 /* DNSecureTests */, 8940024C24ACBD2800EBE74B /* DNSecureTests */,
899AD0472E66101100449710 /* DNSecureUITests */, 8940025724ACBD2800EBE74B /* DNSecureUITests */,
8940023924ACBD2700EBE74B /* Products */, 8940023924ACBD2700EBE74B /* Products */,
8940026724ACBE4900EBE74B /* Frameworks */, 8940026724ACBE4900EBE74B /* Frameworks */,
); );
@ -114,6 +172,47 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
8940023A24ACBD2700EBE74B /* DNSecure */ = {
isa = PBXGroup;
children = (
8940026624ACBE4900EBE74B /* DNSecure.entitlements */,
8940023B24ACBD2700EBE74B /* DNSecureApp.swift */,
893AA816258F790D0060B022 /* Views */,
893AA817258F79F70060B022 /* Models */,
893AA818258F7A0C0060B022 /* Extensions */,
8940023F24ACBD2800EBE74B /* Assets.xcassets */,
8940024424ACBD2800EBE74B /* Info.plist */,
8940024124ACBD2800EBE74B /* Preview Content */,
);
path = DNSecure;
sourceTree = "<group>";
};
8940024124ACBD2800EBE74B /* Preview Content */ = {
isa = PBXGroup;
children = (
8940024224ACBD2800EBE74B /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
8940024C24ACBD2800EBE74B /* DNSecureTests */ = {
isa = PBXGroup;
children = (
8940024D24ACBD2800EBE74B /* DNSecureTests.swift */,
8940024F24ACBD2800EBE74B /* Info.plist */,
);
path = DNSecureTests;
sourceTree = "<group>";
};
8940025724ACBD2800EBE74B /* DNSecureUITests */ = {
isa = PBXGroup;
children = (
8940025824ACBD2800EBE74B /* DNSecureUITests.swift */,
8940025A24ACBD2800EBE74B /* Info.plist */,
);
path = DNSecureUITests;
sourceTree = "<group>";
};
8940026724ACBE4900EBE74B /* Frameworks */ = { 8940026724ACBE4900EBE74B /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -137,9 +236,6 @@
); );
dependencies = ( dependencies = (
); );
fileSystemSynchronizedGroups = (
899AD0232E66100500449710 /* DNSecure */,
);
name = DNSecure; name = DNSecure;
productName = DNSecure; productName = DNSecure;
productReference = 8940023824ACBD2700EBE74B /* DNSecure.app */; productReference = 8940023824ACBD2700EBE74B /* DNSecure.app */;
@ -158,9 +254,6 @@
dependencies = ( dependencies = (
8940024B24ACBD2800EBE74B /* PBXTargetDependency */, 8940024B24ACBD2800EBE74B /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = (
899AD0422E66100E00449710 /* DNSecureTests */,
);
name = DNSecureTests; name = DNSecureTests;
productName = DNSecureTests; productName = DNSecureTests;
productReference = 8940024924ACBD2800EBE74B /* DNSecureTests.xctest */; productReference = 8940024924ACBD2800EBE74B /* DNSecureTests.xctest */;
@ -179,9 +272,6 @@
dependencies = ( dependencies = (
8940025624ACBD2800EBE74B /* PBXTargetDependency */, 8940025624ACBD2800EBE74B /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = (
899AD0472E66101100449710 /* DNSecureUITests */,
);
name = DNSecureUITests; name = DNSecureUITests;
productName = DNSecureUITests; productName = DNSecureUITests;
productReference = 8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */; productReference = 8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */;
@ -195,7 +285,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1200; LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 2600; LastUpgradeCheck = 1620;
TargetAttributes = { TargetAttributes = {
8940023724ACBD2700EBE74B = { 8940023724ACBD2700EBE74B = {
CreatedOnToolsVersion = 12.0; CreatedOnToolsVersion = 12.0;
@ -211,6 +301,7 @@
}; };
}; };
buildConfigurationList = 8940023324ACBD2700EBE74B /* Build configuration list for PBXProject "DNSecure" */; buildConfigurationList = 8940023324ACBD2700EBE74B /* Build configuration list for PBXProject "DNSecure" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@ -218,8 +309,6 @@
Base, Base,
); );
mainGroup = 8940022F24ACBD2700EBE74B; mainGroup = 8940022F24ACBD2700EBE74B;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 8940023924ACBD2700EBE74B /* Products */; productRefGroup = 8940023924ACBD2700EBE74B /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -236,6 +325,8 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8940024324ACBD2800EBE74B /* Preview Assets.xcassets in Resources */,
8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -260,6 +351,25 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */,
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */,
890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */,
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */,
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */,
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */,
894F33652C46D2F00060F385 /* RestorationView.swift in Sources */,
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */,
894958AD2548405E009691D5 /* RuleView.swift in Sources */,
8998041828DCDEEF00C8B421 /* DoHSections.swift in Sources */,
89E8B71A29F80164002C2AEF /* LazyTextField.swift in Sources */,
890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */,
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */,
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */,
8998041628DCDED800C8B421 /* DoTSections.swift in Sources */,
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */,
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */,
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */,
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -267,6 +377,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -274,6 +385,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -300,7 +412,8 @@
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
@ -328,11 +441,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@ -347,14 +459,12 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
}; };
name = Debug; name = Debug;
}; };
@ -365,7 +475,8 @@
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
@ -393,11 +504,10 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@ -406,12 +516,11 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 6.0; SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -421,35 +530,24 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements; CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_ASSET_PATHS = "\"DNSecure/Preview Content\"";
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_APP_SANDBOX = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DNSecure/Info.plist; INFOPLIST_FILE = DNSecure/Info.plist;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.6.2; MARKETING_VERSION = 1.5.1;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 6.0;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@ -459,35 +557,24 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements; CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_ASSET_PATHS = "\"DNSecure/Preview Content\"";
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_APP_SANDBOX = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DNSecure/Info.plist; INFOPLIST_FILE = DNSecure/Info.plist;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.6.2; MARKETING_VERSION = 1.5.1;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 6.0;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
@ -497,15 +584,18 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DNSecureTests/Info.plist;
MARKETING_VERSION = 1.0; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DNSecure"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/DNSecure";
}; };
name = Debug; name = Debug;
}; };
@ -514,15 +604,18 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DNSecureTests/Info.plist;
MARKETING_VERSION = 1.0; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DNSecure"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/DNSecure";
}; };
name = Release; name = Release;
}; };
@ -530,13 +623,16 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DNSecureUITests/Info.plist;
MARKETING_VERSION = 1.0; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = DNSecure; TEST_TARGET_NAME = DNSecure;
}; };
@ -546,13 +642,16 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2; DEVELOPMENT_TEAM = X4678G5DL2;
GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DNSecureUITests/Info.plist;
MARKETING_VERSION = 1.0; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests; PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = DNSecure; TEST_TARGET_NAME = DNSecure;
}; };

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "2600" LastUpgradeVersion = "1620"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,29 +0,0 @@
{
"fill" : {
"automatic-gradient" : "srgb:0.96863,0.77255,0.28235,1.00000"
},
"groups" : [
{
"layers" : [
{
"image-name" : "DNSecure-icon.svg",
"name" : "DNSecure-icon"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View file

@ -1,14 +0,0 @@
{
"images" : [
{
"filename" : "Icon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,116 @@
{
"images" : [
{
"filename" : "Icon-40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon-58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon-80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-121.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "Icon-41.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "Icon-59.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-42.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "Icon-81.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "Icon-152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -8,5 +8,9 @@
</array> </array>
<key>com.apple.developer.networking.wifi-info</key> <key>com.apple.developer.networking.wifi-info</key>
<true/> <true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View file

@ -1,10 +0,0 @@
//
// NEEvaluateConnectionRuleAction+Codable.swift
// DNSecure
//
// Created by Kenta Kubo on 8/31/25.
//
import NetworkExtension
extension NEEvaluateConnectionRuleAction: @retroactive Codable {}

View file

@ -0,0 +1,14 @@
//
// NEOnDemandRuleAction+CaseIterable.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleAction: @retroactive CaseIterable {
public static var allCases: [Self] {
[.connect, .disconnect, .evaluateConnection, .ignore]
}
}

View file

@ -7,4 +7,4 @@
import NetworkExtension import NetworkExtension
extension NEOnDemandRuleAction: @retroactive Codable {} extension NEOnDemandRuleAction: Codable {}

View file

@ -10,11 +10,16 @@ import NetworkExtension
extension NEOnDemandRuleAction: @retroactive CustomStringConvertible { extension NEOnDemandRuleAction: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case .connect: "Apply settings" case .connect:
case .disconnect: "Do not apply settings" return "Apply settings"
case .evaluateConnection: "Apply with excluded domains" case .disconnect:
case .ignore: "As is" return "Do not apply settings"
@unknown case _: "Unknown" case .evaluateConnection:
return "Apply with excluded domains"
case .ignore:
return "As is"
default:
return "Unknown"
} }
} }
} }

View file

@ -10,9 +10,9 @@ import NetworkExtension
extension NEOnDemandRuleInterfaceType: @retroactive CaseIterable { extension NEOnDemandRuleInterfaceType: @retroactive CaseIterable {
public static var allCases: [Self] { public static var allCases: [Self] {
#if os(macOS) #if os(macOS)
[.any, .ethernet, .wiFi] return [.any, .ethernet, .wiFi]
#else #else
[.any, .wiFi, .cellular] return [.any, .wiFi, .cellular]
#endif #endif
} }
} }

View file

@ -7,4 +7,4 @@
import NetworkExtension import NetworkExtension
extension NEOnDemandRuleInterfaceType: @retroactive Codable {} extension NEOnDemandRuleInterfaceType: Codable {}

View file

@ -10,15 +10,20 @@ import NetworkExtension
extension NEOnDemandRuleInterfaceType: @retroactive CustomStringConvertible { extension NEOnDemandRuleInterfaceType: @retroactive CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case .any: "Any" case .any:
return "Any"
#if os(macOS) #if os(macOS)
case .ethernet: "Ethernet" case .ethernet:
return "Ethernet"
#endif #endif
case .wiFi: "Wi-Fi" case .wiFi:
return "Wi-Fi"
#if os(iOS) #if os(iOS)
case .cellular: "Cellular" case .cellular:
return "Cellular"
#endif #endif
@unknown case _: "Unknown" default:
return "Unknown"
} }
} }
} }

View file

@ -1,17 +0,0 @@
//
// NEOnDemandRuleInterfaceType+isSSIDUsed.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleInterfaceType {
var isSSIDUsed: Bool {
switch self {
case .any, .wiFi: true
case _: false
}
}
}

View file

@ -0,0 +1,19 @@
//
// NEOnDemandRuleInterfaceType+ssidIsUsed.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleInterfaceType {
var ssidIsUsed: Bool {
switch self {
case .any, .wiFi:
return true
default:
return false
}
}
}

View file

@ -2,10 +2,53 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>
<false/> <false/>
</dict> </dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict> </dict>
</plist> </plist>

View file

@ -1,86 +0,0 @@
//
// 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")
}
}
}
}

View file

@ -106,6 +106,77 @@ extension Configuration: CustomStringConvertible {
} }
} }
struct OnDemandRule {
var id = UUID()
var name: String
var action: NEOnDemandRuleAction = .ignore
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.ssidIsUsed {
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.ssidIsUsed {
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.ssidIsUsed {
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.ssidIsUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
default:
preconditionFailure("Unexpected NEOnDemandRuleAction")
}
}
}
}
struct Resolver { struct Resolver {
var id = UUID() var id = UUID()
var name: String var name: String

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -5,33 +5,21 @@
// Created by Kenta Kubo on 7/1/20. // Created by Kenta Kubo on 7/1/20.
// //
@preconcurrency import NetworkExtension import NetworkExtension
import SwiftUI import SwiftUI
struct ContentView { struct ContentView {
@Environment(\.scenePhase) private var scenePhase
@Environment(\.horizontalSizeClass) private var hSizeClass @Environment(\.horizontalSizeClass) private var hSizeClass
@Binding var servers: Resolvers @Binding var servers: Resolvers
@Binding var usedID: String? @Binding var usedID: String?
@State private var isActivated = false @State private var isEnabled = false
@State private var selection: Int? @State private var selection: Int?
@State private var isAlertPresented = false @State private var alertIsPresented = false
@State private var alertTitle = "" @State private var alertTitle = ""
@State private var alertMessage = "" @State private var alertMessage = ""
@State private var isGuidePresented = false @State private var guideIsPresented = false
@State private var isRestoring = false @State private var isRestoring = false
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
@State private var sidebarEditMode: EditMode = .inactive
@State private var detailEditMode: EditMode = .inactive
#endif
private var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode {
if #available(iOS 26, *) {
.inline
} else {
.automatic
}
}
private func addNewDoTServer() { private func addNewDoTServer() {
self.servers.append( self.servers.append(
@ -53,8 +41,8 @@ struct ContentView {
self.selection = self.servers.count - 1 self.selection = self.servers.count - 1
} }
private func restoreFromPresets(resolver: Resolver) { private func restoreFromPresets(resolvers: Set<Resolver>) {
self.servers.append(resolver) self.servers.append(contentsOf: resolvers)
} }
private func removeServers(at indexSet: IndexSet) { private func removeServers(at indexSet: IndexSet) {
@ -90,7 +78,7 @@ struct ContentView {
logger.error("\(err.localizedDescription)") logger.error("\(err.localizedDescription)")
self.alert("Load Error", err.localizedDescription) self.alert("Load Error", err.localizedDescription)
} else { } else {
self.isActivated = manager.isEnabled self.isEnabled = manager.isEnabled
} }
} }
#endif #endif
@ -108,7 +96,6 @@ struct ContentView {
} }
let manager = NEDNSSettingsManager.shared() let manager = NEDNSSettingsManager.shared()
manager.localizedDescription = server.name
manager.dnsSettings = server.configuration.toDNSSettings() manager.dnsSettings = server.configuration.toDNSSettings()
manager.onDemandRules = server.onDemandRules.toNEOnDemandRules() manager.onDemandRules = server.onDemandRules.toNEOnDemandRules()
manager.saveToPreferences { saveError in manager.saveToPreferences { saveError in
@ -125,7 +112,7 @@ struct ContentView {
self.removeSettings() self.removeSettings()
return return
} }
logger.debug("The DNS settings were saved") logger.debug("DNS settings was saved")
} }
#endif #endif
} }
@ -151,7 +138,7 @@ struct ContentView {
self.alert("Remove Error", removeError.localizedDescription) self.alert("Remove Error", removeError.localizedDescription)
return return
} }
logger.debug("The DNS settings were removed") logger.debug("DNS settings was removed")
} }
#endif #endif
} }
@ -159,7 +146,7 @@ struct ContentView {
private func alert(_ title: String, _ message: String) { private func alert(_ title: String, _ message: String) {
self.alertTitle = title self.alertTitle = title
self.alertMessage = message self.alertMessage = message
self.isAlertPresented = true self.alertIsPresented = true
} }
} }
@ -180,9 +167,7 @@ extension ContentView: View {
private var modernBody: some View { private var modernBody: some View {
NavigationSplitView { NavigationSplitView {
List(selection: self.$selection) { List(selection: self.$selection) {
if !self.isActivated && self.hSizeClass == .compact {
NavigationLink("Instructions", value: -1) NavigationLink("Instructions", value: -1)
}
Section("Servers") { Section("Servers") {
ForEach(0..<self.servers.count, id: \.self) { i in ForEach(0..<self.servers.count, id: \.self) { i in
NavigationLink(value: i) { NavigationLink(value: i) {
@ -194,42 +179,21 @@ extension ContentView: View {
} }
} }
.navigationTitle(Bundle.main.displayName!) .navigationTitle(Bundle.main.displayName!)
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode) .toolbar { self.toolbarContent }
.toolbar { .alert(self.alertTitle, isPresented: self.$alertIsPresented) {
#if canImport(SwiftUI, _version: 7)
if #available(iOS 26, *) {
ToolbarItem(placement: .subtitle) {
self.statusIndicator
.foregroundStyle(.secondary)
}
}
#endif
self.toolbarContent
}
.alert(self.alertTitle, isPresented: self.$isAlertPresented) {
} message: { } message: {
Text(self.alertMessage) Text(self.alertMessage)
} }
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
.environment(\.editMode, self.$sidebarEditMode)
#endif
} detail: { } detail: {
if let i = self.selection, i >= 0 { if self.selection == -1 {
NavigationStack { HowToActivateView()
self.detailView(at: i) } else if let i = self.selection {
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode) self.detailView(at: i)
} } else if !self.isEnabled {
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
.environment(\.editMode, self.$detailEditMode)
#endif
} else if !self.isActivated || self.selection == -1 {
HowToActivateView() HowToActivateView()
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
} else { } else {
Text("Select a server on the sidebar") Text("Select a server on the sidebar")
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode) .navigationBarHidden(true)
} }
} }
.onAppear(perform: self.updateStatus) .onAppear(perform: self.updateStatus)
@ -241,13 +205,25 @@ extension ContentView: View {
self.updateStatus() self.updateStatus()
} }
} }
.onChange(of: self.scenePhase) { phase in
if phase == .background {
// FIXME: This is a workaround for self.$severs[i].
// That cannot save settings as soon as it is modified.
guard let id = self.usedID,
let uuid = UUID(uuidString: id),
let server = self.servers.find(by: uuid)
else {
return
}
self.saveSettings(of: server)
}
}
} }
@available(iOS, deprecated: 16)
private var legacyBody: some View { private var legacyBody: some View {
NavigationView { NavigationView {
List { List {
if !self.isActivated && self.hSizeClass == .compact { if self.hSizeClass == .compact {
NavigationLink( NavigationLink(
"Instructions", "Instructions",
tag: -1, tag: -1,
@ -255,6 +231,11 @@ extension ContentView: View {
) { ) {
HowToActivateView() HowToActivateView()
} }
} else {
// Workaround for iOS 15
Button("Instructions") {
self.selection = -1
}
} }
Section("Servers") { Section("Servers") {
ForEach(0..<self.servers.count, id: \.self) { i in ForEach(0..<self.servers.count, id: \.self) { i in
@ -282,17 +263,20 @@ extension ContentView: View {
} }
.navigationTitle(Bundle.main.displayName!) .navigationTitle(Bundle.main.displayName!)
.toolbar { self.toolbarContent } .toolbar { self.toolbarContent }
.alert(self.alertTitle, isPresented: self.$isAlertPresented) { .alert(self.alertTitle, isPresented: self.$alertIsPresented) {
} message: { } message: {
Text(self.alertMessage) Text(self.alertMessage)
} }
if let i = self.selection, i >= 0 { if self.selection == -1 {
HowToActivateView()
} else if let i = self.selection {
self.detailView(at: i) self.detailView(at: i)
} else if !self.isActivated || self.selection == -1 { } else if !self.isEnabled {
HowToActivateView() HowToActivateView()
} else { } else {
Text("Select a server on the sidebar") Text("Select a server on the sidebar")
.navigationBarHidden(true)
} }
} }
.onAppear(perform: self.updateStatus) .onAppear(perform: self.updateStatus)
@ -304,76 +288,75 @@ extension ContentView: View {
self.updateStatus() self.updateStatus()
} }
} }
.onChange(of: self.scenePhase) { phase in
if phase == .background {
// FIXME: This is a workaround for self.$severs[i].
// That cannot save settings as soon as it is modified.
guard let id = self.usedID,
let uuid = UUID(uuidString: id),
let server = self.servers.find(by: uuid)
else {
return
}
self.saveSettings(of: server)
}
}
} }
@ToolbarContentBuilder private var toolbarContent: some ToolbarContent { @ToolbarContentBuilder private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
if #available(iOS 26, *) { Menu {
#if targetEnvironment(macCatalyst)
// Workaround for the issue that the checkmark icon is hard to see if System Settings > Appearance > Theme > Color is Multicolor.
Button {
withAnimation {
if !self.sidebarEditMode.isEditing {
self.sidebarEditMode = .active
} else {
self.sidebarEditMode = .inactive
}
}
} label: {
if !self.sidebarEditMode.isEditing {
Text("Edit")
} else {
Label("Done", systemImage: "checkmark")
}
}
.tint(self.sidebarEditMode.isEditing ? .accentColor : .primary)
#else
EditButton()
#endif
} else {
self.addMenu
}
}
ToolbarItem(placement: .topBarTrailing) {
if #available(iOS 26, *) {
self.addMenu
} else {
EditButton()
}
}
ToolbarItem(placement: .status) {
if #available(iOS 26, *) {
#if !canImport(SwiftUI, _version: 7)
self.statusIndicator
#endif
} else {
self.statusIndicator
}
}
}
private var addMenu: some View {
Menu("Add", systemImage: "plus") {
Button("DNS-over-TLS", action: self.addNewDoTServer) Button("DNS-over-TLS", action: self.addNewDoTServer)
Button("DNS-over-HTTPS", action: self.addNewDoHServer) Button("DNS-over-HTTPS", action: self.addNewDoHServer)
Button("Restore from Presets") { Button("Restore from Presets") {
self.isRestoring = true self.isRestoring = true
} }
} label: {
Image(systemName: "plus")
} }
.sheet(isPresented: self.$isRestoring) { .sheet(isPresented: self.$isRestoring) {
RestorationView(onAdd: self.restoreFromPresets) RestorationView(onAdd: self.restoreFromPresets)
} }
} }
ToolbarItem(placement: .topBarTrailing) {
private var statusIndicator: some View { EditButton()
Label { }
Text(self.isActivated ? "Active" : "Inactive") ToolbarItem(placement: .status) {
} icon: { VStack(spacing: 0) {
HStack {
Circle() Circle()
.fill(self.isActivated ? .green : .secondary)
.frame(width: 10, height: 10) .frame(width: 10, height: 10)
.foregroundStyle(self.isEnabled ? .green : .secondary)
Text(self.isEnabled ? "Active" : "Inactive")
}
if !self.isEnabled {
Button {
self.guideIsPresented = true
} label: {
Label("How to Activate", systemImage: "questionmark.circle")
} }
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
.font(.caption)
.sheet(isPresented: self.$guideIsPresented) {
NavigationView {
HowToActivateView()
.safeAreaInset(edge: .bottom) {
Button("Dismiss") {
self.guideIsPresented = false
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.hoverEffect()
.padding()
.frame(maxWidth: .infinity)
.background(Color(.systemBackground))
}
}
.navigationViewStyle(.stack)
}
}
}
}
} }
private func sidebarRow(at i: Int) -> some View { private func sidebarRow(at i: Int) -> some View {
@ -385,10 +368,6 @@ extension ContentView: View {
} }
if self.usedID == self.servers[i].id.uuidString { if self.usedID == self.servers[i].id.uuidString {
Spacer() Spacer()
if !self.isActivated {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(.red)
}
Image(systemName: "checkmark") Image(systemName: "checkmark")
} }
} }
@ -397,7 +376,7 @@ extension ContentView: View {
private func detailView(at i: Int) -> some View { private func detailView(at i: Int) -> some View {
DetailView( DetailView(
server: self.$servers[i], server: self.$servers[i],
isSelected: .init( isOn: .init(
get: { get: {
self.usedID == self.servers[i].id.uuidString self.usedID == self.servers[i].id.uuidString
}, },
@ -408,8 +387,7 @@ extension ContentView: View {
self.removeSettings() self.removeSettings()
} }
} }
), )
isActivated: self.$isActivated
) )
} }
} }

View file

@ -1,50 +0,0 @@
import NetworkExtension
import SwiftUI
struct DNSSearchDomainMatchView {
@Binding var rule: OnDemandRule
}
extension DNSSearchDomainMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.dnsSearchDomainMatch.count, id: \.self) { i in
LazyTextField(
"Search Domain",
// self.$rule.dnsSearchDomainMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsSearchDomainMatch[i] },
set: { self.rule.dnsSearchDomainMatch[i] = $0 }
)
)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.dnsSearchDomainMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsSearchDomainMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Search Domain") {
self.rule.dnsSearchDomainMatch.append("")
}
} footer: {
Text(
"If the current default search domain is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("DNS Search Domain Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
DNSSearchDomainMatchView(rule: $rule)
}
}

View file

@ -1,50 +0,0 @@
import NetworkExtension
import SwiftUI
struct DNSServerAddressMatchView {
@Binding var rule: OnDemandRule
}
extension DNSServerAddressMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.dnsServerAddressMatch.count, id: \.self) { i in
LazyTextField(
"IP Address",
// self.$rule.dnsServerAddressMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsServerAddressMatch[i] },
set: { self.rule.dnsServerAddressMatch[i] = $0 }
)
)
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.dnsServerAddressMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsServerAddressMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Server Address") {
self.rule.dnsServerAddressMatch.append("")
}
} footer: {
Text(
"If each of the current default DNS servers is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("DNS Server Address Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
DNSServerAddressMatchView(rule: $rule)
}
}

View file

@ -9,9 +9,7 @@ import SwiftUI
struct DetailView { struct DetailView {
@Binding var server: Resolver @Binding var server: Resolver
@Binding var isSelected: Bool @Binding var isOn: Bool
@Binding var isActivated: Bool
@State private var isGuidePresented = false
private func binding(for id: UUID) -> Binding<OnDemandRule> { private func binding(for id: UUID) -> Binding<OnDemandRule> {
guard let index = self.server.onDemandRules.map(\.id).firstIndex(of: id) else { guard let index = self.server.onDemandRules.map(\.id).firstIndex(of: id) else {
@ -24,42 +22,57 @@ struct DetailView {
@MainActor @MainActor
extension DetailView: View { extension DetailView: View {
var body: some View { var body: some View {
if #available(iOS 16, *) {
self.modernBody
} else {
self.legacyBody
}
}
@available(iOS 16, *)
private var modernBody: some View {
NavigationStack {
Form { Form {
Section { Section {
Toggle("Use This Server", isOn: self.$isSelected) Toggle("Use This Server", isOn: self.$isOn)
if self.isSelected && !self.isActivated {
Button("One more step is required.", systemImage: "exclamationmark.triangle") {
self.isGuidePresented = true
} }
.labelStyle(.titleAndIcon) Section("Name") {
.tint(.red) LazyTextField("Name", text: self.$server.name)
.sheet(isPresented: self.$isGuidePresented) {
NavigationView {
HowToActivateView()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
#if canImport(SwiftUI, _version: 7)
if #available(iOS 26, *) {
Button("Close", systemImage: "xmark", role: .close) {
self.isGuidePresented = false
} }
} else { self.serverConfigurationSections
Button("Close", systemImage: "xmark", role: .cancel) { Section {
self.isGuidePresented = false ForEach(self.server.onDemandRules) { rule in
NavigationLink(rule.name, value: rule.id)
} }
.onDelete { self.server.onDemandRules.remove(atOffsets: $0) }
.onMove { self.server.onDemandRules.move(fromOffsets: $0, toOffset: $1) }
Button("Add New Rule") {
self.server.onDemandRules
.append(OnDemandRule(name: "New Rule"))
} }
#else } header: {
Button("Close", systemImage: "xmark", role: .cancel) { EditButton()
self.isGuidePresented = false .frame(maxWidth: .infinity, alignment: .trailing)
} .overlay(alignment: .leading) {
#endif Text("On Demand Rules")
} }
} }
} }
.navigationViewStyle(.stack) .navigationDestination(for: UUID.self) { id in
// When RuleView is opened and tap another server on the sidebar, the previous server's rule comes here.
if self.server.onDemandRules.map(\.id).contains(id) {
RuleView(rule: self.binding(for: id))
} }
} }
} }
.navigationTitle(self.server.name)
}
private var legacyBody: some View {
Form {
Section {
Toggle("Use This Server", isOn: self.$isOn)
}
Section("Name") { Section("Name") {
LazyTextField("Name", text: self.$server.name) LazyTextField("Name", text: self.$server.name)
} }
@ -80,16 +93,11 @@ extension DetailView: View {
EditButton() EditButton()
.frame(maxWidth: .infinity, alignment: .trailing) .frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) { .overlay(alignment: .leading) {
Text("On-Demand Rules") Text("On Demand Rules")
} }
} }
} }
.navigationTitle(self.server.name) .navigationTitle(self.server.name)
.onChange(of: self.server) { [oldValue = self.server] newValue in
guard self.isSelected, oldValue.id == newValue.id else { return }
// When the selected server's configuration is modified, the server will be deactivated or deselected so that the user can save the changes by toggling the Use This Server switch again.
self.isSelected = false
}
} }
@ViewBuilder private var serverConfigurationSections: some View { @ViewBuilder private var serverConfigurationSections: some View {
@ -120,7 +128,6 @@ extension DetailView: View {
configuration: .dnsOverTLS(DoTConfiguration()) configuration: .dnsOverTLS(DoTConfiguration())
) )
), ),
isSelected: .constant(true), isOn: .constant(true)
isActivated: .constant(true)
) )
} }

View file

@ -1,49 +0,0 @@
import SwiftUI
struct ExcludedDomainsView {
@Binding var domains: [String]
}
extension ExcludedDomainsView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.domains.count, id: \.self) { i in
LazyTextField(
"Domain",
// self.$rule.excludedDomains[i] causes crash on deletion
text: .init(
get: { self.domains[i] },
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) }
Button("Add Domain") {
self.domains.append("")
}
} footer: {
Text(
"Each domain is matched against the destination hostname using suffix matching, and each label in the domain must match an entire label in the hostname. For example, the domain `example.com` will match the hostname `www.example.com` but not `www.anotherexample.com`."
)
}
}
.navigationTitle("Excluded Domains")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var domains = ["example.com"]
NavigationStack {
ExcludedDomainsView(domains: $domains)
}
}

View file

@ -1,35 +0,0 @@
import NetworkExtension
import SwiftUI
struct InterfaceTypeMatchView {
@Binding var rule: OnDemandRule
}
extension InterfaceTypeMatchView: View {
var body: some View {
Form {
Section {
Picker("Interface Type", selection: self.$rule.interfaceType) {
ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) {
Text($0.description)
}
}
.pickerStyle(.inline)
.labelsHidden()
} footer: {
Text(
"If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("Interface Type Match")
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
InterfaceTypeMatchView(rule: $rule)
}
}

View file

@ -26,13 +26,13 @@ extension LazyTextField: View {
TextField(self.title, text: self.$localText) TextField(self.title, text: self.$localText)
.focused(self.$isFocused) .focused(self.$isFocused)
if self.isFocused && !self.localText.isEmpty { if self.isFocused && !self.localText.isEmpty {
Button("Clear", systemImage: "xmark.circle.fill") { Button {
self.text.removeAll() self.text.removeAll()
self.localText.removeAll() } label: {
} Image(systemName: "xmark.circle.fill")
.foregroundStyle(Color.primary) .foregroundStyle(Color.primary)
.opacity(0.2) .opacity(0.2)
.labelStyle(.iconOnly) }
.buttonStyle(.borderless) .buttonStyle(.borderless)
.hoverEffect() .hoverEffect()
} }

View file

@ -1,40 +0,0 @@
import NetworkExtension
import SwiftUI
struct ProbeURLView {
@Binding var rule: OnDemandRule
private var probeURL: Binding<String> {
.init(
get: { self.rule.probeURL?.absoluteString ?? "" },
set: { self.rule.probeURL = URL(string: $0) }
)
}
}
extension ProbeURLView: View {
var body: some View {
Form {
Section {
LazyTextField("Probe URL", text: self.probeURL)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
} footer: {
Text(
"If a request sent to this URL results in a HTTP 200 OK response and all of the other conditions in the rule match, then the rule matches. If you don't want to use this rule, leave it empty."
)
}
}
.navigationTitle("Probe URL")
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
ProbeURLView(rule: $rule)
}
}

View file

@ -9,9 +9,9 @@ import SwiftUI
struct RestorationView { struct RestorationView {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var added = Set<Resolver>() @State private var selection = Set<Resolver>()
@State private var keyword = "" @State private var keyword = ""
let onAdd: (Resolver) -> Void let onAdd: (Set<Resolver>) -> Void
private var servers: Resolvers { private var servers: Resolvers {
guard !self.keyword.isEmpty else { return Presets.servers } guard !self.keyword.isEmpty else { return Presets.servers }
@ -23,6 +23,13 @@ extension RestorationView: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
List(self.servers, id: \.self) { resolver in List(self.servers, id: \.self) { resolver in
Button {
if self.selection.contains(resolver) {
self.selection.remove(resolver)
} else {
self.selection.insert(resolver)
}
} label: {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(resolver.name) Text(resolver.name)
@ -30,34 +37,29 @@ extension RestorationView: View {
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
Spacer() Spacer()
if self.added.contains(resolver) { if self.selection.contains(resolver) {
HStack(spacing: 5) {
Image(systemName: "checkmark") Image(systemName: "checkmark")
Text("Added")
}
.foregroundStyle(.secondary)
} else {
Button {
self.added.insert(resolver)
self.onAdd(resolver)
} label: {
HStack(spacing: 5) {
Image(systemName: "plus")
Text("Add")
} }
} }
.buttonStyle(.borderedProminent) .tint(.primary)
.buttonBorderShape(.capsule)
.hoverEffect()
}
} }
} }
.navigationTitle("Presets") .navigationTitle("Presets")
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .primaryAction) {
Button("Cancel", systemImage: "xmark", role: .cancel) { Button("Add") {
self.onAdd(self.selection)
self.dismiss() self.dismiss()
} }
.disabled(self.selection.isEmpty)
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", role: .cancel) {
self.dismiss()
}
}
ToolbarItem(placement: .bottomBar) {
Text("\(self.selection.count) Selected")
} }
} }
} }

View file

@ -12,7 +12,6 @@ struct RuleView {
@Binding var rule: OnDemandRule @Binding var rule: OnDemandRule
} }
@MainActor
extension RuleView: View { extension RuleView: View {
var body: some View { var body: some View {
Form { Form {
@ -20,193 +19,136 @@ extension RuleView: View {
LazyTextField("Name", text: self.$rule.name) LazyTextField("Name", text: self.$rule.name)
} }
Section("Matching Conditions") { Section {
NavigationLink { Picker("Action", selection: self.$rule.action) {
InterfaceTypeMatchView(rule: self.$rule) ForEach(NEOnDemandRuleAction.allCases, id: \.self) {
} label: { Text($0.description)
self.interfaceTypeMatchLabel
} }
if self.rule.interfaceType.isSSIDUsed {
NavigationLink {
SSIDMatchView(rule: self.$rule)
} label: {
self.ssidMatchLabel
}
}
NavigationLink {
DNSSearchDomainMatchView(rule: self.$rule)
} label: {
self.dnsSearchDomainMatchLabel
}
NavigationLink {
DNSServerAddressMatchView(rule: self.$rule)
} label: {
self.dnsServerAddressMatchLabel
}
NavigationLink {
ProbeURLView(rule: self.$rule)
} label: {
self.probeURLLabel
} }
} }
Section { Section {
Picker("Action", selection: self.$rule.action) { Picker("Interface Type", selection: self.$rule.interfaceType) {
ForEach( ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) {
[
NEOnDemandRuleAction.connect,
.evaluateConnection,
.disconnect,
],
id: \.self
) {
Text($0.description) Text($0.description)
} }
} }
if self.rule.action == .evaluateConnection { } header: {
NavigationLink { Text("Interface Type Match")
ExcludedDomainsView( } footer: {
domains: .init( Text(
get: { self.rule.excludedDomains ?? [] }, "If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches."
set: { self.rule.excludedDomains = $0 } )
}
if self.rule.interfaceType.ssidIsUsed {
Section {
ForEach(0..<self.rule.ssidMatch.count, id: \.self) { i in
LazyTextField(
"SSID",
// self.$rule.ssidMatch[i] causes crash on deletion
text: .init(
get: { self.rule.ssidMatch[i] },
set: { self.rule.ssidMatch[i] = $0 }
) )
) )
} label: { }
HStack { .onDelete { self.rule.ssidMatch.remove(atOffsets: $0) }
Text("Excluded Domains") .onMove { self.rule.ssidMatch.move(fromOffsets: $0, toOffset: $1) }
Spacer() Button("Add SSID") {
Text("\(self.rule.excludedDomains?.count ?? 0)") NEHotspotNetwork.fetchCurrent { network in
.foregroundStyle(.secondary) self.rule.ssidMatch.append(network?.ssid ?? "")
} }
} }
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("SSID Match")
} }
} footer: {
Text(
"If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
Section {
ForEach(0..<self.rule.dnsSearchDomainMatch.count, id: \.self) { i in
LazyTextField(
"Search Domain",
// self.$rule.dnsSearchDomainMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsSearchDomainMatch[i] },
set: { self.rule.dnsSearchDomainMatch[i] = $0 }
)
)
}
.onDelete { self.rule.dnsSearchDomainMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsSearchDomainMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Search Domain") {
self.rule.dnsSearchDomainMatch.append("")
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("DNS Search Domain Match")
}
} footer: {
Text(
"If the current default search domain is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
Section {
ForEach(0..<self.rule.dnsServerAddressMatch.count, id: \.self) { i in
LazyTextField(
"IP Address",
// self.$rule.dnsServerAddressMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsServerAddressMatch[i] },
set: { self.rule.dnsServerAddressMatch[i] = $0 }
)
)
}
.onDelete { self.rule.dnsServerAddressMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsServerAddressMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Server Address") {
self.rule.dnsServerAddressMatch.append("")
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("DNS Server Address Match")
}
} footer: {
Text(
"If each of the current default DNS servers is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
Section {
LazyTextField(
"Probe URL",
text: .init(
get: { self.rule.probeURL?.absoluteString ?? "" },
set: { self.rule.probeURL = URL(string: $0) }
)
)
} header: {
Text("Probe URL Match")
} footer: {
Text(
"If a request sent to this URL results in a HTTP 200 OK response and all of the other conditions in the rule match, then the rule matches. If you don't want to use this rule, leave it empty."
)
} }
} }
.navigationTitle(self.rule.name) .navigationTitle(self.rule.name)
} }
@ViewBuilder private var interfaceTypeMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("Interface Type Match", value: self.rule.interfaceType.description)
} else {
HStack {
Text("Interface Type Match")
Spacer()
Text(self.rule.interfaceType.description)
.foregroundStyle(.secondary)
}
}
} }
@ViewBuilder private var ssidMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("SSID Match") {
if !self.rule.ssidMatch.isEmpty {
Text("\(self.rule.ssidMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("SSID Match")
Spacer()
Group {
if !self.rule.ssidMatch.isEmpty {
Text("\(self.rule.ssidMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var dnsSearchDomainMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("DNS Search Domain Match") {
if !self.rule.dnsSearchDomainMatch.isEmpty {
Text("\(self.rule.dnsSearchDomainMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("DNS Search Domain Match")
Spacer()
Group {
if !self.rule.dnsSearchDomainMatch.isEmpty {
Text("\(self.rule.dnsSearchDomainMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var dnsServerAddressMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("DNS Server Address Match") {
if !self.rule.dnsServerAddressMatch.isEmpty {
Text("\(self.rule.dnsServerAddressMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("DNS Server Address Match")
Spacer()
Group {
if !self.rule.dnsServerAddressMatch.isEmpty {
Text("\(self.rule.dnsServerAddressMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var probeURLLabel: some View {
if #available(iOS 16, *) {
LabeledContent("Probe URL") {
if let url = self.rule.probeURL?.absoluteString {
Text(url)
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("Probe URL")
Spacer()
Group {
if let url = self.rule.probeURL?.absoluteString {
Text(url)
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
}
@available(iOS 17, *)
#Preview { #Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule") RuleView(rule: .constant(OnDemandRule(name: "Preview Rule")))
NavigationStack {
RuleView(rule: $rule)
}
} }

View file

@ -1,50 +0,0 @@
import NetworkExtension
import SwiftUI
struct SSIDMatchView {
@Binding var rule: OnDemandRule
}
extension SSIDMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.ssidMatch.count, id: \.self) { i in
LazyTextField(
"SSID",
// self.$rule.ssidMatch[i] causes crash on deletion
text: .init(
get: { self.rule.ssidMatch[i] },
set: { self.rule.ssidMatch[i] = $0 }
)
)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.ssidMatch.remove(atOffsets: $0) }
.onMove { self.rule.ssidMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add SSID") {
NEHotspotNetwork.fetchCurrent { network in
self.rule.ssidMatch.append(network?.ssid ?? "")
}
}
} footer: {
Text(
"If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("SSID Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
SSIDMatchView(rule: $rule)
}
}

View file

@ -5,6 +5,28 @@
// Created by Kenta Kubo on 7/1/20. // Created by Kenta Kubo on 7/1/20.
// //
import Testing import XCTest
@testable import DNSecure @testable import DNSecure
class DNSecureTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

22
DNSecureTests/Info.plist Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -7,7 +7,7 @@
import XCTest import XCTest
final class DNSecureUITests: XCTestCase { class DNSecureUITests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
@ -21,20 +21,21 @@ final class DNSecureUITests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
@MainActor
func testExample() throws { func testExample() throws {
// UI tests must launch the application that they test. // UI tests must launch the application that they test.
let app = XCUIApplication() let app = XCUIApplication()
app.launch() app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results. // Use XCTAssert and related functions to verify your tests produce the correct results.
} }
@MainActor
func testLaunchPerformance() throws { func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
// This measures how long it takes to launch your application. // This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) { measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch() XCUIApplication().launch()
} }
} }
} }
}

View file

@ -1,32 +0,0 @@
//
// DNSecureUITestsLaunchTests.swift
// DNSecureUITests
//
// Created by Kenta Kubo on 9/3/25.
//
import XCTest
final class DNSecureUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2026 Kenta Kubo Copyright (c) 2020-2025 Kenta Kubo
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -18,9 +18,9 @@ let package = Package(
targets: ["DNSecure"], targets: ["DNSecure"],
bundleIdentifier: "xyz.kebo.DNSecure", bundleIdentifier: "xyz.kebo.DNSecure",
teamIdentifier: "X4678G5DL2", teamIdentifier: "X4678G5DL2",
displayVersion: "1.6.2", displayVersion: "1.5.1",
bundleVersion: "1", bundleVersion: "24",
appIcon: .asset("AppIcon-legacy"), appIcon: .asset("AppIcon"),
accentColor: .asset("AccentColor"), accentColor: .asset("AccentColor"),
supportedDeviceFamilies: [ supportedDeviceFamilies: [
.pad, .pad,
@ -43,7 +43,7 @@ let package = Package(
.testTarget( .testTarget(
name: "DNSecureTests", name: "DNSecureTests",
dependencies: [ dependencies: [
.target(name: "DNSecure") "DNSecure"
], ],
path: "DNSecureTests" path: "DNSecureTests"
), ),