Browse Source

cache control

David Rose 23 years ago
parent
commit
31ebf3bd31

+ 9 - 0
panda/src/downloader/Sources.pp

@@ -17,12 +17,15 @@
     bioStreamPtr.I bioStreamPtr.h \
     bioStream.I bioStream.h bioStreamBuf.h \
     chunkedStream.I chunkedStream.h chunkedStreamBuf.h \
+    documentSpec.I documentSpec.h \
     extractor.h \
     httpAuthorization.I httpAuthorization.h \
     httpBasicAuthorization.I httpBasicAuthorization.h \
     httpClient.I httpClient.h \
     httpChannel.I httpChannel.h \
+    httpDate.I httpDate.h \
     httpDigestAuthorization.I httpDigestAuthorization.h \
+    httpEntityTag.I httpEntityTag.h \
     httpEnum.h \
     identityStream.I identityStream.h identityStreamBuf.h \
     multiplexStream.I multiplexStream.h \
@@ -40,12 +43,15 @@
     bioStreamPtr.cxx \
     bioStream.cxx bioStreamBuf.cxx \
     chunkedStream.cxx chunkedStreamBuf.cxx \
+    documentSpec.cxx \
     extractor.cxx \
     httpAuthorization.cxx \
     httpBasicAuthorization.cxx \
     httpClient.cxx \
     httpChannel.cxx \
+    httpDate.cxx \
     httpDigestAuthorization.cxx \
+    httpEntityTag.cxx \
     httpEnum.cxx \
     identityStream.cxx identityStreamBuf.cxx \
     multiplexStream.cxx multiplexStreamBuf.cxx \
@@ -62,13 +68,16 @@
     chunkedStream.I chunkedStream.h chunkedStreamBuf.h \
     config_downloader.h \
     decompressor.h \
+    documentSpec.h documentSpec.I \
     download_utils.h downloadDb.h downloadDb.I \
     extractor.h \
     httpAuthorization.I httpAuthorization.h \
     httpBasicAuthorization.I httpBasicAuthorization.h \
     httpClient.I httpClient.h \
     httpChannel.I httpChannel.h \
+    httpDate.I httpDate.h \
     httpDigestAuthorization.I httpDigestAuthorization.h \
+    httpEntityTag.I httpEntityTag.h \
     httpEnum.h \
     identityStream.I identityStream.h identityStreamBuf.h \
     multiplexStream.I multiplexStream.h \

+ 298 - 0
panda/src/downloader/documentSpec.I

@@ -0,0 +1,298 @@
+// Filename: documentSpec.I
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: DocumentSpec::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE DocumentSpec::
+DocumentSpec() {
+  _request_mode = RM_any;
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE DocumentSpec::
+DocumentSpec(const string &url) :
+  _url(url)
+{
+  _request_mode = RM_any;
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE DocumentSpec::
+DocumentSpec(const URLSpec &url) :
+  _url(url)
+{
+  _request_mode = RM_any;
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE DocumentSpec::
+DocumentSpec(const DocumentSpec &copy) :
+  _url(copy._url),
+  _tag(copy._tag),
+  _date(copy._date),
+  _request_mode(copy._request_mode),
+  _flags(copy._flags)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+operator = (const DocumentSpec &copy) {
+  _url = copy._url;
+  _tag = copy._tag;
+  _date = copy._date;
+  _request_mode = copy._request_mode;
+  _flags = copy._flags;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::operator ==
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool DocumentSpec::
+operator == (const DocumentSpec &other) const {
+  return compare_to(other) == 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::operator !=
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool DocumentSpec::
+operator != (const DocumentSpec &other) const {
+  return compare_to(other) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::operator <
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool DocumentSpec::
+operator < (const DocumentSpec &other) const {
+  return compare_to(other) < 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::set_url
+//       Access: Published
+//  Description: Changes the URL of the DocumentSpec without modifying
+//               its other properties.  Normally this would be a
+//               strange thing to do, because the tag and date are
+//               usually strongly associated with the URL.  To get a
+//               DocumentSpec pointing to a new URL, you would
+//               normally create a new DocumentSpec object.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+set_url(const URLSpec &url) {
+  _url = url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::get_url
+//       Access: Published
+//  Description: Retrieves the URL of the DocumentSpec.
+////////////////////////////////////////////////////////////////////
+INLINE const URLSpec &DocumentSpec::
+get_url() const {
+  return _url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::set_tag
+//       Access: Published
+//  Description: Changes the identity tag associated with the
+//               DocumentSpec.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+set_tag(const HTTPEntityTag &tag) {
+  _tag = tag;
+  _flags |= F_has_tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::has_tag
+//       Access: Published
+//  Description: Returns true if an identity tag is associated with
+//               the DocumentSpec.
+////////////////////////////////////////////////////////////////////
+INLINE bool DocumentSpec::
+has_tag() const {
+  return (_flags & F_has_tag) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::get_tag
+//       Access: Published
+//  Description: Returns the identity tag associated with the
+//               DocumentSpec, if there is one.  It is an error to
+//               call this if has_tag() returns false.
+//
+//               The identity tag is set by the HTTP server to
+//               uniquely refer to a particular version of a document.
+////////////////////////////////////////////////////////////////////
+INLINE const HTTPEntityTag &DocumentSpec::
+get_tag() const {
+  nassertr(has_tag(), _tag);
+  return _tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::clear_tag
+//       Access: Published
+//  Description: Removes the identity tag associated with the
+//               DocumentSpec, if there is one.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+clear_tag() {
+  _flags &= ~F_has_tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::set_date
+//       Access: Published
+//  Description: Changes the last-modified date associated with the
+//               DocumentSpec.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+set_date(const HTTPDate &date) {
+  _date = date;
+  _flags |= F_has_date;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::has_date
+//       Access: Published
+//  Description: Returns true if a last-modified date is associated
+//               with the DocumentSpec.
+////////////////////////////////////////////////////////////////////
+INLINE bool DocumentSpec::
+has_date() const {
+  return (_flags & F_has_date) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::get_date
+//       Access: Published
+//  Description: Returns the last-modified date associated with the
+//               DocumentSpec, if there is one.  It is an error to
+//               call this if has_date() returns false.
+////////////////////////////////////////////////////////////////////
+INLINE const HTTPDate &DocumentSpec::
+get_date() const {
+  nassertr(has_date(), _date);
+  return _date;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::clear_date
+//       Access: Published
+//  Description: Removes the last-modified date associated with the
+//               DocumentSpec, if there is one.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+clear_date() {
+  _flags &= ~F_has_date;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::set_request_mode
+//       Access: Published
+//  Description: Sets the request mode of this DocumentSpec.  This is
+//               only relevant when using the DocumentSpec to generate
+//               a request (for instance, in HTTPChannel).  This
+//               specifies whether the document request will ask the
+//               server for a newer version than the indicated
+//               version, or the exact version, neither, or either.
+//
+//               The possible values are:
+//
+//                 RM_any: ignore date and tag (if specified), and
+//                 retrieve any document that matches the URL.  For a
+//                 subrange request, if the document matches the
+//                 version indicated exactly, retrieve the subrange
+//                 only; otherwise, retrieve the entire document.
+//
+//                 RM_equal: request only the precise version of the
+//                 document that matches the particular date and/or
+//                 tag exactly, if specified; fail if this version is
+//                 not available.
+//
+//                 RM_newer: request any document that is newer than
+//                 the version indicated by the particular date and/or
+//                 tag; fail if only that version (or older versions)
+//                 are available.
+//
+//                 RM_newer_or_equal: request any document that
+//                 matches the version indicated by the particular
+//                 date and/or tag, or is a newer version; fail if
+//                 only older versions are available.
+//
+//               In any of the above, you may specify either or both
+//               of the last-modified date and the identity tag,
+//               whichever is known to the client.
+//
+//               The default mode is RM_any.
+////////////////////////////////////////////////////////////////////
+INLINE void DocumentSpec::
+set_request_mode(DocumentSpec::RequestMode request_mode) {
+  _request_mode = request_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::get_request_mode
+//       Access: Published
+//  Description: Returns the request mode of this DocumentSpec.  See
+//               set_request_mode().
+////////////////////////////////////////////////////////////////////
+INLINE DocumentSpec::RequestMode DocumentSpec::
+get_request_mode() const {
+  return _request_mode;
+}
+
+
+INLINE ostream &
+operator << (ostream &out, const DocumentSpec &doc) {
+  doc.output(out);
+  return out;
+}

+ 76 - 0
panda/src/downloader/documentSpec.cxx

@@ -0,0 +1,76 @@
+// Filename: documentSpec.cxx
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "documentSpec.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::compare_to
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+int DocumentSpec::
+compare_to(const DocumentSpec &other) const {
+  if (_flags != other._flags) {
+    return (_flags - other._flags);
+  }
+  if (_request_mode != other._request_mode) {
+    return (int)_request_mode - (int)other._request_mode;
+  }
+  int c = _url.compare_to(other._url);
+  if (c != 0) {
+    return c;
+  }
+  c = _tag.compare_to(other._tag);
+  if (c != 0) {
+    return c;
+  }
+  return _date.compare_to(other._date);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void DocumentSpec::
+output(ostream &out) const {
+  out << get_url();
+  if (has_tag()) {
+    out << " (" << get_tag() << ")";
+  }
+  if (has_date()) {
+    out << " " << get_date();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DocumentSpec::write
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void DocumentSpec::
+write(ostream &out) const {
+  out << get_url() << "\n";
+  if (has_tag()) {
+    out << "  " << get_tag() << "\n";
+  }
+  if (has_date()) {
+    out << "  " << get_date() << "\n";
+  }
+}

+ 93 - 0
panda/src/downloader/documentSpec.h

@@ -0,0 +1,93 @@
+// Filename: documentSpec.h
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 DOCUMENTSPEC_H
+#define DOCUMENTSPEC_H
+
+#include "pandabase.h"
+#include "urlSpec.h"
+#include "httpEntityTag.h"
+#include "httpDate.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : DocumentSpec
+// Description : A descriptor that refers to a particular version of a
+//               document.  This includes the URL of the document and
+//               its identity tag and last-modified dates.
+//
+//               The DocumentSpec may also be used to request a newer
+//               document than a particular one if available, for
+//               instance to refresh a cached document.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS DocumentSpec {
+PUBLISHED:
+  INLINE DocumentSpec();
+  INLINE DocumentSpec(const string &url);
+  INLINE DocumentSpec(const URLSpec &url);
+  INLINE DocumentSpec(const DocumentSpec &copy);
+  INLINE void operator = (const DocumentSpec &copy);
+
+  INLINE bool operator == (const DocumentSpec &other) const;
+  INLINE bool operator != (const DocumentSpec &other) const;
+  INLINE bool operator < (const DocumentSpec &other) const;
+  int compare_to(const DocumentSpec &other) const;
+
+  INLINE void set_url(const URLSpec &url);
+  INLINE const URLSpec &get_url() const;
+
+  INLINE void set_tag(const HTTPEntityTag &tag);
+  INLINE bool has_tag() const;
+  INLINE const HTTPEntityTag &get_tag() const;
+  INLINE void clear_tag();
+
+  INLINE void set_date(const HTTPDate &date);
+  INLINE bool has_date() const;
+  INLINE const HTTPDate &get_date() const;
+  INLINE void clear_date();
+
+  enum RequestMode {
+    RM_any,
+    RM_equal,
+    RM_newer,
+    RM_equal_or_newer,
+  };
+
+  INLINE void set_request_mode(RequestMode request_mode);
+  INLINE RequestMode get_request_mode() const;
+
+  void output(ostream &out) const;
+  void write(ostream &out) const;
+
+private:
+  URLSpec _url;
+  HTTPEntityTag _tag;
+  HTTPDate _date;
+  RequestMode _request_mode;
+
+  enum Flags {
+    F_has_tag    = 0x0001,
+    F_has_date   = 0x0002,
+  };
+  int _flags;
+};
+
+INLINE ostream &operator << (ostream &out, const DocumentSpec &doc);
+
+#include "documentSpec.I"
+
+#endif

+ 4 - 0
panda/src/downloader/downloader_composite1.cxx

@@ -7,3 +7,7 @@
 #include "chunkedStream.cxx"
 #include "chunkedStreamBuf.cxx"
 #include "extractor.cxx"
+#include "httpDate.cxx"
+#include "httpEntityTag.cxx"
+#include "documentSpec.cxx"
+

+ 32 - 14
panda/src/downloader/httpChannel.I

@@ -54,7 +54,25 @@ is_connection_ready() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const URLSpec &HTTPChannel::
 get_url() const {
-  return _url;
+  return _document_spec.get_url();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPChannel::get_document_spec
+//       Access: Published
+//  Description: Returns the DocumentSpec associated with the most
+//               recent document.  This includes its actual URL
+//               (following redirects) along with the identity tag and
+//               last-modified date, if supplied by the server.
+//
+//               This structure may be saved and used to retrieve the
+//               same version of the document later, or to
+//               conditionally retrieve a newer version if it is
+//               available.
+////////////////////////////////////////////////////////////////////
+INLINE const DocumentSpec &HTTPChannel::
+get_document_spec() const {
+  return _document_spec;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -462,7 +480,7 @@ send_extra_header(const string &key, const string &value) {
 //               Returns true if successful, false otherwise.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-get_document(const URLSpec &url) {
+get_document(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_get, url, string(), false, 0, 0);
   run();
   return is_valid();
@@ -479,7 +497,7 @@ get_document(const URLSpec &url) {
 //               not of the complete document.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) {
+get_subdocument(const DocumentSpec &url, size_t first_byte, size_t last_byte) {
   begin_request(HTTPEnum::M_get, url, string(), false, first_byte, last_byte);
   run();
   return is_valid();
@@ -495,7 +513,7 @@ get_subdocument(const URLSpec &url, size_t first_byte, size_t last_byte) {
 //               server gives us this information).
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-get_header(const URLSpec &url) {
+get_header(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_head, url, string(), false, 0, 0);
   run();
   return is_valid();
@@ -508,7 +526,7 @@ get_header(const URLSpec &url) {
 //               response.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-post_form(const URLSpec &url, const string &body) {
+post_form(const DocumentSpec &url, const string &body) {
   begin_request(HTTPEnum::M_post, url, body, false, 0, 0);
   run();
   return is_valid();
@@ -521,7 +539,7 @@ post_form(const URLSpec &url, const string &body) {
 //               the indicated URL, if the server allows this.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-put_document(const URLSpec &url, const string &body) {
+put_document(const DocumentSpec &url, const string &body) {
   begin_request(HTTPEnum::M_put, url, body, false, 0, 0);
   run();
   return is_valid();
@@ -533,7 +551,7 @@ put_document(const URLSpec &url, const string &body) {
 //  Description: Requests the server to remove the indicated URL.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-delete_document(const URLSpec &url) {
+delete_document(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_delete, url, string(), false, 0, 0);
   run();
   return is_valid();
@@ -547,7 +565,7 @@ delete_document(const URLSpec &url) {
 //               it, allowing inspection of proxy hops, etc.
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-get_trace(const URLSpec &url) {
+get_trace(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_trace, url, string(), false, 0, 0);
   run();
   return is_valid();
@@ -566,7 +584,7 @@ get_trace(const URLSpec &url) {
 //               begin_connect_to().
 ////////////////////////////////////////////////////////////////////
 INLINE bool HTTPChannel::
-connect_to(const URLSpec &url) {
+connect_to(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_connect, url, string(), false, 0, 0);
   run();
   return is_connection_ready();
@@ -587,7 +605,7 @@ connect_to(const URLSpec &url) {
 //               is discarded.
 ////////////////////////////////////////////////////////////////////
 INLINE void HTTPChannel::
-begin_get_document(const URLSpec &url) {
+begin_get_document(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_get, url, string(), true, 0, 0);
 }
 
@@ -603,7 +621,7 @@ begin_get_document(const URLSpec &url) {
 //               the complete document.
 ////////////////////////////////////////////////////////////////////
 INLINE void HTTPChannel::
-begin_get_subdocument(const URLSpec &url, size_t first_byte, 
+begin_get_subdocument(const DocumentSpec &url, size_t first_byte, 
                       size_t last_byte) {
   begin_request(HTTPEnum::M_get, url, string(), true, first_byte, last_byte);
 }
@@ -615,7 +633,7 @@ begin_get_subdocument(const URLSpec &url, size_t first_byte,
 //               header.  See begin_get_document() and get_header().
 ////////////////////////////////////////////////////////////////////
 INLINE void HTTPChannel::
-begin_get_header(const URLSpec &url) {
+begin_get_header(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_head, url, string(), true, 0, 0);
 }
 
@@ -634,7 +652,7 @@ begin_get_header(const URLSpec &url) {
 //               may not get posted.
 ////////////////////////////////////////////////////////////////////
 INLINE void HTTPChannel::
-begin_post_form(const URLSpec &url, const string &body) {
+begin_post_form(const DocumentSpec &url, const string &body) {
   begin_request(HTTPEnum::M_post, url, body, true, 0, 0);
 }
 
@@ -657,7 +675,7 @@ begin_post_form(const URLSpec &url, const string &body) {
 //               connect_to().
 ////////////////////////////////////////////////////////////////////
 INLINE void HTTPChannel::
-begin_connect_to(const URLSpec &url) {
+begin_connect_to(const DocumentSpec &url) {
   begin_request(HTTPEnum::M_connect, url, string(), true, 0, 0);
 }
 

+ 109 - 37
panda/src/downloader/httpChannel.cxx

@@ -270,7 +270,7 @@ run() {
       _proxy = _client->get_proxy();
       
       if (_proxy.empty()) {
-        _bio = new BioPtr(_url);
+        _bio = new BioPtr(_request.get_url());
       } else {
         _bio = new BioPtr(_proxy);
       }
@@ -686,7 +686,7 @@ run_connecting_wait() {
 
   if (downloader_cat.is_debug()) {
     downloader_cat.debug()
-      << "waiting to connect to " << _url.get_server_and_port() << ".\n";
+      << "waiting to connect to " << _request.get_url().get_server_and_port() << ".\n";
   }
   fd_set wset;
   FD_ZERO(&wset);
@@ -717,7 +717,8 @@ run_connecting_wait() {
          _started_connecting_time > get_connect_timeout())) {
       // Time to give up.
       downloader_cat.info()
-        << "Timeout connecting to " << _url.get_server_and_port() << ".\n";
+        << "Timeout connecting to " 
+        << _request.get_url().get_server_and_port() << ".\n";
       _state = S_failure;
       return false;
     }
@@ -894,7 +895,7 @@ run_ssl_handshake() {
     }
     downloader_cat.info()
       << "Could not establish SSL handshake with " 
-      << _url.get_server_and_port() << "\n";
+      << _request.get_url().get_server_and_port() << "\n";
 #ifdef REPORT_OPENSSL_ERRORS
     ERR_print_errors_fp(stderr);
 #endif
@@ -921,7 +922,7 @@ run_ssl_handshake() {
   long verify_result = SSL_get_verify_result(ssl);
   if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) {
     downloader_cat.info()
-      << "Expired certificate from " << _url.get_server_and_port() << "\n";
+      << "Expired certificate from " << _request.get_url().get_server_and_port() << "\n";
     if (_client->get_verify_ssl() == HTTPClient::VS_normal) {
       _state = S_failure;
       return false;
@@ -929,7 +930,7 @@ run_ssl_handshake() {
 
   } else if (verify_result == X509_V_ERR_CERT_NOT_YET_VALID) {
     downloader_cat.info()
-      << "Premature certificate from " << _url.get_server_and_port() << "\n";
+      << "Premature certificate from " << _request.get_url().get_server_and_port() << "\n";
     if (_client->get_verify_ssl() == HTTPClient::VS_normal) {
       _state = S_failure;
       return false;
@@ -937,7 +938,7 @@ run_ssl_handshake() {
 
   } else if (verify_result != X509_V_OK) {
     downloader_cat.info()
-      << "Unable to verify identity of " << _url.get_server_and_port()
+      << "Unable to verify identity of " << _request.get_url().get_server_and_port()
       << ", verify error code " << verify_result << "\n";
     if (_client->get_verify_ssl() != HTTPClient::VS_no_verify) {
       _state = S_failure;
@@ -1046,7 +1047,7 @@ run_request_sent() {
                _sent_request_time > get_http_timeout()) {
       // Time to give up.
       downloader_cat.info()
-        << "Timeout waiting for " << _url.get_server_and_port() << ".\n";
+        << "Timeout waiting for " << _request.get_url().get_server_and_port() << ".\n";
       _state = S_failure;
     }
 
@@ -1093,7 +1094,7 @@ run_reading_header() {
                _sent_request_time > get_http_timeout()) {
       // Time to give up.
       downloader_cat.info()
-        << "Timeout waiting for " << _url.get_server_and_port() << ".\n";
+        << "Timeout waiting for " << _request.get_url().get_server_and_port() << ".\n";
       _state = S_failure;
     }
     return true;
@@ -1133,6 +1134,17 @@ run_reading_header() {
     _last_byte = 0;
   }
 
+  // Set the _document_spec to reflect what we just retrieved.
+  _document_spec = DocumentSpec(_request.get_url());
+  string tag = get_header_value("ETag");
+  if (!tag.empty()) {
+    _document_spec.set_tag(HTTPEntityTag(tag));
+  }
+  string date = get_header_value("Last-Modified");
+  if (!date.empty()) {
+    _document_spec.set_date(HTTPDate(date));
+  }
+
   // In case we've got a download in effect, reset the download
   // position to match our starting byte.
   if (!reset_download_position()) {
@@ -1185,10 +1197,10 @@ 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");
-    _www_auth = _client->generate_auth(_url, false, authenticate_request);
+    _www_auth = _client->generate_auth(_request.get_url(), false, authenticate_request);
     if (_www_auth != (HTTPAuthorization *)NULL) {
       _www_realm = _www_auth->get_realm();
-      _www_username = _client->select_username(_url, false, _www_realm);
+      _www_username = _client->select_username(_request.get_url(), false, _www_realm);
       if (!_www_username.empty()) {
         make_request_text();
       
@@ -1214,10 +1226,11 @@ run_reading_header() {
           downloader_cat.debug()
             << "following redirect to " << new_url << "\n";
         }
-        if (_url.has_username()) {
-          new_url.set_username(_url.get_username());
+        if (_request.get_url().has_username()) {
+          new_url.set_username(_request.get_url().get_username());
         }
-        set_url(new_url);
+        reset_url(_request.get_url(), new_url);
+        _document_spec.set_url(new_url);
         make_header();
         make_request_text();
 
@@ -1507,7 +1520,7 @@ run_download_to_ram() {
 //               necessary.
 ////////////////////////////////////////////////////////////////////
 void HTTPChannel::
-begin_request(HTTPEnum::Method method, const URLSpec &url,
+begin_request(HTTPEnum::Method method, const DocumentSpec &url,
               const string &body, bool nonblocking, 
               size_t first_byte, size_t last_byte) {
   reset_for_new_request();
@@ -1524,13 +1537,15 @@ begin_request(HTTPEnum::Method method, const URLSpec &url,
     reset_to_new();
   }
 
-  set_url(url);
+  reset_url(_request.get_url(), url.get_url());
+  _request = url;
+  _document_spec = DocumentSpec();
   _method = method;
   _body = body;
 
   // An https-style request means we'll need to establish an SSL
   // connection.
-  _want_ssl = (_url.get_scheme() == "https");
+  _want_ssl = (_request.get_url().get_scheme() == "https");
 
   // If we have a proxy, we'll be asking the proxy to hand us the
   // document--except when we also have https, in which case we'll be
@@ -1550,11 +1565,11 @@ begin_request(HTTPEnum::Method method, const URLSpec &url,
     // requested a direct connection somewhere.
     ostringstream request;
     request 
-      << "CONNECT " << _url.get_server_and_port()
+      << "CONNECT " << _request.get_url().get_server_and_port()
       << " " << _client->get_http_version_string() << "\r\n";
     if (_client->get_http_version() >= HTTPEnum::HV_11) {
       request 
-        << "Host: " << _url.get_server() << "\r\n";
+        << "Host: " << _request.get_url().get_server() << "\r\n";
     }
     _proxy_header = request.str();
     make_proxy_request_text();
@@ -2241,7 +2256,7 @@ x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) {
 //       Access: Private
 //  Description: Formats the appropriate GET or POST (or whatever)
 //               request to send to the server, based on the current
-//               _method, _url, _body, and _proxy settings.
+//               _method, _document_spec, _body, and _proxy settings.
 ////////////////////////////////////////////////////////////////////
 void HTTPChannel::
 make_header() {
@@ -2261,25 +2276,25 @@ make_header() {
     return;
   }
 
-  _www_auth = _client->select_auth(_url, false, _www_realm);
+  _www_auth = _client->select_auth(_request.get_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);
+    _www_username = _client->select_username(_request.get_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;
+    URLSpec url_no_username = _request.get_url();
     url_no_username.set_username(string());
     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.
-    request_path = _url.get_path();
+    request_path = _request.get_url().get_path();
   }
 
   ostringstream stream;
@@ -2290,7 +2305,7 @@ make_header() {
 
   if (_client->get_http_version() >= HTTPEnum::HV_11) {
     stream 
-      << "Host: " << _url.get_server() << "\r\n";
+      << "Host: " << _request.get_url().get_server() << "\r\n";
     if (!get_persistent_connection()) {
       stream
         << "Connection: close\r\n";
@@ -2306,6 +2321,64 @@ make_header() {
       << "Range: bytes=" << _first_byte << "-\r\n";
   }
 
+  switch (_request.get_request_mode()) {
+  case DocumentSpec::RM_any:
+    // No particular request; give us any document that matches the
+    // URL.  
+    if (_first_byte != 0) {
+      // Unless we're requesting a subrange, in which case if the
+      // exact document matches, retrieve the subrange indicated;
+      // otherwise, retrieve the entire document.
+      if (_request.has_tag()) {
+        stream
+          << "If-Range: " << _request.get_tag().get_string() << "\r\n";
+      } else if (_request.has_date()) {
+        stream
+          << "If-Range: " << _request.get_date().get_string() << "\r\n";
+      }
+    }
+    break;
+
+  case DocumentSpec::RM_equal:
+    // Give us only this particular version of the document, or
+    // nothing.
+    if (_request.has_tag()) {
+      stream
+        << "If-Match: " << _request.get_tag().get_string() << "\r\n";
+    }
+    if (_request.has_date()) {
+      stream
+        << "If-Unmodified-Since: " << _request.get_date().get_string()
+        << "\r\n";
+    }
+    break;
+
+  case DocumentSpec::RM_newer:
+    // Give us anything newer than this document, or nothing.
+    if (_request.has_tag()) {
+      stream
+        << "If-None-Match: " << _request.get_tag().get_string() << "\r\n";
+    }
+    if (_request.has_date()) {
+      stream
+        << "If-Modified-Since: " << _request.get_date().get_string()
+        << "\r\n";
+    }
+    break;
+
+  case DocumentSpec::RM_equal_or_newer:
+    // Just don't give us anything older.
+    if (_request.has_date()) {
+      // This is a little unreliable: we ask for any document that's
+      // been modified since one second before our last-modified-date.
+      // Who knows whether the server will honor this properly.
+      stream
+        << "If-Modified-Since: " << (_request.get_date() - 1).get_string()
+        << "\r\n";
+    }
+    break;
+  }
+
   if (!_body.empty()) {
     stream
       << "Content-Type: application/x-www-form-urlencoded\r\n"
@@ -2331,7 +2404,7 @@ make_proxy_request_text() {
   if (_proxy_auth != (HTTPAuthorization *)NULL && !_proxy_username.empty()) {
     _proxy_request_text += "Proxy-Authorization: ";
     _proxy_request_text += 
-      _proxy_auth->generate(HTTPEnum::M_connect, _url.get_server_and_port(),
+      _proxy_auth->generate(HTTPEnum::M_connect, _request.get_url().get_server_and_port(),
                             _proxy_username, _body);
     _proxy_request_text += "\r\n";
   }
@@ -2354,7 +2427,7 @@ make_request_text() {
       _proxy_auth != (HTTPAuthorization *)NULL && !_proxy_username.empty()) {
     _request_text += "Proxy-Authorization: ";
     _request_text += 
-      _proxy_auth->generate(_method, _url.get_url(), _proxy_username, _body);
+      _proxy_auth->generate(_method, _request.get_url().get_url(), _proxy_username, _body);
     _request_text += "\r\n";
   }
 
@@ -2362,7 +2435,7 @@ make_request_text() {
     string authorization = 
     _request_text += "Authorization: ";
     _request_text +=
-      _www_auth->generate(_method, _url.get_path(), _www_username, _body);
+      _www_auth->generate(_method, _request.get_url().get_path(), _www_username, _body);
     _request_text += "\r\n";
   }
 
@@ -2372,25 +2445,24 @@ make_request_text() {
 }
   
 ////////////////////////////////////////////////////////////////////
-//     Function: HTTPChannel::set_url
+//     Function: HTTPChannel::reset_url
 //       Access: Private
-//  Description: Specifies the document's URL before attempting a
-//               connection.  This controls the name of the server to
-//               be contacted, etc.
+//  Description: Redirects the next connection to the indicated URL
+//               (from the previous URL).  This resets the socket if
+//               necessary when we are about to switch servers.
 ////////////////////////////////////////////////////////////////////
 void HTTPChannel::
-set_url(const URLSpec &url) {
+reset_url(const URLSpec &old_url, const URLSpec &new_url) {
   // If we change between http and https, we have to reset the
   // connection regardless of proxy.  Otherwise, we have to drop the
   // connection if the server or port changes, unless we're
   // communicating through a proxy.
 
-  if (url.get_scheme() != _url.get_scheme() ||
-      (_proxy.empty() && (url.get_server() != _url.get_server() || 
-                          url.get_port() != _url.get_port()))) {
+  if (new_url.get_scheme() != old_url.get_scheme() ||
+      (_proxy.empty() && (new_url.get_server() != old_url.get_server() || 
+                          new_url.get_port() != old_url.get_port()))) {
     reset_to_new();
   }
-  _url = url;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 20 - 17
panda/src/downloader/httpChannel.h

@@ -31,6 +31,7 @@
 #include "httpClient.h"
 #include "httpEnum.h"
 #include "urlSpec.h"
+#include "documentSpec.h"
 #include "virtualFile.h"
 #include "bioPtr.h"
 #include "bioStreamPtr.h"
@@ -77,6 +78,7 @@ PUBLISHED:
   INLINE bool is_valid() const;
   INLINE bool is_connection_ready() const;
   INLINE const URLSpec &get_url() const;
+  INLINE const DocumentSpec &get_document_spec() const;
   INLINE HTTPEnum::HTTPVersion get_http_version() const;
   INLINE const string &get_http_version_string() const;
   INLINE int get_status_code() const;
@@ -115,23 +117,23 @@ PUBLISHED:
   INLINE void clear_extra_headers();
   INLINE void send_extra_header(const string &key, const string &value);
 
-  INLINE bool get_document(const URLSpec &url);
-  INLINE bool get_subdocument(const URLSpec &url, 
+  INLINE bool get_document(const DocumentSpec &url);
+  INLINE bool get_subdocument(const DocumentSpec &url, 
                               size_t first_byte, size_t last_byte);
-  INLINE bool get_header(const URLSpec &url);
-  INLINE bool post_form(const URLSpec &url, const string &body);
-  INLINE bool put_document(const URLSpec &url, const string &body);
-  INLINE bool delete_document(const URLSpec &url);
-  INLINE bool get_trace(const URLSpec &url);
-  INLINE bool connect_to(const URLSpec &url);
-
-  INLINE void begin_get_document(const URLSpec &url);
-  INLINE void begin_get_subdocument(const URLSpec &url, 
+  INLINE bool get_header(const DocumentSpec &url);
+  INLINE bool post_form(const DocumentSpec &url, const string &body);
+  INLINE bool put_document(const DocumentSpec &url, const string &body);
+  INLINE bool delete_document(const DocumentSpec &url);
+  INLINE bool get_trace(const DocumentSpec &url);
+  INLINE bool connect_to(const DocumentSpec &url);
+
+  INLINE void begin_get_document(const DocumentSpec &url);
+  INLINE void begin_get_subdocument(const DocumentSpec &url, 
                                     size_t first_byte, size_t last_byte);
-  INLINE void begin_get_header(const URLSpec &url);
-  INLINE void begin_post_form(const URLSpec &url, const string &body);
+  INLINE void begin_get_header(const DocumentSpec &url);
+  INLINE void begin_post_form(const DocumentSpec &url, const string &body);
   bool run();
-  INLINE void begin_connect_to(const URLSpec &url);
+  INLINE void begin_connect_to(const DocumentSpec &url);
 
   ISocketStream *read_body();
   bool download_to_file(const Filename &filename, bool subdocument_resumes = true);
@@ -166,7 +168,7 @@ private:
   bool run_download_to_file();
   bool run_download_to_ram();
 
-  void begin_request(HTTPEnum::Method method, const URLSpec &url, 
+  void begin_request(HTTPEnum::Method method, const DocumentSpec &url, 
                      const string &body, bool nonblocking,
                      size_t first_byte, size_t last_byte);
   void reset_for_new_request();
@@ -190,7 +192,7 @@ private:
   void make_proxy_request_text();
   void make_request_text();
 
-  void set_url(const URLSpec &url);
+  void reset_url(const URLSpec &old_url, const URLSpec &new_url);
   void store_header_field(const string &field_name, const string &field_value);
 
 #ifndef NDEBUG
@@ -217,7 +219,8 @@ private:
   bool _nonblocking;
   string _send_extra_headers;
 
-  URLSpec _url;
+  DocumentSpec _document_spec;
+  DocumentSpec _request;
   HTTPEnum::Method _method;
   string request_path;
   string _header;

+ 199 - 0
panda/src/downloader/httpDate.I

@@ -0,0 +1,199 @@
+// Filename: httpDate.I
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: HTTPDate::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate::
+HTTPDate() : _time(-1) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate::
+HTTPDate(time_t time) : _time(time) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate::
+HTTPDate(const HTTPDate &copy) : _time(copy._time) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPDate::
+operator = (const HTTPDate &copy) {
+  _time = copy._time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::now (named constructor)
+//       Access: Published, Static
+//  Description: Returns an HTTPDate that represents the current time
+//               and date.
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate HTTPDate::
+now() {
+  return HTTPDate(time(NULL));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::is_valid
+//       Access: Published
+//  Description: Returns true if the date is meaningful, or false if
+//               it is -1 (which generally indicates the source string
+//               could not be parsed.)
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPDate::
+is_valid() const {
+  return (_time != (time_t)(-1));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::get_time
+//       Access: Published
+//  Description: Returns the date as a C time_t value.
+////////////////////////////////////////////////////////////////////
+INLINE time_t HTTPDate::
+get_time() const {
+  return _time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Operator ==
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPDate::
+operator == (const HTTPDate &other) const {
+  return _time == other._time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Operator !=
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPDate::
+operator != (const HTTPDate &other) const {
+  return !operator == (other);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Operator <
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPDate::
+operator < (const HTTPDate &other) const {
+  return _time < other._time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::compare_to
+//       Access: Published
+//  Description: Returns a number less than zero if this HTTPDate
+//               sorts before the other one, greater than zero if it
+//               sorts after, or zero if they are equivalent.
+////////////////////////////////////////////////////////////////////
+INLINE int HTTPDate::
+compare_to(const HTTPDate &other) const {
+  return (int)(_time - other._time);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::operator +=
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPDate::
+operator += (int seconds) {
+  _time += seconds;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::operator -=
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPDate::
+operator -= (int seconds) {
+  _time -= seconds;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::operator +
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate HTTPDate::
+operator + (int seconds) const {
+  return HTTPDate(_time + seconds);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::operator -
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPDate HTTPDate::
+operator - (int seconds) const {
+  return HTTPDate(_time - seconds);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::operator -
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int HTTPDate::
+operator - (const HTTPDate &other) const {
+  return (int)(_time - other._time);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPDate::
+output(ostream &out) const {
+  out << get_string();
+}
+
+
+INLINE ostream &
+operator << (ostream &out, const HTTPDate &date) {
+  date.output(out);
+  return out;
+}
+
+

+ 313 - 0
panda/src/downloader/httpDate.cxx

@@ -0,0 +1,313 @@
+// Filename: httpDate.cxx
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "httpDate.h"
+
+#include <ctype.h>
+
+static const int num_weekdays = 7;
+static const char * const weekdays[num_weekdays] = {
+  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const int num_months = 12;
+static const char * const months[num_months] = {
+  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::Constructor
+//       Access: Published
+//  Description: Decodes the string into a sensible date.  Returns 0
+//               (!is_valid()) if the string cannot be correctly
+//               decoded.
+////////////////////////////////////////////////////////////////////
+HTTPDate::
+HTTPDate(const string &format) {
+  _time = (time_t)(-1);
+
+  struct tm t;
+  memset(&t, 0, sizeof(t));
+
+  bool got_weekday = false;
+  bool got_month = false;
+  bool got_day = false;
+  bool got_year = false;
+  bool got_hour = false;
+  bool got_minute = false;
+  bool got_second = false;
+  bool got_timezone = false;
+
+  enum ExpectNext { 
+    EN_none,
+    EN_second,
+    EN_year
+  };
+  ExpectNext expect_next = EN_none;
+
+  size_t pos = 0;
+  string token = get_token(format, pos);
+  while (!token.empty()) {
+    ExpectNext expected = expect_next;
+    expect_next = EN_none;
+
+    if (isdigit(token[0])) {
+      // Here's a number.
+      int value = atoi(token.c_str());
+      if (token[token.length() - 1] == ':') {
+        // If it ends in a colon, it must be hh or mm.
+        if (!got_hour) {
+          t.tm_hour = value;
+          got_hour = true;
+          
+        } else if (!got_minute) {
+          t.tm_min = value;
+          got_minute = true;
+          expect_next = EN_second;
+
+        } else {
+          return;
+        }
+
+      } else if (token[token.length() - 1] == '/') {
+        // If it ends in a colon, it must be mm/dd/.
+        if (!got_month) {
+          t.tm_mon = value - 1;
+          got_month = true;
+          
+        } else if (!got_day) {
+          t.tm_mday = value;
+          got_day = true;
+          expect_next = EN_year;
+
+        } else {
+          return;
+        }
+
+      } else {
+        if (expected == EN_second) {
+          // The first number following hh:mm: is always the seconds.
+          t.tm_sec = value;
+          got_second = true;
+
+        } else if (expected == EN_year) {
+          // The first number following mm/dd/ is always the year.
+          t.tm_year = value;
+          got_year = true;
+          
+        } else if (!got_day) {
+          // Assume it's a day.
+          t.tm_mday = value;
+          got_day = true;
+          
+        } else if (!got_year) {
+          // It must be the year.
+          t.tm_year = value;
+          got_year = true;
+          
+        } else if (!got_hour) {
+          t.tm_hour = value;
+          got_hour = true;
+          
+        } else if (!got_minute) {
+          t.tm_min = value;
+          got_minute = true;
+          
+        } else if (!got_second) {
+          t.tm_sec = value;
+          got_second = true;
+          
+        } else {
+          // Huh, an unexpected numeric value.
+          return;
+        }
+      }
+
+    } else {
+      // This is a string token.  It should be either a month name or
+      // a day name, or a timezone name--but the only timezone name we
+      // expect to see is "GMT".
+      bool matched = false;
+      int i;
+
+      for (i = 0; i < num_weekdays && !matched; i++) {
+        if (token == weekdays[i]) {
+          if (got_weekday) {
+            return;
+          }
+          matched = true;
+          got_weekday = true;
+          t.tm_wday = i;
+        }
+      }
+
+      for (i = 0; i < num_months && !matched; i++) {
+        if (token == months[i]) {
+          if (got_month) {
+            return;
+          }
+          matched = true;
+          got_month = true;
+          t.tm_mon = i;
+        }
+      }
+
+      if (!matched && token == "Gmt") {
+        matched = true;
+        got_timezone = true;
+      }
+
+      if (!matched) {
+        // Couldn't figure this one out.
+        return;
+      }
+    }
+
+    token = get_token(format, pos);
+  }
+
+  // Now check that we got the minimum expected tokens.
+  if (!(got_month && got_day && got_year && got_hour && got_minute)) {
+    return;
+  }
+
+  // Also validate the tokens we did get.
+  if (t.tm_year < 100) {
+    // Two-digit year.  Assume it's in the same century, unless
+    // that assumption puts it more than 50 years in the future.
+    time_t now = time(NULL);
+    struct tm *tp = gmtime(&now);
+    t.tm_year += 100 * (tp->tm_year / 100);
+    if (t.tm_year - tp->tm_year > 50) {
+      t.tm_year -= 100;
+    }
+    
+  } else if (t.tm_year < 1900) {
+    // Invalid three- or four-digit year.  Give up.
+    return;
+    
+  } else {
+    t.tm_year -= 1900;
+  }
+          
+  if (!((t.tm_mon >= 0 && t.tm_mon < num_months) &&
+        (t.tm_mday >= 1 && t.tm_mday <= 31) &&
+        (t.tm_hour >= 0 && t.tm_hour < 60) &&
+        (t.tm_min >= 0 && t.tm_min < 60) &&
+        (t.tm_sec >= 0 && t.tm_sec < 62) /* maybe leap seconds */)) {
+    return;
+  }
+
+  // Everything checks out; convert the date.
+  _time = mktime(&t);
+
+  if (_time != (time_t)-1) {
+    // Unfortunately, mktime() assumes local time; convert this back
+    // to GMT.
+    extern long int timezone;
+    _time -= timezone;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::get_string
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+string HTTPDate::
+get_string() const {
+  if (!is_valid()) {
+    return "Invalid Date";
+  }
+
+  struct tm *tp = gmtime(&_time);
+
+  ostringstream result;
+  result
+    << weekdays[tp->tm_wday] << ", " 
+    << setw(2) << setfill('0') << tp->tm_mday << " "
+    << months[tp->tm_mon] << " "
+    << setw(4) << setfill('0') << tp->tm_year + 1900 << " "
+    << setw(2) << setfill('0') << tp->tm_hour << ":"
+    << setw(2) << setfill('0') << tp->tm_min << ":"
+    << setw(2) << setfill('0') << tp->tm_sec << " GMT";
+
+  return result.str();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPDate::get_token
+//       Access: Published
+//  Description: Extracts the next token from the string starting at
+//               the indicated position.  Returns the token and
+//               updates pos.  When the last token has been extracted,
+//               returns empty string.
+//
+//               A token is defined as a contiguous sequence of digits
+//               or letters.  If it is a sequence of letters, the
+//               function quietly truncates it to three letters before
+//               returning, and forces the first letter to capital and
+//               the second two to lowercase.  If it is a sequence of
+//               digits, the function also returns the next character
+//               following the last digit (unless it is a letter).
+////////////////////////////////////////////////////////////////////
+string HTTPDate::
+get_token(const string &str, size_t &pos) {
+  // Start by scanning for the first alphanumeric character.
+  size_t start = pos;
+  while (start < str.length() && !isalnum(str[start])) {
+    start++;
+  }
+
+  if (start >= str.length()) {
+    // End of the line.
+    pos = string::npos;
+    return string();
+  }
+
+  string token;
+
+  if (isalpha(str[start])) {
+    // A string of letters.
+    token = toupper(str[start]);
+    pos = start + 1;
+    while (pos < str.length() && isalpha(str[pos])) {
+      if (token.length() < 3) {
+        token += tolower(str[pos]);
+      }
+      pos++;
+    }
+
+  } else {
+    // A string of digits.
+    pos = start + 1;
+    while (pos < str.length() && isdigit(str[pos])) {
+      pos++;
+    }
+    // Get one more character, so we can identify things like hh:
+    if (pos < str.length() && !isalpha(str[pos])) {
+      pos++;
+    }
+    token = str.substr(start, pos - start);
+  }
+
+  return token;
+}

+ 70 - 0
panda/src/downloader/httpDate.h

@@ -0,0 +1,70 @@
+// Filename: httpDate.h
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 HTTPDATE_H
+#define HTTPDATE_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : HTTPDate
+// Description : A container for an HTTP-legal time/date indication.
+//               This can accept a string from an HTTP header and will
+//               decode it into a C time_t value; conversely, it can
+//               accept a time_t value and encode it for output as a
+//               string.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS HTTPDate {
+PUBLISHED:
+  INLINE HTTPDate();
+  INLINE HTTPDate(time_t time);
+  HTTPDate(const string &format);
+  INLINE HTTPDate(const HTTPDate &copy);
+  INLINE void operator = (const HTTPDate &copy);
+  INLINE static HTTPDate now();
+
+  INLINE bool is_valid() const;
+
+  string get_string() const;
+  INLINE time_t get_time() const;
+
+  INLINE bool operator == (const HTTPDate &other) const;
+  INLINE bool operator != (const HTTPDate &other) const;
+  INLINE bool operator < (const HTTPDate &other) const;
+  INLINE int compare_to(const HTTPDate &other) const;
+
+  INLINE void operator += (int seconds);
+  INLINE void operator -= (int seconds);
+
+  INLINE HTTPDate operator + (int seconds) const;
+  INLINE HTTPDate operator - (int seconds) const;
+  INLINE int operator - (const HTTPDate &other) const;
+
+  INLINE void output(ostream &out) const;
+
+private:
+  static string get_token(const string &str, size_t &pos);
+
+  time_t _time;
+};
+
+INLINE ostream &operator << (ostream &out, const URLSpec &url);
+
+#include "httpDate.I"
+
+#endif

+ 179 - 0
panda/src/downloader/httpEntityTag.I

@@ -0,0 +1,179 @@
+// Filename: httpEntityTag.I
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: HTTPEntityTag::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE HTTPEntityTag::
+HTTPEntityTag() {
+  _weak = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Constructor
+//       Access: Published
+//  Description: This constructor accepts an explicit weak flag and a
+//               literal (not quoted) tag string.
+////////////////////////////////////////////////////////////////////
+INLINE HTTPEntityTag::
+HTTPEntityTag(bool weak, const string &tag) :
+  _weak(weak),
+  _tag(tag)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE HTTPEntityTag::
+HTTPEntityTag(const HTTPEntityTag &copy) : 
+  _weak(copy._weak),
+  _tag(copy._tag)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPEntityTag::
+operator = (const HTTPEntityTag &copy) {
+  _weak = copy._weak;
+  _tag = copy._tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::is_weak
+//       Access: Published
+//  Description: Returns true if the entity tag is marked as "weak".
+//               A consistent weak entity tag does not guarantee that
+//               its resource has not changed in any way, but it does
+//               promise that the resource has not changed in any
+//               semantically meaningful way.
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+is_weak() const {
+  return _weak;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::get_tag
+//       Access: Published
+//  Description: Returns the tag as a literal string.
+////////////////////////////////////////////////////////////////////
+INLINE const string &HTTPEntityTag::
+get_tag() const {
+  return _tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::strong_equiv
+//       Access: Published
+//  Description: Returns true if the two tags have "strong" equivalence:
+//               they are the same tag, and both are "strong".
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+strong_equiv(const HTTPEntityTag &other) const {
+  return _tag == other._tag && !_weak && !other._weak;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::weak_equiv
+//       Access: Published
+//  Description: Returns true if the two tags have "weak" equivalence:
+//               they are the same tag, and one or both may be "weak".
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+weak_equiv(const HTTPEntityTag &other) const {
+  return _tag == other._tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Operator ==
+//       Access: Published
+//  Description: The == operator tests object equivalence; see also
+//               strong_equiv() and weak_equiv() for the two kinds of
+//               HTTP equivalence.
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+operator == (const HTTPEntityTag &other) const {
+  return _weak == other._weak && _tag == other._tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Operator !=
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+operator != (const HTTPEntityTag &other) const {
+  return !operator == (other);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Operator <
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HTTPEntityTag::
+operator < (const HTTPEntityTag &other) const {
+  if (_weak != other._weak) {
+    return (int)_weak < (int)other._weak;
+  }
+  return _tag < other._tag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::compare_to
+//       Access: Published
+//  Description: Returns a number less than zero if this HTTPEntityTag
+//               sorts before the other one, greater than zero if it
+//               sorts after, or zero if they are equivalent.
+////////////////////////////////////////////////////////////////////
+INLINE int HTTPEntityTag::
+compare_to(const HTTPEntityTag &other) const {
+  if (_weak != other._weak) {
+    return (int)_weak - (int)other._weak;
+  }
+  return strcmp(_tag.c_str(), other._tag.c_str());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPEntityTag::
+output(ostream &out) const {
+  out << get_string();
+}
+
+
+INLINE ostream &
+operator << (ostream &out, const HTTPEntityTag &entityTag) {
+  entityTag.output(out);
+  return out;
+}
+
+

+ 87 - 0
panda/src/downloader/httpEntityTag.cxx

@@ -0,0 +1,87 @@
+// Filename: httpEntityTag.cxx
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "httpEntityTag.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::Constructor
+//       Access: Published
+//  Description: This constructor accepts a string as formatted from
+//               an HTTP server (e.g. the tag is quoted, with an
+//               optional W/ prefix.)
+////////////////////////////////////////////////////////////////////
+HTTPEntityTag::
+HTTPEntityTag(const string &text) {
+  _weak = false;
+
+  size_t p = 0;
+  if (text.length() >= 2) {
+    string sub = text.substr(0, 2);
+    if (sub == "W/" || sub == "w/") {
+      _weak = true;
+      p = 2;
+    }
+  }
+
+  // Unquote the string.
+  bool quoted = false;
+  if (p < text.length() && text[p] == '"') {
+    quoted = true;
+    p++;
+  }
+  while (p < text.length() && !(quoted && text[p] == '"')) {
+    if (text[p] == '\\') {
+      p++;
+    }
+    _tag += text[p];
+    p++;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPEntityTag::get_string
+//       Access: Published
+//  Description: Returns the entity tag formatted for sending to an
+//               HTTP server (the tag is quoted, with a conditional W/
+//               prefix).
+////////////////////////////////////////////////////////////////////
+string HTTPEntityTag::
+get_string() const {
+  ostringstream result;
+  if (_weak) {
+    result << "W/";
+  }
+  result << '"';
+  
+  for (string::const_iterator ti = _tag.begin(); ti != _tag.end(); ++ti) {
+    switch (*ti) {
+    case '"':
+    case '\\':
+      result << '\\';
+      // fall through
+
+    default:
+      result << (*ti);
+    }
+  }
+
+  result << '"';
+
+  return result.str();
+}

+ 62 - 0
panda/src/downloader/httpEntityTag.h

@@ -0,0 +1,62 @@
+// Filename: httpEntityTag.h
+// Created by:  drose (28Jan03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 HTTPENTITYTAG_H
+#define HTTPENTITYTAG_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : HTTPEntityTag
+// Description : A container for an "entity tag" from an HTTP server.
+//               This is used to identify a particular version of a
+//               document or resource, particularly useful for
+//               verifying caches.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS HTTPEntityTag {
+PUBLISHED:
+  INLINE HTTPEntityTag();
+  HTTPEntityTag(const string &text);
+  INLINE HTTPEntityTag(bool weak, const string &tag);
+  INLINE HTTPEntityTag(const HTTPEntityTag &copy);
+  INLINE void operator = (const HTTPEntityTag &copy);
+
+  INLINE bool is_weak() const;
+  INLINE const string &get_tag() const;
+  string get_string() const;
+
+  INLINE bool strong_equiv(const HTTPEntityTag &other) const;
+  INLINE bool weak_equiv(const HTTPEntityTag &other) const;
+
+  INLINE bool operator == (const HTTPEntityTag &other) const;
+  INLINE bool operator != (const HTTPEntityTag &other) const;
+  INLINE bool operator < (const HTTPEntityTag &other) const;
+  INLINE int compare_to(const HTTPEntityTag &other) const;
+
+  INLINE void output(ostream &out) const;
+
+private:
+  bool _weak;
+  string _tag;
+};
+
+INLINE ostream &operator << (ostream &out, const URLSpec &url);
+
+#include "httpEntityTag.I"
+
+#endif

+ 12 - 0
panda/src/downloader/urlSpec.I

@@ -77,6 +77,18 @@ operator < (const URLSpec &other) const {
   return _url < other._url;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: URLSpec::compare_to
+//       Access: Published
+//  Description: Returns a number less than zero if this URLSpec
+//               sorts before the other one, greater than zero if it
+//               sorts after, or zero if they are equivalent.
+////////////////////////////////////////////////////////////////////
+INLINE int URLSpec::
+compare_to(const URLSpec &other) const {
+  return strcmp(_url.c_str(), other._url.c_str());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: URLSpec::has_scheme
 //       Access: Published

+ 1 - 0
panda/src/downloader/urlSpec.h

@@ -41,6 +41,7 @@ PUBLISHED:
   INLINE bool operator == (const URLSpec &other) const;
   INLINE bool operator != (const URLSpec &other) const;
   INLINE bool operator < (const URLSpec &other) const;
+  INLINE int compare_to(const URLSpec &other) const;
 
   INLINE bool has_scheme() const;
   INLINE bool has_authority() const;