PageRenderTime 109ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/mordor/http/proxy.cpp

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