Files

60 lines
1.9 KiB
Swift

import Foundation
enum FirewallBlockStatus: Equatable, Sendable {
case permitted
case blocked
case unknown
}
protocol FirewallStatusChecking: Sendable {
func status(for executableURL: URL) async -> FirewallBlockStatus
}
/// `socketfilterfw --getappblocked` 是应用防火墙拒绝态的只读系统边界。
/// 命令缺失、输出漂移或执行失败一律降级为 unknown,不阻断回环/本机代理。
struct SocketFilterFirewallChecker: FirewallStatusChecking, @unchecked Sendable {
typealias CommandRunner = (URL, [String]) throws -> String
private static let toolURL = URL(
fileURLWithPath: "/usr/libexec/ApplicationFirewall/socketfilterfw")
private let runCommand: CommandRunner
init(_ runCommand: @escaping CommandRunner = Self.run) {
self.runCommand = runCommand
}
func status(for executableURL: URL) async -> FirewallBlockStatus {
statusSynchronously(for: executableURL)
}
private func statusSynchronously(for executableURL: URL) -> FirewallBlockStatus {
guard
let output = try? runCommand(
Self.toolURL, ["--getappblocked", executableURL.path])
else { return .unknown }
let normalized = output.lowercased()
if normalized.contains(" is blocked") { return .blocked }
if normalized.contains(" is permitted") { return .permitted }
return .unknown
}
private static func run(executableURL: URL, arguments: [String]) throws -> String {
let process = Process()
process.executableURL = executableURL
process.arguments = arguments
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
try process.run()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
guard process.terminationStatus == 0 else {
throw CocoaError(.executableLoad)
}
guard let output = String(data: data, encoding: .utf8) else {
throw CocoaError(.fileReadInapplicableStringEncoding)
}
return output
}
}