diff --git a/panda/src/downloader/Sources.pp b/panda/src/downloader/Sources.pp index 2930bb52c1..609125db52 100644 --- a/panda/src/downloader/Sources.pp +++ b/panda/src/downloader/Sources.pp @@ -6,7 +6,7 @@ #begin lib_target #define TARGET downloader - #define COMBINED_SOURCES $[TARGET]_composite1.cxx \ + #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx \ $[if $[HAVE_NET], $[TARGET]_composite3.cxx] \ $[if $[HAVE_ZLIB], $[TARGET]_composite4.cxx] \ @@ -18,8 +18,12 @@ bioStream.I bioStream.h bioStreamBuf.h \ chunkedStream.I chunkedStream.h chunkedStreamBuf.h \ extractor.h \ + httpAuthorization.I httpAuthorization.h \ + httpBasicAuthorization.I httpBasicAuthorization.h \ httpClient.I httpClient.h \ httpChannel.I httpChannel.h \ + httpDigestAuthorization.I httpDigestAuthorization.h \ + httpEnum.h \ identityStream.I identityStream.h identityStreamBuf.h \ multiplexStream.I multiplexStream.h \ multiplexStreamBuf.I multiplexStreamBuf.h \ @@ -37,8 +41,12 @@ bioStream.cxx bioStreamBuf.cxx \ chunkedStream.cxx chunkedStreamBuf.cxx \ extractor.cxx \ + httpAuthorization.cxx \ + httpBasicAuthorization.cxx \ httpClient.cxx \ httpChannel.cxx \ + httpDigestAuthorization.cxx \ + httpEnum.cxx \ identityStream.cxx identityStreamBuf.cxx \ multiplexStream.cxx multiplexStreamBuf.cxx \ socketStream.cxx \ @@ -57,8 +65,12 @@ download_utils.h downloadDb.h downloadDb.I \ downloader.h downloader.I \ extractor.h \ + httpAuthorization.I httpAuthorization.h \ + httpBasicAuthorization.I httpBasicAuthorization.h \ httpClient.I httpClient.h \ httpChannel.I httpChannel.h \ + httpDigestAuthorization.I httpDigestAuthorization.h \ + httpEnum.h \ identityStream.I identityStream.h identityStreamBuf.h \ multiplexStream.I multiplexStream.h \ multiplexStreamBuf.I multiplexStreamBuf.I \ diff --git a/panda/src/downloader/bioStream.cxx b/panda/src/downloader/bioStream.cxx index 25afbcc1f1..c82b080fe7 100644 --- a/panda/src/downloader/bioStream.cxx +++ b/panda/src/downloader/bioStream.cxx @@ -25,7 +25,7 @@ // because the socket has genuinely closed, or false if // we can expect more data to come along shortly. //////////////////////////////////////////////////////////////////// -INLINE bool IBioStream:: +bool IBioStream:: is_closed() { if (_buf._is_closed) { return true; @@ -42,7 +42,7 @@ is_closed() { // false if we can expect to send more data along // shortly. //////////////////////////////////////////////////////////////////// -INLINE bool OBioStream:: +bool OBioStream:: is_closed() { if (_buf._is_closed) { return true; @@ -59,7 +59,7 @@ is_closed() { // false if we can expect to read or send more data // shortly. //////////////////////////////////////////////////////////////////// -INLINE bool BioStream:: +bool BioStream:: is_closed() { if (_buf._is_closed) { return true; diff --git a/panda/src/downloader/chunkedStream.cxx b/panda/src/downloader/chunkedStream.cxx index cfa3f1d002..e5af7ba4ea 100644 --- a/panda/src/downloader/chunkedStream.cxx +++ b/panda/src/downloader/chunkedStream.cxx @@ -28,7 +28,7 @@ // because the socket has genuinely closed, or false if // we can expect more data to come along shortly. //////////////////////////////////////////////////////////////////// -INLINE bool IChunkedStream:: +bool IChunkedStream:: is_closed() { if (_buf._done || (*_buf._source)->is_closed()) { return true; diff --git a/panda/src/downloader/config_downloader.cxx b/panda/src/downloader/config_downloader.cxx index d17d2a0451..3981ec3bac 100644 --- a/panda/src/downloader/config_downloader.cxx +++ b/panda/src/downloader/config_downloader.cxx @@ -22,7 +22,7 @@ #include "httpChannel.h" -Configure(config_downloader); +ConfigureDef(config_downloader); NotifyCategoryDef(downloader, ""); // How often we write to disk is determined by this ratio which is diff --git a/panda/src/downloader/config_downloader.h b/panda/src/downloader/config_downloader.h index ce50e06f32..25d5ba6a32 100644 --- a/panda/src/downloader/config_downloader.h +++ b/panda/src/downloader/config_downloader.h @@ -19,9 +19,11 @@ #ifndef CONFIG_DOWNLOADER_H #define CONFIG_DOWNLOADER_H -#include -#include +#include "pandabase.h" +#include "notifyCategoryProxy.h" +#include "dconfig.h" +ConfigureDecl(config_downloader, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS); NotifyCategoryDecl(downloader, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS); extern const int downloader_disk_write_frequency; diff --git a/panda/src/downloader/downloader_composite1.cxx b/panda/src/downloader/downloader_composite1.cxx index be4ada0327..7eac478e90 100644 --- a/panda/src/downloader/downloader_composite1.cxx +++ b/panda/src/downloader/downloader_composite1.cxx @@ -7,12 +7,3 @@ #include "chunkedStream.cxx" #include "chunkedStreamBuf.cxx" #include "extractor.cxx" -#include "httpClient.cxx" -#include "httpChannel.cxx" -#include "identityStream.cxx" -#include "identityStreamBuf.cxx" -#include "multiplexStream.cxx" -#include "multiplexStreamBuf.cxx" -#include "socketStream.cxx" -#include "urlSpec.cxx" - diff --git a/panda/src/downloader/downloader_composite2.cxx b/panda/src/downloader/downloader_composite2.cxx new file mode 100644 index 0000000000..5a92b86729 --- /dev/null +++ b/panda/src/downloader/downloader_composite2.cxx @@ -0,0 +1,12 @@ +#include "httpAuthorization.cxx" +#include "httpBasicAuthorization.cxx" +#include "httpClient.cxx" +#include "httpChannel.cxx" +#include "httpDigestAuthorization.cxx" +#include "httpEnum.cxx" +#include "identityStream.cxx" +#include "identityStreamBuf.cxx" +#include "multiplexStream.cxx" +#include "multiplexStreamBuf.cxx" +#include "socketStream.cxx" +#include "urlSpec.cxx" diff --git a/panda/src/downloader/httpAuthorization.I b/panda/src/downloader/httpAuthorization.I new file mode 100644 index 0000000000..a6f271c444 --- /dev/null +++ b/panda/src/downloader/httpAuthorization.I @@ -0,0 +1,44 @@ +// Filename: httpAuthorization.I +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::get_realm +// Access: Public +// Description: Returns the realm to which this authorization +// applies. This is the server-supplied string that may +// have meaning to the user, and describes the general +// collection of things protected by this password. +//////////////////////////////////////////////////////////////////// +const string &HTTPAuthorization:: +get_realm() const { + return _realm; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::get_domain +// Access: Public +// Description: Returns the set of domain strings on which this +// authorization applies. This is the set of URL +// prefixes for which this authorization should be +// used. +//////////////////////////////////////////////////////////////////// +const vector_string &HTTPAuthorization:: +get_domain() const { + return _domain; +} diff --git a/panda/src/downloader/httpAuthorization.cxx b/panda/src/downloader/httpAuthorization.cxx new file mode 100644 index 0000000000..deee43afe2 --- /dev/null +++ b/panda/src/downloader/httpAuthorization.cxx @@ -0,0 +1,296 @@ +// Filename: httpAuthorization.cxx +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "httpAuthorization.h" +#include "httpChannel.h" +#include "urlSpec.h" + +#ifdef HAVE_SSL + +static const char base64_table[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +HTTPAuthorization:: +HTTPAuthorization(const HTTPAuthorization::Tokens &tokens, + const URLSpec &url, bool is_proxy) { + Tokens::const_iterator ti; + ti = tokens.find("realm"); + if (ti != tokens.end()) { + _realm = (*ti).second; + } + + URLSpec canon = get_canonical_url(url); + + ti = tokens.find("domain"); + if (ti != tokens.end() && !is_proxy) { + // Now the domain consists of a series of space-separated URL + // prefixes. + const string &domain = (*ti).second; + size_t p = 0; + while (p < domain.length()) { + while (p < domain.length() && isspace(domain[p])) { + ++p; + } + size_t q = p; + while (q < domain.length() && !isspace(domain[q])) { + ++q; + } + if (q > p) { + string domain_str = domain.substr(p, q - p); + URLSpec domain_url(domain_str); + if (domain_url.has_server()) { + // A fully-qualified URL. + _domain.push_back(get_canonical_url(domain_url).get_url()); + } else { + // A relative URL; relative to this path. + domain_url = canon; + domain_url.set_path(domain_str); + _domain.push_back(domain_url.get_url()); + } + } + p = q; + } + + } else { + // If no domain is defined by the server, use the supplied URL. + // Truncate it to the rightmost slash. + string canon_str = canon.get_url(); + size_t slash = canon_str.rfind('/'); + nassertv(slash != string::npos); + _domain.push_back(canon_str.substr(0, slash + 1)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +HTTPAuthorization:: +~HTTPAuthorization() { +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::is_valid +// Access: Public, Virtual +// Description: Returns true if the authorization challenge was +// correctly parsed and is usable, or false if there was +// some unsupported algorithm or some such requested by +// the server, rendering the challenge unmeetable. +//////////////////////////////////////////////////////////////////// +bool HTTPAuthorization:: +is_valid() { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::parse_authentication_schemes +// Access: Public, Static +// Description: Decodes the text following a WWW-Authenticate: or +// Proxy-Authenticate: header field. +//////////////////////////////////////////////////////////////////// +void HTTPAuthorization:: +parse_authentication_schemes(HTTPAuthorization::AuthenticationSchemes &schemes, + const string &field_value) { + // This string will consist of one or more records of the form: + // + // scheme token=value[,token=value[,...]] + // + // If there are multiple records, they will be comma-delimited, + // which makes parsing just a bit tricky. + + // Start by skipping initial whitespace. + size_t p = 0; + while (p < field_value.length() && isspace(field_value[p])) { + ++p; + } + + if (p < field_value.length()) { + size_t q = p; + while (q < field_value.length() && !isspace(field_value[q])) { + ++q; + } + // Here's our first scheme. + string scheme = HTTPChannel::downcase(field_value.substr(p, q - p)); + Tokens *tokens = &(schemes[scheme]); + + // Now pull off the tokens, one at a time. + p = q + 1; + while (p < field_value.length()) { + q = p; + while (q < field_value.length() && field_value[q] != '=' && + field_value[q] != ',' && !isspace(field_value[q])) { + ++q; + } + if (field_value[q] == '=') { + // This is a token. + string token = HTTPChannel::downcase(field_value.substr(p, q - p)); + string value; + p = scan_quoted_or_unquoted_string(value, field_value, q + 1); + (*tokens)[token] = value; + + // Skip trailing whitespace and extra commas. + while (p < field_value.length() && + (field_value[p] == ',' || isspace(field_value[p]))) { + ++p; + } + + } else { + // This is not a token; it must be the start of a new scheme. + scheme = HTTPChannel::downcase(field_value.substr(p, q - p)); + tokens = &(schemes[scheme]); + p = q + 1; + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::get_canonical_url +// Access: Public, Static +// Description: Returns the "canonical" URL corresponding to this +// URL. This is the same URL with an explicit port +// indication, an explicit scheme, and a non-empty path, +// etc. +//////////////////////////////////////////////////////////////////// +URLSpec HTTPAuthorization:: +get_canonical_url(const URLSpec &url) { + URLSpec canon = url; + canon.set_scheme(canon.get_scheme()); + canon.set_username(string()); + canon.set_port(canon.get_port()); + canon.set_path(canon.get_path()); + + return canon; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::base64_encode +// Access: Public, Static +// Description: Returns the input string encoded using base64. No +// respect is paid to maintaining a 76-char line length. +//////////////////////////////////////////////////////////////////// +string HTTPAuthorization:: +base64_encode(const string &s) { + // Collect the string 3 bytes at a time into 24-bit words, then + // output each word using 4 bytes. + size_t num_words = (s.size() + 2) / 3; + string result; + result.reserve(num_words * 4); + size_t p; + for (p = 0; p + 2 < s.size(); p += 3) { + unsigned int word = + ((unsigned)s[p] << 16) | + ((unsigned)s[p + 1] << 8) | + ((unsigned)s[p + 2]); + result += base64_table[(word >> 18) & 0x3f]; + result += base64_table[(word >> 12) & 0x3f]; + result += base64_table[(word >> 6) & 0x3f]; + result += base64_table[(word) & 0x3f]; + } + // What's left over? + if (p < s.size()) { + unsigned int word = ((unsigned)s[p] << 16); + p++; + if (p < s.size()) { + word |= ((unsigned)s[p] << 8); + p++; + nassertr(p == s.size(), result); + + result += base64_table[(word >> 18) & 0x3f]; + result += base64_table[(word >> 12) & 0x3f]; + result += base64_table[(word >> 6) & 0x3f]; + result += '='; + } else { + result += base64_table[(word >> 18) & 0x3f]; + result += base64_table[(word >> 12) & 0x3f]; + result += '='; + result += '='; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPAuthorization::scan_quoted_or_unquoted_string +// Access: Protected, Static +// Description: Scans the string source beginning at character +// position start, to identify either the +// (space-delimited) unquoted string there, or the +// (quote-delimited) quoted string. In either case, +// fills the string found into result, and returns the +// next character position after the string (or after +// its closing quote mark). +//////////////////////////////////////////////////////////////////// +size_t HTTPAuthorization:: +scan_quoted_or_unquoted_string(string &result, const string &source, + size_t start) { + result = string(); + + if (start < source.length()) { + if (source[start] == '"') { + // Quoted string. + size_t p = start + 1; + while (p < source.length() && source[p] != '"') { + if (source[p] == '\\') { + // Backslash escapes. + ++p; + if (p < source.length()) { + result += source[p]; + ++p; + } + } else { + result += source[p]; + ++p; + } + } + if (p < source.length()) { + ++p; + } + return p; + } + + // Unquoted string. + size_t p = start; + while (p < source.length() && source[p] != ',' && !isspace(source[p])) { + result += source[p]; + ++p; + } + + return p; + } + + // Empty string. + return start; +} + +#endif // HAVE_SSL diff --git a/panda/src/downloader/httpAuthorization.h b/panda/src/downloader/httpAuthorization.h new file mode 100644 index 0000000000..64158af626 --- /dev/null +++ b/panda/src/downloader/httpAuthorization.h @@ -0,0 +1,86 @@ +// Filename: httpAuthorization.h +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef HTTPAUTHORIZATION_H +#define HTTPAUTHORIZATION_H + +#include "pandabase.h" + +// This module requires OpenSSL to compile, even though it doesn't +// actually use any OpenSSL code, because it is a support module for +// HTTPChannel, which *does* use OpenSSL code. + +#ifdef HAVE_SSL + +#include "referenceCount.h" +#include "httpEnum.h" + +class URLSpec; + +//////////////////////////////////////////////////////////////////// +// Class : HTTPAuthorization +// Description : A base class for storing information used to fulfill +// authorization requests in the past, which can +// possibly be re-used for future requests to the same +// server. +// +// This class does not need to be exported from the DLL +// because it has no public interface; it is simply a +// helper class for HTTPChannel. +//////////////////////////////////////////////////////////////////// +class HTTPAuthorization : public ReferenceCount { +public: + typedef pmap Tokens; + typedef pmap AuthenticationSchemes; + +protected: + HTTPAuthorization(const Tokens &tokens, const URLSpec &url, + bool is_proxy); +public: + virtual ~HTTPAuthorization(); + + virtual const string &get_mechanism() const=0; + virtual bool is_valid(); + + INLINE const string &get_realm() const; + INLINE const vector_string &get_domain() const; + + virtual string generate(HTTPEnum::Method method, const string &request_path, + const string &username, const string &body)=0; + + static void parse_authentication_schemes(AuthenticationSchemes &schemes, + const string &field_value); + static URLSpec get_canonical_url(const URLSpec &url); + static string base64_encode(const string &s); + +protected: + static size_t scan_quoted_or_unquoted_string(string &result, + const string &source, + size_t start); + +protected: + string _realm; + vector_string _domain; +}; + +#include "httpAuthorization.I" + +#endif // HAVE_SSL + +#endif + diff --git a/panda/src/downloader/httpBasicAuthorization.I b/panda/src/downloader/httpBasicAuthorization.I new file mode 100644 index 0000000000..8171ba8f1d --- /dev/null +++ b/panda/src/downloader/httpBasicAuthorization.I @@ -0,0 +1,17 @@ +// Filename: httpBasicAuthorization.I +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// diff --git a/panda/src/downloader/httpBasicAuthorization.cxx b/panda/src/downloader/httpBasicAuthorization.cxx new file mode 100644 index 0000000000..ae000f53c6 --- /dev/null +++ b/panda/src/downloader/httpBasicAuthorization.cxx @@ -0,0 +1,71 @@ +// Filename: httpBasicAuthorization.cxx +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "httpBasicAuthorization.h" + +#ifdef HAVE_SSL + +const string HTTPBasicAuthorization::_mechanism = "basic"; + +//////////////////////////////////////////////////////////////////// +// Function: HTTPBasicAuthorization::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +HTTPBasicAuthorization:: +HTTPBasicAuthorization(const HTTPAuthorization::Tokens &tokens, + const URLSpec &url, bool is_proxy) : + HTTPAuthorization(tokens, url, is_proxy) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPBasicAuthorization::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +HTTPBasicAuthorization:: +~HTTPBasicAuthorization() { +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPBasicAuthorization::get_mechanism +// Access: Public, Virtual +// Description: Returns the type of authorization mechanism, +// represented as a string, e.g. "basic". +//////////////////////////////////////////////////////////////////// +const string &HTTPBasicAuthorization:: +get_mechanism() const { + return _mechanism; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPBasicAuthorization::generate +// Access: Public, Virtual +// Description: Generates a suitable authorization string to send +// to the server, based on the data stored within this +// object, for retrieving the indicated URL with the +// given username:password. +//////////////////////////////////////////////////////////////////// +string HTTPBasicAuthorization:: +generate(HTTPEnum::Method, const string &, + const string &username, const string &) { + return "Basic " + base64_encode(username); +} + +#endif // HAVE_SSL diff --git a/panda/src/downloader/httpBasicAuthorization.h b/panda/src/downloader/httpBasicAuthorization.h new file mode 100644 index 0000000000..c8aeb56de8 --- /dev/null +++ b/panda/src/downloader/httpBasicAuthorization.h @@ -0,0 +1,59 @@ +// Filename: httpBasicAuthorization.h +// Created by: drose (22Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef HTTPBASICAUTHORIZATION_H +#define HTTPBASICAUTHORIZATION_H + +#include "pandabase.h" + +// This module requires OpenSSL to compile, even though it doesn't +// actually use any OpenSSL code, because it is a support module for +// HTTPChannel, which *does* use OpenSSL code. + +#ifdef HAVE_SSL + +#include "httpAuthorization.h" + +//////////////////////////////////////////////////////////////////// +// Class : HTTPBasicAuthorization +// Description : Implements the "Basic" type of HTTP authorization. +// This authorization sends usernames and passwords over +// the net in cleartext, so it's not much in the way of +// security, but it's easy to implement and therefore +// widely supported. +//////////////////////////////////////////////////////////////////// +class HTTPBasicAuthorization : public HTTPAuthorization { +public: + HTTPBasicAuthorization(const Tokens &tokens, const URLSpec &url, + bool is_proxy); + virtual ~HTTPBasicAuthorization(); + + virtual const string &get_mechanism() const; + virtual string generate(HTTPEnum::Method method, const string &request_path, + const string &username, const string &body); + +private: + static const string _mechanism; +}; + +#include "httpBasicAuthorization.I" + +#endif // HAVE_SSL + +#endif + diff --git a/panda/src/downloader/httpChannel.I b/panda/src/downloader/httpChannel.I index 13315d95bf..b0b6d9c499 100644 --- a/panda/src/downloader/httpChannel.I +++ b/panda/src/downloader/httpChannel.I @@ -27,7 +27,7 @@ INLINE bool HTTPChannel:: is_valid() const { return (_state != S_failure && (_status_code / 100) == 2 && - (has_no_body() || !_source.is_null())); + (_server_response_has_no_body || !_source.is_null())); } //////////////////////////////////////////////////////////////////// @@ -64,7 +64,7 @@ get_url() const { // server, as one of the HTTPClient enumerated types, // e.g. HTTPClient::HV_11. //////////////////////////////////////////////////////////////////// -INLINE HTTPClient::HTTPVersion HTTPChannel:: +INLINE HTTPEnum::HTTPVersion HTTPChannel:: get_http_version() const { return _http_version; } @@ -113,26 +113,34 @@ get_status_string() const { } //////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::get_realm +// Function: HTTPChannel::get_www_realm // Access: Published // Description: If the document failed to connect because of a 401 -// (Authorization required) or 407 (Proxy authorization -// required) error, this method should return the +// (Authorization required), this method will return the // "realm" returned by the server in which the requested // document must be authenticated. This string may be // presented to the user to request an associated // username and password (which then should be stored in // HTTPClient::set_username()). -// -// If the document was retrieved successfully, or failed -// to connect for some other reason, this string will be -// empty. If it is empty even in the presence of a 401 -// or 407 error, then the client code and the server do -// not have a common scheme for exchanging passwords. //////////////////////////////////////////////////////////////////// INLINE const string &HTTPChannel:: -get_realm() const { - return _realm; +get_www_realm() const { + return _www_realm; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::get_proxy_realm +// Access: Published +// Description: If the document failed to connect because of a 407 +// (Proxy authorization required), this method will +// return the "realm" returned by the proxy. This +// string may be presented to the user to request an +// associated username and password (which then should +// be stored in HTTPClient::set_username()). +//////////////////////////////////////////////////////////////////// +INLINE const string &HTTPChannel:: +get_proxy_realm() const { + return _proxy_realm; } //////////////////////////////////////////////////////////////////// @@ -383,7 +391,7 @@ reset() { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: post_form(const URLSpec &url, const string &body) { - begin_request(M_post, url, body, false, 0, 0); + begin_request(HTTPEnum::M_post, url, body, false, 0, 0); run(); return is_valid(); } @@ -396,7 +404,7 @@ post_form(const URLSpec &url, const string &body) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_document(const URLSpec &url) { - begin_request(M_get, url, string(), false, 0, 0); + begin_request(HTTPEnum::M_get, url, string(), false, 0, 0); run(); return is_valid(); } @@ -413,7 +421,7 @@ get_document(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { - begin_request(M_get, url, string(), false, first_byte, last_byte); + begin_request(HTTPEnum::M_get, url, string(), false, first_byte, last_byte); run(); return is_valid(); } @@ -429,7 +437,7 @@ get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: get_header(const URLSpec &url) { - begin_request(M_head, url, string(), false, 0, 0); + begin_request(HTTPEnum::M_head, url, string(), false, 0, 0); run(); return is_valid(); } @@ -448,7 +456,7 @@ get_header(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE bool HTTPChannel:: connect_to(const URLSpec &url) { - begin_request(M_connect, url, string(), false, 0, 0); + begin_request(HTTPEnum::M_connect, url, string(), false, 0, 0); run(); return is_connection_ready(); } @@ -469,7 +477,7 @@ connect_to(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_post_form(const URLSpec &url, const string &body) { - begin_request(M_post, url, body, true, 0, 0); + begin_request(HTTPEnum::M_post, url, body, true, 0, 0); } //////////////////////////////////////////////////////////////////// @@ -488,7 +496,7 @@ begin_post_form(const URLSpec &url, const string &body) { //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_get_document(const URLSpec &url) { - begin_request(M_get, url, string(), true, 0, 0); + begin_request(HTTPEnum::M_get, url, string(), true, 0, 0); } //////////////////////////////////////////////////////////////////// @@ -505,7 +513,7 @@ begin_get_document(const URLSpec &url) { INLINE void HTTPChannel:: begin_get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) { - begin_request(M_get, url, string(), true, first_byte, last_byte); + begin_request(HTTPEnum::M_get, url, string(), true, first_byte, last_byte); } //////////////////////////////////////////////////////////////////// @@ -516,7 +524,7 @@ begin_get_subdocument(const URLSpec &url, size_t first_byte, //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_get_header(const URLSpec &url) { - begin_request(M_head, url, string(), true, 0, 0); + begin_request(HTTPEnum::M_head, url, string(), true, 0, 0); } //////////////////////////////////////////////////////////////////// @@ -539,7 +547,7 @@ begin_get_header(const URLSpec &url) { //////////////////////////////////////////////////////////////////// INLINE void HTTPChannel:: begin_connect_to(const URLSpec &url) { - begin_request(M_connect, url, string(), true, 0, 0); + begin_request(HTTPEnum::M_connect, url, string(), true, 0, 0); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/downloader/httpChannel.cxx b/panda/src/downloader/httpChannel.cxx index 6de7749b8d..548e273514 100644 --- a/panda/src/downloader/httpChannel.cxx +++ b/panda/src/downloader/httpChannel.cxx @@ -24,22 +24,12 @@ #include "config_downloader.h" #include "clockObject.h" #include "buffer.h" // for Ramfile +#include #ifdef HAVE_SSL TypeHandle HTTPChannel::_type_handle; -static const char base64_table[64] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/', -}; - //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::Constructor // Access: Private @@ -138,7 +128,7 @@ is_regular_file() const { //////////////////////////////////////////////////////////////////// bool HTTPChannel:: will_close_connection() const { - if (get_http_version() < HTTPClient::HV_11) { + if (get_http_version() < HTTPEnum::HV_11) { // pre-HTTP 1.1 always closes. return true; } @@ -153,22 +143,6 @@ will_close_connection() const { return false; } -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::has_no_body -// Access: Public -// Description: Returns true if the nature of the request is such -// that there is no associated body to read, false if -// there should be a body (even if that body might be -// empty). -//////////////////////////////////////////////////////////////////// -bool HTTPChannel:: -has_no_body() const { - return (get_status_code() / 100 == 1 || - get_status_code() == 204 || - get_status_code() == 304 || - _method == M_head); -} - //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::open_read_file // Access: Public, Virtual @@ -569,6 +543,23 @@ get_connection() { return stream; } +//////////////////////////////////////////////////////////////////// +// Function: HTTPChannel::downcase +// Access: Public, Static +// Description: Returns the input string with all uppercase letters +// converted to lowercase. +//////////////////////////////////////////////////////////////////// +string HTTPChannel:: +downcase(const string &s) { + string result; + result.reserve(s.size()); + string::const_iterator p; + for (p = s.begin(); p != s.end(); ++p) { + result += tolower(*p); + } + return result; +} + //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::reached_done_state // Access: Private @@ -792,14 +783,23 @@ run_proxy_reading_header() { return true; } - if (get_status_code() == 407 && !_proxy.empty()) { + _server_response_has_no_body = + (get_status_code() / 100 == 1 || + get_status_code() == 204 || + get_status_code() == 304); + + int last_status = _last_status_code; + _last_status_code = get_status_code(); + + if (get_status_code() == 407 && last_status != 407 && !_proxy.empty()) { // 407: not authorized to proxy. Try to get the authorization. string authenticate_request = get_header_value("Proxy-Authenticate"); - string authorization; - if (get_authorization(authorization, authenticate_request, _proxy, true)) { - if (_client->_proxy_authorization != authorization) { - // Change the authorization. - _client->_proxy_authorization = authorization; + _proxy_auth = + _client->generate_auth(_proxy, true, authenticate_request); + if (_proxy_auth != (HTTPAuthorization *)NULL) { + _proxy_realm = _proxy_auth->get_realm(); + _proxy_username = _client->select_username(_proxy, false, _proxy_realm); + if (!_proxy_username.empty()) { make_proxy_request_text(); // Roll the state forward to force a new request. @@ -825,7 +825,7 @@ run_proxy_reading_header() { // Now we have a tunnel opened through the proxy. _proxy_tunnel = true; - make_request_text(string()); + make_request_text(); _state = _want_ssl ? S_setup_ssl : S_ready; return false; @@ -1039,7 +1039,11 @@ run_reading_header() { return true; } - _realm = string(); + _server_response_has_no_body = + (get_status_code() / 100 == 1 || + get_status_code() == 204 || + get_status_code() == 304 || + _method == HTTPEnum::M_head); // Look for key properties in the header fields. if (get_status_code() == 206) { @@ -1063,7 +1067,7 @@ run_reading_header() { _state = S_read_header; - if (has_no_body() && will_close_connection()) { + if (_server_response_has_no_body && will_close_connection()) { // If the server said it will close the connection, we should // close it too. close_connection(); @@ -1076,12 +1080,13 @@ run_reading_header() { if (get_status_code() == 407 && last_status != 407 && !_proxy.empty()) { // 407: not authorized to proxy. Try to get the authorization. string authenticate_request = get_header_value("Proxy-Authenticate"); - string authorization; - if (get_authorization(authorization, authenticate_request, _proxy, true)) { - if (_client->_proxy_authorization != authorization) { - // Change the authorization. - _client->_proxy_authorization = authorization; - make_request_text(string()); + _proxy_auth = + _client->generate_auth(_proxy, true, authenticate_request); + if (_proxy_auth != (HTTPAuthorization *)NULL) { + _proxy_realm = _proxy_auth->get_realm(); + _proxy_username = _client->select_username(_proxy, false, _proxy_realm); + if (!_proxy_username.empty()) { + make_request_text(); // Roll the state forward to force a new request. _state = S_begin_body; @@ -1093,19 +1098,24 @@ run_reading_header() { if (get_status_code() == 401 && last_status != 401) { // 401: not authorized to remote server. Try to get the authorization. string authenticate_request = get_header_value("WWW-Authenticate"); - string authorization; - if (get_authorization(authorization, authenticate_request, _url, false)) { - make_request_text(authorization); + _www_auth = _client->generate_auth(_url, false, authenticate_request); + if (_www_auth != (HTTPAuthorization *)NULL) { + _www_realm = _www_auth->get_realm(); + _www_username = _client->select_username(_url, false, _www_realm); + if (!_www_username.empty()) { + make_request_text(); - // Roll the state forward to force a new request. - _state = S_begin_body; - return false; + // Roll the state forward to force a new request. + _state = S_begin_body; + return false; + } } } if ((get_status_code() / 100) == 3 && get_status_code() != 305) { // Redirect. Should we handle it automatically? - if (!get_redirect().empty() && (_method == M_get || _method == M_head)) { + if (!get_redirect().empty() && (_method == HTTPEnum::M_get || + _method == HTTPEnum::M_head)) { // Sure! URLSpec new_url = get_redirect(); if (!_redirect_trail.insert(new_url).second) { @@ -1122,7 +1132,7 @@ run_reading_header() { } set_url(new_url); make_header(); - make_request_text(string()); + make_request_text(); // Roll the state forward to force a new request. _state = S_begin_body; @@ -1173,9 +1183,9 @@ run_begin_body() { return false; } - if (has_no_body()) { + if (_server_response_has_no_body) { // We have already "read" the nonexistent body. - _state = S_ready; + _state = S_read_trailer; } else if (_file_size > 8192) { // If we know the size of the body we are about to skip and it's @@ -1406,7 +1416,7 @@ run_download_to_ram() { // necessary. //////////////////////////////////////////////////////////////////// void HTTPChannel:: -begin_request(HTTPChannel::Method method, const URLSpec &url, +begin_request(HTTPEnum::Method method, const URLSpec &url, const string &body, bool nonblocking, size_t first_byte, size_t last_byte) { reset_for_new_request(); @@ -1440,9 +1450,9 @@ begin_request(HTTPChannel::Method method, const URLSpec &url, _last_byte = last_byte; make_header(); - make_request_text(string()); + make_request_text(); - if (!_proxy.empty() && (_want_ssl || _method == M_connect)) { + if (!_proxy.empty() && (_want_ssl || _method == HTTPEnum::M_connect)) { // Maybe we need to tunnel through the proxy to connect to the // server directly. We need this for HTTPS, or if the user // requested a direct connection somewhere. @@ -1450,7 +1460,7 @@ begin_request(HTTPChannel::Method method, const URLSpec &url, request << "CONNECT " << _url.get_server() << ":" << _url.get_port() << " " << _client->get_http_version_string() << "\r\n"; - if (_client->get_http_version() >= HTTPClient::HV_11) { + if (_client->get_http_version() >= HTTPEnum::HV_11) { request << "Host: " << _url.get_server() << "\r\n"; } @@ -1472,7 +1482,7 @@ begin_request(HTTPChannel::Method method, const URLSpec &url, _state = S_begin_body; } - _done_state = (_method == M_connect) ? S_ready : S_read_header; + _done_state = (_method == HTTPEnum::M_connect) ? S_ready : S_read_header; } //////////////////////////////////////////////////////////////////// @@ -2085,7 +2095,14 @@ x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) { //////////////////////////////////////////////////////////////////// void HTTPChannel:: make_header() { - if (_method == M_connect) { + _proxy_auth = _client->select_auth(_proxy, true, _proxy_realm); + _proxy_username = string(); + if (_proxy_auth != (HTTPAuthorization *)NULL) { + _proxy_realm = _proxy_auth->get_realm(); + _proxy_username = _client->select_username(_proxy, true, _proxy_realm); + } + + if (_method == HTTPEnum::M_connect) { // This method doesn't require an HTTP header at all; we'll just // open a plain connection. (Except when we're using a proxy; but // in that case, it's the proxy_header we'll need, not the regular @@ -2094,45 +2111,34 @@ make_header() { return; } - string path; + _www_auth = _client->select_auth(_url, false, _www_realm); + _www_username = string(); + if (_www_auth != (HTTPAuthorization *)NULL) { + _www_realm = _www_auth->get_realm(); + _www_username = _client->select_username(_url, false, _www_realm); + } + + string request_path; if (_proxy_serves_document) { // If we'll be asking the proxy for the document, we need its full // URL--but we omit the username, which is information just for us. URLSpec url_no_username = _url; url_no_username.set_username(string()); - path = url_no_username.get_url(); + request_path = url_no_username.get_url(); } else { // If we'll be asking the server directly for the document, we // just want its path relative to the server. - path = _url.get_path(); + request_path = _url.get_path(); } ostringstream stream; - switch (_method) { - case M_get: - stream << "GET"; - break; - - case M_head: - stream << "HEAD"; - break; - - case M_post: - stream << "POST"; - break; - - case M_connect: - stream << "CONNECT"; - break; - } - stream - << " " << path << " " + << _method << " " << request_path << " " << _client->get_http_version_string() << "\r\n"; - if (_client->get_http_version() >= HTTPClient::HV_11) { + if (_client->get_http_version() >= HTTPEnum::HV_11) { stream << "Host: " << _url.get_server() << "\r\n"; if (!get_persistent_connection()) { @@ -2172,9 +2178,14 @@ void HTTPChannel:: make_proxy_request_text() { _proxy_request_text = _proxy_header; - if (!_client->_proxy_authorization.empty()) { + if (_proxy_auth != (HTTPAuthorization *)NULL && !_proxy_username.empty()) { + ostringstream strm; + strm << _url.get_server() << ":" << _url.get_port(); + _proxy_request_text += "Proxy-Authorization: "; - _proxy_request_text += _client->_proxy_authorization; + _proxy_request_text += + _proxy_auth->generate(HTTPEnum::M_connect, strm.str(), + _proxy_username, _body); _proxy_request_text += "\r\n"; } @@ -2189,19 +2200,22 @@ make_proxy_request_text() { // pass, based on the current header and body. //////////////////////////////////////////////////////////////////// void HTTPChannel:: -make_request_text(const string &authorization) { +make_request_text() { _request_text = _header; - if (!_proxy.empty() && !_client->_proxy_authorization.empty() && - !_proxy_tunnel) { + if (!_proxy.empty() && + _proxy_auth != (HTTPAuthorization *)NULL && !_proxy_username.empty()) { _request_text += "Proxy-Authorization: "; - _request_text += _client->_proxy_authorization; + _request_text += + _proxy_auth->generate(_method, _url.get_url(), _proxy_username, _body); _request_text += "\r\n"; } - if (!authorization.empty()) { + if (_www_auth != (HTTPAuthorization *)NULL && !_www_username.empty()) { + string authorization = _request_text += "Authorization: "; - _request_text += authorization; + _request_text += + _www_auth->generate(_method, _url.get_path(), _www_username, _body); _request_text += "\r\n"; } @@ -2252,153 +2266,6 @@ store_header_field(const string &field_name, const string &field_value) { } } -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::get_authorization -// Access: Private -// Description: Looks for a username:password to satisfy the given -// authenticate_request string from the server or proxy. -// If found, fills in authorization and returns true; -// otherwise, returns false. -//////////////////////////////////////////////////////////////////// -bool HTTPChannel:: -get_authorization(string &authorization, const string &authenticate_request, - const URLSpec &url, bool is_proxy) { - AuthenticationSchemes schemes; - parse_authentication_schemes(schemes, authenticate_request); - - AuthenticationSchemes::iterator si; - si = schemes.find("basic"); - if (si != schemes.end()) { - return get_basic_authorization(authorization, (*si).second, url, is_proxy); - } - - 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"; - } - - return false; -} - -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::downcase -// Access: Private, Static -// Description: Returns the input string with all uppercase letters -// converted to lowercase. -//////////////////////////////////////////////////////////////////// -string HTTPChannel:: -downcase(const string &s) { - string result; - result.reserve(s.size()); - string::const_iterator p; - for (p = s.begin(); p != s.end(); ++p) { - result += tolower(*p); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::base64_encode -// Access: Private, Static -// Description: Returns the input string encoded using base64. No -// respect is paid to maintaining a 76-char line length. -//////////////////////////////////////////////////////////////////// -string HTTPChannel:: -base64_encode(const string &s) { - // Collect the string 3 bytes at a time into 24-bit words, then - // output each word using 4 bytes. - size_t num_words = (s.size() + 2) / 3; - string result; - result.reserve(num_words * 4); - size_t p; - for (p = 0; p + 2 < s.size(); p += 3) { - unsigned int word = - ((unsigned)s[p] << 16) | - ((unsigned)s[p + 1] << 8) | - ((unsigned)s[p + 2]); - result += base64_table[(word >> 18) & 0x3f]; - result += base64_table[(word >> 12) & 0x3f]; - result += base64_table[(word >> 6) & 0x3f]; - result += base64_table[(word) & 0x3f]; - } - // What's left over? - if (p < s.size()) { - unsigned int word = ((unsigned)s[p] << 16); - p++; - if (p < s.size()) { - word |= ((unsigned)s[p] << 8); - p++; - nassertr(p == s.size(), result); - - result += base64_table[(word >> 18) & 0x3f]; - result += base64_table[(word >> 12) & 0x3f]; - result += base64_table[(word >> 6) & 0x3f]; - result += '='; - } else { - result += base64_table[(word >> 18) & 0x3f]; - result += base64_table[(word >> 12) & 0x3f]; - result += '='; - result += '='; - } - } - - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::scan_quoted_or_unquoted_string -// Access: Private, Static -// Description: Scans the string source beginning at character -// position start, to identify either the -// (spaced-delimited) unquoted string there, or the -// (quote-delimited) quoted string. In either case, -// fills the string found into result, and returns the -// next character position after the string (or after -// its closing quote mark). -//////////////////////////////////////////////////////////////////// -size_t HTTPChannel:: -scan_quoted_or_unquoted_string(string &result, const string &source, - size_t start) { - result = string(); - - if (start < source.length()) { - if (source[start] == '"') { - // Quoted string. - size_t p = start + 1; - while (p < source.length() && source[p] != '"') { - if (source[p] == '\\') { - // Backslash escapes. - ++p; - if (p < source.length()) { - result += source[p]; - ++p; - } - } else { - result += source[p]; - ++p; - } - } - if (p < source.length()) { - ++p; - } - return p; - } - - // Unquoted string. - size_t p = start; - while (p < source.length() && source[p] != ',' && !isspace(source[p])) { - result += source[p]; - ++p; - } - - return p; - } - - // Empty string. - return start; -} - #ifndef NDEBUG //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::show_send @@ -2425,68 +2292,6 @@ show_send(const string &message) { } #endif // NDEBUG -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::parse_authentication_schemes -// Access: Private, Static -// Description: Decodes the text following a WWW-Authenticate: or -// Proxy-Authenticate: header field. -//////////////////////////////////////////////////////////////////// -void HTTPChannel:: -parse_authentication_schemes(HTTPChannel::AuthenticationSchemes &schemes, - const string &field_value) { - // This string will consist of one or more records of the form: - // - // scheme token=value[,token=value[,...]] - // - // If there are multiple records, they will be comma-delimited, - // which makes parsing just a bit tricky. - - // Start by skipping initial whitespace. - size_t p = 0; - while (p < field_value.length() && isspace(field_value[p])) { - ++p; - } - - if (p < field_value.length()) { - size_t q = p; - while (q < field_value.length() && !isspace(field_value[q])) { - ++q; - } - // Here's our first scheme. - string scheme = downcase(field_value.substr(p, q - p)); - Tokens *tokens = &(schemes[scheme]); - - // Now pull off the tokens, one at a time. - p = q + 1; - while (p < field_value.length()) { - q = p; - while (q < field_value.length() && field_value[q] != '=' && - field_value[q] != ',' && !isspace(field_value[q])) { - ++q; - } - if (field_value[q] == '=') { - // This is a token. - string token = downcase(field_value.substr(p, q - p)); - string value; - p = scan_quoted_or_unquoted_string(value, field_value, q + 1); - (*tokens)[token] = value; - - // Skip trailing whitespace and extra commas. - while (p < field_value.length() && - (field_value[p] == ',' || isspace(field_value[p]))) { - ++p; - } - - } else { - // This is not a token; it must be the start of a new scheme. - scheme = downcase(field_value.substr(p, q - p)); - tokens = &(schemes[scheme]); - p = q + 1; - } - } - } -} - //////////////////////////////////////////////////////////////////// // Function: HTTPChannel::reset_download_to // Access: Private @@ -2533,66 +2338,4 @@ close_connection() { _read_index++; } -//////////////////////////////////////////////////////////////////// -// Function: HTTPChannel::get_basic_authorization -// Access: Private -// Description: Looks for a username:password to satisfy the "Basic" -// scheme authorization request from the server or -// proxy. -//////////////////////////////////////////////////////////////////// -bool HTTPChannel:: -get_basic_authorization(string &authorization, const HTTPChannel::Tokens &tokens, const URLSpec &url, bool is_proxy) { - Tokens::const_iterator ti; - ti = tokens.find("realm"); - if (ti != tokens.end()) { - _realm = (*ti).second; - } - - 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 = _client->get_username("*proxy", _realm); - } - if (username.empty()) { - // Then, try *proxy/any realm. - username = _client->get_username("*proxy", string()); - } - } - if (username.empty()) { - // Try the specific server/realm. - username = _client->get_username(url.get_server(), _realm); - } - if (username.empty()) { - // Then, try the specific server/any realm. - username = _client->get_username(url.get_server(), string()); - } - if (username.empty()) { - // Then, try any server with this realm. - username = _client->get_username(string(), _realm); - } - if (username.empty()) { - // Then, take the general password. - username = _client->get_username(string(), string()); - } - - if (username.empty()) { - // No username:password available. - return false; - } - - authorization = "Basic " + base64_encode(username); - return true; -} - #endif // HAVE_SSL diff --git a/panda/src/downloader/httpChannel.h b/panda/src/downloader/httpChannel.h index 83784c65a3..2e3fc43046 100644 --- a/panda/src/downloader/httpChannel.h +++ b/panda/src/downloader/httpChannel.h @@ -29,6 +29,7 @@ #ifdef HAVE_SSL #include "httpClient.h" +#include "httpEnum.h" #include "urlSpec.h" #include "virtualFile.h" #include "bioPtr.h" @@ -71,17 +72,17 @@ public: virtual istream *open_read_file() const; bool will_close_connection() const; - bool has_no_body() const; PUBLISHED: INLINE bool is_valid() const; INLINE bool is_connection_ready() const; INLINE const URLSpec &get_url() const; - INLINE HTTPClient::HTTPVersion get_http_version() const; + INLINE HTTPEnum::HTTPVersion get_http_version() const; INLINE const string &get_http_version_string() const; INLINE int get_status_code() const; INLINE const string &get_status_string() const; - INLINE const string &get_realm() const; + INLINE const string &get_www_realm() const; + INLINE const string &get_proxy_realm() const; INLINE const URLSpec &get_redirect() const; string get_header_value(const string &key) const; @@ -132,14 +133,10 @@ PUBLISHED: INLINE size_t get_bytes_requested() const; INLINE bool is_download_complete() const; -private: - enum Method { - M_get, - M_head, - M_post, - M_connect - }; +public: + static string downcase(const string &s); +private: bool reached_done_state(); bool run_connecting(); bool run_connecting_wait(); @@ -160,7 +157,7 @@ private: bool run_download_to_file(); bool run_download_to_ram(); - void begin_request(Method method, const URLSpec &url, + void begin_request(HTTPEnum::Method method, const URLSpec &url, const string &body, bool nonblocking, size_t first_byte, size_t last_byte); void reset_for_new_request(); @@ -181,17 +178,10 @@ private: void make_header(); void make_proxy_request_text(); - void make_request_text(const string &authorization); + void make_request_text(); void set_url(const URLSpec &url); void store_header_field(const string &field_name, const string &field_value); - bool get_authorization(string &authorization, - const string &authenticate_request, - const URLSpec &url, bool is_proxy); - - static string downcase(const string &s); - static string base64_encode(const string &s); - static size_t scan_quoted_or_unquoted_string(string &result, const string &source, size_t start); #ifndef NDEBUG static void show_send(const string &message); @@ -216,11 +206,13 @@ private: bool _nonblocking; URLSpec _url; - Method _method; + HTTPEnum::Method _method; + string request_path; string _header; string _body; bool _want_ssl; bool _proxy_serves_document; + bool _server_response_has_no_body; size_t _first_byte; size_t _last_byte; @@ -236,13 +228,20 @@ private: int _read_index; - HTTPClient::HTTPVersion _http_version; + HTTPEnum::HTTPVersion _http_version; string _http_version_string; int _status_code; string _status_string; - string _realm; URLSpec _redirect; + string _proxy_realm; + string _proxy_username; + PT(HTTPAuthorization) _proxy_auth; + + string _www_realm; + string _www_username; + PT(HTTPAuthorization) _www_auth; + enum ResponseType { RT_none, RT_hangup, @@ -302,13 +301,6 @@ private: int _last_status_code; double _last_run_time; - typedef pmap Tokens; - typedef pmap AuthenticationSchemes; - static void parse_authentication_schemes(AuthenticationSchemes &schemes, - const string &field_value); - bool get_basic_authorization(string &authorization, const Tokens &tokens, - const URLSpec &url, bool is_proxy); - public: virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/downloader/httpClient.I b/panda/src/downloader/httpClient.I index efaad11281..23351b0380 100644 --- a/panda/src/downloader/httpClient.I +++ b/panda/src/downloader/httpClient.I @@ -26,7 +26,6 @@ INLINE void HTTPClient:: set_proxy(const URLSpec &proxy) { _proxy = proxy; - _proxy_authorization = string(); } //////////////////////////////////////////////////////////////////// @@ -49,7 +48,7 @@ get_proxy() const { // request the server use the older interface. //////////////////////////////////////////////////////////////////// INLINE void HTTPClient:: -set_http_version(HTTPClient::HTTPVersion version) { +set_http_version(HTTPEnum::HTTPVersion version) { _http_version = version; } @@ -59,7 +58,7 @@ set_http_version(HTTPClient::HTTPVersion version) { // Description: Returns the client's current setting for HTTP // version. See set_http_version(). //////////////////////////////////////////////////////////////////// -INLINE HTTPClient::HTTPVersion HTTPClient:: +INLINE HTTPEnum::HTTPVersion HTTPClient:: get_http_version() const { return _http_version; } @@ -91,3 +90,4 @@ INLINE HTTPClient::VerifySSL HTTPClient:: get_verify_ssl() const { return _verify_ssl; } + diff --git a/panda/src/downloader/httpClient.cxx b/panda/src/downloader/httpClient.cxx index 50e9961593..0e8f287c7e 100644 --- a/panda/src/downloader/httpClient.cxx +++ b/panda/src/downloader/httpClient.cxx @@ -23,6 +23,8 @@ #include "config_express.h" #include "virtualFileSystem.h" #include "executionEnvironment.h" +#include "httpBasicAuthorization.h" +#include "httpDigestAuthorization.h" #ifdef HAVE_SSL @@ -51,7 +53,7 @@ X509_STORE *HTTPClient::_x509_store = NULL; //////////////////////////////////////////////////////////////////// HTTPClient:: HTTPClient() { - _http_version = HV_11; + _http_version = HTTPEnum::HV_11; _verify_ssl = verify_ssl ? VS_normal : VS_no_verify; _ssl_ctx = (SSL_CTX *)NULL; @@ -191,16 +193,16 @@ get_username(const string &server, const string &realm) const { string HTTPClient:: get_http_version_string() const { switch (_http_version) { - case HV_09: + case HTTPEnum::HV_09: return "HTTP/0.9"; - case HV_10: + case HTTPEnum::HV_10: return "HTTP/1.0"; - case HV_11: + case HTTPEnum::HV_11: return "HTTP/1.1"; - case HV_other: + case HTTPEnum::HV_other: // Report the best we can do. return "HTTP/1.1"; } @@ -216,16 +218,16 @@ get_http_version_string() const { // the appropriate enumerated value, or HV_other if the // version is unknown. //////////////////////////////////////////////////////////////////// -HTTPClient::HTTPVersion HTTPClient:: +HTTPEnum::HTTPVersion HTTPClient:: parse_http_version_string(const string &version) { if (version == "HTTP/1.0") { - return HV_10; + return HTTPEnum::HV_10; } else if (version == "HTTP/1.1") { - return HV_11; + return HTTPEnum::HV_11; } else if (version.substr(0, 6) == "HTTP/0") { - return HV_09; + return HTTPEnum::HV_09; } else { - return HV_other; + return HTTPEnum::HV_other; } } @@ -508,6 +510,159 @@ add_http_username(const string &http_username) { } } +//////////////////////////////////////////////////////////////////// +// 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 @@ -745,4 +900,3 @@ ssl_msg_callback(int write_p, int version, int content_type, #endif // defined(SSL_097) && !defined(NDEBUG) #endif // HAVE_SSL - diff --git a/panda/src/downloader/httpClient.h b/panda/src/downloader/httpClient.h index 0d35c4cf05..307ea816fd 100644 --- a/panda/src/downloader/httpClient.h +++ b/panda/src/downloader/httpClient.h @@ -29,6 +29,8 @@ #ifdef HAVE_SSL #include "urlSpec.h" +#include "httpAuthorization.h" +#include "httpEnum.h" #include "pointerTo.h" #include @@ -63,17 +65,10 @@ PUBLISHED: void set_username(const string &server, const string &realm, const string &username); string get_username(const string &server, const string &realm) const; - enum HTTPVersion { - HV_09, // HTTP 0.9 or older - HV_10, // HTTP 1.0 - HV_11, // HTTP 1.1 - HV_other, - }; - - INLINE void set_http_version(HTTPVersion version); - INLINE HTTPVersion get_http_version() const; + INLINE void set_http_version(HTTPEnum::HTTPVersion version); + INLINE HTTPEnum::HTTPVersion get_http_version() const; string get_http_version_string() const; - static HTTPVersion parse_http_version_string(const string &version); + static HTTPEnum::HTTPVersion parse_http_version_string(const string &version); bool load_certificates(const Filename &filename); @@ -99,6 +94,13 @@ public: private: void add_http_username(const string &http_username); + string select_username(const URLSpec &url, bool is_proxy, + const string &realm) const; + + HTTPAuthorization *select_auth(const URLSpec &url, bool is_proxy, + const string &last_realm); + PT(HTTPAuthorization) generate_auth(const URLSpec &url, bool is_proxy, + const string &challenge); static void initialize_ssl(); static int load_verify_locations(SSL_CTX *ctx, const Filename &ca_file); @@ -112,13 +114,20 @@ private: #endif URLSpec _proxy; - string _proxy_authorization; - HTTPVersion _http_version; + HTTPEnum::HTTPVersion _http_version; VerifySSL _verify_ssl; typedef pmap Usernames; Usernames _usernames; + typedef map Realms; + class Domain { + public: + Realms _realms; + }; + typedef pmap Domains; + Domains _proxy_domains, _www_domains; + // List of allowable SSL servers to connect to. If the list is // empty, any server is acceptable. typedef pvector ExpectedServers; diff --git a/panda/src/downloader/httpDigestAuthorization.I b/panda/src/downloader/httpDigestAuthorization.I new file mode 100644 index 0000000000..230e998d76 --- /dev/null +++ b/panda/src/downloader/httpDigestAuthorization.I @@ -0,0 +1,30 @@ +// Filename: httpDigestAuthorization.I +// Created by: drose (25Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::hexdigit +// Access: Private, Static +// Description: Returns the ASCII character corresponding to the +// hexadecimal representation of the indicated value, +// which must be 0 <= value <= 15. +//////////////////////////////////////////////////////////////////// +INLINE char HTTPDigestAuthorization:: +hexdigit(int value) { + return (value < 10) ? (value + '0') : (value - 10 + 'a'); +} diff --git a/panda/src/downloader/httpDigestAuthorization.cxx b/panda/src/downloader/httpDigestAuthorization.cxx new file mode 100644 index 0000000000..8faa5e8207 --- /dev/null +++ b/panda/src/downloader/httpDigestAuthorization.cxx @@ -0,0 +1,373 @@ +// Filename: httpDigestAuthorization.cxx +// Created by: drose (25Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "httpDigestAuthorization.h" + +#ifdef HAVE_SSL + +#include +#include + +const string HTTPDigestAuthorization::_mechanism = "digest"; + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +HTTPDigestAuthorization:: +HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens, + const URLSpec &url, bool is_proxy) : + HTTPAuthorization(tokens, url, is_proxy) +{ + Tokens::const_iterator ti; + ti = tokens.find("nonce"); + if (ti != tokens.end()) { + _nonce = (*ti).second; + } + _nonce_count = 0; + + ti = tokens.find("opaque"); + if (ti != tokens.end()) { + _opaque = (*ti).second; + } + + _algorithm = A_md5; + ti = tokens.find("algorithm"); + if (ti != tokens.end()) { + string algo_str = HTTPChannel::downcase((*ti).second); + if (algo_str == "md5") { + _algorithm = A_md5; + } else if (algo_str == "md5-sess") { + _algorithm = A_md5_sess; + } else { + _algorithm = A_unknown; + } + } + + _qop = 0; + ti = tokens.find("qop"); + if (ti != tokens.end()) { + string qop_str = HTTPChannel::downcase((*ti).second); + // A comma-delimited list of tokens. + + size_t p = 0; + while (p < qop_str.length()) { + while (p < qop_str.length() && isspace(qop_str[p])) { + ++p; + } + size_t q = p; + size_t last_char = p; + while (q < qop_str.length() && qop_str[q] != ',') { + if (!isspace(qop_str[q])) { + qop_str[q] = tolower(qop_str[q]); + last_char = q; + } + ++q; + } + + if (last_char > p) { + _qop |= match_qop_token(qop_str.substr(p, last_char - p + 1)); + } + p = q + 1; + } + } + + // Compute an arbitrary client nonce. + ostringstream strm; + strm << time(NULL) << ":" << clock() << ":" + << url.get_url() << ":Panda"; + + _cnonce = calc_md5(strm.str()); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +HTTPDigestAuthorization:: +~HTTPDigestAuthorization() { +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::is_valid +// Access: Public, Virtual +// Description: Returns true if the authorization challenge was +// correctly parsed and is usable, or false if there was +// some unsupported algorithm or some such requested by +// the server, rendering the challenge unmeetable. +//////////////////////////////////////////////////////////////////// +bool HTTPDigestAuthorization:: +is_valid() { + return (_algorithm != A_unknown); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::get_mechanism +// Access: Public, Virtual +// Description: Returns the type of authorization mechanism, +// represented as a string, e.g. "digest". +//////////////////////////////////////////////////////////////////// +const string &HTTPDigestAuthorization:: +get_mechanism() const { + return _mechanism; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::generate +// Access: Public, Virtual +// Description: Generates a suitable authorization string to send +// to the server, based on the data stored within this +// object, for retrieving the indicated URL with the +// given username:password. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +generate(HTTPEnum::Method method, const string &request_path, + const string &username, const string &body) { + _nonce_count++; + + size_t colon = username.find(':'); + string username_only = username.substr(0, colon); + string password_only = username.substr(colon + 1); + + string digest = calc_request_digest(username_only, password_only, + method, request_path, body); + + ostringstream strm; + strm << "Digest username=\"" << username.substr(0, colon) << "\"" + << ", realm=\"" << get_realm() << "\"" + << ", nonce=\"" << _nonce << "\"" + << ", uri=" << request_path + << ", response=\"" << digest << "\"" + << ", algorithm=" << _algorithm; + + if (!_opaque.empty()) { + strm << ", opaque=\"" << _opaque << "\""; + } + + if (_chosen_qop != Q_unused) { + strm << ", qop=" << _chosen_qop + << ", cnonce=\"" << _cnonce << "\"" + << ", nc=" << get_hex_nonce_count(); + } + + return strm.str(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::match_qop_token +// Access: Private, Static +// Description: Returns the bitfield corresponding to the indicated +// qop token string, or 0 if the token string is +// unrecognized. +//////////////////////////////////////////////////////////////////// +int HTTPDigestAuthorization:: +match_qop_token(const string &token) { + if (token == "auth") { + return Q_auth; + } else if (token == "auth-int") { + return Q_auth_int; + } + return 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::calc_request_digest +// Access: Private +// Description: Calculates the appropriate digest response, according +// to RFC 2617. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +calc_request_digest(const string &username, const string &password, + HTTPEnum::Method method, const string &request_path, + const string &body) { + _chosen_qop = Q_unused; + string h_a1 = calc_h(get_a1(username, password)); + string h_a2 = calc_h(get_a2(method, request_path, body)); + + ostringstream strm; + + if (_qop == 0) { + _chosen_qop = Q_unused; + strm << _nonce << ":" << h_a2; + + } else { + strm << _nonce << ":" << get_hex_nonce_count() << ":" + << _cnonce << ":" << _chosen_qop << ":" + << h_a2; + } + + return calc_kd(h_a1, strm.str()); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::calc_h +// Access: Private +// Description: Applies the specified checksum algorithm to the data, +// according to RFC 2617. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +calc_h(const string &data) const { + switch (_algorithm) { + case A_unknown: + case A_md5: + case A_md5_sess: + return calc_md5(data); + } + + return string(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::calc_kd +// Access: Private +// Description: Applies the specified digest algorithm to the +// indicated data with the indicated secret, according +// to RFC 2617. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +calc_kd(const string &secret, const string &data) const { + switch (_algorithm) { + case A_unknown: + case A_md5: + case A_md5_sess: + return calc_h(secret + ":" + data); + } + + return string(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::get_a1 +// Access: Private +// Description: Returns the A1 value, as defined by RFC 2617. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +get_a1(const string &username, const string &password) { + switch (_algorithm) { + case A_unknown: + case A_md5: + return username + ":" + get_realm() + ":" + password; + + case A_md5_sess: + if (_a1.empty()) { + _a1 = calc_h(username + ":" + get_realm() + ":" + password) + + ":" + _nonce + ":" + _cnonce; + } + return _a1; + } + + return string(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::get_a2 +// Access: Private +// Description: Returns the A2 value, as defined by RFC 2617. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +get_a2(HTTPEnum::Method method, const string &request_path, + const string &body) { + ostringstream strm; + + if ((_qop & Q_auth_int) != 0 && !body.empty()) { + _chosen_qop = Q_auth_int; + strm << method << ":" << request_path << ":" << calc_h(body); + + } else { + _chosen_qop = Q_auth; + strm << method << ":" << request_path; + } + + return strm.str(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::get_hex_nonce_count +// Access: Private +// Description: Returns the current nonce count (the number of times +// we have used the server's nonce value, including this +// time) as an eight-digit hexadecimal value. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +get_hex_nonce_count() const { + ostringstream strm; + strm << hex << setfill('0') << setw(8) << _nonce_count; + return strm.str(); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDigestAuthorization::calc_md5 +// Access: Private, Static +// Description: Computes the MD5 of the indicated source string and +// returns it as a hexadecimal string of 32 ASCII +// characters. +//////////////////////////////////////////////////////////////////// +string HTTPDigestAuthorization:: +calc_md5(const string &source) { + unsigned char binary[MD5_DIGEST_LENGTH]; + + MD5((const unsigned char *)source.data(), source.length(), binary); + + string result; + result.reserve(MD5_DIGEST_LENGTH * 2); + + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + result += hexdigit((binary[i] >> 4) & 0xf); + result += hexdigit(binary[i] & 0xf); + } + + return result; +} + +ostream & +operator << (ostream &out, HTTPDigestAuthorization::Algorithm algorithm) { + switch (algorithm) { + case HTTPDigestAuthorization::A_md5: + out << "MD5"; + break; + case HTTPDigestAuthorization::A_md5_sess: + out << "MD5-sess"; + break; + case HTTPDigestAuthorization::A_unknown: + out << "unknown"; + break; + } + + return out; +} + +ostream & +operator << (ostream &out, HTTPDigestAuthorization::Qop qop) { + switch (qop) { + case HTTPDigestAuthorization::Q_auth: + out << "auth"; + break; + case HTTPDigestAuthorization::Q_auth_int: + out << "auth-int"; + break; + case HTTPDigestAuthorization::Q_unused: + out << "unused"; + break; + } + + return out; +} + +#endif // HAVE_SSL diff --git a/panda/src/downloader/httpDigestAuthorization.h b/panda/src/downloader/httpDigestAuthorization.h new file mode 100644 index 0000000000..4b11953dc3 --- /dev/null +++ b/panda/src/downloader/httpDigestAuthorization.h @@ -0,0 +1,102 @@ +// Filename: httpDigestAuthorization.h +// Created by: drose (25Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef HTTPDIGESTAUTHORIZATION_H +#define HTTPDIGESTAUTHORIZATION_H + +#include "pandabase.h" + +// This module requires OpenSSL to compile, even though it doesn't +// actually use any OpenSSL code, because it is a support module for +// HTTPChannel, which *does* use OpenSSL code. + +#ifdef HAVE_SSL + +#include "httpAuthorization.h" + +//////////////////////////////////////////////////////////////////// +// Class : HTTPDigestAuthorization +// Description : Implements the "Digest" type of HTTP authorization. +// This is designed to be an improvement over "Basic" +// authorization, in that it does not send passwords +// over the net in cleartext, and it is harder to spoof. +//////////////////////////////////////////////////////////////////// +class HTTPDigestAuthorization : public HTTPAuthorization { +public: + HTTPDigestAuthorization(const Tokens &tokens, const URLSpec &url, + bool is_proxy); + virtual ~HTTPDigestAuthorization(); + + virtual const string &get_mechanism() const; + virtual bool is_valid(); + + virtual string generate(HTTPEnum::Method method, const string &request_path, + const string &username, const string &body); + +public: + enum Algorithm { + A_unknown, + A_md5, + A_md5_sess, + }; + enum Qop { + // These are used as a bitfield. + Q_unused = 0x000, + Q_auth = 0x001, + Q_auth_int = 0x002, + }; + +private: + static int match_qop_token(const string &token); + + string calc_request_digest(const string &username, const string &password, + HTTPEnum::Method method, + const string &request_path, const string &body); + string calc_h(const string &data) const; + string calc_kd(const string &secret, const string &data) const; + string get_a1(const string &username, const string &password); + string get_a2(HTTPEnum::Method method, const string &request_path, + const string &body); + string get_hex_nonce_count() const; + + static string calc_md5(const string &source); + INLINE static char hexdigit(int value); + + string _cnonce; + string _nonce; + int _nonce_count; + string _opaque; + + Algorithm _algorithm; + string _a1; + + int _qop; + Qop _chosen_qop; + + static const string _mechanism; +}; + +ostream &operator << (ostream &out, HTTPDigestAuthorization::Algorithm algorithm); +ostream &operator << (ostream &out, HTTPDigestAuthorization::Qop qop); + +#include "httpDigestAuthorization.I" + +#endif // HAVE_SSL + +#endif + diff --git a/panda/src/downloader/httpEnum.cxx b/panda/src/downloader/httpEnum.cxx new file mode 100644 index 0000000000..fb2e153901 --- /dev/null +++ b/panda/src/downloader/httpEnum.cxx @@ -0,0 +1,50 @@ +// Filename: httpEnum.cxx +// Created by: drose (25Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "httpEnum.h" + +#ifdef HAVE_SSL + +//////////////////////////////////////////////////////////////////// +// Function: HTTPEnum::Method::output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +operator << (ostream &out, HTTPEnum::Method method) { + switch (method) { + case HTTPEnum::M_get: + out << "GET"; + break; + + case HTTPEnum::M_head: + out << "HEAD"; + break; + + case HTTPEnum::M_post: + out << "POST"; + break; + + case HTTPEnum::M_connect: + out << "CONNECT"; + break; + } + + return out; +} + +#endif // HAVE_SSL diff --git a/panda/src/downloader/httpEnum.h b/panda/src/downloader/httpEnum.h new file mode 100644 index 0000000000..09539d0e69 --- /dev/null +++ b/panda/src/downloader/httpEnum.h @@ -0,0 +1,59 @@ +// Filename: httpEnum.h +// Created by: drose (25Oct02) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef HTTPENUM_H +#define HTTPENUM_H + +#include "pandabase.h" + +// This module requires OpenSSL to compile, even if you do not intend +// to use this to establish https connections; this is because it uses +// the OpenSSL library to portably handle all of the socket +// communications. + +#ifdef HAVE_SSL + +//////////////////////////////////////////////////////////////////// +// Class : HTTPEnum +// Description : This class is just used as a namespace wrapper for +// some of the enumerated types used by various classes +// within the HTTPClient family. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS HTTPEnum { +public: + enum HTTPVersion { + HV_09, // HTTP 0.9 or older + HV_10, // HTTP 1.0 + HV_11, // HTTP 1.1 + HV_other, + }; + + enum Method { + M_get, + M_head, + M_post, + M_connect + }; +}; + +ostream &operator << (ostream &out, HTTPEnum::Method method); + +#endif // HAVE_SSL + +#endif + diff --git a/panda/src/downloader/identityStream.cxx b/panda/src/downloader/identityStream.cxx index dac5db3917..e6bc3688fa 100644 --- a/panda/src/downloader/identityStream.cxx +++ b/panda/src/downloader/identityStream.cxx @@ -28,7 +28,7 @@ // because the socket has genuinely closed, or false if // we can expect more data to come along shortly. //////////////////////////////////////////////////////////////////// -INLINE bool IIdentityStream:: +bool IIdentityStream:: is_closed() { if ((_buf._has_content_length && _buf._bytes_remaining == 0) || (*_buf._source)->is_closed()) { diff --git a/panda/src/downloader/urlSpec.cxx b/panda/src/downloader/urlSpec.cxx index e5c7cefb26..4e47aee74d 100644 --- a/panda/src/downloader/urlSpec.cxx +++ b/panda/src/downloader/urlSpec.cxx @@ -317,16 +317,26 @@ set_path(const string &path) { } else if (!has_path()) { // Insert a new path specification. - length_adjust = path.length(); + string cpath = path; + if (cpath[0] != '/') { + // Paths must always begin with a slash. + cpath = '/' + cpath; + } + length_adjust = cpath.length(); - _url = _url.substr(0, _path_start) + path + _url.substr(_path_end); + _url = _url.substr(0, _path_start) + cpath + _url.substr(_path_end); _flags |= F_has_path; } else { // Replace an existing path specification. + string cpath = path; + if (cpath[0] != '/') { + // Paths must always begin with a slash. + cpath = '/' + cpath; + } int old_length = (int)_path_end - (int)_path_start; - length_adjust = path.length() - old_length; - _url = _url.substr(0, _path_start) + path + _url.substr(_path_end); + length_adjust = cpath.length() - old_length; + _url = _url.substr(0, _path_start) + cpath + _url.substr(_path_end); } _path_end += length_adjust;