Browse Source

add digest authentication

David Rose 23 years ago
parent
commit
b4b34572c7

+ 13 - 1
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 \

+ 3 - 3
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;

+ 1 - 1
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;

+ 1 - 1
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

+ 4 - 2
panda/src/downloader/config_downloader.h

@@ -19,9 +19,11 @@
 #ifndef CONFIG_DOWNLOADER_H
 #define CONFIG_DOWNLOADER_H
 
-#include <pandabase.h>
-#include <notifyCategoryProxy.h>
+#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;

+ 0 - 9
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"
-

+ 12 - 0
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"

+ 44 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     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;
+}

+ 296 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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

+ 86 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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<string, string> Tokens;
+  typedef pmap<string, Tokens> 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
+

+ 17 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////

+ 71 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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

+ 59 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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
+

+ 31 - 23
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);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 104 - 361
panda/src/downloader/httpChannel.cxx

@@ -24,22 +24,12 @@
 #include "config_downloader.h"
 #include "clockObject.h"
 #include "buffer.h"  // for Ramfile
+#include <sys/time.h>
 
 #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

+ 21 - 29
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<string, string> Tokens;
-  typedef pmap<string, Tokens> 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();

+ 3 - 3
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;
 }
+

+ 165 - 11
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
-

+ 21 - 12
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 <openssl/ssl.h>
@@ -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<string, string> Usernames;
   Usernames _usernames;
 
+  typedef map<string, PT(HTTPAuthorization) > Realms;
+  class Domain {
+  public:
+    Realms _realms;
+  };
+  typedef pmap<string, Domain> 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<X509_NAME *> ExpectedServers;

+ 30 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     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');
+}

+ 373 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "httpDigestAuthorization.h"
+
+#ifdef HAVE_SSL
+
+#include <openssl/ssl.h>
+#include <time.h>
+
+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

+ 102 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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
+

+ 50 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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

+ 59 - 0
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
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#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
+

+ 1 - 1
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()) {

+ 14 - 4
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;