diff options
Diffstat (limited to 'webrtc/base/proxydetect.cc')
-rw-r--r-- | webrtc/base/proxydetect.cc | 1303 |
1 files changed, 1303 insertions, 0 deletions
diff --git a/webrtc/base/proxydetect.cc b/webrtc/base/proxydetect.cc new file mode 100644 index 0000000000..b144d20a97 --- /dev/null +++ b/webrtc/base/proxydetect.cc @@ -0,0 +1,1303 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/proxydetect.h" + +#if defined(WEBRTC_WIN) +#include "webrtc/base/win32.h" +#include <shlobj.h> +#endif // WEBRTC_WIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) +#include <SystemConfiguration/SystemConfiguration.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#include <Security/Security.h> +#include "macconversion.h" +#endif + +#ifdef WEBRTC_IOS +#include <CFNetwork/CFNetwork.h> +#include "macconversion.h" +#endif + +#include <map> + +#include "webrtc/base/fileutils.h" +#include "webrtc/base/httpcommon.h" +#include "webrtc/base/httpcommon-inl.h" +#include "webrtc/base/pathutils.h" +#include "webrtc/base/stringutils.h" + +#if defined(WEBRTC_WIN) +#define _TRY_WINHTTP 1 +#define _TRY_JSPROXY 0 +#define _TRY_WM_FINDPROXY 0 +#define _TRY_IE_LAN_SETTINGS 1 +#endif // WEBRTC_WIN + +// For all platforms try Firefox. +#define _TRY_FIREFOX 1 + +// Use profiles.ini to find the correct profile for this user. +// If not set, we'll just look for the default one. +#define USE_FIREFOX_PROFILES_INI 1 + +static const size_t kMaxLineLength = 1024; +static const char kFirefoxPattern[] = "Firefox"; +static const char kInternetExplorerPattern[] = "MSIE"; + +struct StringMap { + public: + void Add(const char * name, const char * value) { map_[name] = value; } + const std::string& Get(const char * name, const char * def = "") const { + std::map<std::string, std::string>::const_iterator it = + map_.find(name); + if (it != map_.end()) + return it->second; + def_ = def; + return def_; + } + bool IsSet(const char * name) const { + return (map_.find(name) != map_.end()); + } + private: + std::map<std::string, std::string> map_; + mutable std::string def_; +}; + +enum UserAgent { + UA_FIREFOX, + UA_INTERNETEXPLORER, + UA_OTHER, + UA_UNKNOWN +}; + +#if _TRY_WINHTTP +//#include <winhttp.h> +// Note: From winhttp.h + +const char WINHTTP[] = "winhttp"; + +typedef LPVOID HINTERNET; + +typedef struct { + DWORD dwAccessType; // see WINHTTP_ACCESS_* types below + LPWSTR lpszProxy; // proxy server list + LPWSTR lpszProxyBypass; // proxy bypass list +} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO; + +typedef struct { + DWORD dwFlags; + DWORD dwAutoDetectFlags; + LPCWSTR lpszAutoConfigUrl; + LPVOID lpvReserved; + DWORD dwReserved; + BOOL fAutoLogonIfChallenged; +} WINHTTP_AUTOPROXY_OPTIONS; + +typedef struct { + BOOL fAutoDetect; + LPWSTR lpszAutoConfigUrl; + LPWSTR lpszProxy; + LPWSTR lpszProxyBypass; +} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + +extern "C" { + typedef HINTERNET (WINAPI * pfnWinHttpOpen) + ( + IN LPCWSTR pwszUserAgent, + IN DWORD dwAccessType, + IN LPCWSTR pwszProxyName OPTIONAL, + IN LPCWSTR pwszProxyBypass OPTIONAL, + IN DWORD dwFlags + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle) + ( + IN HINTERNET hInternet + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl) + ( + IN HINTERNET hSession, + IN LPCWSTR lpcwszUrl, + IN WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, + OUT WINHTTP_PROXY_INFO * pProxyInfo + ); + typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig) + ( + IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig + ); + +} // extern "C" + +#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 +#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 +#define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000 +#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000 +#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 +#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 +#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 +#define WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 +#define WINHTTP_NO_PROXY_NAME NULL +#define WINHTTP_NO_PROXY_BYPASS NULL + +#endif // _TRY_WINHTTP + +#if _TRY_JSPROXY +extern "C" { + typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo) + ( + LPCSTR lpszUrl, + DWORD dwUrlLength, + LPSTR lpszUrlHostName, + DWORD dwUrlHostNameLength, + LPSTR * lplpszProxyHostName, + LPDWORD lpdwProxyHostNameLength + ); +} // extern "C" +#endif // _TRY_JSPROXY + +#if _TRY_WM_FINDPROXY +#include <comutil.h> +#include <wmnetsourcecreator.h> +#include <wmsinternaladminnetsource.h> +#endif // _TRY_WM_FINDPROXY + +#if _TRY_IE_LAN_SETTINGS +#include <wininet.h> +#include <string> +#endif // _TRY_IE_LAN_SETTINGS + +namespace rtc { + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +#if defined(WEBRTC_WIN) +#ifdef _UNICODE + +typedef std::wstring tstring; +std::string Utf8String(const tstring& str) { return ToUtf8(str); } + +#else // !_UNICODE + +typedef std::string tstring; +std::string Utf8String(const tstring& str) { return str; } + +#endif // !_UNICODE +#endif // WEBRTC_WIN + +bool ProxyItemMatch(const Url<char>& url, char * item, size_t len) { + // hostname:443 + if (char * port = ::strchr(item, ':')) { + *port++ = '\0'; + if (url.port() != atol(port)) { + return false; + } + } + + // A.B.C.D or A.B.C.D/24 + int a, b, c, d, m; + int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m); + if (match >= 4) { + uint32_t ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | + (d & 0xFF); + if ((match < 5) || (m > 32)) + m = 32; + else if (m < 0) + m = 0; + uint32_t mask = (m == 0) ? 0 : (~0UL) << (32 - m); + SocketAddress addr(url.host(), 0); + // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway. + return !addr.IsUnresolved() && + ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask)); + } + + // .foo.com + if (*item == '.') { + size_t hostlen = url.host().length(); + return (hostlen > len) + && (stricmp(url.host().c_str() + (hostlen - len), item) == 0); + } + + // localhost or www.*.com + if (!string_match(url.host().c_str(), item)) + return false; + + return true; +} + +bool ProxyListMatch(const Url<char>& url, const std::string& proxy_list, + char sep) { + const size_t BUFSIZE = 256; + char buffer[BUFSIZE]; + const char* list = proxy_list.c_str(); + while (*list) { + // Remove leading space + if (isspace(*list)) { + ++list; + continue; + } + // Break on separator + size_t len; + const char * start = list; + if (const char * end = ::strchr(list, sep)) { + len = (end - list); + list += len + 1; + } else { + len = strlen(list); + list += len; + } + // Remove trailing space + while ((len > 0) && isspace(start[len-1])) + --len; + // Check for oversized entry + if (len >= BUFSIZE) + continue; + memcpy(buffer, start, len); + buffer[len] = 0; + if (!ProxyItemMatch(url, buffer, len)) + continue; + return true; + } + return false; +} + +bool Better(ProxyType lhs, const ProxyType rhs) { + // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN + const int PROXY_VALUE[5] = { 0, 2, 3, 1 }; + return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]); +} + +bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) { + const size_t kMaxAddressLength = 1024; + // Allow semicolon, space, or tab as an address separator + const char* const kAddressSeparator = " ;\t"; + + ProxyType ptype; + std::string host; + uint16_t port; + + const char* address = saddress.c_str(); + while (*address) { + size_t len; + const char * start = address; + if (const char * sep = strchr(address, kAddressSeparator)) { + len = (sep - address); + address += len + 1; + while (*address != '\0' && ::strchr(kAddressSeparator, *address)) { + address += 1; + } + } else { + len = strlen(address); + address += len; + } + + if (len > kMaxAddressLength - 1) { + LOG(LS_WARNING) << "Proxy address too long [" << start << "]"; + continue; + } + + char buffer[kMaxAddressLength]; + memcpy(buffer, start, len); + buffer[len] = 0; + + char * colon = ::strchr(buffer, ':'); + if (!colon) { + LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]"; + continue; + } + + *colon = 0; + char * endptr; + port = static_cast<uint16_t>(strtol(colon + 1, &endptr, 0)); + if (*endptr != 0) { + LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]"; + continue; + } + + if (char * equals = ::strchr(buffer, '=')) { + *equals = 0; + host = equals + 1; + if (_stricmp(buffer, "socks") == 0) { + ptype = PROXY_SOCKS5; + } else if (_stricmp(buffer, "https") == 0) { + ptype = PROXY_HTTPS; + } else { + LOG(LS_WARNING) << "Proxy address with unknown protocol [" + << buffer << "]"; + ptype = PROXY_UNKNOWN; + } + } else { + host = buffer; + ptype = PROXY_UNKNOWN; + } + + if (Better(ptype, proxy->type)) { + proxy->type = ptype; + proxy->address.SetIP(host); + proxy->address.SetPort(port); + } + } + + return proxy->type != PROXY_NONE; +} + +UserAgent GetAgent(const char* agent) { + if (agent) { + std::string agent_str(agent); + if (agent_str.find(kFirefoxPattern) != std::string::npos) { + return UA_FIREFOX; + } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) { + return UA_INTERNETEXPLORER; + } else if (agent_str.empty()) { + return UA_UNKNOWN; + } + } + return UA_OTHER; +} + +bool EndsWith(const std::string& a, const std::string& b) { + if (b.size() > a.size()) { + return false; + } + int result = a.compare(a.size() - b.size(), b.size(), b); + return result == 0; +} + +bool GetFirefoxProfilePath(Pathname* path) { +#if defined(WEBRTC_WIN) + wchar_t w_path[MAX_PATH]; + if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) != + S_OK) { + LOG(LS_ERROR) << "SHGetFolderPath failed"; + return false; + } + path->SetFolder(ToUtf8(w_path, wcslen(w_path))); + path->AppendFolder("Mozilla"); + path->AppendFolder("Firefox"); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + FSRef fr; + if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType, + kCreateFolder, &fr)) { + LOG(LS_ERROR) << "FSFindFolder failed"; + return false; + } + char buffer[NAME_MAX + 1]; + if (0 != FSRefMakePath(&fr, reinterpret_cast<uint8_t*>(buffer), + ARRAY_SIZE(buffer))) { + LOG(LS_ERROR) << "FSRefMakePath failed"; + return false; + } + path->SetFolder(std::string(buffer)); + path->AppendFolder("Firefox"); +#else + char* user_home = getenv("HOME"); + if (user_home == NULL) { + return false; + } + path->SetFolder(std::string(user_home)); + path->AppendFolder(".mozilla"); + path->AppendFolder("firefox"); +#endif // WEBRTC_WIN + return true; +} + +bool GetDefaultFirefoxProfile(Pathname* profile_path) { + ASSERT(NULL != profile_path); + Pathname path; + if (!GetFirefoxProfilePath(&path)) { + return false; + } + +#if USE_FIREFOX_PROFILES_INI + // [Profile0] + // Name=default + // IsRelative=1 + // Path=Profiles/2de53ejb.default + // Default=1 + + // Note: we are looking for the first entry with "Default=1", or the last + // entry in the file + path.SetFilename("profiles.ini"); + scoped_ptr<FileStream> fs(Filesystem::OpenFile(path, "r")); + if (!fs) { + return false; + } + Pathname candidate; + bool relative = true; + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + if (line.length() == 0) { + continue; + } + if (line.at(0) == '[') { + relative = true; + candidate.clear(); + } else if (line.find("IsRelative=") == 0 && + line.length() >= 12) { + // TODO: The initial Linux public launch revealed a fairly + // high number of machines where IsRelative= did not have anything after + // it. Perhaps that is legal profiles.ini syntax? + relative = (line.at(11) != '0'); + } else if (line.find("Path=") == 0 && + line.length() >= 6) { + if (relative) { + candidate = path; + } else { + candidate.clear(); + } + candidate.AppendFolder(line.substr(5)); + } else if (line.find("Default=") == 0 && + line.length() >= 9) { + if ((line.at(8) != '0') && !candidate.empty()) { + break; + } + } + } + fs->Close(); + if (candidate.empty()) { + return false; + } + profile_path->SetPathname(candidate.pathname()); + +#else // !USE_FIREFOX_PROFILES_INI + path.AppendFolder("Profiles"); + DirectoryIterator* it = Filesystem::IterateDirectory(); + it->Iterate(path); + std::string extension(".default"); + while (!EndsWith(it->Name(), extension)) { + if (!it->Next()) { + return false; + } + } + + profile_path->SetPathname(path); + profile->AppendFolder("Profiles"); + profile->AppendFolder(it->Name()); + delete it; + +#endif // !USE_FIREFOX_PROFILES_INI + + return true; +} + +bool ReadFirefoxPrefs(const Pathname& filename, + const char * prefix, + StringMap* settings) { + scoped_ptr<FileStream> fs(Filesystem::OpenFile(filename, "r")); + if (!fs) { + LOG(LS_ERROR) << "Failed to open file: " << filename.pathname(); + return false; + } + + std::string line; + while (fs->ReadLine(&line) == SR_SUCCESS) { + size_t prefix_len = strlen(prefix); + + // Skip blank lines and too long lines. + if ((line.length() == 0) || (line.length() > kMaxLineLength) + || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0 + || line.compare(0, 2, " *") == 0) { + continue; + } + + char buffer[kMaxLineLength]; + strcpyn(buffer, sizeof(buffer), line.c_str()); + int nstart = 0, nend = 0, vstart = 0, vend = 0; + sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);", + &nstart, &nend, &vstart, &vend); + if (vend > 0) { + char* name = buffer + nstart; + name[nend - nstart] = 0; + if ((vend - vstart >= 2) && (buffer[vstart] == '"')) { + vstart += 1; + vend -= 1; + } + char* value = buffer + vstart; + value[vend - vstart] = 0; + if ((strncmp(name, prefix, prefix_len) == 0) && *value) { + settings->Add(name + prefix_len, value); + } + } else { + LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]"; + } + } + fs->Close(); + return true; +} + +bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) { + Url<char> purl(url); + Pathname path; + bool success = false; + if (GetDefaultFirefoxProfile(&path)) { + StringMap settings; + path.SetFilename("prefs.js"); + if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) { + success = true; + proxy->bypass_list = + settings.Get("no_proxies_on", "localhost, 127.0.0.1"); + if (settings.Get("type") == "1") { + // User has manually specified a proxy, try to figure out what + // type it is. + if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) { + // Our url is in the list of url's to bypass proxy. + } else if (settings.Get("share_proxy_settings") == "true") { + proxy->type = PROXY_UNKNOWN; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } else if (settings.IsSet("socks")) { + proxy->type = PROXY_SOCKS5; + proxy->address.SetIP(settings.Get("socks")); + proxy->address.SetPort(atoi(settings.Get("socks_port").c_str())); + } else if (settings.IsSet("ssl")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("ssl")); + proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str())); + } else if (settings.IsSet("http")) { + proxy->type = PROXY_HTTPS; + proxy->address.SetIP(settings.Get("http")); + proxy->address.SetPort(atoi(settings.Get("http_port").c_str())); + } + } else if (settings.Get("type") == "2") { + // Browser is configured to get proxy settings from a given url. + proxy->autoconfig_url = settings.Get("autoconfig_url").c_str(); + } else if (settings.Get("type") == "4") { + // Browser is configured to auto detect proxy config. + proxy->autodetect = true; + } else { + // No proxy set. + } + } + } + return success; +} + +#if defined(WEBRTC_WIN) // Windows specific implementation for reading Internet + // Explorer proxy settings. + +void LogGetProxyFault() { + LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!"; +} + +BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU, + HINTERNET hWinHttp, LPCWSTR url, + WINHTTP_AUTOPROXY_OPTIONS *options, + WINHTTP_PROXY_INFO *info) { + // WinHttpGetProxyForUrl() can call plugins which can crash. + // In the case of McAfee scriptproxy.dll, it does crash in + // older versions. Try to catch crashes here and treat as an + // error. + BOOL success = FALSE; + +#if (_HAS_EXCEPTIONS == 0) + __try { + success = pWHGPFU(hWinHttp, url, options, info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // This is a separate function to avoid + // Visual C++ error 2712 when compiling with C++ EH + LogGetProxyFault(); + } +#else + success = pWHGPFU(hWinHttp, url, options, info); +#endif // (_HAS_EXCEPTIONS == 0) + + return success; +} + +bool IsDefaultBrowserFirefox() { + HKEY key; + LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command", + 0, KEY_READ, &key); + if (ERROR_SUCCESS != result) + return false; + + DWORD size, type; + bool success = false; + result = RegQueryValueEx(key, L"", 0, &type, NULL, &size); + if (result == ERROR_SUCCESS && type == REG_SZ) { + wchar_t* value = new wchar_t[size+1]; + BYTE* buffer = reinterpret_cast<BYTE*>(value); + result = RegQueryValueEx(key, L"", 0, &type, buffer, &size); + if (result == ERROR_SUCCESS) { + // Size returned by RegQueryValueEx is in bytes, convert to number of + // wchar_t's. + size /= sizeof(value[0]); + value[size] = L'\0'; + for (size_t i = 0; i < size; ++i) { + value[i] = tolowercase(value[i]); + } + success = (NULL != strstr(value, L"firefox.exe")); + } + delete[] value; + } + + RegCloseKey(key); + return success; +} + +bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) { + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg; + memset(&iecfg, 0, sizeof(iecfg)); + Url<char> purl(url); + pfnWinHttpGetIEProxyConfig pWHGIEPC = + reinterpret_cast<pfnWinHttpGetIEProxyConfig>( + GetProcAddress(winhttp_handle, + "WinHttpGetIEProxyConfigForCurrentUser")); + bool success = false; + if (pWHGIEPC && pWHGIEPC(&iecfg)) { + // We were read proxy config successfully. + success = true; + if (iecfg.fAutoDetect) { + proxy->autodetect = true; + } + if (iecfg.lpszAutoConfigUrl) { + proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl); + GlobalFree(iecfg.lpszAutoConfigUrl); + } + if (iecfg.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass); + GlobalFree(iecfg.lpszProxyBypass); + } + if (iecfg.lpszProxy) { + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + ParseProxy(ToUtf8(iecfg.lpszProxy), proxy); + } + GlobalFree(iecfg.lpszProxy); + } + } + FreeLibrary(winhttp_handle); + return success; +} + +// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE +// have slightly different option dialogs for proxy settings. In Firefox, +// either a location of a proxy configuration file can be specified or auto +// detection can be selected. In IE theese two options can be independently +// selected. For the case where both options are selected (only IE) we try to +// fetch the config file first, and if that fails we'll perform an auto +// detection. +// +// Returns true if we successfully performed an auto detection not depending on +// whether we found a proxy or not. Returns false on error. +bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url, + ProxyInfo* proxy) { + Url<char> purl(url); + bool success = true; + HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll"); + if (winhttp_handle == NULL) { + LOG(LS_ERROR) << "Failed to load winhttp.dll."; + return false; + } + pfnWinHttpOpen pWHO = + reinterpret_cast<pfnWinHttpOpen>(GetProcAddress(winhttp_handle, + "WinHttpOpen")); + pfnWinHttpCloseHandle pWHCH = + reinterpret_cast<pfnWinHttpCloseHandle>( + GetProcAddress(winhttp_handle, "WinHttpCloseHandle")); + pfnWinHttpGetProxyForUrl pWHGPFU = + reinterpret_cast<pfnWinHttpGetProxyForUrl>( + GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl")); + if (pWHO && pWHCH && pWHGPFU) { + if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(), + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0)) { + BOOL result = FALSE; + WINHTTP_PROXY_INFO info; + memset(&info, 0, sizeof(info)); + if (proxy->autodetect) { + // Use DHCP and DNS to try to find any proxy to use. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.fAutoLogonIfChallenged = TRUE; + + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (!result && !proxy->autoconfig_url.empty()) { + // We have the location of a proxy config file. Download it and + // execute it to find proxy settings for our url. + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + memset(&info, 0, sizeof(info)); + options.fAutoLogonIfChallenged = TRUE; + + std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url)); + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = autoconfig_url16.c_str(); + + result = MyWinHttpGetProxyForUrl( + pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info); + } + if (result) { + // Either the given auto config url was valid or auto + // detection found a proxy on this network. + if (info.lpszProxy) { + // TODO: Does this bypass list differ from the list + // retreived from GetWinHttpProxySettings earlier? + if (info.lpszProxyBypass) { + proxy->bypass_list = ToUtf8(info.lpszProxyBypass); + GlobalFree(info.lpszProxyBypass); + } else { + proxy->bypass_list.clear(); + } + if (!ProxyListMatch(purl, proxy->bypass_list, ';')) { + // Found proxy for this URL. If parsing the address turns + // out ok then we are successful. + success = ParseProxy(ToUtf8(info.lpszProxy), proxy); + } + GlobalFree(info.lpszProxy); + } + } else { + // We could not find any proxy for this url. + LOG(LS_INFO) << "No proxy detected for " << url; + } + pWHCH(hWinHttp); + } + } else { + LOG(LS_ERROR) << "Failed loading WinHTTP functions."; + success = false; + } + FreeLibrary(winhttp_handle); + return success; +} + +#if 0 // Below functions currently not used. + +bool GetJsProxySettings(const char* url, ProxyInfo* proxy) { + Url<char> purl(url); + bool success = false; + + if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) { + pfnInternetGetProxyInfo pIGPI = + reinterpret_cast<pfnInternetGetProxyInfo>( + GetProcAddress(hModJS, "InternetGetProxyInfo")); + if (pIGPI) { + char proxy[256], host[256]; + memset(proxy, 0, sizeof(proxy)); + char * ptr = proxy; + DWORD proxylen = sizeof(proxy); + std::string surl = Utf8String(url); + DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S", + purl.secure() ? "s" : "", purl.server()); + if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) { + LOG(INFO) << "Proxy: " << proxy; + } else { + LOG_GLE(INFO) << "InternetGetProxyInfo"; + } + } + FreeLibrary(hModJS); + } + return success; +} + +bool GetWmProxySettings(const char* url, ProxyInfo* proxy) { + Url<char> purl(url); + bool success = false; + + INSNetSourceCreator * nsc = 0; + HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL, + IID_INSNetSourceCreator, (LPVOID *) &nsc); + if (SUCCEEDED(hr)) { + if (SUCCEEDED(hr = nsc->Initialize())) { + VARIANT dispatch; + VariantInit(&dispatch); + if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) { + IWMSInternalAdminNetSource * ians = 0; + if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface( + IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) { + _bstr_t host(purl.server()); + BSTR proxy = 0; + BOOL bProxyEnabled = FALSE; + DWORD port, context = 0; + if (SUCCEEDED(hr = ians->FindProxyForURL( + L"http", host, &bProxyEnabled, &proxy, &port, &context))) { + success = true; + if (bProxyEnabled) { + _bstr_t sproxy = proxy; + proxy->ptype = PT_HTTPS; + proxy->host = sproxy; + proxy->port = port; + } + } + SysFreeString(proxy); + if (FAILED(hr = ians->ShutdownProxyContext(context))) { + LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext" + << "failed: " << hr; + } + ians->Release(); + } + } + VariantClear(&dispatch); + if (FAILED(hr = nsc->Shutdown())) { + LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr; + } + } + nsc->Release(); + } + return success; +} + +bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) { + Url<char> purl(url); + bool success = false; + + INTERNET_PER_CONN_OPTION_LIST list; + INTERNET_PER_CONN_OPTION options[3]; + memset(&list, 0, sizeof(list)); + memset(&options, 0, sizeof(options)); + + list.dwSize = sizeof(list); + list.dwOptionCount = 3; + list.pOptions = options; + options[0].dwOption = INTERNET_PER_CONN_FLAGS; + options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; + options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; + DWORD dwSize = sizeof(list); + + if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, + &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) { + success = true; + if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) { + ParseProxy(nonnull(options[1].Value.pszValue), proxy); + } + } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) { + success = true; + } else { + LOG(LS_INFO) << "unknown internet access type: " + << options[0].Value.dwValue; + } + if (options[1].Value.pszValue) { + GlobalFree(options[1].Value.pszValue); + } + if (options[2].Value.pszValue) { + GlobalFree(options[2].Value.pszValue); + } + return success; +} + +#endif // 0 + +// Uses the InternetQueryOption function to retrieve proxy settings +// from the registry. This will only give us the 'static' settings, +// ie, not any information about auto config etc. +bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) { + Url<char> purl(url); + bool success = false; + + wchar_t buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + INTERNET_PROXY_INFO * info = reinterpret_cast<INTERNET_PROXY_INFO *>(buffer); + DWORD dwSize = sizeof(buffer); + + if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) { + LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError(); + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) { + success = true; + } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) { + success = true; + if (!ProxyListMatch(purl, nonnull(reinterpret_cast<const char*>( + info->lpszProxyBypass)), ' ')) { + ParseProxy(nonnull(reinterpret_cast<const char*>(info->lpszProxy)), + proxy); + } + } else { + LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType; + } + return success; +} + +bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) { + bool success = GetWinHttpProxySettings(url, proxy); + if (!success) { + // TODO: Should always call this if no proxy were detected by + // GetWinHttpProxySettings? + // WinHttp failed. Try using the InternetOptionQuery method instead. + return GetIeLanProxySettings(url, proxy); + } + return true; +} + +#endif // WEBRTC_WIN + +#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) // WEBRTC_MAC && !defined(WEBRTC_IOS) specific implementation for reading system wide + // proxy settings. + +bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy, + ProxyType type, + const CFDictionaryRef proxyDict, + const CFStringRef enabledKey, + const CFStringRef hostKey, + const CFStringRef portKey) { + // whether or not we set up the proxy info. + bool result = false; + + // we use this as a scratch variable for determining if operations + // succeeded. + bool converted = false; + + // the data we need to construct the SocketAddress for the proxy. + std::string hostname; + int port; + + if ((proxyDict != NULL) && + (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) { + // CoreFoundation stuff that we'll have to get from + // the dictionaries and interpret or convert into more usable formats. + CFNumberRef enabledCFNum; + CFNumberRef portCFNum; + CFStringRef hostCFStr; + + enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey); + + if (p_isCFNumberTrue(enabledCFNum)) { + // let's see if we can get the address and port. + hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey); + converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname); + if (converted) { + portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey); + converted = p_convertCFNumberToInt(portCFNum, &port); + if (converted) { + // we have something enabled, with a hostname and a port. + // That's sufficient to set up the proxy info. + proxy->type = type; + proxy->address.SetIP(hostname); + proxy->address.SetPort(port); + result = true; + } + } + } + } + + return result; +} + +// Looks for proxy information in the given dictionary, +// return true if it found sufficient information to define one, +// false otherwise. This is guaranteed to not change the values in proxy +// unless a full-fledged proxy description was discovered in the dictionary. +// However, at the present time this does not support username or password. +// Checks first for a SOCKS proxy, then for HTTPS, then HTTP. +bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy, + const CFDictionaryRef proxyDict) { + // the function result. + bool gotProxy = false; + + + // first we see if there's a SOCKS proxy in place. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_SOCKS5, + proxyDict, + kSCPropNetProxiesSOCKSEnable, + kSCPropNetProxiesSOCKSProxy, + kSCPropNetProxiesSOCKSPort); + + if (!gotProxy) { + // okay, no SOCKS proxy, let's look for https. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy, + PROXY_HTTPS, + proxyDict, + kSCPropNetProxiesHTTPSEnable, + kSCPropNetProxiesHTTPSProxy, + kSCPropNetProxiesHTTPSPort); + if (!gotProxy) { + // Finally, try HTTP proxy. Note that flute doesn't + // differentiate between HTTPS and HTTP, hence we are using the + // same flute type here, ie. PROXY_HTTPS. + gotProxy = p_getProxyInfoForTypeFromDictWithKeys( + proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable, + kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort); + } + } + return gotProxy; +} + +// TODO(hughv) Update keychain functions. They work on 10.8, but are depricated. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +bool p_putPasswordInProxyInfo(ProxyInfo* proxy) { + bool result = true; // by default we assume we're good. + // for all we know there isn't any password. We'll set to false + // if we find a problem. + + // Ask the keychain for an internet password search for the given protocol. + OSStatus oss = 0; + SecKeychainAttributeList attrList; + attrList.count = 3; + SecKeychainAttribute attributes[3]; + attrList.attr = attributes; + + attributes[0].tag = kSecProtocolItemAttr; + attributes[0].length = sizeof(SecProtocolType); + SecProtocolType protocol; + switch (proxy->type) { + case PROXY_HTTPS : + protocol = kSecProtocolTypeHTTPS; + break; + case PROXY_SOCKS5 : + protocol = kSecProtocolTypeSOCKS; + break; + default : + LOG(LS_ERROR) << "asked for proxy password for unknown proxy type."; + result = false; + break; + } + attributes[0].data = &protocol; + + UInt32 port = proxy->address.port(); + attributes[1].tag = kSecPortItemAttr; + attributes[1].length = sizeof(UInt32); + attributes[1].data = &port; + + std::string ip = proxy->address.ipaddr().ToString(); + attributes[2].tag = kSecServerItemAttr; + attributes[2].length = ip.length(); + attributes[2].data = const_cast<char*>(ip.c_str()); + + if (result) { + LOG(LS_INFO) << "trying to get proxy username/password"; + SecKeychainSearchRef sref; + oss = SecKeychainSearchCreateFromAttributes(NULL, + kSecInternetPasswordItemClass, + &attrList, &sref); + if (0 == oss) { + LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good"; + // Get the first item, if there is one. + SecKeychainItemRef iref; + oss = SecKeychainSearchCopyNext(sref, &iref); + if (0 == oss) { + LOG(LS_INFO) << "...looks like we have the username/password data"; + // If there is, get the username and the password. + + SecKeychainAttributeInfo attribsToGet; + attribsToGet.count = 1; + UInt32 tag = kSecAccountItemAttr; + UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING; + void *data; + UInt32 length; + SecKeychainAttributeList *localList; + + attribsToGet.tag = &tag; + attribsToGet.format = &format; + OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref, + &attribsToGet, + NULL, + &localList, + &length, + &data); + if (0 == copyres) { + LOG(LS_INFO) << "...and we can pull it out."; + // now, we know from experimentation (sadly not from docs) + // that the username is in the local attribute list, + // and the password in the data, + // both without null termination but with info on their length. + // grab the password from the data. + std::string password; + password.append(static_cast<const char*>(data), length); + + // make the password into a CryptString + // huh, at the time of writing, you can't. + // so we'll skip that for now and come back to it later. + + // now put the username in the proxy. + if (1 <= localList->attr->length) { + proxy->username.append( + static_cast<const char*>(localList->attr->data), + localList->attr->length); + LOG(LS_INFO) << "username is " << proxy->username; + } else { + LOG(LS_ERROR) << "got keychain entry with no username"; + result = false; + } + } else { + LOG(LS_ERROR) << "couldn't copy info from keychain."; + result = false; + } + SecKeychainItemFreeAttributesAndData(localList, data); + } else if (errSecItemNotFound == oss) { + LOG(LS_INFO) << "...username/password info not found"; + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } else if (errSecItemNotFound == oss) { // noop + } else { + // oooh, neither 0 nor itemNotFound. + LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss; + result = false; + } + } + + return result; +} + +bool GetMacProxySettings(ProxyInfo* proxy) { + // based on the Apple Technical Q&A QA1234 + // http://developer.apple.com/qa/qa2001/qa1234.html + CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL); + bool result = false; + + if (proxyDict != NULL) { + // sending it off to another function makes it easier to unit test + // since we can make our own dictionary to hand to that function. + result = GetMacProxySettingsFromDictionary(proxy, proxyDict); + + if (result) { + result = p_putPasswordInProxyInfo(proxy); + } + + // We created the dictionary with something that had the + // word 'copy' in it, so we have to release it, according + // to the Carbon memory management standards. + CFRelease(proxyDict); + } else { + LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed"; + } + + return result; +} +#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) + +#ifdef WEBRTC_IOS +// iOS has only http proxy +bool GetiOSProxySettings(ProxyInfo* proxy) { + + bool result = false; + + CFDictionaryRef proxy_dict = CFNetworkCopySystemProxySettings(); + if (!proxy_dict) { + LOG(LS_ERROR) << "CFNetworkCopySystemProxySettings failed"; + return false; + } + + CFNumberRef proxiesHTTPEnable = (CFNumberRef)CFDictionaryGetValue( + proxy_dict, kCFNetworkProxiesHTTPEnable); + if (!p_isCFNumberTrue(proxiesHTTPEnable)) { + CFRelease(proxy_dict); + return false; + } + + CFStringRef proxy_address = (CFStringRef)CFDictionaryGetValue( + proxy_dict, kCFNetworkProxiesHTTPProxy); + CFNumberRef proxy_port = (CFNumberRef)CFDictionaryGetValue( + proxy_dict, kCFNetworkProxiesHTTPPort); + + // the data we need to construct the SocketAddress for the proxy. + std::string hostname; + int port; + if (p_convertHostCFStringRefToCPPString(proxy_address, hostname) && + p_convertCFNumberToInt(proxy_port, &port)) { + // We have something enabled, with a hostname and a port. + // That's sufficient to set up the proxy info. + // Finally, try HTTP proxy. Note that flute doesn't + // differentiate between HTTPS and HTTP, hence we are using the + // same flute type here, ie. PROXY_HTTPS. + proxy->type = PROXY_HTTPS; + + proxy->address.SetIP(hostname); + proxy->address.SetPort(port); + result = true; + } + + // We created the dictionary with something that had the + // word 'copy' in it, so we have to release it, according + // to the Carbon memory management standards. + CFRelease(proxy_dict); + + return result; +} +#endif // WEBRTC_IOS + +bool AutoDetectProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return WinHttpAutoDetectProxyForUrl(agent, url, proxy); +#else + LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform"; + return false; +#endif +} + +bool GetSystemDefaultProxySettings(const char* agent, const char* url, + ProxyInfo* proxy) { +#if defined(WEBRTC_WIN) + return GetIeProxySettings(agent, url, proxy); +#elif defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) + return GetMacProxySettings(proxy); +#elif defined(WEBRTC_IOS) + return GetiOSProxySettings(proxy); +#else + // TODO: Get System settings if browser is not firefox. + return GetFirefoxProxySettings(url, proxy); +#endif +} + +bool GetProxySettingsForUrl(const char* agent, const char* url, + ProxyInfo* proxy, bool long_operation) { + UserAgent a = GetAgent(agent); + bool result; + switch (a) { + case UA_FIREFOX: { + result = GetFirefoxProxySettings(url, proxy); + break; + } +#if defined(WEBRTC_WIN) + case UA_INTERNETEXPLORER: + result = GetIeProxySettings(agent, url, proxy); + break; + case UA_UNKNOWN: + // Agent not defined, check default browser. + if (IsDefaultBrowserFirefox()) { + result = GetFirefoxProxySettings(url, proxy); + } else { + result = GetIeProxySettings(agent, url, proxy); + } + break; +#endif // WEBRTC_WIN + default: + result = GetSystemDefaultProxySettings(agent, url, proxy); + break; + } + + // TODO: Consider using the 'long_operation' parameter to + // decide whether to do the auto detection. + if (result && (proxy->autodetect || + !proxy->autoconfig_url.empty())) { + // Use WinHTTP to auto detect proxy for us. + result = AutoDetectProxySettings(agent, url, proxy); + if (!result) { + // Either auto detection is not supported or we simply didn't + // find any proxy, reset type. + proxy->type = rtc::PROXY_NONE; + } + } + return result; +} + +} // namespace rtc |