panda3d/panda/src/downloader/httpClient.cxx
2003-04-26 01:38:54 +00:00

1333 lines
43 KiB
C++

// Filename: httpClient.cxx
// Created by: drose (24Sep02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "httpClient.h"
#include "httpChannel.h"
#include "config_downloader.h"
#include "filename.h"
#include "config_express.h"
#include "virtualFileSystem.h"
#include "executionEnvironment.h"
#include "httpBasicAuthorization.h"
#include "httpDigestAuthorization.h"
#ifdef HAVE_SSL
#include <openssl/rand.h>
#ifdef REPORT_OPENSSL_ERRORS
#include <openssl/err.h>
#endif
// Windows may define this macro inappropriately.
#ifdef X509_NAME
#undef X509_NAME
#endif
bool HTTPClient::_ssl_initialized = false;
// This is created once and never freed.
X509_STORE *HTTPClient::_x509_store = NULL;
////////////////////////////////////////////////////////////////////
// Function: trim_blanks
// Description:
////////////////////////////////////////////////////////////////////
static string
trim_blanks(const string &str) {
size_t start = 0;
while (start < str.length() && isspace(str[start])) {
start++;
}
size_t end = str.length();
while (end > start && isspace(str[end - 1])) {
end--;
}
return str.substr(start, end - start);
}
////////////////////////////////////////////////////////////////////
// Function: tokenize
// Description: Chops the source string up into pieces delimited by
// any of the characters specified in delimiters.
// Repeated delimiter characters represent zero-length
// tokens.
//
// It is the user's responsibility to ensure the output
// vector is cleared before calling this function; the
// results will simply be appended to the end of the
// vector.
////////////////////////////////////////////////////////////////////
static void
tokenize(const string &str, vector_string &words, const string &delimiters) {
size_t p = 0;
while (p < str.length()) {
size_t q = str.find_first_of(delimiters, p);
if (q == string::npos) {
words.push_back(str.substr(p));
return;
}
words.push_back(str.substr(p, q - p));
p = q + 1;
}
words.push_back(string());
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
HTTPClient::
HTTPClient() {
_http_version = HTTPEnum::HV_11;
_verify_ssl = verify_ssl ? VS_normal : VS_no_verify;
_ssl_ctx = (SSL_CTX *)NULL;
set_proxy_spec(http_proxy);
if (!http_proxy_username.empty()) {
set_username("*proxy", "", http_proxy_username);
}
{
// Also load in the general usernames.
Config::ConfigTable::Symbol http_usernames;
config_downloader.GetAll("http-username", http_usernames);
// When we use GetAll(), we might inadvertently read duplicate
// lines. Filter them out with a set.
pset<string> already_read;
Config::ConfigTable::Symbol::iterator si;
for (si = http_usernames.begin(); si != http_usernames.end(); ++si) {
string http_username = (*si).Val();
if (already_read.insert(http_username).second) {
add_http_username(http_username);
}
}
}
// The first time we create an HTTPClient, we must initialize the
// OpenSSL library.
if (!_ssl_initialized) {
initialize_ssl();
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::Copy Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
HTTPClient::
HTTPClient(const HTTPClient &copy) {
_ssl_ctx = (SSL_CTX *)NULL;
(*this) = copy;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::Copy Assignment Operator
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void HTTPClient::
operator = (const HTTPClient &copy) {
_proxies_by_scheme = copy._proxies_by_scheme;
_direct_hosts = copy._direct_hosts;
_http_version = copy._http_version;
_verify_ssl = copy._verify_ssl;
_usernames = copy._usernames;
clear_expected_servers();
ExpectedServers::const_iterator ei;
for (ei = copy._expected_servers.begin();
ei != copy._expected_servers.end();
++ei) {
X509_NAME *orig_name = (*ei);
X509_NAME *new_name = X509_NAME_dup(orig_name);
_expected_servers.push_back(new_name);
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::Destructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
HTTPClient::
~HTTPClient() {
// Before we can free the context, we must remove the X509_STORE
// pointer from it, so it won't be destroyed along with it (this
// object is shared among all contexts).
if (_ssl_ctx != (SSL_CTX *)NULL) {
nassertv(_ssl_ctx->cert_store == _x509_store);
_ssl_ctx->cert_store = NULL;
SSL_CTX_free(_ssl_ctx);
}
// Free all of the expected server definitions.
clear_expected_servers();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::set_proxy
// Access: Published
// Description: Specifies the proxy URL to handle all http and
// https requests. Deprecated.
////////////////////////////////////////////////////////////////////
void HTTPClient::
set_proxy(const URLSpec &proxy) {
set_proxy_spec(proxy.get_url());
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxy
// Access: Published
// Description: Returns the proxy URL to handle all http and
// https requests. Deprecated.
////////////////////////////////////////////////////////////////////
URLSpec HTTPClient::
get_proxy() const {
pvector<URLSpec> proxies;
get_proxies_for_url(URLSpec("http://"), proxies);
if (!proxies.empty()) {
return proxies[0];
}
return URLSpec();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::set_proxy_spec
// Access: Published
// Description: Specifies the complete set of proxies to use for all
// schemes. This is either a semicolon-delimited set of
// hostname:ports, or a semicolon-delimited set of pairs
// of the form "scheme=hostname:port", or a combination.
// A particular scheme and/or proxy host may be listed
// more than once. This is a convenience function that
// can be used in place of explicit calls to add_proxy()
// for each scheme/proxy pair.
////////////////////////////////////////////////////////////////////
void HTTPClient::
set_proxy_spec(const string &proxy_spec) {
clear_proxy();
// Tokenize the string based on the semicolons.
vector_string proxies;
tokenize(proxy_spec, proxies, ";");
for (vector_string::const_iterator pi = proxies.begin();
pi != proxies.end();
++pi) {
const string &spec = (*pi);
// Divide out the scheme and the hostname.
string scheme;
URLSpec url;
size_t equals = spec.find('=');
if (equals == string::npos) {
scheme = "";
url = URLSpec(spec, true);
} else {
scheme = trim_blanks(spec.substr(0, equals));
url = URLSpec(spec.substr(equals + 1), true);
}
if (!url.has_scheme()) {
// The default scheme for talking to proxies is HTTP.
url.set_scheme("http");
}
add_proxy(scheme, url);
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxy_spec
// Access: Published
// Description: Returns the complete set of proxies to use for all
// schemes. This is a string of the form specified by
// set_proxy_spec(), above. Note that the string
// returned by this function may not be exactly the same
// as the string passed into set_proxy_spec(), since the
// string is regenerated from the internal storage
// structures and may therefore be reordered.
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_proxy_spec() const {
string result;
ProxiesByScheme::const_iterator si;
for (si = _proxies_by_scheme.begin(); si != _proxies_by_scheme.end(); ++si) {
const string &scheme = (*si).first;
const Proxies &proxies = (*si).second;
Proxies::const_iterator pi;
for (pi = proxies.begin(); pi != proxies.end(); ++pi) {
const URLSpec &url = (*pi);
if (!result.empty()) {
result += ";";
}
if (!scheme.empty()) {
result += scheme;
result += "=";
}
result += url.get_url();
}
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::set_direct_host_spec
// Access: Published
// Description: Specifies the set of hosts that should be connected
// to directly, without using a proxy. This is a
// semicolon-separated list of hostnames or ip addresses,
// that may contain wildcard characters ("*").
////////////////////////////////////////////////////////////////////
void HTTPClient::
set_direct_host_spec(const string &direct_host_spec) {
clear_direct_host();
// Tokenize the string based on the semicolons.
vector_string hosts;
tokenize(direct_host_spec, hosts, ";");
for (vector_string::const_iterator hi = hosts.begin();
hi != hosts.end();
++hi) {
const string &spec = (*hi);
add_direct_host(trim_blanks(spec));
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_direct_host_spec
// Access: Published
// Description: Returns the set of hosts that should be connected
// to directly, without using a proxy, as a
// semicolon-separated list of hostnames or ip addresses,
// that may contain wildcard characters ("*").
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_direct_host_spec() const {
string result;
vector_string::const_iterator si;
for (si = _direct_hosts.begin(); si != _direct_hosts.end(); ++si) {
const string &host = (*si);
if (!result.empty()) {
result += ";";
}
result += host;
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::clear_proxy
// Access: Published
// Description: Resets the proxy spec to empty. Subsequent calls to
// add_proxy() may be made to build up the set of proxy
// servers.
////////////////////////////////////////////////////////////////////
void HTTPClient::
clear_proxy() {
_proxies_by_scheme.clear();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_proxy
// Access: Published
// Description: Adds the indicated proxy host as a proxy for
// communications on the given scheme. Usually the
// scheme is "http" or "https". It may be the empty
// string to indicate a general proxy.
////////////////////////////////////////////////////////////////////
void HTTPClient::
add_proxy(const string &scheme, const URLSpec &proxy) {
// The scheme is always converted to lowercase.
string lc_scheme;
lc_scheme.reserve(scheme.length());
for (string::const_iterator si = scheme.begin(); si != scheme.end(); ++si) {
lc_scheme += tolower(*si);
}
// Remove the trailing colon, if there is one.
if (!lc_scheme.empty() && lc_scheme[lc_scheme.length() - 1] == ':') {
lc_scheme = lc_scheme.substr(0, lc_scheme.length() - 1);
}
_proxies_by_scheme[lc_scheme].push_back(proxy);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::clear_direct_host
// Access: Published
// Description: Resets the set of direct hosts to empty. Subsequent
// calls to add_direct_host() may be made to build up
// the list of hosts that do not require a proxy
// connection.
////////////////////////////////////////////////////////////////////
void HTTPClient::
clear_direct_host() {
_direct_hosts.clear();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_direct_host
// Access: Published
// Description: Adds the indicated name to the set of hostnames that
// are connected to directly, without using a proxy.
// This name may be either a DNS name or an IP address,
// and it may include the * as a wildcard character.
////////////////////////////////////////////////////////////////////
void HTTPClient::
add_direct_host(const string &hostname) {
// The hostname is always converted to lowercase.
string lc_hostname;
lc_hostname.reserve(hostname.length());
for (string::const_iterator si = hostname.begin();
si != hostname.end();
++si) {
lc_hostname += tolower(*si);
}
_direct_hosts.push_back(lc_hostname);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxies_for_url
// Access: Published
// Description: Fills up the indicated vector with the list of
// URLSpec objects, in the order in which they should be
// tried, that are appropriate for the indicated URL.
// The vector is left empty if a direct connection
// should be used.
//
// It is the user's responsibility to empty this vector
// before calling this method; otherwise, the proxy
// URL's will simply be appended to the existing list.
////////////////////////////////////////////////////////////////////
void HTTPClient::
get_proxies_for_url(const URLSpec &url, pvector<URLSpec> &proxies) const {
// First, check if the hostname matches any listed in direct_hosts.
string hostname = url.get_server();
// TODO: This should be a glob match, not a literal match.
vector_string::const_iterator si;
for (si = _direct_hosts.begin(); si != _direct_hosts.end(); ++si) {
if ((*si) == hostname) {
// It matches, so don't use any proxies.
return;
}
}
// Now choose the appropriate proxy based on the scheme.
string scheme = url.get_scheme();
bool got_any = false;
if (scheme.empty()) {
// An empty scheme implies we will want to make a direct
// connection to this host, so we will need a socks-style or
// https-style scheme.
if (get_proxies_for_scheme("socks", proxies)) {
got_any = true;
}
if (get_proxies_for_scheme("https", proxies)) {
got_any = true;
}
} else {
// Otherwise, try to match the proxy to the scheme.
if (get_proxies_for_scheme(scheme, proxies)) {
got_any = true;
}
}
// If we didn't find our scheme of choice, fall back to the default
// proxy type.
if (!got_any) {
if (get_proxies_for_scheme("", proxies)) {
got_any = true;
}
}
// And failing that, try the http proxy.
if (!got_any) {
get_proxies_for_scheme("http", proxies);
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxies_for_url
// Access: Published
// Description: Returns a semicolon-delimited list of proxies, in the
// order in which they should be tried, that are
// appropriate for the indicated URL. The empty string
// is returned if a direct connection should be used.
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_proxies_for_url(const URLSpec &url) const {
pvector<URLSpec> proxies;
get_proxies_for_url(url, proxies);
string result;
if (!proxies.empty()) {
pvector<URLSpec>::const_iterator pi = proxies.begin();
result += (*pi).get_url();
++pi;
while (pi != proxies.end()) {
result += ";";
result += (*pi).get_url();
++pi;
}
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::set_username
// Access: Published
// Description: Specifies the username:password string corresponding
// to a particular server and/or realm, when demanded by
// the server. Either or both of the server or realm
// may be empty; if so, they match anything. Also, the
// server may be set to the special string "*proxy",
// which will match any proxy server.
//
// If the username is set to the empty string, this
// clears the password for the particular server/realm
// pair.
////////////////////////////////////////////////////////////////////
void HTTPClient::
set_username(const string &server, const string &realm, const string &username) {
string key = server + ":" + realm;
if (username.empty()) {
_usernames.erase(key);
} else {
_usernames[key] = username;
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_username
// Access: Published
// Description: Returns the username:password string set for this
// server/realm pair, or empty string if nothing has
// been set. See set_username().
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_username(const string &server, const string &realm) const {
string key = server + ":" + realm;
Usernames::const_iterator ui;
ui = _usernames.find(key);
if (ui != _usernames.end()) {
return (*ui).second;
}
return string();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_http_version_string
// Access: Published
// Description: Returns the current HTTP version setting as a string,
// e.g. "HTTP/1.0" or "HTTP/1.1".
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_http_version_string() const {
switch (_http_version) {
case HTTPEnum::HV_09:
return "HTTP/0.9";
case HTTPEnum::HV_10:
return "HTTP/1.0";
case HTTPEnum::HV_11:
return "HTTP/1.1";
case HTTPEnum::HV_other:
// Report the best we can do.
return "HTTP/1.1";
}
return "unknown";
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::parse_http_version_string
// Access: Published
// Description: Matches the string representing a particular HTTP
// version against any of the known versions and returns
// the appropriate enumerated value, or HV_other if the
// version is unknown.
////////////////////////////////////////////////////////////////////
HTTPEnum::HTTPVersion HTTPClient::
parse_http_version_string(const string &version) {
if (version == "HTTP/1.0") {
return HTTPEnum::HV_10;
} else if (version == "HTTP/1.1") {
return HTTPEnum::HV_11;
} else if (version.substr(0, 6) == "HTTP/0") {
return HTTPEnum::HV_09;
} else {
return HTTPEnum::HV_other;
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::load_certificates
// Access: Published
// Description: Reads the certificate(s) (delimited by -----BEGIN
// CERTIFICATE----- and -----END CERTIFICATE-----) from
// the indicated file and makes them known as trusted
// public keys for validating future connections.
// Returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool HTTPClient::
load_certificates(const Filename &filename) {
int result;
if (use_vfs) {
result = load_verify_locations(_ssl_ctx, filename);
} else {
string os_specific = filename.to_os_specific();
result =
SSL_CTX_load_verify_locations(_ssl_ctx, os_specific.c_str(), NULL);
}
if (result <= 0) {
downloader_cat.info()
<< "Could not load certificates from " << filename << ".\n";
#ifdef REPORT_OPENSSL_ERRORS
ERR_print_errors_fp(stderr);
#endif
return false;
}
downloader_cat.info()
<< "Appending " << result << " SSL certificates from "
<< filename << "\n";
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_expected_server
// Access: Published
// Description: Adds the indicated string as a definition of a valid
// server to contact via https. If no servers have been
// been added, an https connection will be allowed to
// any server. If at least one server has been added,
// an https connection will be allowed to any of the
// named servers, but none others.
//
// The string passed in defines a subset of the server
// properties that are to be insisted on, using the X509
// naming convention, e.g. O=WDI/OU=VRStudio/CN=ttown.
//
// It makes sense to use this in conjunction with
// set_verify_ssl(), which insists that the https
// connection uses a verifiable certificate.
////////////////////////////////////////////////////////////////////
bool HTTPClient::
add_expected_server(const string &server_attributes) {
X509_NAME *name = parse_x509_name(server_attributes);
if (name == (X509_NAME *)NULL) {
return false;
}
_expected_servers.push_back(name);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::clear_expected_servers
// Access: Published
// Description: Clears the set of expected servers; the HTTPClient
// will allow an https connection to any server.
////////////////////////////////////////////////////////////////////
void HTTPClient::
clear_expected_servers() {
for (ExpectedServers::iterator ei = _expected_servers.begin();
ei != _expected_servers.end();
++ei) {
X509_NAME *name = (*ei);
X509_NAME_free(name);
}
_expected_servers.clear();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::make_channel
// Access: Published
// Description: Returns a new HTTPChannel object that may be used
// for reading multiple documents using the same
// connection, for greater network efficiency than
// calling HTTPClient::get_document() repeatedly (which
// would force a new connection for each document).
//
// Also, HTTPChannel has some additional, less common
// interface methods than the basic interface methods
// that exist on HTTPClient; if you wish to call any of
// these methods you must first obtain an HTTPChannel.
//
// Pass true for persistent_connection to gain this
// network efficiency. If, on the other hand, your
// intention is to use the channel to retrieve only one
// document, then pass false to inform the server that
// we will be dropping the connection after the first
// document.
////////////////////////////////////////////////////////////////////
PT(HTTPChannel) HTTPClient::
make_channel(bool persistent_connection) {
PT(HTTPChannel) doc = new HTTPChannel(this);
doc->set_persistent_connection(persistent_connection);
return doc;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::post_form
// Access: Published
// Description: Posts form data to a particular URL and retrieves the
// response. Returns a new HTTPChannel object whether
// the document is successfully read or not; you can
// test is_valid() and get_return_code() to determine
// whether the document was retrieved.
////////////////////////////////////////////////////////////////////
PT(HTTPChannel) HTTPClient::
post_form(const URLSpec &url, const string &body) {
PT(HTTPChannel) doc = new HTTPChannel(this);
doc->post_form(url, body);
return doc;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_document
// Access: Published
// Description: Opens the named document for reading. Returns a new
// HTTPChannel object whether the document is
// successfully read or not; you can test is_valid() and
// get_return_code() to determine whether the document
// was retrieved.
////////////////////////////////////////////////////////////////////
PT(HTTPChannel) HTTPClient::
get_document(const URLSpec &url) {
PT(HTTPChannel) doc = new HTTPChannel(this);
doc->get_document(url);
return doc;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_header
// Access: Published
// Description: Like get_document(), except only the header
// associated with the document is retrieved. This may
// be used to test for existence of the document; it
// might also return the size of the document (if the
// server gives us this information).
////////////////////////////////////////////////////////////////////
PT(HTTPChannel) HTTPClient::
get_header(const URLSpec &url) {
PT(HTTPChannel) doc = new HTTPChannel(this);
doc->get_header(url);
return doc;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_ssl_ctx
// Access: Public
// Description: Returns the OpenSSL context object, creating it first
// if needed.
////////////////////////////////////////////////////////////////////
SSL_CTX *HTTPClient::
get_ssl_ctx() {
if (_ssl_ctx != (SSL_CTX *)NULL) {
return _ssl_ctx;
}
_ssl_ctx = SSL_CTX_new(SSLv23_client_method());
#if defined(SSL_097) && !defined(NDEBUG)
// If we have debugging enabled, set a callback that allows us to
// report the SSL messages as they are sent and received.
if (downloader_cat.is_debug()) {
SSL_CTX_set_msg_callback(_ssl_ctx, ssl_msg_callback);
}
#endif
// Get the configured set of expected servers.
{
// Load in any default certificates listed in the Configrc file.
Config::ConfigTable::Symbol expected_servers;
config_downloader.GetAll("expected-ssl-server", expected_servers);
// When we use GetAll(), we might inadvertently read duplicate
// lines. Filter them out with a set.
pset<string> already_read;
Config::ConfigTable::Symbol::iterator si;
for (si = expected_servers.begin(); si != expected_servers.end(); ++si) {
string expected_server = (*si).Val();
if (already_read.insert(expected_server).second) {
add_expected_server(expected_server);
}
}
}
if (_x509_store != (X509_STORE *)NULL) {
// If we've already created an x509 store object, share it with
// this context. It would be better to make a copy of the store
// object for each context, so we could locally add certificates,
// but (a) there doesn't seem to be an interface for this, and (b)
// something funny about loading certificates that seems to save
// some persistent global state anyway.
SSL_CTX_set_cert_store(_ssl_ctx, _x509_store);
} else {
// Create the first x509 store object, and fill it up with our
// certificates.
_x509_store = X509_STORE_new();
SSL_CTX_set_cert_store(_ssl_ctx, _x509_store);
// Load in any default certificates listed in the Configrc file.
Config::ConfigTable::Symbol cert_files;
config_downloader.GetAll("ssl-certificates", cert_files);
// When we use GetAll(), we might inadvertently read duplicate
// lines. Filter them out with a set.
pset<string> already_read;
Config::ConfigTable::Symbol::iterator si;
for (si = cert_files.begin(); si != cert_files.end(); ++si) {
string cert_file = (*si).Val();
if (already_read.insert(cert_file).second) {
Filename filename = Filename::from_os_specific(ExecutionEnvironment::expand_string(cert_file));
load_certificates(filename);
}
}
}
return _ssl_ctx;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxies_for_scheme
// Access: Private
// Description: Adds the proxy servers associated with the indicated
// scheme, if any, to the list. Returns true if any
// were added, false otherwise.
////////////////////////////////////////////////////////////////////
bool HTTPClient::
get_proxies_for_scheme(const string &scheme, pvector<URLSpec> &proxies) const {
ProxiesByScheme::const_iterator si = _proxies_by_scheme.find(scheme);
if (si == _proxies_by_scheme.end()) {
return false;
}
const Proxies &scheme_proxies = (*si).second;
if (scheme_proxies.empty()) {
return false;
}
Proxies::const_iterator pi;
for (pi = scheme_proxies.begin(); pi != scheme_proxies.end(); ++pi) {
proxies.push_back(*pi);
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_http_username
// Access: Private
// Description: Handles a Configrc definition for http-username as
// server:realm:username:password, where either or both
// of server and realm may be empty, or just
// server:username:password or username:password.
////////////////////////////////////////////////////////////////////
void HTTPClient::
add_http_username(const string &http_username) {
size_t c1 = http_username.find(':');
if (c1 != string::npos) {
size_t c2 = http_username.find(':', c1 + 1);
if (c2 != string::npos) {
size_t c3 = http_username.find(':', c2 + 1);
if (c3 != string::npos) {
size_t c4 = http_username.find(':', c3 + 1);
if (c4 != string::npos) {
// Oops, we have five? Problem.
downloader_cat.error()
<< "Invalid http-username " << http_username << "\n";
} else {
// Ok, we have four.
set_username(http_username.substr(0, c1),
http_username.substr(c1 + 1, c2 - (c1 + 1)),
http_username.substr(c2 + 1));
}
} else {
// We have only three.
set_username(string(),
http_username.substr(0, c1),
http_username.substr(c1 + 1));
}
} else {
// We have only two.
set_username(string(), string(), http_username);
}
} else {
// We have only one? Problem.
downloader_cat.error()
<< "Invalid http-username " << http_username << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::select_username
// Access: Private
// Description: Chooses a suitable username:password string for the
// given URL and realm.
////////////////////////////////////////////////////////////////////
string HTTPClient::
select_username(const URLSpec &url, bool is_proxy, const string &realm) const {
string username;
// Look in several places in order to find the matching username.
// Fist, if there's a username on the URL, that always wins (except
// when we are looking for a proxy username).
if (url.has_username() && !is_proxy) {
username = url.get_username();
}
// Otherwise, start looking on the HTTPClient.
if (is_proxy) {
if (username.empty()) {
// Try the *proxy/realm.
username = get_username("*proxy", realm);
}
if (username.empty()) {
// Then, try *proxy/any realm.
username = get_username("*proxy", string());
}
}
if (username.empty()) {
// Try the specific server/realm.
username = get_username(url.get_server(), realm);
}
if (username.empty()) {
// Then, try the specific server/any realm.
username = get_username(url.get_server(), string());
}
if (username.empty()) {
// Then, try any server with this realm.
username = get_username(string(), realm);
}
if (username.empty()) {
// Then, take the general password.
username = get_username(string(), string());
}
return username;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::select_auth
// Access: Private
// Description: Chooses a suitable pre-computed authorization for the
// indicated URL. Returns NULL if no authorization
// matches.
////////////////////////////////////////////////////////////////////
HTTPAuthorization *HTTPClient::
select_auth(const URLSpec &url, bool is_proxy, const string &last_realm) {
Domains &domains = is_proxy ? _proxy_domains : _www_domains;
string canon = HTTPAuthorization::get_canonical_url(url).get_url();
// Look for the longest domain string that is a prefix of our
// canonical URL. We have to make a linear scan through the list.
Domains::const_iterator best_di = domains.end();
size_t longest_length = 0;
Domains::const_iterator di;
for (di = domains.begin(); di != domains.end(); ++di) {
const string &domain = (*di).first;
size_t length = domain.length();
if (domain == canon.substr(0, length)) {
// This domain string matches. Is it the longest?
if (length > longest_length) {
best_di = di;
longest_length = length;
}
}
}
if (best_di != domains.end()) {
// Ok, we found a matching domain. Use it.
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "Choosing domain " << (*best_di).first << " for " << url << "\n";
}
const Realms &realms = (*best_di).second._realms;
// First, try our last realm.
Realms::const_iterator ri;
ri = realms.find(last_realm);
if (ri != realms.end()) {
return (*ri).second;
}
if (!realms.empty()) {
// Oh well, just return the first realm.
return (*realms.begin()).second;
}
}
// No matching domains.
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::generate_auth
// Access: Private
// Description: Generates a new authorization entry in response to a
// 401 or 407 challenge from the server or proxy. The
// new authorization entry is stored for future
// connections to the same server (or, more precisely,
// the same domain, which may be a subset of the server,
// or it may include multiple servers).
////////////////////////////////////////////////////////////////////
PT(HTTPAuthorization) HTTPClient::
generate_auth(const URLSpec &url, bool is_proxy, const string &challenge) {
HTTPAuthorization::AuthenticationSchemes schemes;
HTTPAuthorization::parse_authentication_schemes(schemes, challenge);
PT(HTTPAuthorization) auth;
HTTPAuthorization::AuthenticationSchemes::iterator si;
si = schemes.find("digest");
if (si != schemes.end()) {
auth = new HTTPDigestAuthorization((*si).second, url, is_proxy);
}
if (auth == (HTTPAuthorization *)NULL || !auth->is_valid()) {
si = schemes.find("basic");
if (si != schemes.end()) {
auth = new HTTPBasicAuthorization((*si).second, url, is_proxy);
}
}
if (auth == (HTTPAuthorization *)NULL || !auth->is_valid()) {
downloader_cat.warning()
<< "Don't know how to use any of the server's available authorization schemes:\n";
for (si = schemes.begin(); si != schemes.end(); ++si) {
downloader_cat.warning() << (*si).first << "\n";
}
} else {
// Now that we've got an authorization, store it under under each
// of its suggested domains for future use.
Domains &domains = is_proxy ? _proxy_domains : _www_domains;
const vector_string &domain = auth->get_domain();
vector_string::const_iterator si;
for (si = domain.begin(); si != domain.end(); ++si) {
domains[(*si)]._realms[auth->get_realm()] = auth;
}
}
return auth;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::initialize_ssl
// Access: Private, Static
// Description: Called once the first time this class is used to
// initialize the OpenSSL library.
////////////////////////////////////////////////////////////////////
void HTTPClient::
initialize_ssl() {
#ifdef REPORT_OPENSSL_ERRORS
ERR_load_crypto_strings();
ERR_load_SSL_strings();
#endif
OpenSSL_add_all_algorithms();
// Call RAND_status() here to force the random number generator to
// initialize early.
if (early_random_seed) {
RAND_status();
}
_ssl_initialized = true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::load_verify_locations
// Access: Private, Static
// Description: An implementation of the OpenSSL-provided
// SSL_CTX_load_verify_locations() that takes a Filename
// (and supports Panda vfs).
//
// This reads the certificates from the named ca_file
// and makes them available to the given SSL context.
// It returns a positive number on success, or <= 0 on
// failure.
////////////////////////////////////////////////////////////////////
int HTTPClient::
load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
// First, read the complete file into memory.
string data;
if (!vfs->read_file(ca_file, data)) {
// Could not find or read file.
downloader_cat.info()
<< "Could not read " << ca_file << ".\n";
return 0;
}
STACK_OF(X509_INFO) *inf;
// Now create an in-memory BIO to read the "file" from the buffer we
// just read, and call the low-level routines to read the
// certificates from the BIO.
BIO *mbio = BIO_new_mem_buf((void *)data.data(), data.length());
// We have to be sure and clear the OpenSSL error state before we
// call this function, or it will get confused.
#ifdef REPORT_OPENSSL_ERRORS
ERR_clear_error();
#endif
inf = PEM_X509_INFO_read_bio(mbio, NULL, NULL, NULL);
BIO_free(mbio);
if (!inf) {
// Could not scan certificates.
downloader_cat.info()
<< "PEM_X509_INFO_read_bio() returned NULL.\n";
#ifdef REPORT_OPENSSL_ERRORS
ERR_print_errors_fp(stderr);
#endif
return 0;
}
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "PEM_X509_INFO_read_bio() found " << sk_X509_INFO_num(inf)
<< " entries.\n";
}
// Now add the certificates to the context.
X509_STORE *store = ctx->cert_store;
int count = 0;
int num_entries = sk_X509_INFO_num(inf);
for (int i = 0; i < num_entries; i++) {
X509_INFO *itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509) {
X509_STORE_add_cert(store, itmp->x509);
count++;
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "Entry " << i << " is x509\n";
}
} else if (itmp->crl) {
X509_STORE_add_crl(store, itmp->crl);
count++;
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "Entry " << i << " is crl\n";
}
} else {
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "Entry " << i << " is unknown type\n";
}
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
return count;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::parse_x509_name
// Access: Private, Static
// Description: Parses a string of the form
// /type0=value0/type1=value1/type2=... into a newly
// allocated X509_NAME object. Returns NULL if the
// string is invalid.
////////////////////////////////////////////////////////////////////
X509_NAME *HTTPClient::
parse_x509_name(const string &source) {
X509_NAME *result = NULL;
result = X509_NAME_new();
bool added_any = false;
string::const_iterator si;
si = source.begin();
while (si != source.end()) {
if ((*si) == '/') {
// Skip a slash delimiter.
++si;
} else {
string type;
while (si != source.end() && (*si) != '=' && (*si) != '/') {
if ((*si) == '\\') {
++si;
if (si != source.end()) {
type += (*si);
++si;
}
} else {
type += (*si);
++si;
}
}
int nid = OBJ_txt2nid((char *)type.c_str());
if (nid == NID_undef) {
downloader_cat.info()
<< "Unknown type " << type << " in X509 name: " << source
<< "\n";
X509_NAME_free(result);
return NULL;
}
string value;
if (si != source.end() && (*si) == '=') {
++si;
while (si != source.end() && (*si) != '/') {
if ((*si) == '\\') {
++si;
if (si != source.end()) {
value += (*si);
++si;
}
} else {
value += (*si);
++si;
}
}
}
if (!value.empty()) {
int add_result =
X509_NAME_add_entry_by_NID(result, nid, V_ASN1_APP_CHOOSE,
(unsigned char *)value.c_str(), -1, -1, 0);
if (!add_result) {
downloader_cat.info()
<< "Unable to add " << type << "=" << value << " in X509 name: "
<< source << "\n";
X509_NAME_free(result);
return NULL;
}
added_any = true;
}
}
}
if (!added_any) {
downloader_cat.info()
<< "Invalid empty X509 name: " << source << "\n";
X509_NAME_free(result);
return NULL;
}
return result;
}
#if defined(SSL_097) && !defined(NDEBUG)
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::ssl_msg_callback
// Access: Private, Static
// Description: This method is attached as a callback for SSL
// messages only when debug output is enabled.
////////////////////////////////////////////////////////////////////
void HTTPClient::
ssl_msg_callback(int write_p, int version, int content_type,
const void *, size_t len, SSL *, void *) {
ostringstream describe;
if (write_p) {
describe << "sent ";
} else {
describe << "received ";
}
switch (version) {
case SSL2_VERSION:
describe << "SSL 2.0 ";
break;
case SSL3_VERSION:
describe << "SSL 3.0 ";
break;
case TLS1_VERSION:
describe << "TLS 1.0 ";
break;
default:
describe << "unknown protocol ";
}
describe << "message: ";
if (version != SSL2_VERSION) {
switch (content_type) {
case 20:
describe << "change cipher spec, ";
break;
case 21:
describe << "alert, ";
break;
case 22:
describe << "handshake, ";
break;
case 23:
describe << "application data, ";
break;
default:
describe << "unknown content type, ";
}
}
describe << len << " bytes.\n";
downloader_cat.debug() << describe.str();
}
#endif // defined(SSL_097) && !defined(NDEBUG)
#endif // HAVE_SSL