mac
//
// main.swift
// sptest
// 格式化输出部分的代码来源: https://github.com/janvogt/export-proxies/blob/master/export-proxies/ProxySettings.swift
// API Document: https://developer.apple.com/documentation/systemconfiguration/1437818-scdynamicstorecreatewithoptions
// Created by henry on 2024/4/26.
//
import SystemConfiguration
import CoreFoundation
import Foundation
private protocol ValueGetter {
var type: ProxySetting { get }
func getValueFromDict(_ dict: NSDictionary) -> String?
}
fileprivate enum ProxySetting {
case http, https, ftp, socks, exceptions
var envVariableName: String {
get {
var envVar: String
switch self {
case .http: envVar = "http"
case .https: envVar = "https"
case .ftp: envVar = "ftp"
case .socks: envVar = "all"
case .exceptions: envVar = "no"
}
return suffixProxy(envVar)
}
}
var protocolName: String? {
get {
var httpsProxyProtocol: String = "https"
if CommandLine.argc == 2 {
if CommandLine.arguments[1] == "--use-http-protocol-for-https-proxy" {
httpsProxyProtocol = "http"
}
}
var proto: String?
switch self {
case .http: proto = "http"
case .https: proto = httpsProxyProtocol
case .ftp: proto = "ftp"
case .socks: proto = "socks"
default: break
}
return proto
}
}
fileprivate func suffixProxy(_ label: String) -> String {
return "\(label)_proxy"
}
}
fileprivate struct ProxyProtocol: ValueGetter {
let type: ProxySetting
let kHost, kPort, kEnabled: String
func getValueFromDict(_ dict: NSDictionary) -> String? {
var value: String?
switch (type.protocolName, dict[kHost] as! NSString?, dict[kPort] as! NSNumber?, dict[kEnabled] as! NSNumber?) {
case (.some(let proto), .some(let host), .some(let port), let enabled) where (enabled != nil && enabled! == 1):
value = "\(proto)://\(host):\(port)"
default: break
}
return value
}
}
fileprivate struct Exception: ValueGetter {
let type = ProxySetting.exceptions
let kExceptions: NSString = kSCPropNetProxiesExceptionsList
func getValueFromDict(_ dict: NSDictionary) -> String? {
var value: String?
switch (type, dict[kExceptions] as? [String]) {
case (.exceptions, .some(let exceptions)):
value = exceptions.map {
$0.replacingOccurrences(of: "*.", with: ".")
}.joined(separator: ",")
default: break
}
return value
}
}
fileprivate let protocols: [ValueGetter] = [
ProxyProtocol(type: .http,
kHost: kSCPropNetProxiesHTTPProxy as String,
kPort: kSCPropNetProxiesHTTPPort as String,
kEnabled: kSCPropNetProxiesHTTPEnable as String),
ProxyProtocol(type: .https,
kHost: kSCPropNetProxiesHTTPSProxy as String,
kPort: kSCPropNetProxiesHTTPSPort as String,
kEnabled: kSCPropNetProxiesHTTPSEnable as String),
ProxyProtocol(type: .ftp,
kHost: kSCPropNetProxiesFTPProxy as String,
kPort: kSCPropNetProxiesFTPPort as String,
kEnabled: kSCPropNetProxiesFTPEnable as String),
ProxyProtocol(type: .socks,
kHost: kSCPropNetProxiesSOCKSProxy as String,
kPort: kSCPropNetProxiesSOCKSPort as String,
kEnabled: kSCPropNetProxiesSOCKSEnable as String),
Exception()
]
let changed: SCDynamicStoreCallBack = {store, keys, pointer in
let swiftArray = keys as! Array<String>
let includeGlobalProxy = swiftArray.contains(where: {$0 == "State:/Network/Global/Proxies"})
if (includeGlobalProxy) {
print("proxy has been changed!")
if let osxProxySettings: NSDictionary = SCDynamicStoreCopyProxies(store) {
for proto in protocols {
switch (proto.type.envVariableName, proto.getValueFromDict(osxProxySettings)) {
case (let name, .some(let value)):
print("name: \(name), value: \(value)")
default: break
}
}
}
} else {
print("other settings has been changed... \(keys)")
}
}
if let store = SCDynamicStoreCreateWithOptions(nil, "app" as CFString, nil, changed, nil) {
let keys = ["State:/Network/Global/Proxies" as CFString] as CFArray
SCDynamicStoreSetNotificationKeys(store, keys, nil)
let loop = SCDynamicStoreCreateRunLoopSource(kCFAllocatorDefault, store, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
CFRunLoopRun()
}
windows
// 原理:监听注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet\ManualProxies
// 依赖Advapi32.dll
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
//void main(int argc, char *argv[])
void __cdecl _tmain(int argc, TCHAR* argv[])
{
DWORD dwFilter = REG_NOTIFY_CHANGE_NAME |
REG_NOTIFY_CHANGE_ATTRIBUTES |
REG_NOTIFY_CHANGE_LAST_SET |
REG_NOTIFY_CHANGE_SECURITY;
HANDLE hEvent;
HKEY hMainKey;
HKEY hKey;
LONG lErrorCode;
hMainKey = HKEY_LOCAL_MACHINE;
// Open a key.
auto subKey = TEXT("SYSTEM\\CurrentControlSet\\Services\\NlaSvc\\Parameters\\Internet\\ManualProxies");
lErrorCode = RegOpenKeyEx(hMainKey, subKey, 0, KEY_NOTIFY, &hKey);
if (lErrorCode != ERROR_SUCCESS)
{
_tprintf(TEXT("Error in RegOpenKeyEx (%d).\n"), lErrorCode);
return;
}
// Create an event.
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hEvent == NULL)
{
_tprintf(TEXT("Error in CreateEvent (%d).\n"), GetLastError());
return;
}
// Watch the registry key for a change of value.
lErrorCode = RegNotifyChangeKeyValue(hKey,
TRUE,
dwFilter,
hEvent,
TRUE);
if (lErrorCode != ERROR_SUCCESS)
{
_tprintf(TEXT("Error in RegNotifyChangeKeyValue (%d).\n"), lErrorCode);
return;
}
// Wait for an event to occur.
_tprintf(TEXT("Waiting for a change in the specified key...\n"));
if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
{
_tprintf(TEXT("Error in WaitForSingleObject (%d).\n"), GetLastError());
return;
}
else _tprintf(TEXT("\nChange has occurred.\n"));
// Close the key.
lErrorCode = RegCloseKey(hKey);
if (lErrorCode != ERROR_SUCCESS)
{
_tprintf(TEXT("Error in RegCloseKey (%d).\n"), GetLastError());
return;
}
// Close the handle.
if (!CloseHandle(hEvent))
{
_tprintf(TEXT("Error in CloseHandle.\n"));
return;
}
}