diff --git a/panda/src/downloader/Sources.pp b/panda/src/downloader/Sources.pp index 76084231c3..6c8e7f644e 100644 --- a/panda/src/downloader/Sources.pp +++ b/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 \ diff --git a/panda/src/downloader/documentSpec.I b/panda/src/downloader/documentSpec.I new file mode 100644 index 0000000000..9d15c325e2 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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 ©) : + _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 ©) { + _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; +} diff --git a/panda/src/downloader/documentSpec.cxx b/panda/src/downloader/documentSpec.cxx new file mode 100644 index 0000000000..cac9687357 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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"; + } +} diff --git a/panda/src/downloader/documentSpec.h b/panda/src/downloader/documentSpec.h new file mode 100644 index 0000000000..d73e25c948 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 ©); + INLINE void operator = (const DocumentSpec ©); + + 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 diff --git a/panda/src/downloader/downloader_composite1.cxx b/panda/src/downloader/downloader_composite1.cxx index 7eac478e90..5abc4359b9 100644 --- a/panda/src/downloader/downloader_composite1.cxx +++ b/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" + diff --git a/panda/src/downloader/httpChannel.I b/panda/src/downloader/httpChannel.I index 305c6b3fc0..4ac3eee1c4 100644 --- a/panda/src/downloader/httpChannel.I +++ b/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); } diff --git a/panda/src/downloader/httpChannel.cxx b/panda/src/downloader/httpChannel.cxx index 8cd7c2d709..fc09f84f7e 100644 --- a/panda/src/downloader/httpChannel.cxx +++ b/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; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/downloader/httpChannel.h b/panda/src/downloader/httpChannel.h index 1d9a3711d2..f95f49d2a9 100644 --- a/panda/src/downloader/httpChannel.h +++ b/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 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 URLSpec &url); - INLINE void begin_get_subdocument(const URLSpec &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; diff --git a/panda/src/downloader/httpDate.I b/panda/src/downloader/httpDate.I new file mode 100644 index 0000000000..a8f273f3e0 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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 ©) : _time(copy._time) { +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDate::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void HTTPDate:: +operator = (const HTTPDate ©) { + _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; +} + + diff --git a/panda/src/downloader/httpDate.cxx b/panda/src/downloader/httpDate.cxx new file mode 100644 index 0000000000..ad3580073d --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "httpDate.h" + +#include + +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; +} diff --git a/panda/src/downloader/httpDate.h b/panda/src/downloader/httpDate.h new file mode 100644 index 0000000000..e2db770669 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 ©); + INLINE void operator = (const HTTPDate ©); + 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 diff --git a/panda/src/downloader/httpEntityTag.I b/panda/src/downloader/httpEntityTag.I new file mode 100644 index 0000000000..7dca7d6904 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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 ©) : + _weak(copy._weak), + _tag(copy._tag) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPEntityTag::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void HTTPEntityTag:: +operator = (const HTTPEntityTag ©) { + _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; +} + + diff --git a/panda/src/downloader/httpEntityTag.cxx b/panda/src/downloader/httpEntityTag.cxx new file mode 100644 index 0000000000..ca9919152f --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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(); +} diff --git a/panda/src/downloader/httpEntityTag.h b/panda/src/downloader/httpEntityTag.h new file mode 100644 index 0000000000..a99cba1e53 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 ©); + INLINE void operator = (const HTTPEntityTag ©); + + 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 diff --git a/panda/src/downloader/urlSpec.I b/panda/src/downloader/urlSpec.I index e33d9d1a47..47337b406e 100644 --- a/panda/src/downloader/urlSpec.I +++ b/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 diff --git a/panda/src/downloader/urlSpec.h b/panda/src/downloader/urlSpec.h index 6fe34bdaa3..9b0350a880 100644 --- a/panda/src/downloader/urlSpec.h +++ b/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;