15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2010 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_handler_factory.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/stl_util.h" 87d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "base/strings/string_util.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/base/net_errors.h" 10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "net/http/http_auth_challenge_tokenizer.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_filter.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_handler_basic.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_handler_digest.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_handler_ntlm.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(USE_KERBEROS) 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_auth_handler_negotiate.h" 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace net { 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int HttpAuthHandlerFactory::CreateAuthHandlerFromString( 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& challenge, 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuth::Target target, 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin, 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const BoundNetLog& net_log, 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<HttpAuthHandler>* handler) { 28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) HttpAuthChallengeTokenizer props(challenge.begin(), challenge.end()); 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return CreateAuthHandler(&props, target, origin, CREATE_CHALLENGE, 1, 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) net_log, handler); 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int HttpAuthHandlerFactory::CreatePreemptiveAuthHandlerFromString( 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& challenge, 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuth::Target target, 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin, 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int digest_nonce_count, 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const BoundNetLog& net_log, 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<HttpAuthHandler>* handler) { 40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) HttpAuthChallengeTokenizer props(challenge.begin(), challenge.end()); 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return CreateAuthHandler(&props, target, origin, CREATE_PREEMPTIVE, 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) digest_nonce_count, net_log, handler); 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpAuthHandlerRegistryFactory* HttpAuthHandlerFactory::CreateDefault( 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HostResolver* host_resolver) { 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(host_resolver); 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerRegistryFactory* registry_factory = 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerRegistryFactory(); 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory( 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "basic", new HttpAuthHandlerBasic::Factory()); 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory( 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "digest", new HttpAuthHandlerDigest::Factory()); 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(USE_KERBEROS) 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerNegotiate::Factory* negotiate_factory = 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerNegotiate::Factory(); 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_POSIX) 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_library(new GSSAPISharedLibrary(std::string())); 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#elif defined(OS_WIN) 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_library(new SSPILibraryDefault()); 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_host_resolver(host_resolver); 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif // defined(USE_KERBEROS) 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerNTLM::Factory* ntlm_factory = 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerNTLM::Factory(); 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_WIN) 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ntlm_factory->set_sspi_library(new SSPILibraryDefault()); 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory("ntlm", ntlm_factory); 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return registry_factory; 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool IsSupportedScheme(const std::vector<std::string>& supported_schemes, 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& scheme) { 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<std::string>::const_iterator it = std::find( 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supported_schemes.begin(), supported_schemes.end(), scheme); 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return it != supported_schemes.end(); 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpAuthHandlerRegistryFactory::HttpAuthHandlerRegistryFactory() { 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpAuthHandlerRegistryFactory::~HttpAuthHandlerRegistryFactory() { 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) STLDeleteContainerPairSecondPointers(factory_map_.begin(), 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) factory_map_.end()); 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void HttpAuthHandlerRegistryFactory::SetURLSecurityManager( 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& scheme, 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) URLSecurityManager* security_manager) { 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerFactory* factory = GetSchemeFactory(scheme); 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (factory) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) factory->set_url_security_manager(security_manager); 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void HttpAuthHandlerRegistryFactory::RegisterSchemeFactory( 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& scheme, 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerFactory* factory) { 1076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) std::string lower_scheme = base::StringToLowerASCII(scheme); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FactoryMap::iterator it = factory_map_.find(lower_scheme); 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (it != factory_map_.end()) { 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delete it->second; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (factory) 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) factory_map_[lower_scheme] = factory; 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) factory_map_.erase(it); 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpAuthHandlerFactory* HttpAuthHandlerRegistryFactory::GetSchemeFactory( 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& scheme) const { 1206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) std::string lower_scheme = base::StringToLowerASCII(scheme); 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FactoryMap::const_iterator it = factory_map_.find(lower_scheme); 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (it == factory_map_.end()) { 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return NULL; // |scheme| is not registered. 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return it->second; 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpAuthHandlerRegistryFactory* HttpAuthHandlerRegistryFactory::Create( 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::vector<std::string>& supported_schemes, 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) URLSecurityManager* security_manager, 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HostResolver* host_resolver, 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& gssapi_library_name, 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool negotiate_disable_cname_lookup, 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool negotiate_enable_port) { 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerRegistryFactory* registry_factory = 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerRegistryFactory(); 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (IsSupportedScheme(supported_schemes, "basic")) 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory( 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "basic", new HttpAuthHandlerBasic::Factory()); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (IsSupportedScheme(supported_schemes, "digest")) 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory( 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "digest", new HttpAuthHandlerDigest::Factory()); 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (IsSupportedScheme(supported_schemes, "ntlm")) { 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerNTLM::Factory* ntlm_factory = 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerNTLM::Factory(); 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ntlm_factory->set_url_security_manager(security_manager); 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_WIN) 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ntlm_factory->set_sspi_library(new SSPILibraryDefault()); 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory("ntlm", ntlm_factory); 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(USE_KERBEROS) 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (IsSupportedScheme(supported_schemes, "negotiate")) { 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuthHandlerNegotiate::Factory* negotiate_factory = 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new HttpAuthHandlerNegotiate::Factory(); 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if defined(OS_POSIX) 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_library( 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new GSSAPISharedLibrary(gssapi_library_name)); 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#elif defined(OS_WIN) 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_library(new SSPILibraryDefault()); 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_url_security_manager(security_manager); 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(host_resolver || negotiate_disable_cname_lookup); 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_host_resolver(host_resolver); 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_disable_cname_lookup(negotiate_disable_cname_lookup); 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) negotiate_factory->set_use_port(negotiate_enable_port); 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory); 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif // defined(USE_KERBEROS) 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return registry_factory; 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int HttpAuthHandlerRegistryFactory::CreateAuthHandler( 176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) HttpAuthChallengeTokenizer* challenge, 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HttpAuth::Target target, 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin, 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) CreateReason reason, 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int digest_nonce_count, 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const BoundNetLog& net_log, 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<HttpAuthHandler>* handler) { 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string scheme = challenge->scheme(); 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (scheme.empty()) { 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) handler->reset(); 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ERR_INVALID_RESPONSE; 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) std::string lower_scheme = base::StringToLowerASCII(scheme); 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FactoryMap::iterator it = factory_map_.find(lower_scheme); 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (it == factory_map_.end()) { 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) handler->reset(); 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ERR_UNSUPPORTED_AUTH_SCHEME; 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(it->second); 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return it->second->CreateAuthHandler(challenge, target, origin, reason, 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) digest_nonce_count, net_log, handler); 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace net 200