PageRenderTime 270ms CodeModel.GetById 60ms app.highlight 174ms RepoModel.GetById 27ms app.codeStats 1ms

/mordor/http/proxy.cpp

http://github.com/mozy/mordor
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