/mordor/http/proxy.cpp
C++ | 683 lines | 541 code | 75 blank | 67 comment | 123 complexity | 80846a9bd3d2d5e20c0a9005f9e1e054 MD5 | raw file
1// Copyright (c) 2009 - Mozy, Inc. 2 3#include "proxy.h" 4#include <boost/algorithm/string.hpp> 5#include <boost/regex.hpp> 6#include "mordor/config.h" 7#include "mordor/http/broker.h" 8#include "mordor/http/client.h" 9#include "mordor/socket.h" 10 11#ifdef WINDOWS 12#include "mordor/runtime_linking.h" 13#elif defined (OSX) 14#include <CoreServices/CoreServices.h> 15#include <SystemConfiguration/SystemConfiguration.h> 16#include "mordor/util.h" 17#include "mordor/streams/file.h" 18#include "mordor/streams/http.h" 19#include "mordor/streams/limited.h" 20#include "mordor/streams/memory.h" 21#include "mordor/streams/transfer.h" 22#include "mordor/sleep.h" 23#endif 24 25namespace Mordor { 26namespace HTTP { 27 28#ifdef WINDOWS 29static std::ostream& operator<<(std::ostream& s, 30 const WINHTTP_PROXY_INFO& info); 31#endif 32 33static int compare(const std::string& s1, const std::string& s2); 34static bool isMatch(const URI& uri, const std::string& pattern); 35 36static ConfigVar<std::string>::ptr g_httpProxy = 37 Config::lookup("http.proxy", std::string(), 38 "HTTP Proxy Server"); 39 40static Logger::ptr proxyLog = Log::lookup("mordor:http:proxy"); 41 42std::vector<URI> proxyFromConfig(const URI &uri) 43{ 44 return proxyFromList(uri, g_httpProxy->val()); 45} 46 47std::vector<URI> proxyFromList(const URI &uri, 48 std::string proxy, 49 std::string bypassList) 50{ 51 MORDOR_LOG_DEBUG(proxyLog) << "Finding proxy for URI " << uri.toString() 52 << "."; 53 54 if (proxy.empty()) { 55 MORDOR_LOG_DEBUG(proxyLog) << "Empty proxy string!"; 56 return std::vector<URI>(); 57 } 58 59 // Split the proxy string on '!', and use the second part as the bypass 60 // list. Note that the provided bypass list should be empty if the proxy 61 // string includes a '!' character; see proxy.h. 62 std::vector<std::string> parts; 63 boost::split(parts, proxy, boost::is_any_of("!")); 64 if (parts.size() > 1) { 65 proxy = parts[0]; 66 if (bypassList.empty()) { 67 bypassList = parts[1]; 68 } 69 } 70 71 // Does this URI match a pattern in the bypass list? 72 std::vector<std::string> bypassEntries; 73 boost::split(bypassEntries, bypassList, boost::is_any_of(";")); 74 for (size_t i = 0; i < bypassEntries.size(); i++) { 75 MORDOR_LOG_DEBUG(proxyLog) << "Considering bypass list entry \"" 76 << bypassEntries[i] << "\"."; 77 if (isMatch(uri, bypassEntries[i])) { 78 MORDOR_LOG_DEBUG(proxyLog) << "Entry matched!"; 79 return std::vector<URI>(); 80 } 81 } 82 83 // Find the correct proxy for this URI scheme. We expect the list of proxies 84 // to look like this: 85 // 86 // list = proxy { ";" proxy } 87 // proxy = { scheme "=" } uri 88 // 89 // Proxies without a scheme are always included in the returned list. A 90 // proxy with a scheme is only included if the scheme matches the scheme 91 // of the provided uri. 92 std::vector<URI> proxyList; 93 std::vector<std::string> v; 94 boost::split(v, proxy, boost::is_any_of(";")); 95 for (size_t i = 0; i < v.size(); i++) { 96 MORDOR_LOG_DEBUG(proxyLog) << "Considering proxy \"" << v[i] << "\"."; 97 98 // Match the scheme and the URI for each proxy in the list. See above. 99 static const boost::regex e("(?:([a-zA-Z]+)=)?(.*)"); 100 boost::smatch m; 101 if (!boost::regex_match(v[i], m, e)) { 102 MORDOR_LOG_DEBUG(proxyLog) << "Invalid proxy \"" << v[i] << "\"."; 103 continue; 104 } 105 106 if (!m[1].matched || compare(m[1].str(), uri.scheme()) == 0) { 107 // XXX The entry in the proxy list typically doesn't include the 108 // scheme in the URI, so we use a regex here to parse it instead of 109 // simply creating a Mordor::URI directly. 110 std::string proxyURI = m[2].str(); 111 static const boost::regex uriRegex("(?:[a-zA-Z]+://)?(.*)"); 112 boost::smatch uriMatch; 113 if (!boost::regex_match(proxyURI, uriMatch, uriRegex)) { 114 MORDOR_LOG_DEBUG(proxyLog) << "Invalid proxy \"" << v[i] 115 << "\"."; 116 continue; 117 } 118 119 std::ostringstream ss; 120 ss << uri.scheme() << "://" << uriMatch[1].str(); 121 122 MORDOR_LOG_DEBUG(proxyLog) << "Proxy matches. Using proxy URI \"" 123 << ss.str() << "\"."; 124 proxyList.push_back(URI(ss.str())); 125 } 126 } 127 128 return proxyList; 129} 130 131#ifdef WINDOWS 132 133static std::vector<URI> proxyFromProxyInfo(const URI &uri, 134 WINHTTP_PROXY_INFO &proxyInfo) 135{ 136 std::vector<URI> result; 137 std::string proxy, bypassList; 138 try { 139 if (proxyInfo.lpszProxy) 140 proxy = toUtf8(proxyInfo.lpszProxy); 141 if (proxyInfo.lpszProxyBypass) 142 bypassList = toUtf8(proxyInfo.lpszProxyBypass); 143 } catch (...) { 144 if (proxyInfo.lpszProxy) 145 GlobalFree(proxyInfo.lpszProxy); 146 if (proxyInfo.lpszProxyBypass) 147 GlobalFree(proxyInfo.lpszProxyBypass); 148 throw; 149 } 150 if (proxyInfo.lpszProxy) 151 GlobalFree(proxyInfo.lpszProxy); 152 if (proxyInfo.lpszProxyBypass) 153 GlobalFree(proxyInfo.lpszProxyBypass); 154 switch (proxyInfo.dwAccessType) { 155 case WINHTTP_ACCESS_TYPE_NAMED_PROXY: 156 return proxyFromList(uri, proxy, bypassList); 157 case WINHTTP_ACCESS_TYPE_NO_PROXY: 158 return result; 159 default: 160 MORDOR_NOTREACHED(); 161 } 162} 163 164std::vector<URI> proxyFromMachineDefault(const URI &uri) 165{ 166 WINHTTP_PROXY_INFO proxyInfo; 167 if (!pWinHttpGetDefaultProxyConfiguration(&proxyInfo)) 168 MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("WinHttpGetDefaultProxyConfiguration"); 169 return proxyFromProxyInfo(uri, proxyInfo); 170} 171 172ProxySettings 173getUserProxySettings() 174{ 175 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig; 176 ProxySettings result; 177 if (!pWinHttpGetIEProxyConfigForCurrentUser(&proxyConfig)) 178 MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("WinHttpGetIEProxyConfigForCurrentUser"); 179 try { 180 result.autoDetect = !!proxyConfig.fAutoDetect; 181 if (proxyConfig.lpszAutoConfigUrl) 182 result.pacScript = toUtf8(proxyConfig.lpszAutoConfigUrl); 183 if (proxyConfig.lpszProxy) 184 result.proxy = toUtf8(proxyConfig.lpszProxy); 185 if (proxyConfig.lpszProxyBypass) 186 result.bypassList = toUtf8(proxyConfig.lpszProxyBypass); 187 } catch (...) { 188 if (proxyConfig.lpszAutoConfigUrl) 189 GlobalFree(proxyConfig.lpszAutoConfigUrl); 190 if (proxyConfig.lpszProxy) 191 GlobalFree(proxyConfig.lpszProxy); 192 if (proxyConfig.lpszProxyBypass) 193 GlobalFree(proxyConfig.lpszProxyBypass); 194 throw; 195 } 196 if (proxyConfig.lpszAutoConfigUrl) 197 GlobalFree(proxyConfig.lpszAutoConfigUrl); 198 if (proxyConfig.lpszProxy) 199 GlobalFree(proxyConfig.lpszProxy); 200 if (proxyConfig.lpszProxyBypass) 201 GlobalFree(proxyConfig.lpszProxyBypass); 202 return result; 203} 204 205ProxyCache::ProxyCache(const std::string &userAgent) 206{ 207 m_hHttpSession = pWinHttpOpen(toUtf16(userAgent).c_str(), 208 WINHTTP_ACCESS_TYPE_NO_PROXY, 209 WINHTTP_NO_PROXY_NAME, 210 WINHTTP_NO_PROXY_BYPASS, 211 0); 212 if (!m_hHttpSession && lastError() != ERROR_CALL_NOT_IMPLEMENTED) 213 MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("WinHttpOpen"); 214} 215 216ProxyCache::~ProxyCache() 217{ 218 if (m_hHttpSession) 219 pWinHttpCloseHandle(m_hHttpSession); 220} 221 222boost::mutex ProxyCache::s_cacheMutex; 223bool ProxyCache::s_failedAutoDetect = false; 224std::set<std::string> ProxyCache::s_invalidConfigURLs; 225 226void ProxyCache::resetDetectionResultCache() 227{ 228 boost::mutex::scoped_lock lock(s_cacheMutex); 229 s_failedAutoDetect = false; 230 s_invalidConfigURLs.clear(); 231} 232 233bool ProxyCache::autoDetectProxy(const URI &uri, const std::string &pacScript, 234 std::vector<URI> &proxyList) 235{ 236 MORDOR_LOG_DEBUG(proxyLog) << "Auto-detecting proxy for URI \"" 237 << uri.toString() << "\"."; 238 239 // We can't auto-detect or auto-configure without an HTTP session handle. 240 if (!m_hHttpSession) { 241 MORDOR_LOG_DEBUG(proxyLog) << "No HTTP session!"; 242 return false; 243 } 244 245 // Check to see we've already tried to process the provided PAC file URL, 246 // and failed. Auto-detection and auto-configuration failures can take a 247 // long time, so we want to avoid trying them repeatedly. 248 { 249 boost::mutex::scoped_lock lock(s_cacheMutex); 250 if (pacScript.empty()) { 251 if (s_failedAutoDetect) { 252 MORDOR_LOG_DEBUG(proxyLog) << "Using cached auto-detection result."; 253 return false; 254 } 255 } else { 256 if (s_invalidConfigURLs.find(pacScript) != s_invalidConfigURLs.end()) { 257 MORDOR_LOG_DEBUG(proxyLog) << "Using cached auto-config result."; 258 return false; 259 } 260 } 261 } 262 263 264 WINHTTP_AUTOPROXY_OPTIONS options = {0}; 265 options.fAutoLogonIfChallenged = TRUE; 266 std::wstring pacScriptW = toUtf16(pacScript); 267 if (!pacScriptW.empty()) { 268 options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; 269 options.lpszAutoConfigUrl = pacScriptW.c_str(); 270 } else { 271 options.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; 272 options.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | 273 WINHTTP_AUTO_DETECT_TYPE_DNS_A; 274 } 275 276 WINHTTP_PROXY_INFO info = {0}; 277 if (!pWinHttpGetProxyForUrl(m_hHttpSession, 278 toUtf16(uri.toString()).c_str(), 279 &options, 280 &info)) { 281 // Record the fact that this PAC file URL failed in our cache, so that 282 // we don't attempt to run it again. 283 { 284 boost::mutex::scoped_lock lock(s_cacheMutex); 285 if (pacScript.empty()) { 286 s_failedAutoDetect = true; 287 } else { 288 s_invalidConfigURLs.insert(pacScript); 289 } 290 } 291 292 error_t error = Mordor::lastError(); 293 MORDOR_LOG_ERROR(proxyLog) << "WinHttpGetProxyForUrl: (" << error 294 << ") : " << uri.toString() << " : " 295 << pacScript ; 296 return false; 297 } 298 299 MORDOR_LOG_DEBUG(proxyLog) << "Auto-detected proxy: " << std::endl << info; 300 proxyList = proxyFromProxyInfo(uri, info); 301 return true; 302} 303 304std::vector<URI> ProxyCache::proxyFromUserSettings(const URI &uri) 305{ 306 ProxySettings settings = getUserProxySettings(); 307 308 if (settings.autoDetect) { 309 std::vector<URI> proxyList; 310 if (autoDetectProxy(uri, "", proxyList)) { 311 return proxyList; 312 } 313 } 314 315 if (!settings.pacScript.empty()) { 316 std::vector<URI> proxyList; 317 if (autoDetectProxy(uri, settings.pacScript, proxyList)) { 318 return proxyList; 319 } 320 } 321 322 return proxyFromList(uri, settings.proxy, settings.bypassList); 323} 324 325#elif defined(OSX) 326 327ProxyCache::ProxyCache(RequestBroker::ptr requestBroker) 328 : m_requestBroker(requestBroker), 329 m_pacThreadCancelled(false) 330{ 331 m_dynamicStore = SCDynamicStoreCreate(NULL, NULL, NULL, NULL); 332} 333 334ProxyCache::~ProxyCache() 335{ 336 // Shut down the PAC worker thread 337 { 338 boost::mutex::scoped_lock lk(m_pacMut); 339 m_pacThreadCancelled = true; 340 } 341 m_pacCond.notify_one(); 342 m_pacThread.join(); 343} 344 345std::vector<URI> ProxyCache::proxyFromCFArray(CFArrayRef proxies, ScopedCFRef<CFURLRef> targeturl, 346 RequestBroker::ptr requestBroker, 347 std::map<URI, ScopedCFRef<CFStringRef> > &cachedScripts) 348{ 349 std::vector<URI> result; 350 for (CFIndex i = 0; i < CFArrayGetCount(proxies); ++i) { 351 CFDictionaryRef thisProxy = 352 (CFDictionaryRef)CFArrayGetValueAtIndex(proxies, i); 353 CFStringRef proxyType = (CFStringRef)CFDictionaryGetValue(thisProxy, 354 kCFProxyTypeKey); 355 if (!proxyType || CFGetTypeID(proxyType) != CFStringGetTypeID()) 356 continue; 357 URI thisProxyUri; 358 if (CFEqual(proxyType, kCFProxyTypeNone)) { 359 result.push_back(URI()); 360 } else if (CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) { 361 CFURLRef cfurl = (CFURLRef)CFDictionaryGetValue(thisProxy, 362 kCFProxyAutoConfigurationURLKey); 363 if (!cfurl || CFGetTypeID(cfurl) != CFURLGetTypeID()) 364 continue; 365 std::vector<URI> pacProxies = proxyFromPacScript(cfurl, targeturl, 366 requestBroker, cachedScripts); 367 result.insert(result.end(), pacProxies.begin(), pacProxies.end()); 368 } else if (CFEqual(proxyType, kCFProxyTypeHTTP)) { 369 thisProxyUri.scheme("http"); 370 } else if (CFEqual(proxyType, kCFProxyTypeHTTPS)) { 371 thisProxyUri.scheme("https"); 372 } else if (CFEqual(proxyType, kCFProxyTypeSOCKS)) { 373 thisProxyUri.scheme("socks"); 374 } 375 if (thisProxyUri.schemeDefined()) { 376 CFStringRef proxyHost = (CFStringRef)CFDictionaryGetValue( 377 thisProxy, kCFProxyHostNameKey); 378 if (!proxyHost || CFGetTypeID(proxyHost) != CFStringGetTypeID()) 379 continue; 380 CFNumberRef proxyPort = (CFNumberRef)CFDictionaryGetValue( 381 thisProxy, kCFProxyPortNumberKey); 382 int port = -1; 383 if (proxyPort && CFGetTypeID(proxyPort) == CFNumberGetTypeID()) 384 CFNumberGetValue(proxyPort, kCFNumberIntType, &port); 385 thisProxyUri.authority.host(toUtf8(proxyHost)); 386 if (port != -1) 387 thisProxyUri.authority.port(port); 388 result.push_back(thisProxyUri); 389 } 390 } 391 return result; 392} 393 394// Worker thread that handles PAC evaluation. This has to be done in a 395// separate, fiber-free thread or the underlying JavaScript VM used in 396// CFNetworkCopyProxiesForAutoConfigurationScript will crash trying to 397// perform garbage collection. 398void ProxyCache::runPacWorker() 399{ 400 while(true) { 401 boost::mutex::scoped_lock lk(m_pacMut); 402 403 while(m_pacQueue.empty() && !m_pacThreadCancelled) 404 m_pacCond.wait(lk); 405 406 if(m_pacThreadCancelled) 407 return; 408 409 while(!m_pacQueue.empty()) { 410 struct PacMessage *msg = m_pacQueue.front(); 411 m_pacQueue.pop(); 412 413 lk.unlock(); 414 415 CFErrorRef error; 416 ScopedCFRef<CFArrayRef> proxies = 417 CFNetworkCopyProxiesForAutoConfigurationScript( 418 msg->pacScript, msg->targeturl, &error); 419 420 lk.lock(); 421 422 msg->result = proxies; 423 msg->processed = true; 424 } 425 } 426} 427 428std::vector<URI> ProxyCache::proxyFromPacScript(CFURLRef cfurl, ScopedCFRef<CFURLRef> targeturl, 429 RequestBroker::ptr requestBroker, 430 std::map<URI, ScopedCFRef<CFStringRef> > &cachedScripts) 431{ 432 std::vector<URI> result; 433 CFStringRef string = CFURLGetString(cfurl); 434 URI uri; 435 try { 436 uri = toUtf8(string); 437 } catch (std::invalid_argument &) { 438 return result; 439 } 440 if (!uri.schemeDefined()) 441 return result; 442 if (uri.scheme() != "http" && uri.scheme() != "https" && 443 uri.scheme() != "file") 444 return result; 445 446 try { 447 ScopedCFRef<CFStringRef> pacScript; 448 std::map<URI, ScopedCFRef<CFStringRef> >::iterator it = 449 cachedScripts.find(uri); 450 if (it != cachedScripts.end()) { 451 pacScript = it->second; 452 } else { 453 Stream::ptr pacStream; 454 MemoryStream localStream; 455 if (uri.scheme() == "file") { 456 if (uri.authority.hostDefined() && 457 uri.authority.host() != "localhost" && 458 !uri.authority.host().empty()) 459 return result; 460 std::ostringstream os; 461 for (std::vector<std::string>::const_iterator it = uri.path.segments.begin(); 462 it != uri.path.segments.end(); 463 ++it) 464 os << '/' << *it; 465 pacStream.reset(new FileStream(os.str(), FileStream::READ)); 466 } else { 467 pacStream.reset(new HTTPStream(uri, requestBroker)); 468 } 469 pacStream.reset(new LimitedStream(pacStream, 1024 * 1024 + 1)); 470 if (transferStream(pacStream, localStream) >= 1024u * 1024u) 471 return result; 472 std::string localBuffer; 473 localBuffer.resize((size_t)localStream.size()); 474 localStream.buffer().copyOut(&localBuffer[0], localBuffer.size()); 475 pacScript = CFStringCreateWithBytes(NULL, 476 (const UInt8*)localBuffer.c_str(), localBuffer.size(), 477 kCFStringEncodingUTF8, true); 478 cachedScripts[uri] = pacScript; 479 } 480 481 // Start the PAC worker thread if not already running 482 // by checking to see if the thread is "Not-a-Thread" 483 if(boost::thread::id() == m_pacThread.get_id()) { 484 m_pacThread = boost::thread(&ProxyCache::runPacWorker, this); 485 MORDOR_LOG_DEBUG(proxyLog) << "PAC worker thread id : " << m_pacThread.get_id(); 486 } 487 488 // Evaluate the PAC in the fiber-free worker thread so that 489 // we don't crash due to the JavaScript VM's garbage collection. 490 struct PacMessage msg; 491 { 492 boost::mutex::scoped_lock lk(m_pacMut); 493 msg.pacScript = pacScript; 494 msg.targeturl = targeturl; 495 msg.result = NULL; 496 msg.processed = false; 497 m_pacQueue.push(&msg); 498 } 499 500 // Notify the worker thread that there is a new message in the queue 501 m_pacCond.notify_one(); 502 503 // Wake up periodically to see if our PAC message has been processed 504 while(true) { 505 boost::mutex::scoped_lock lk(m_pacMut); 506 if(msg.processed) 507 break; 508 lk.unlock(); 509 Mordor::sleep(1000); 510 } 511 512 if(!msg.result) 513 return result; 514 return proxyFromCFArray(msg.result, targeturl, requestBroker, cachedScripts); 515 } catch (...) { 516 return result; 517 } 518} 519 520std::vector<URI> 521ProxyCache::proxyFromSystemConfiguration(const URI &uri) 522{ 523 std::vector<URI> result; 524 525 std::string uristring = uri.toString(); 526 ScopedCFRef<CFURLRef> cfurl = CFURLCreateWithBytes(NULL, 527 (const UInt8 *)uristring.c_str(), 528 uristring.size(), kCFStringEncodingUTF8, NULL); 529 if (!cfurl) 530 return result; 531 532 ScopedCFRef<CFDictionaryRef> proxySettings = 533 SCDynamicStoreCopyProxies(m_dynamicStore); 534 if (!proxySettings) 535 return result; 536 ScopedCFRef<CFArrayRef> proxies = CFNetworkCopyProxiesForURL(cfurl, 537 proxySettings); 538 if (!proxies) 539 return result; 540 return proxyFromCFArray(proxies, cfurl, m_requestBroker, 541 m_cachedScripts); 542} 543#endif 544 545boost::shared_ptr<Stream> 546tunnel(RequestBroker::ptr requestBroker, const URI &proxy, const URI &target) 547{ 548 MORDOR_ASSERT(proxy.schemeDefined()); 549 550 std::ostringstream os; 551 if (!target.authority.hostDefined()) 552 MORDOR_THROW_EXCEPTION(std::invalid_argument("No host defined")); 553 os << target.authority.host() << ':'; 554 if (target.authority.portDefined()) 555 os << target.authority.port(); 556 else if (target.scheme() == "http") 557 os << "80"; 558 else if (target.scheme() == "https") 559 os << "443"; 560 else 561 // TODO: can this be looked up using the system? (getaddrinfo) 562 MORDOR_THROW_EXCEPTION(std::invalid_argument("Unknown protocol for proxying connection")); 563 Request requestHeaders; 564 requestHeaders.requestLine.method = CONNECT; 565 URI &requestUri = requestHeaders.requestLine.uri; 566 requestUri.scheme("http"); 567 requestUri.authority = proxy.authority; 568 requestUri.path.segments.push_back(std::string()); 569 requestUri.path.segments.push_back(os.str()); 570 requestHeaders.request.host = os.str(); 571 requestHeaders.general.connection.insert("Proxy-Connection"); 572 requestHeaders.general.proxyConnection.insert("Keep-Alive"); 573 ClientRequest::ptr request = requestBroker->request(requestHeaders); 574 if (request->response().status.status == HTTP::OK) 575 return request->stream(); 576 else 577 MORDOR_THROW_EXCEPTION(InvalidResponseException("Proxy connection failed", 578 request)); 579} 580 581// Returns true if the given address matches the given bypass list entry. 582bool isMatch(const URI& uri, const std::string& pattern) 583{ 584 MORDOR_ASSERT(uri.schemeDefined()); 585 MORDOR_ASSERT(uri.authority.hostDefined()); 586 587 if (pattern.empty()) { 588 return false; 589 } 590 591 // There is no standard describing what a valid bypass list entry looks 592 // like, and slightly different formats are used by different operating 593 // systems and applications. 594 // 595 // This function accepts entries in the following format: 596 // 597 // entry = [scheme "://"] address [":" port]; 598 // scheme = { alnum } ; 599 // address = { alnum "." } "*" { "." alnum } ; 600 // port = { digit } 601 // 602 // A wildcard character in the address matches any number of characters. 603 // List entries with multiple wildcards are not supported. Note that this 604 // function also doesn't support CIDR-style IP address ranges, which are 605 // supported by some browsers and operating systems. 606 boost::regex e("(?:([a-zA-Z]+)://)?(.*?)(?::(\\d+))?"); 607 boost::smatch m; 608 if (!boost::regex_match(pattern, m, e)) { 609 MORDOR_LOG_DEBUG(proxyLog) << "Invalid rule \"" << pattern << "\"."; 610 return false; 611 } 612 613 std::string patternScheme = m[1].str(); 614 std::string patternAddress = m[2].str(); 615 616 int patternPort = 0; 617 try { 618 if (m[3].matched) { 619 patternPort = boost::lexical_cast<int>(m[3].str()); 620 } 621 } catch(boost::bad_lexical_cast&) { 622 MORDOR_LOG_DEBUG(proxyLog) << "Invalid rule \"" << pattern << "\"."; 623 return false; 624 } 625 626 // If a scheme is present in the rule, it must match the sheme in the 627 // URI. If the rule doesn't specify a scheme, we consider it to match 628 // all schemes. 629 // 630 // XXX Like all string comparisons, this is fraught with peril. We assume 631 // that the provided strings are utf-8 encoded, and already normalized. 632 // 633 // XXX We also should not be using stricmp here, since it will not work on 634 // multibyte characters. 635 if (!patternScheme.empty() && compare(patternScheme, uri.scheme())) { 636 // Scheme doesn't match. 637 return false; 638 } 639 640 // As a special case, if the pattern is "<local>" and the address is a 641 // hostname that doesn't contain any '.' characters, then we consider this 642 // to be a match. This is for compatability with IE on Windows. 643 std::string address = uri.authority.host(); 644 if (patternAddress == "<local>" && std::string::npos == address.find('.')) { 645 return true; 646 } 647 648 boost::replace_all(patternAddress, ".", "\\."); 649 boost::replace_all(patternAddress, "*", ".*"); 650 boost::regex hostRegex(patternAddress, boost::regex::perl | boost::regex::icase); 651 if (!boost::regex_match(address, hostRegex)) { 652 // Address doesn't match. 653 return false; 654 } 655 656 if (0 != patternPort && patternPort != uri.authority.port()) { 657 // Port doesn't match. 658 return false; 659 } 660 661 return true; 662} 663 664int compare(const std::string& s1, const std::string& s2) 665{ 666 return stricmp(s1.c_str(), s2.c_str()); 667} 668 669#ifdef WINDOWS 670std::ostream& operator<<(std::ostream& s, const WINHTTP_PROXY_INFO& info) 671{ 672 std::string proxy = info.lpszProxy ? toUtf8(info.lpszProxy) : "(null)"; 673 std::string bypass = info.lpszProxyBypass ? toUtf8(info.lpszProxyBypass) 674 : "(null)"; 675 s << "Access Type: " << info.dwAccessType << std::endl 676 << "Proxy: " << proxy << std::endl 677 << "Proxy Bypass: " << bypass << std::endl; 678 return s; 679} 680#endif 681 682} // namespace HTTP 683} // namespace Mordor