Автоопределение прокси

Казалось бы, тривиальнейшая задача – определить настройки прокси сервиса, заданные при помощи .pac файла либо полученные в результате работы автоконфигурации. А в действительности оказывается что информации в интернете кот наплакал, а та что есть посвящена венде (что еще печальнее). Чтобы исправить эту жутко не справедливую ситуацию, я опишу как можно определить настройки прокси в Mac OS X приложении.
Итак, необходимый API появился в Mac OS X начиная с версии 10.5, так же API присутствует в iOS, но деталей по версиям я уже сказать не могу.
Необходимая информация запрашивается из двух источников – системных настроек и сетевой подсистемы. Системные настройки позволяют понять, используется ли прокси вообще и не задан ли дрес пользователем “в ручную”. В коде это будет выглядеть примерно так:

CFDictionaryRef array = SCDynamicStoreCopyProxies(NULL);

if( !array )
    return;

if( CFDictionaryContainsKey( array, kSCPropNetProxiesProxyAutoConfigEnable ) )
    detect_auto_proxy( array, site, pinfo );
else
    detect_user_proxy( array, pinfo );

Для того чтоб было понятнее, о чем ниже идет речь, вот определение пары структур, используемых в примере:

struct proxy_info
{
    CFStringRef addr_;      
    CFNumberRef port_;
};

struct proxy_callback
{
    proxy_info pinfo;
    bool finished_;
};

Функция SCDynamicStoreCopyProxies экспортируется из фрэймворка SystemConfiguration и служит для получения словаря с системными настройками. В случае если включена автоконфигурация, то по ключу kSCPropNetProxiesProxyAutoConfigEnable будет находиться соответствующее значение, если автоконфигурация выключена то значения соответствующее ключу не будет существовать.
В том случае, если адрес прокси сервера был задан пользователем “в ручную”, то количество дополнительных действий минимально, достаточно считать значения соответствующие ключам HTTPProxy и HTTPPort (HTTPSProxy и HTTPSPort соответственно, для HTTPS прокси).

static void detect_user_proxy( CFDictionaryRef array, proxy_info &pinfo )
{
    pinfo.addr_ = (CFStringRef)CFDictionaryGetValue( array, CFSTR("HTTPProxy") );
    pinfo.port_ = (CFNumberRef)CFDictionaryGetValue( array, CFSTR("HTTPPort") );
}

А вот в случае с автоконфигурированием или .pac файлом все несколько сложнее. Первым делом необходимо получить путь к файлу автоконфигурации, который будет существовать как в случае если он был задан явно (путь к .pac файлу), так и в случае с автоконфигурацией прокси.

CFStringRef autoCnfgPath = (CFStringRef)(CFDictionaryGetValue(array, kSCPropNetProxiesProxyAutoConfigURLString));

После этого, необходимо вызвать функцию CFNetworkExecuteProxyAutoConfigurationURL, которая принимает на вход путь к файлу конфигурации в виде CURLRef, url к которому мы хотим обратиться, callback функцию и указатель на структуру CFStreamClientContext. В результате вызова создается объект типа CFRunLoopSourceRef, который и необходимо выполнить для получения искомых настроек. Да, относительно указателя на структуру CFStreamClientContext в функции CFNetworkExecuteProxyAutoConfigurationURL; этот параметр не должен быть равен NULL, даже если он не используется в дальнейшем, т.к. нулевой значение приводит к ошибке доступа.

О том как использовать RunLoop можно написать отдельный большой пост, поэтому я просто покажу как выполнить полученный CFRunLoopSourceRef объект наиболее простым и примитивным способом:

CFStringRef mode = CFSTR("ProxyAutoConfigurationResultCallback");
CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, mode);
if (loopSource) CFRelease(loopSource);
do {
    CFRunLoopRunInMode(mode, 0.1, true);
} while (false == callback_info.finished_);

После того как настройки прокси-сервера будут определены, вызывается callback функция, переданная в CFNetworkExecuteProxyAutoConfigurationURL. И уже в этой функции необходимо получить соответствующую информацию:

static void autoconf_callback(void *client, CFArrayRef proxyList, CFErrorRef error)
{
    proxy_callback *info = (proxy_callback*)client;
    CFDictionaryRef firstProxy = CFArrayGetCount(proxyList) ?
        (CFDictionaryRef)CFArrayGetValueAtIndex(proxyList, 0) :
        (CFDictionaryRef)0;

    info->pinfo.addr_ = (CFStringRef)CFDictionaryGetValue( firstProxy, CFSTR("kCFProxyHostNameKey") );
    info->pinfo.port_ = (CFNumberRef)CFDictionaryGetValue( firstProxy, CFSTR("kCFProxyPortNumberKey") );

    info->finished_ = true;
}

Код иллюстрирующий этот подход можно скачать тут.

Leave a Reply