diff --git a/panda/src/downloader/chunkedStream.I b/panda/src/downloader/chunkedStream.I index 3e2cbb7c27..9915dda94c 100644 --- a/panda/src/downloader/chunkedStream.I +++ b/panda/src/downloader/chunkedStream.I @@ -32,8 +32,9 @@ IChunkedStream() : istream(&_buf) { // Description: //////////////////////////////////////////////////////////////////// INLINE IChunkedStream:: -IChunkedStream(istream *source, bool owns_source) : istream(&_buf) { - open(source, owns_source); +IChunkedStream(istream *source, bool owns_source, + HTTPDocument *doc) : istream(&_buf) { + open(source, owns_source, doc); } //////////////////////////////////////////////////////////////////// @@ -42,9 +43,9 @@ IChunkedStream(istream *source, bool owns_source) : istream(&_buf) { // Description: //////////////////////////////////////////////////////////////////// INLINE IChunkedStream &IChunkedStream:: -open(istream *source, bool owns_source) { +open(istream *source, bool owns_source, HTTPDocument *doc) { clear(0); - _buf.open_read(source, owns_source); + _buf.open_read(source, owns_source, doc); return *this; } diff --git a/panda/src/downloader/chunkedStream.h b/panda/src/downloader/chunkedStream.h index 226a148f8e..8df9d87e7b 100644 --- a/panda/src/downloader/chunkedStream.h +++ b/panda/src/downloader/chunkedStream.h @@ -23,6 +23,8 @@ #include "chunkedStreamBuf.h" +class HTTPDocument; + //////////////////////////////////////////////////////////////////// // Class : IChunkedStream // Description : An input stream object that reads data from a source @@ -34,9 +36,11 @@ class EXPCL_PANDAEXPRESS IChunkedStream : public istream { public: INLINE IChunkedStream(); - INLINE IChunkedStream(istream *source, bool owns_source); + INLINE IChunkedStream(istream *source, bool owns_source, + HTTPDocument *doc = NULL); - INLINE IChunkedStream &open(istream *source, bool owns_source); + INLINE IChunkedStream &open(istream *source, bool owns_source, + HTTPDocument *doc = NULL); INLINE IChunkedStream &close(); private: diff --git a/panda/src/downloader/chunkedStreamBuf.cxx b/panda/src/downloader/chunkedStreamBuf.cxx index 29de97a126..7b759f0961 100644 --- a/panda/src/downloader/chunkedStreamBuf.cxx +++ b/panda/src/downloader/chunkedStreamBuf.cxx @@ -64,14 +64,26 @@ ChunkedStreamBuf:: //////////////////////////////////////////////////////////////////// // Function: ChunkedStreamBuf::open_read // Access: Public -// Description: +// Description: If the document pointer is non-NULL, it will be +// updated with the length of the file as it is derived +// from the chunked encoding. //////////////////////////////////////////////////////////////////// void ChunkedStreamBuf:: -open_read(istream *source, bool owns_source) { +open_read(istream *source, bool owns_source, HTTPDocument *doc) { _source = source; _owns_source = owns_source; _chunk_remaining = 0; _done = false; + _doc = doc; + + if (_doc != (HTTPDocument *)NULL) { + _doc->_file_size = 0; + + // Read a little bit from the file to get the first chunk (and + // therefore the file size, or at least the size of the first + // chunk). + underflow(); + } } //////////////////////////////////////////////////////////////////// @@ -157,6 +169,10 @@ read_chars(char *start, size_t length) { return 0; } + if (_doc != (HTTPDocument *)NULL) { + _doc->_file_size += (size_t)chunk_size; + } + _chunk_remaining = (size_t)chunk_size; return read_chars(start, length); } diff --git a/panda/src/downloader/chunkedStreamBuf.h b/panda/src/downloader/chunkedStreamBuf.h index 3c3cab6aa3..f77692b6c4 100644 --- a/panda/src/downloader/chunkedStreamBuf.h +++ b/panda/src/downloader/chunkedStreamBuf.h @@ -20,6 +20,8 @@ #define CHUNKEDSTREAMBUF_H #include "pandabase.h" +#include "httpDocument.h" +#include "pointerTo.h" //////////////////////////////////////////////////////////////////// // Class : ChunkedStreamBuf @@ -31,7 +33,7 @@ public: ChunkedStreamBuf(); virtual ~ChunkedStreamBuf(); - void open_read(istream *source, bool owns_source); + void open_read(istream *source, bool owns_source, HTTPDocument *doc); void close_read(); protected: @@ -44,6 +46,8 @@ private: bool _owns_source; size_t _chunk_remaining; bool _done; + + PT(HTTPDocument) _doc; }; #endif diff --git a/panda/src/downloader/downloadDb.cxx b/panda/src/downloader/downloadDb.cxx index c040e5c6ec..395cdb3c20 100644 --- a/panda/src/downloader/downloadDb.cxx +++ b/panda/src/downloader/downloadDb.cxx @@ -803,7 +803,7 @@ read(istream &read_stream, bool want_server_info) { uchar *header_buf = new uchar[_header_length]; // Read the header read_stream.read((char *)header_buf, _header_length); - if (read_stream.gcount() != _header_length) { + if (read_stream.gcount() != (size_t)_header_length) { downloader_cat.error() << "DownloadDb::read() - Empty file" << endl; return false; } diff --git a/panda/src/downloader/downloader.cxx b/panda/src/downloader/downloader.cxx index 0bd0634bc5..c7ad54a526 100644 --- a/panda/src/downloader/downloader.cxx +++ b/panda/src/downloader/downloader.cxx @@ -16,16 +16,16 @@ // //////////////////////////////////////////////////////////////////// -#include "config_downloader.h" -#include -#include +#include "downloader.h" +#include "config_downloader.h" +#include "urlSpec.h" +#include "error_utils.h" +#include "filename.h" #include #include -#include "downloader.h" - #if !defined(WIN32_VC) #include #include @@ -92,12 +92,11 @@ Downloader:: // Description: //////////////////////////////////////////////////////////////////// int Downloader:: -connect_to_server_by_proxy(const string &proxy_name, uint proxy_port, - const string &server_name) { - if (connect_to_server(proxy_name, proxy_port) != EU_success) { +connect_to_server_by_proxy(const URLSpec &proxy, const string &server_name) { + if (connect_to_server(proxy.get_server(), proxy.get_port()) != EU_success) { downloader_cat.error() << "Downloader::connect_to_server_by_proxy() - could not connect to: " - << proxy_name << endl; + << proxy << endl; return EU_error_abort; } @@ -544,7 +543,7 @@ run(void) { } // Attempt to receive the bytes from the socket - int fret; + int fret = 0; // Handle the case of a fast connection if (_receive_size > (ulong)MAX_RECEIVE_BYTES) { int repeat = (int)(_receive_size / MAX_RECEIVE_BYTES); @@ -667,7 +666,7 @@ run_to_ram(void) { } // Attempt to receive the bytes from the socket - int fret; + int fret = 0; // Handle the case of a fast connection if (_receive_size > (ulong)MAX_RECEIVE_BYTES) { int repeat = (int)(_receive_size / MAX_RECEIVE_BYTES); diff --git a/panda/src/downloader/downloader.h b/panda/src/downloader/downloader.h index dcca274b8b..0d967a6dd2 100644 --- a/panda/src/downloader/downloader.h +++ b/panda/src/downloader/downloader.h @@ -37,6 +37,8 @@ #include #endif +class URLSpec; + //////////////////////////////////////////////////////////////////// // Class : Downloader // Description : @@ -46,8 +48,7 @@ PUBLISHED: Downloader(void); virtual ~Downloader(void); - int connect_to_server_by_proxy(const string &proxy_name, uint proxy_port, - const string &server_name); + int connect_to_server_by_proxy(const URLSpec &proxy, const string &server_name); int connect_to_server(const string &name, uint port=80); void disconnect_from_server(void); diff --git a/panda/src/downloader/httpClient.I b/panda/src/downloader/httpClient.I index f58f8b0016..60f2a9ce0c 100644 --- a/panda/src/downloader/httpClient.I +++ b/panda/src/downloader/httpClient.I @@ -39,6 +39,30 @@ get_proxy() const { return _proxy; } +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::set_http_version +// Access: Published +// Description: Specifies the version of HTTP that the client uses to +// identify itself to the server. The default is HV_11, +// or HTTP 1.0; you can set this to HV_10 (HTTP 1.0) to +// request the server use the older interface. +//////////////////////////////////////////////////////////////////// +INLINE void HTTPClient:: +set_http_version(HTTPClient::HTTPVersion version) { + _http_version = version; +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::get_http_version +// Access: Published +// Description: Returns the client's current setting for HTTP +// version. See set_http_version(). +//////////////////////////////////////////////////////////////////// +INLINE HTTPClient::HTTPVersion HTTPClient:: +get_http_version() const { + return _http_version; +} + //////////////////////////////////////////////////////////////////// // Function: HTTPClient::set_verify_ssl // Access: Published @@ -68,3 +92,37 @@ INLINE bool HTTPClient:: get_verify_ssl() const { return _verify_ssl; } + +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::get_document +// Access: Published +// Description: Opens the named document for reading, or if body is +// nonempty, posts data for a particular URL and +// retrieves the response. Returns a new HTTPDocument +// object whether the document is successfully read or +// not; you can test is_valid() and get_return_code() to +// determine whether the document was retrieved. +//////////////////////////////////////////////////////////////////// +INLINE PT(HTTPDocument) HTTPClient:: +get_document(const URLSpec &url, const string &body) { + const char *method = "GET"; + if (!body.empty()) { + method = "POST"; + } + + return make_request(method, url, body); +} + +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::get_header +// Access: Published +// Description: Like get_document(), except only the header +// associated with the file is retrieved. This may be +// used to test for existence of the file; it might also +// return the size of the file (if the server gives us +// this information). +//////////////////////////////////////////////////////////////////// +INLINE PT(HTTPDocument) HTTPClient:: +get_header(const URLSpec &url) { + return make_request("HEAD", url, string()); +} diff --git a/panda/src/downloader/httpClient.cxx b/panda/src/downloader/httpClient.cxx index 214dbbe5a8..fe1ff0783f 100644 --- a/panda/src/downloader/httpClient.cxx +++ b/panda/src/downloader/httpClient.cxx @@ -43,6 +43,7 @@ X509_STORE *HTTPClient::_x509_store = NULL; //////////////////////////////////////////////////////////////////// HTTPClient:: HTTPClient() { + _http_version = HV_11; _verify_ssl = verify_ssl; make_ctx(); } @@ -54,6 +55,9 @@ HTTPClient() { //////////////////////////////////////////////////////////////////// HTTPClient:: HTTPClient(const HTTPClient ©) { + // We can initialize these to default values because the operator = + // function will copy them in a second. + _http_version = HV_11; _verify_ssl = verify_ssl; make_ctx(); @@ -68,6 +72,7 @@ HTTPClient(const HTTPClient ©) { void HTTPClient:: operator = (const HTTPClient ©) { _proxy = copy._proxy; + _http_version = copy._http_version; set_verify_ssl(copy._verify_ssl); clear_expected_servers(); @@ -99,6 +104,25 @@ HTTPClient:: clear_expected_servers(); } +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::get_http_version_string +// Access: Published +// Description: Returns the current HTTP version setting as a string, +// e.g. "HTTP/1.0" or "HTTP/1.1". +//////////////////////////////////////////////////////////////////// +string HTTPClient:: +get_http_version_string() const { + switch (_http_version) { + case HV_10: + return "HTTP/1.0"; + + case HV_11: + return "HTTP/1.1"; + } + + return "unknown"; +} + //////////////////////////////////////////////////////////////////// // Function: HTTPClient::load_certificates // Access: Published @@ -183,36 +207,6 @@ clear_expected_servers() { _expected_servers.clear(); } -//////////////////////////////////////////////////////////////////// -// Function: HTTPClient::get_document -// Access: Published -// Description: Opens the named document for reading, or if body is -// nonempty, posts data for a particular URL and -// retrieves the response. Returns a new HTTPDocument -// object whether the document is successfully read or -// not; you can test is_valid() and get_return_code() to -// determine whether the document was retrieved. -//////////////////////////////////////////////////////////////////// -PT(HTTPDocument) HTTPClient:: -get_document(const URLSpec &url, const string &body) { - BIO *bio; - - if (_proxy.empty()) { - if (url.get_scheme() == "https") { - bio = get_https(url, body); - } else { - bio = get_http(url, body); - } - } else { - if (url.get_scheme() == "https") { - bio = get_https_proxy(url, body); - } else { - bio = get_http_proxy(url, body); - } - } - - return new HTTPDocument(bio, true); -} //////////////////////////////////////////////////////////////////// // Function: HTTPClient::make_ctx @@ -379,6 +373,36 @@ load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) { return count; } +//////////////////////////////////////////////////////////////////// +// Function: HTTPClient::make_request +// Access: Private +// Description: Chooses a suitable mechanism to handle this request, +// based on whether it is an http or https request, and +// according to whether we have a proxy in place. +// Issues the request and returns an HTTPDocument that +// represents the results. +//////////////////////////////////////////////////////////////////// +PT(HTTPDocument) HTTPClient:: +make_request(const string &method, const URLSpec &url, const string &body) { + BIO *bio; + + if (_proxy.empty()) { + if (url.get_scheme() == "https") { + bio = get_https(method, url, body); + } else { + bio = get_http(method, url, body); + } + } else { + if (url.get_scheme() == "https") { + bio = get_https_proxy(method, url, body); + } else { + bio = get_http_proxy(method, url, body); + } + } + + return new HTTPDocument(bio, true); +} + //////////////////////////////////////////////////////////////////// // Function: HTTPClient::get_https // Access: Private @@ -386,7 +410,7 @@ load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) { // document. //////////////////////////////////////////////////////////////////// BIO *HTTPClient:: -get_http(const URLSpec &url, const string &body) { +get_http(const string &method, const URLSpec &url, const string &body) { ostringstream server; server << url.get_server() << ":" << url.get_port(); string server_str = server.str(); @@ -405,7 +429,7 @@ get_http(const URLSpec &url, const string &body) { return NULL; } - send_get_request(bio, url.get_path(), url.get_server(), body); + send_request(bio, method, url.get_path(), url.get_server(), body); return bio; } @@ -416,7 +440,7 @@ get_http(const URLSpec &url, const string &body) { // document. //////////////////////////////////////////////////////////////////// BIO *HTTPClient:: -get_https(const URLSpec &url, const string &body) { +get_https(const string &method, const URLSpec &url, const string &body) { ostringstream server; server << url.get_server() << ":" << url.get_port(); string server_str = server.str(); @@ -438,7 +462,7 @@ get_https(const URLSpec &url, const string &body) { BIO *sbio = make_https_connection(bio, url); if (sbio != (BIO *)NULL) { - send_get_request(sbio, url.get_path(), url.get_server(), body); + send_request(sbio, method, url.get_path(), url.get_server(), body); } return sbio; } @@ -450,7 +474,7 @@ get_https(const URLSpec &url, const string &body) { // http document. //////////////////////////////////////////////////////////////////// BIO *HTTPClient:: -get_http_proxy(const URLSpec &url, const string &body) { +get_http_proxy(const string &method, const URLSpec &url, const string &body) { ostringstream proxy_server; proxy_server << _proxy.get_server() << ":" << _proxy.get_port(); string proxy_server_str = proxy_server.str(); @@ -470,7 +494,7 @@ get_http_proxy(const URLSpec &url, const string &body) { return NULL; } - send_get_request(bio, url, url.get_server(), body); + send_request(bio, method, url, url.get_server(), body); return bio; } @@ -481,7 +505,7 @@ get_http_proxy(const URLSpec &url, const string &body) { // document. //////////////////////////////////////////////////////////////////// BIO *HTTPClient:: -get_https_proxy(const URLSpec &url, const string &body) { +get_https_proxy(const string &method, const URLSpec &url, const string &body) { // First, ask the proxy to open a connection for us. ostringstream proxy_server; proxy_server << _proxy.get_server() << ":" << _proxy.get_port(); @@ -505,7 +529,7 @@ get_https_proxy(const URLSpec &url, const string &body) { { ostringstream request; request - << "CONNECT " << url.get_authority() << " HTTP/1.1\r\n" + << "CONNECT " << url.get_authority() << " " << get_http_version_string() << "\r\n"; string request_str = request.str(); @@ -540,7 +564,7 @@ get_https_proxy(const URLSpec &url, const string &body) { // us.) if ((doc->get_status_code() / 100) == 4) { BIO_free_all(bio); - return get_http_proxy(url, body); + return get_http_proxy(method, url, body); } } return NULL; @@ -557,7 +581,7 @@ get_https_proxy(const URLSpec &url, const string &body) { BIO *sbio = make_https_connection(bio, url); if (sbio != (BIO *)NULL) { - send_get_request(sbio, url.get_path(), url.get_server(), body); + send_request(sbio, method, url.get_path(), url.get_server(), body); } return sbio; } @@ -630,7 +654,7 @@ make_https_connection(BIO *bio, const URLSpec &url) const { downloader_cat.info() << "Server is " << common_name << " from " << org_unit_name - << " of " << org_name << "\n"; + << " / " << org_name << "\n"; if (!verify_server(subject)) { downloader_cat.info() @@ -824,34 +848,30 @@ certificate signing //////////////////////////////////////////////////////////////////// -// Function: HTTPClient::send_get_request +// Function: HTTPClient::send_request // Access: Private -// Description: Sends the appropriate GET or POST request to the -// server on the indicated connection. +// Description: Sends the appropriate GET or POST (or whatever) +// request to the server on the indicated connection. //////////////////////////////////////////////////////////////////// void HTTPClient:: -send_get_request(BIO *bio, - const string &path, const string &server, - const string &body) const { +send_request(BIO *bio, const string &method, + const string &path, const string &server, + const string &body) const { ostringstream request; - if (body.empty()) { + request + << method << " " << path << " " << get_http_version_string() << "\r\n"; + + if (_http_version > HV_10) { request - << "GET " << path << " HTTP/1.1\r\n" << "Host: " << server << "\r\n" - << "Connection: close\r\n" - << "\r\n"; - } else { - request - << "POST " << path << " HTTP/1.1\r\n" - << "Host: " << server << "\r\n" - << "Connection: close\r\n" - << "Content-type: application/x-www-form-urlencoded\r\n" - << "Content-Length: " << body.length() << "\r\n" - << "\r\n" - << body; + << "Connection: close\r\n"; } + request + << "\r\n" + << body; + string request_str = request.str(); #ifndef NDEBUG if (downloader_cat.is_debug()) { diff --git a/panda/src/downloader/httpClient.h b/panda/src/downloader/httpClient.h index 671b891101..166525d73d 100644 --- a/panda/src/downloader/httpClient.h +++ b/panda/src/downloader/httpClient.h @@ -60,6 +60,15 @@ PUBLISHED: INLINE void set_proxy(const URLSpec &proxy); INLINE const URLSpec &get_proxy() const; + enum HTTPVersion { + HV_10, // HTTP 1.0 + HV_11, // HTTP 1.1 + }; + + INLINE void set_http_version(HTTPVersion version); + INLINE HTTPVersion get_http_version() const; + string get_http_version_string() const; + bool load_certificates(const Filename &filename); INLINE void set_verify_ssl(bool verify_ssl); @@ -68,22 +77,27 @@ PUBLISHED: bool add_expected_server(const string &server_attributes); void clear_expected_servers(); - PT(HTTPDocument) get_document(const URLSpec &url, const string &body = string()); + INLINE PT(HTTPDocument) get_document(const URLSpec &url, + const string &body = string()); + INLINE PT(HTTPDocument) get_header(const URLSpec &url); private: void make_ctx(); static void initialize_ssl(); static int load_verify_locations(SSL_CTX *ctx, const Filename &ca_file); - BIO *get_http(const URLSpec &url, const string &body); - BIO *get_https(const URLSpec &url, const string &body); - BIO *get_http_proxy(const URLSpec &url, const string &body); - BIO *get_https_proxy(const URLSpec &url, const string &body); + PT(HTTPDocument) make_request(const string &method, const URLSpec &url, + const string &body); + + BIO *get_http(const string &method, const URLSpec &url, const string &body); + BIO *get_https(const string &method, const URLSpec &url, const string &body); + BIO *get_http_proxy(const string &method, const URLSpec &url, const string &body); + BIO *get_https_proxy(const string &method, const URLSpec &url, const string &body); BIO *make_https_connection(BIO *bio, const URLSpec &url) const; - void send_get_request(BIO *bio, - const string &path, const string &server, - const string &body) const; + void send_request(BIO *bio, const string &method, + const string &path, const string &server, + const string &body) const; bool verify_server(X509_NAME *subject) const; static X509_NAME *parse_x509_name(const string &source); @@ -101,6 +115,7 @@ private: #endif URLSpec _proxy; + HTTPVersion _http_version; bool _verify_ssl; // List of allowable SSL servers to connect to. If the list is diff --git a/panda/src/downloader/httpDocument.I b/panda/src/downloader/httpDocument.I index b91c690c5c..f0ccefdcfb 100644 --- a/panda/src/downloader/httpDocument.I +++ b/panda/src/downloader/httpDocument.I @@ -64,3 +64,17 @@ INLINE const string &HTTPDocument:: get_status_string() const { return _status_string; } + +//////////////////////////////////////////////////////////////////// +// Function: HTTPDocument::get_file_size +// Access: Published +// Description: Returns the size of the file, if it is known. +// Returns 0 if the file size is not known. This may +// increase as the file is read due to the nature of +// HTTP/1.1 requests which can change their minds +// midstream about how much data they're sending you. +//////////////////////////////////////////////////////////////////// +INLINE size_t HTTPDocument:: +get_file_size() const { + return _file_size; +} diff --git a/panda/src/downloader/httpDocument.cxx b/panda/src/downloader/httpDocument.cxx index f30ade6912..56f30e17d8 100644 --- a/panda/src/downloader/httpDocument.cxx +++ b/panda/src/downloader/httpDocument.cxx @@ -31,9 +31,12 @@ TypeHandle HTTPDocument::_type_handle; //////////////////////////////////////////////////////////////////// HTTPDocument:: HTTPDocument(BIO *bio, bool owns_bio) { + _file_size = 0; + if (bio != (BIO *)NULL) { _source = new IBioStream(bio, owns_bio); read_headers(); + determine_content_length(); } else { _source = (IBioStream *)NULL; @@ -94,7 +97,8 @@ is_regular_file() const { // Description: Opens the document for reading. Returns a newly // allocated istream on success (which you should // eventually delete when you are done reading). -// Returns NULL on failure. +// Returns NULL on failure. This may only be called +// once for a particular HTTPDocument. //////////////////////////////////////////////////////////////////// istream *HTTPDocument:: open_read_file() const { @@ -109,14 +113,13 @@ open_read_file() const { (*si) = tolower(*si); } + istream *result = _source; if (transfer_coding == "chunked") { - return new IChunkedStream(_source, false); - - } else { - istream *result = _source; - ((HTTPDocument *)this)->_source = (IBioStream *)NULL; - return result; + result = new IChunkedStream(_source, true, (HTTPDocument *)this); } + + ((HTTPDocument *)this)->_source = (IBioStream *)NULL; + return result; } //////////////////////////////////////////////////////////////////// @@ -256,4 +259,19 @@ read_headers() { // A blank line terminates the headers. } +//////////////////////////////////////////////////////////////////// +// Function: HTTPDocument::determine_content_length +// Access: Private +// Description: Determines the file size based on the Content-Length +// field if it has been supplied. +//////////////////////////////////////////////////////////////////// +void HTTPDocument:: +determine_content_length() { + string content_length = get_header_value("Content-Length"); + if (!content_length.empty()) { + _file_size = atoi(content_length.c_str()); + } +} + + #endif // HAVE_SSL diff --git a/panda/src/downloader/httpDocument.h b/panda/src/downloader/httpDocument.h index 8c52fb4f8d..7670366dc2 100644 --- a/panda/src/downloader/httpDocument.h +++ b/panda/src/downloader/httpDocument.h @@ -57,10 +57,13 @@ PUBLISHED: INLINE const string &get_status_string() const; string get_header_value(const string &key) const; + INLINE size_t get_file_size() const; + void write_headers(ostream &out) const; private: void read_headers(); + void determine_content_length(); IBioStream *_source; @@ -71,6 +74,8 @@ private: typedef pmap Headers; Headers _headers; + size_t _file_size; + public: virtual TypeHandle get_type() const { @@ -88,6 +93,7 @@ public: private: static TypeHandle _type_handle; + friend class ChunkedStreamBuf; }; #include "httpDocument.I"