Files
ProxyIOS/UI/More/DNSSpoofingView.swift
Trey t 148bc3887c Add iPad support, auto-pinning, and comprehensive logging
- Adaptive iPhone/iPad layout with NavigationSplitView sidebar
- Auto-detect SSL-pinned domains, fall back to passthrough
- Certificate install via local HTTP server (Safari profile flow)
- App Group-backed CA, per-domain leaf cert LRU cache
- DB-backed config repository, Darwin notification throttling
- Rules engine, breakpoint rules, pinned domain tracking
- os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui
- Fix dyld framework embed, race conditions, thread safety
2026-04-11 12:52:18 -05:00

171 lines
6.5 KiB
Swift

import SwiftUI
import ProxyCore
import GRDB
struct DNSSpoofingView: View {
@State private var isEnabled = IPCManager.shared.isDNSSpoofingEnabled
@State private var rules: [DNSSpoofRule] = []
@State private var showAddRule = false
@State private var editingRule: DNSSpoofRule?
@State private var ruleToDelete: DNSSpoofRule?
@State private var showDeleteConfirmation = false
@State private var observation: AnyDatabaseCancellable?
private let rulesRepo = RulesRepository()
var body: some View {
List {
Section {
ToggleHeaderView(
title: "DNS Spoofing",
description: "Redirect domain resolution to a different target. Useful for routing production domains to development servers.",
isEnabled: $isEnabled
)
.onChange(of: isEnabled) { _, newValue in
IPCManager.shared.isDNSSpoofingEnabled = newValue
IPCManager.shared.post(.configurationChanged)
}
}
.listRowInsets(EdgeInsets())
Section("Rules") {
if rules.isEmpty {
EmptyStateView(
icon: "network",
title: "No DNS Spoofing Rules",
subtitle: "Tap + to create a new rule."
)
} else {
ForEach(rules) { rule in
Button {
editingRule = rule
} label: {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(rule.sourceDomain)
.font(.subheadline)
Image(systemName: "arrow.right")
.font(.caption)
.foregroundStyle(.secondary)
Text(rule.targetDomain)
.font(.subheadline)
.foregroundStyle(.blue)
}
}
}
.tint(.primary)
}
.onDelete { indexSet in
if let index = indexSet.first {
ruleToDelete = rules[index]
showDeleteConfirmation = true
}
}
}
}
}
.navigationTitle("DNS Spoofing")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button { showAddRule = true } label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showAddRule) {
AddDNSSpoofRuleSheet(rulesRepo: rulesRepo, isPresented: $showAddRule)
}
.sheet(item: $editingRule) { rule in
AddDNSSpoofRuleSheet(rulesRepo: rulesRepo, existingRule: rule, isPresented: .constant(true))
}
.confirmationDialog("Delete this rule?", isPresented: $showDeleteConfirmation, presenting: ruleToDelete) { rule in
Button("Delete", role: .destructive) {
if let id = rule.id {
try? rulesRepo.deleteDNSSpoofRule(id: id)
}
}
}
.task {
observation = rulesRepo.observeDNSSpoofRules()
.start(in: DatabaseManager.shared.dbPool) { error in
print("DNS Spoof observation error: \(error)")
} onChange: { newRules in
rules = newRules
}
}
}
}
// MARK: - Add DNS Spoof Rule Sheet
private struct AddDNSSpoofRuleSheet: View {
let rulesRepo: RulesRepository
let existingRule: DNSSpoofRule?
@Binding var isPresented: Bool
@Environment(\.dismiss) private var dismiss
@State private var sourceDomain = ""
@State private var targetDomain = ""
init(rulesRepo: RulesRepository, existingRule: DNSSpoofRule? = nil, isPresented: Binding<Bool>) {
self.rulesRepo = rulesRepo
self.existingRule = existingRule
self._isPresented = isPresented
}
var body: some View {
NavigationStack {
Form {
Section {
TextField("Source Domain (e.g. api.example.com)", text: $sourceDomain)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
TextField("Target Domain (e.g. dev.example.com)", text: $targetDomain)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
Section {
Button("Save") {
if let existing = existingRule {
let updated = DNSSpoofRule(
id: existing.id,
sourceDomain: sourceDomain,
targetDomain: targetDomain,
isEnabled: existing.isEnabled,
createdAt: existing.createdAt
)
try? rulesRepo.updateDNSSpoofRule(updated)
} else {
var rule = DNSSpoofRule(
sourceDomain: sourceDomain,
targetDomain: targetDomain
)
try? rulesRepo.insertDNSSpoofRule(&rule)
}
isPresented = false
dismiss()
}
.disabled(sourceDomain.isEmpty || targetDomain.isEmpty)
}
}
.navigationTitle(existingRule == nil ? "New DNS Rule" : "Edit DNS Rule")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
isPresented = false
dismiss()
}
}
}
.onAppear {
if let rule = existingRule {
sourceDomain = rule.sourceDomain
targetDomain = rule.targetDomain
}
}
}
}
}