robustify http handling

This commit is contained in:
David Rose 2002-10-09 23:45:46 +00:00
parent 8931a17f0a
commit 893078a291
18 changed files with 1609 additions and 279 deletions

View File

@ -18,6 +18,7 @@
extractor.h \
httpClient.I httpClient.h \
httpDocument.I httpDocument.h \
identityStream.I identityStream.h identityStreamBuf.h \
multiplexStream.I multiplexStream.h \
multiplexStreamBuf.I multiplexStreamBuf.h \
urlSpec.I urlSpec.h \
@ -33,6 +34,7 @@
extractor.cxx \
httpClient.cxx \
httpDocument.cxx \
identityStream.cxx identityStreamBuf.cxx \
multiplexStream.cxx multiplexStreamBuf.cxx \
urlSpec.cxx \
$[if $[HAVE_NET], downloadDb.cxx downloader.cxx] \
@ -49,6 +51,7 @@
extractor.h \
httpClient.I httpClient.h \
httpDocument.I httpDocument.h \
identityStream.I identityStream.h identityStreamBuf.h \
multiplexStream.I multiplexStream.h \
multiplexStreamBuf.I multiplexStreamBuf.I \
patcher.h patcher.I \

View File

@ -33,14 +33,15 @@ class HTTPDocument;
//
// Seeking is not supported.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEXPRESS IChunkedStream : public istream {
// No need to export from DLL.
class IChunkedStream : public istream {
public:
INLINE IChunkedStream();
INLINE IChunkedStream(istream *source, bool owns_source,
HTTPDocument *doc = NULL);
HTTPDocument *doc);
INLINE IChunkedStream &open(istream *source, bool owns_source,
HTTPDocument *doc = NULL);
HTTPDocument *doc);
INLINE IChunkedStream &close();
private:
@ -52,3 +53,4 @@ private:
#endif

View File

@ -77,6 +77,7 @@ open_read(istream *source, bool owns_source, HTTPDocument *doc) {
_doc = doc;
if (_doc != (HTTPDocument *)NULL) {
_read_index = doc->_read_index;
_doc->_file_size = 0;
// Read a little bit from the file to get the first chunk (and
@ -162,17 +163,20 @@ read_chars(char *start, size_t length) {
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
int chunk_size = strtol(line.c_str(), NULL, 16);
if (chunk_size <= 0) {
size_t chunk_size = (size_t)strtol(line.c_str(), NULL, 16);
if (chunk_size == 0) {
// Last chunk; we're done.
_done = true;
if (_doc != (HTTPDocument *)NULL && _read_index == _doc->_read_index) {
_doc->_state = HTTPDocument::S_read_body;
}
return 0;
}
if (_doc != (HTTPDocument *)NULL) {
_doc->_file_size += (size_t)chunk_size;
if (_doc != (HTTPDocument *)NULL && _read_index == _doc->_read_index) {
_doc->_file_size += chunk_size;
}
_chunk_remaining = (size_t)chunk_size;
_chunk_remaining = chunk_size;
return read_chars(start, length);
}

View File

@ -28,7 +28,8 @@
// Description : The streambuf object that implements
// IChunkedStream.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEXPRESS ChunkedStreamBuf : public streambuf {
// No need to export from DLL.
class ChunkedStreamBuf : public streambuf {
public:
ChunkedStreamBuf();
virtual ~ChunkedStreamBuf();
@ -48,6 +49,7 @@ private:
bool _done;
PT(HTTPDocument) _doc;
int _read_index;
};
#endif

View File

@ -7,6 +7,8 @@
#include "extractor.cxx"
#include "httpClient.cxx"
#include "httpDocument.cxx"
#include "identityStream.cxx"
#include "identityStreamBuf.cxx"
#include "multiplexStream.cxx"
#include "multiplexStreamBuf.cxx"
#include "urlSpec.cxx"

View File

@ -92,37 +92,3 @@ 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());
}

View File

@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////
#include "httpClient.h"
#include "httpDocument.h"
#include "config_downloader.h"
#include "filename.h"
#include "config_express.h"
@ -106,6 +107,48 @@ HTTPClient::
clear_expected_servers();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::set_username
// Access: Published
// Description: Specifies the username:password string corresponding
// to a particular server and/or realm, when demanded by
// the server. Either or both of the server or realm
// may be empty; if so, they match anything. Also, the
// server may be set to the special string "*proxy",
// which will match any proxy server.
//
// If the username is set to the empty string, this
// clears the password for the particular server/realm
// pair.
////////////////////////////////////////////////////////////////////
void HTTPClient::
set_username(const string &server, const string &realm, const string &username) {
string key = server + ":" + realm;
if (username.empty()) {
_usernames.erase(key);
} else {
_usernames[key] = username;
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_username
// Access: Published
// Description: Returns the username:password string set for this
// server/realm pair, or empty string if nothing has
// been set. See set_username().
////////////////////////////////////////////////////////////////////
string HTTPClient::
get_username(const string &server, const string &realm) const {
string key = server + ":" + realm;
Usernames::const_iterator ui;
ui = _usernames.find(key);
if (ui != _usernames.end()) {
return (*ui).second;
}
return string();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_http_version_string
// Access: Published
@ -120,11 +163,34 @@ get_http_version_string() const {
case HV_11:
return "HTTP/1.1";
case HV_other:
// Report the best we can do.
return "HTTP/1.1";
}
return "unknown";
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::parse_http_version_string
// Access: Published
// Description: Matches the string representing a particular HTTP
// version against any of the known versions and returns
// the appropriate enumerated value, or HV_other if the
// version is unknown.
////////////////////////////////////////////////////////////////////
HTTPClient::HTTPVersion HTTPClient::
parse_http_version_string(const string &version) {
if (version == "HTTP/1.0") {
return HV_10;
} else if (version == "HTTP/1.1") {
return HV_11;
} else {
return HV_other;
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::load_certificates
// Access: Published
@ -209,6 +275,55 @@ clear_expected_servers() {
_expected_servers.clear();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_documents
// Access: Published
// Description: Returns a new HTTPDocument object that may be used
// for reading multiple documents using the same
// connection, for greater network efficiency than
// calling HTTPClient::get_document() repeatedly (and
// thus forcing a new connection for each document).
////////////////////////////////////////////////////////////////////
PT(HTTPDocument) HTTPClient::
get_documents() {
PT(HTTPDocument) doc = new HTTPDocument(this);
doc->set_persistent_connection(true);
return doc;
}
////////////////////////////////////////////////////////////////////
// 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) {
PT(HTTPDocument) doc = new HTTPDocument(this);
doc->get_document(url, body);
return doc;
}
////////////////////////////////////////////////////////////////////
// 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).
////////////////////////////////////////////////////////////////////
PT(HTTPDocument) HTTPClient::
get_header(const URLSpec &url) {
PT(HTTPDocument) doc = new HTTPDocument(this);
doc->get_header(url);
return doc;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::make_ctx
@ -376,73 +491,44 @@ load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) {
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::make_request
// Function: HTTPClient::establish_connection
// 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.
// Description: Establishes a connection to the server, using the
// appropriate means. Returns the newly allocated BIO
// representing the connection, or NULL if a connection
// cannot be established.
////////////////////////////////////////////////////////////////////
PT(HTTPDocument) HTTPClient::
make_request(const string &method, const URLSpec &url, const string &body) {
BIO *HTTPClient::
establish_connection(const URLSpec &url) {
BIO *bio;
if (_proxy.empty()) {
if (url.get_scheme() == "https") {
bio = get_https(method, url, body);
bio = establish_https(url);
} else {
bio = get_http(method, url, body);
bio = establish_http(url);
}
} else {
if (url.get_scheme() == "https") {
bio = get_https_proxy(method, url, body);
bio = establish_https_proxy(url);
} else {
bio = get_http_proxy(method, url, body);
bio = establish_http_proxy(url);
}
}
return new HTTPDocument(bio, true);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https
// Access: Private
// Description: Opens the indicated URL directly as an ordinary http
// document.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
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();
BIO *bio = BIO_new_connect((char *)server_str.c_str());
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "connecting to " << server_str << "\n";
}
if (BIO_do_connect(bio) <= 0) {
downloader_cat.info()
<< "Could not contact server " << server_str << "\n";
#ifdef REPORT_SSL_ERRORS
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
send_request(bio, method, url.get_path(), url.get_server(), body);
return bio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https
// Function: HTTPClient::establish_http
// Access: Private
// Description: Opens the indicated URL directly as an https
// document.
// Description: Establishes a connection to the server directly,
// without using a proxy. Returns the newly allocated
// BIO representing the connection, or NULL if a
// connection cannot be established.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_https(const string &method, const URLSpec &url, const string &body) {
establish_http(const URLSpec &url) {
ostringstream server;
server << url.get_server() << ":" << url.get_port();
string server_str = server.str();
@ -461,22 +547,50 @@ get_https(const string &method, const URLSpec &url, const string &body) {
return NULL;
}
BIO *sbio = make_https_connection(bio, url);
if (sbio != (BIO *)NULL) {
send_request(sbio, method, url.get_path(), url.get_server(), body);
}
return sbio;
return bio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_http_proxy
// Function: HTTPClient::establish_https
// Access: Private
// Description: Opens the indicated URL via the proxy as an ordinary
// http document.
// Description: Establishes a connection to the secure server
// directly, without using a proxy. Returns the newly
// allocated BIO representing the connection, or NULL if
// a connection cannot be established.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_http_proxy(const string &method, const URLSpec &url, const string &body) {
establish_https(const URLSpec &url) {
ostringstream server;
server << url.get_server() << ":" << url.get_port();
string server_str = server.str();
BIO *bio = BIO_new_connect((char *)server_str.c_str());
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "connecting to " << server_str << "\n";
}
if (BIO_do_connect(bio) <= 0) {
downloader_cat.info()
<< "Could not contact server " << server_str << "\n";
#ifdef REPORT_SSL_ERRORS
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
return make_https_connection(bio, url);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::establish_http_proxy
// Access: Private
// Description: Establishes a connection to the server through a
// proxy. Returns the newly allocated BIO representing
// the connection, or NULL if a connection cannot be
// established.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
establish_http_proxy(const URLSpec &url) {
ostringstream proxy_server;
proxy_server << _proxy.get_server() << ":" << _proxy.get_port();
string proxy_server_str = proxy_server.str();
@ -496,18 +610,19 @@ get_http_proxy(const string &method, const URLSpec &url, const string &body) {
return NULL;
}
send_request(bio, method, url, url.get_server(), body);
return bio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https_proxy
// Function: HTTPClient::establish_https_proxy
// Access: Private
// Description: Opens the indicated URL via the proxy as an https
// document.
// Description: Establishes a connection to the secure server through
// a proxy. Returns the newly allocated BIO
// representing the connection, or NULL if a connection
// cannot be established.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_https_proxy(const string &method, const URLSpec &url, const string &body) {
establish_https_proxy(const URLSpec &url) {
// First, ask the proxy to open a connection for us.
ostringstream proxy_server;
proxy_server << _proxy.get_server() << ":" << _proxy.get_port();
@ -528,35 +643,21 @@ get_https_proxy(const string &method, const URLSpec &url, const string &body) {
return NULL;
}
{
ostringstream request;
request
<< "CONNECT " << url.get_server() << ":" << url.get_port()
<< " " << get_http_version_string() << "\r\n"
<< "\r\n";
string request_str = request.str();
ostringstream request;
request
<< "CONNECT " << url.get_server() << ":" << url.get_port()
<< " " << get_http_version_string() << "\r\n";
string connect_header = request.str();
#ifndef NDEBUG
if (downloader_cat.is_debug()) {
show_send(request_str);
}
#endif
BIO_puts(bio, request_str.c_str());
}
// Create a temporary HTTPDocument to read the response from the
// proxy.
// Create a temporary HTTPDocument to issue the request and read the
// response from the proxy.
{
PT(HTTPDocument) doc = new HTTPDocument(bio, false);
if (!doc->is_valid()) {
PT(HTTPDocument) doc = new HTTPDocument(this, bio);
if (!doc->send_request(connect_header, string())) {
downloader_cat.info()
<< "proxy would not open connection to " << url.get_authority()
<< ": " << doc->get_status_code() << " "
<< doc->get_status_string() << "\n";
if (downloader_cat.is_debug()) {
doc->write_headers(downloader_cat.debug(false));
}
if (!get_verify_ssl()) {
// If the proxy refused to open a raw connection for us, see
@ -567,7 +668,7 @@ get_https_proxy(const string &method, const URLSpec &url, const string &body) {
// us.)
if ((doc->get_status_code() / 100) == 4) {
BIO_free_all(bio);
return get_http_proxy(method, url, body);
return establish_http_proxy(url);
}
}
return NULL;
@ -581,12 +682,7 @@ get_https_proxy(const string &method, const URLSpec &url, const string &body) {
// Ok, we now have a connection to our actual server, so start
// speaking SSL and then ask for the document we really want.
BIO *sbio = make_https_connection(bio, url);
if (sbio != (BIO *)NULL) {
send_request(sbio, method, url.get_path(), url.get_server(), body);
}
return sbio;
return make_https_connection(bio, url);
}
////////////////////////////////////////////////////////////////////
@ -850,46 +946,6 @@ certificate signing
*/
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::send_request
// Access: Private
// Description: Sends the appropriate GET or POST (or whatever)
// request to the server on the indicated connection.
////////////////////////////////////////////////////////////////////
void HTTPClient::
send_request(BIO *bio, const string &method,
const string &path, const string &server,
const string &body) const {
ostringstream request;
request
<< method << " " << path << " " << get_http_version_string() << "\r\n";
if (_http_version > HV_10) {
request
<< "Host: " << server << "\r\n"
<< "Connection: close\r\n";
}
if (!body.empty()) {
request
<< "Content-type: application/x-www-form-urlencoded\r\n"
<< "Content-Length: " << body.length() << "\r\n";
}
request
<< "\r\n"
<< body;
string request_str = request.str();
#ifndef NDEBUG
if (downloader_cat.is_debug()) {
show_send(request_str);
}
#endif
BIO_puts(bio, request_str.c_str());
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::verify_server
// Access: Private
@ -1089,31 +1145,6 @@ x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) {
return true;
}
#ifndef NDEBUG
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::show_send
// Access: Private, Static
// Description: Writes the outgoing message, one line at a time, to
// the debugging log.
////////////////////////////////////////////////////////////////////
void HTTPClient::
show_send(const string &message) {
size_t start = 0;
size_t newline = message.find('\n', start);
while (newline != string::npos) {
downloader_cat.debug()
<< "send: " << message.substr(start, newline - start + 1);
start = newline + 1;
newline = message.find('\n', start);
}
if (start < message.length()) {
downloader_cat.debug()
<< "send: " << message.substr(start) << " (no newline)\n";
}
}
#endif // NDEBUG
#if defined(SSL_097) && !defined(NDEBUG)
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::ssl_msg_callback

View File

@ -29,17 +29,17 @@
#ifdef HAVE_SSL
#include "urlSpec.h"
#include "httpDocument.h"
#include "pointerTo.h"
#include <openssl/ssl.h>
// Windows may define this macro inappropriately.
#ifdef WIN32
#ifdef X509_NAME
#undef X509_NAME
#endif
class Filename;
class HTTPDocument;
////////////////////////////////////////////////////////////////////
// Class : HTTPClient
@ -60,14 +60,19 @@ PUBLISHED:
INLINE void set_proxy(const URLSpec &proxy);
INLINE const URLSpec &get_proxy() const;
void set_username(const string &server, const string &realm, const string &username);
string get_username(const string &server, const string &realm) const;
enum HTTPVersion {
HV_10, // HTTP 1.0
HV_11, // HTTP 1.1
HV_other,
};
INLINE void set_http_version(HTTPVersion version);
INLINE HTTPVersion get_http_version() const;
string get_http_version_string() const;
static HTTPVersion parse_http_version_string(const string &version);
bool load_certificates(const Filename &filename);
@ -77,37 +82,29 @@ PUBLISHED:
bool add_expected_server(const string &server_attributes);
void clear_expected_servers();
INLINE PT(HTTPDocument) get_document(const URLSpec &url,
const string &body = string());
INLINE PT(HTTPDocument) get_header(const URLSpec &url);
PT(HTTPDocument) get_documents();
PT(HTTPDocument) get_document(const URLSpec &url,
const string &body = string());
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);
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 *establish_connection(const URLSpec &url);
BIO *establish_http(const URLSpec &url);
BIO *establish_https(const URLSpec &url);
BIO *establish_http_proxy(const URLSpec &url);
BIO *establish_https_proxy(const URLSpec &url);
BIO *make_https_connection(BIO *bio, const URLSpec &url) 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);
static string get_x509_name_component(X509_NAME *name, int nid);
static bool x509_name_subset(X509_NAME *name_a, X509_NAME *name_b);
#ifndef NDEBUG
static void show_send(const string &message);
#endif
#if defined(SSL_097) && !defined(NDEBUG)
static void ssl_msg_callback(int write_p, int version, int content_type,
const void *buf, size_t len, SSL *ssl,
@ -118,6 +115,9 @@ private:
HTTPVersion _http_version;
bool _verify_ssl;
typedef pmap<string, string> Usernames;
Usernames _usernames;
// List of allowable SSL servers to connect to. If the list is
// empty, any server is acceptable.
typedef pvector<X509_NAME *> ExpectedServers;
@ -127,6 +127,7 @@ private:
static bool _ssl_initialized;
static X509_STORE *_x509_store;
friend class HTTPDocument;
};
#include "httpClient.I"

View File

@ -29,15 +29,39 @@ is_valid() const {
(_status_code / 100) == 2);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_url
// Access: Published
// Description: Returns the URL that was used to retrieve this
// document: whatever URL was last passed to
// get_document() or get_header().
////////////////////////////////////////////////////////////////////
INLINE const URLSpec &HTTPDocument::
get_url() const {
return _url;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_http_version
// Access: Published
// Description: Returns the HTTP version number returned by the
// server, as one of the HTTPClient enumerated types,
// e.g. HTTPClient::HV_11.
////////////////////////////////////////////////////////////////////
INLINE HTTPClient::HTTPVersion HTTPDocument::
get_http_version() const {
return _http_version;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_http_version_string
// Access: Published
// Description: Returns the HTTP version number returned by the
// server, formatted as a string, e.g. "HTTP/1.1".
////////////////////////////////////////////////////////////////////
INLINE const string &HTTPDocument::
get_http_version() const {
return _http_version;
get_http_version_string() const {
return _http_version_string;
}
////////////////////////////////////////////////////////////////////
@ -65,6 +89,80 @@ get_status_string() const {
return _status_string;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_realm
// Access: Published
// Description: If the document failed to connect because of a 401
// (Authorization required) or 407 (Proxy authorization
// required) error, this method should return the
// "realm" returned by the server in which the requested
// document must be authenticated. This string may be
// presented to the user to request an associated
// username and password (which then should be stored in
// HTTPClient::set_username()).
//
// If the document was retrieved successfully, or failed
// to connect for some other reason, this string will be
// empty. If it is empty even in the presence of a 401
// or 407 error, then the client code and the server do
// not have a common scheme for exchanging passwords.
////////////////////////////////////////////////////////////////////
INLINE const string &HTTPDocument::
get_realm() const {
return _realm;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_redirect
// Access: Published
// Description: If the document failed with a redirect code (300
// series), this will generally contain the new URL the
// server wants us to try. In many cases, the client
// will automatically follow redirects; if these are
// succesful the client will return a successful code
// and get_redirect() will return empty, but get_url()
// will return the new, redirected URL.
////////////////////////////////////////////////////////////////////
INLINE const URLSpec &HTTPDocument::
get_redirect() const {
return _redirect;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::set_persistent_connection
// Access: Published
// Description: Indicates whether the HTTPDocument should try to keep
// the connection to the server open and reuse that
// connection for multiple documents, or whether it
// should close the connection and open a new one for
// each request. Set this true to keep the connections
// around when possible, false to recycle them.
//
// It makes most sense to set this false when the
// HTTPDocument will be used only once to retrieve a
// single document, true when you will be using the
// HTTPDocument interface to retrieve multiple
// documents.
////////////////////////////////////////////////////////////////////
INLINE void HTTPDocument::
set_persistent_connection(bool persistent_connection) {
_persistent_connection = persistent_connection;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_persistent_connection
// Access: Published
// Description: Returns whether the HTTPDocument should try to keep
// the connection to the server open and reuse that
// connection for multiple documents, or whether it
// should close the connection and open a new one for
// each request. See set_persistent_connection().
////////////////////////////////////////////////////////////////////
INLINE bool HTTPDocument::
get_persistent_connection() const {
return _persistent_connection;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_file_size
// Access: Published
@ -83,3 +181,34 @@ INLINE size_t HTTPDocument::
get_file_size() const {
return _file_size;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::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.
////////////////////////////////////////////////////////////////////
INLINE bool HTTPDocument::
get_document(const URLSpec &url, const string &body) {
const char *method = "GET";
if (!body.empty()) {
method = "POST";
}
return send_request(method, url, body);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::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 bool HTTPDocument::
get_header(const URLSpec &url) {
return send_request("HEAD", url, string());
}

View File

@ -17,32 +17,148 @@
////////////////////////////////////////////////////////////////////
#include "httpDocument.h"
#include "httpClient.h"
#include "bioStream.h"
#include "chunkedStream.h"
#include "identityStream.h"
#ifdef HAVE_SSL
TypeHandle HTTPDocument::_type_handle;
static const char base64_table[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/',
};
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::Constructor
// Access: Public
// Access: Private
// Description:
////////////////////////////////////////////////////////////////////
HTTPDocument::
HTTPDocument(BIO *bio, bool owns_bio) {
HTTPDocument(HTTPClient *client, BIO *bio) :
_client(client),
_bio(bio)
{
_owns_bio = false;
_source = (IBioStream *)NULL;
_persistent_connection = false;
_state = S_new;
_read_index = 0;
_file_size = 0;
_status_code = 0;
_status_string = "No connection";
_proxy = _client->get_proxy();
_http_version = _client->get_http_version();
_http_version_string = _client->get_http_version_string();
}
if (bio != (BIO *)NULL) {
_source = new IBioStream(bio, owns_bio);
read_headers();
determine_content_length();
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::send_request
// Access: Private
// Description: This is normally called immediately after
// construction to send the request to the server and
// read the response. It can't be called as part of the
// constructor because it may involve an up-and-down
// change in the reference count of the HTTPDocument
// object, which would inadvertently cause the object to
// be deleted if it hasn't returned from its constructor
// yet!
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
send_request(const string &method, const URLSpec &url, const string &body) {
// Let's call this before we call make_header(), so we'll get the
// right HTTP version and proxy information etc.
set_url(url);
prepare_for_next();
} else {
_source = (IBioStream *)NULL;
_status_code = 0;
_status_string = "No connection";
string header;
make_header(header, method, url, body);
send_request(header, body);
if ((get_status_code() / 100) == 3 && get_status_code() != 305) {
// Redirect. Should we handle it automatically?
if (!get_redirect().empty() && (method == "GET" || method == "HEAD")) {
// Sure!
pset<URLSpec> already_seen;
bool keep_going;
do {
keep_going = false;
if (downloader_cat.is_debug()) {
downloader_cat.debug()
<< "following redirect to " << get_redirect() << "\n";
}
URLSpec new_url = get_redirect();
if (already_seen.insert(new_url).second) {
if (url.has_username()) {
new_url.set_username(url.get_username());
}
set_url(new_url);
if (prepare_for_next()) {
make_header(header, method, new_url, body);
send_request(header, body);
keep_going =
((get_status_code() / 100) == 3 && get_status_code() != 305);
}
}
} while (keep_going);
}
}
return is_valid();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::send_request
// Access: Private
// Description: This is a lower-level interface than the above
// send_request(); it accepts a header and body string
// that have already been defined.
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
send_request(const string &header, const string &body) {
if (prepare_for_next()) {
issue_request(header, body);
if (get_status_code() == 407 && !_proxy.empty()) {
// 407: not authorized to proxy. Try to get the authorization.
string authenticate_request = get_header_value("Proxy-Authenticate");
string authorization;
if (get_authorization(authorization, authenticate_request, _proxy, true)) {
string new_header = header;
new_header += "Proxy-Authorization: ";
new_header += authorization;
new_header += "\r\n";
if (prepare_for_next()) {
issue_request(new_header, body);
}
}
}
if (get_status_code() == 401) {
// 401: not authorized to remote server. Try to get the authorization.
string authenticate_request = get_header_value("WWW-Authenticate");
string authorization;
if (get_authorization(authorization, authenticate_request, _url, false)) {
string new_header = header;
new_header += "Authorization: ";
new_header += authorization;
new_header += "\r\n";
if (prepare_for_next()) {
issue_request(new_header, body);
}
}
}
}
return is_valid();
}
////////////////////////////////////////////////////////////////////
@ -52,10 +168,7 @@ HTTPDocument(BIO *bio, bool owns_bio) {
////////////////////////////////////////////////////////////////////
HTTPDocument::
~HTTPDocument() {
if (_source != (IBioStream *)NULL) {
delete _source;
_source = (IBioStream *)NULL;
}
free_bio();
}
////////////////////////////////////////////////////////////////////
@ -91,6 +204,31 @@ is_regular_file() const {
return is_valid();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::will_close_connection
// Access: Public
// Description: Returns true if the server has indicated it will
// close the connection after this document has been
// read, or false if it will remain open (and future
// documents may be requested on the same connection).
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
will_close_connection() const {
if (get_http_version() == HTTPClient::HV_10) {
// HTTP 1.0 always closes.
return true;
}
string connection = get_header_value("Connection");
if (downcase(connection) == "close") {
// The server says it will close.
return true;
}
// Assume the serve will keep it open.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::open_read_file
// Access: Public, Virtual
@ -102,24 +240,7 @@ is_regular_file() const {
////////////////////////////////////////////////////////////////////
istream *HTTPDocument::
open_read_file() const {
if (_source == (IBioStream *)NULL) {
return NULL;
}
string transfer_coding = get_header_value("Transfer-Encoding");
for (string::iterator si = transfer_coding.begin();
si != transfer_coding.end();
++si) {
(*si) = tolower(*si);
}
istream *result = _source;
if (transfer_coding == "chunked") {
result = new IChunkedStream(_source, true, (HTTPDocument *)this);
}
((HTTPDocument *)this)->_source = (IBioStream *)NULL;
return result;
return ((HTTPDocument *)this)->read_body(true);
}
////////////////////////////////////////////////////////////////////
@ -131,7 +252,7 @@ open_read_file() const {
////////////////////////////////////////////////////////////////////
string HTTPDocument::
get_header_value(const string &key) const {
Headers::const_iterator hi = _headers.find(key);
Headers::const_iterator hi = _headers.find(downcase(key));
if (hi != _headers.end()) {
return (*hi).second;
}
@ -153,15 +274,123 @@ write_headers(ostream &out) const {
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::read_headers
// Function: HTTPDocument::make_header
// Access: Private
// Description: Formats the appropriate GET or POST (or whatever)
// request to send to the server. Also saves the
// indicated url.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
make_header(string &header, const string &method,
const URLSpec &url, const string &body) {
set_url(url);
_method = method;
string path;
if (_proxy.empty()) {
path = _url.get_path();
} else {
URLSpec url_no_username = _url;
url_no_username.set_username(string());
path = url_no_username.get_url();
}
ostringstream stream;
stream
<< method << " " << path << " " << get_http_version_string() << "\r\n";
if (_http_version > HTTPClient::HV_10) {
stream
<< "Host: " << _url.get_server() << "\r\n";
if (!get_persistent_connection()) {
stream
<< "Connection: close\r\n";
}
}
if (!body.empty()) {
stream
<< "Content-Type: application/x-www-form-urlencoded\r\n"
<< "Content-Length: " << body.length() << "\r\n";
}
header = stream.str();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::set_url
// Access: Private
// Description: Specifies the document's URL before attempting a
// connection. This controls the name of the server to
// be contacted, etc.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
set_url(const URLSpec &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()))) {
free_bio();
}
_url = url;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::issue_request
// Access: Private
// Description: Issues the request to the HTTP server and waits for a
// response.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
issue_request(const string &header, const string &body) {
if (_bio != (BIO *)NULL) {
string request = header;
request += "\r\n";
request += body;
#ifndef NDEBUG
if (downloader_cat.is_spam()) {
show_send(request);
}
#endif
BIO_puts(_bio, request.c_str());
read_http_response();
if (_source->eof() || _source->fail()) {
if (downloader_cat.is_debug()) {
downloader_cat.debug()
<< "Whoops, socket closed.\n";
free_bio();
if (prepare_for_next()) {
#ifndef NDEBUG
if (downloader_cat.is_spam()) {
show_send(request);
}
#endif
BIO_puts(_bio, request.c_str());
read_http_response();
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::read_http_response
// Access: Private
// Description: Reads all of the responses from the server up until
// the first blank line, and stores the list of header
// key:value pairs so retrieved.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
read_headers() {
read_http_response() {
nassertv(_source != (IBioStream *)NULL);
_headers.clear();
_realm = string();
// The first line back should include the HTTP version and the
// result code.
@ -170,9 +399,10 @@ read_headers() {
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "recv: " << line << "\n";
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "recv: " << line << "\n";
}
if (!(*_source) || line.length() < 5 || line.substr(0, 5) != "HTTP/") {
// Not an HTTP response.
_status_code = 0;
@ -185,7 +415,8 @@ read_headers() {
while (p < line.length() && !isspace(line[p])) {
p++;
}
_http_version = line.substr(0, p);
_http_version_string = line.substr(0, p);
_http_version = HTTPClient::parse_http_version_string(_http_version_string);
while (p < line.length() && isspace(line[p])) {
p++;
@ -211,9 +442,10 @@ read_headers() {
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "recv: " << line << "\n";
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "recv: " << line << "\n";
}
while (!_source->eof() && !_source->fail() && !line.empty()) {
if (isspace(line[0])) {
// If the line begins with a space, that continues the previous
@ -228,13 +460,13 @@ read_headers() {
// If the line does not begin with a space, that defines a new
// field.
if (!field_name.empty()) {
_headers[field_name] = field_value;
store_header_field(field_name, field_value);
field_value = string();
}
size_t colon = line.find(':');
if (colon != string::npos) {
field_name = line.substr(0, colon);
field_name = downcase(line.substr(0, colon));
p = colon + 1;
while (p < line.length() && isspace(line[p])) {
p++;
@ -247,31 +479,531 @@ read_headers() {
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "recv: " << line << "\n";
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "recv: " << line << "\n";
}
}
if (!field_name.empty()) {
_headers[field_name] = field_value;
store_header_field(field_name, field_value);
field_value = string();
}
// A blank line terminates the headers.
}
_state = S_read_header;
////////////////////////////////////////////////////////////////////
// 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() {
if (get_status_code() / 100 == 1 ||
get_status_code() == 204 ||
get_status_code() == 304 ||
_method == "HEAD") {
// These status codes, or method HEAD, indicate we have no body.
// Therefore, we have already read the (nonexistent) body.
_state = S_read_trailer;
}
_file_size = 0;
string content_length = get_header_value("Content-Length");
if (!content_length.empty()) {
_file_size = atoi(content_length.c_str());
}
_redirect = get_header_value("Location");
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::store_header_field
// Access: Private
// Description: Stores a single name: value pair in the header list,
// or appends the value to the end of the existing
// value, if the header has been repeated.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
store_header_field(const string &field_name, const string &field_value) {
pair<Headers::iterator, bool> insert_result =
_headers.insert(Headers::value_type(field_name, field_value));
if (!insert_result.second) {
// It didn't insert; thus, the field already existed. Append the
// new value.
Headers::iterator hi = insert_result.first;
(*hi).second += ", ";
(*hi).second += field_value;
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_authorization
// Access: Private
// Description: Looks for a username:password to satisfy the given
// authenticate_request string from the server or proxy.
// If found, fills in authorization and returns true;
// otherwise, returns false.
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
get_authorization(string &authorization, const string &authenticate_request,
const URLSpec &url, bool is_proxy) {
AuthenticationSchemes schemes;
parse_authentication_schemes(schemes, authenticate_request);
AuthenticationSchemes::iterator si;
si = schemes.find("basic");
if (si != schemes.end()) {
return get_basic_authorization(authorization, (*si).second, url, is_proxy);
}
downloader_cat.warning()
<< "Don't know how to use any of the server's available authorization schemes:\n";
for (si = schemes.begin(); si != schemes.end(); ++si) {
downloader_cat.warning() << (*si).first << "\n";
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::downcase
// Access: Private, Static
// Description: Returns the input string with all uppercase letters
// converted to lowercase.
////////////////////////////////////////////////////////////////////
string HTTPDocument::
downcase(const string &s) {
string result;
result.reserve(s.size());
string::const_iterator p;
for (p = s.begin(); p != s.end(); ++p) {
result += tolower(*p);
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::base64_encode
// Access: Private, Static
// Description: Returns the input string encoded using base64. No
// respect is paid to maintaining a 76-char line length.
////////////////////////////////////////////////////////////////////
string HTTPDocument::
base64_encode(const string &s) {
// Collect the string 3 bytes at a time into 24-bit words, then
// output each word using 4 bytes.
size_t num_words = (s.size() + 2) / 3;
string result;
result.reserve(num_words * 4);
size_t p;
for (p = 0; p + 2 < s.size(); p += 3) {
unsigned int word =
((unsigned)s[p] << 16) |
((unsigned)s[p + 1] << 8) |
((unsigned)s[p + 2]);
result += base64_table[(word >> 18) & 0x3f];
result += base64_table[(word >> 12) & 0x3f];
result += base64_table[(word >> 6) & 0x3f];
result += base64_table[(word) & 0x3f];
}
// What's left over?
if (p < s.size()) {
unsigned int word = ((unsigned)s[p] << 16);
p++;
if (p < s.size()) {
word |= ((unsigned)s[p] << 8);
p++;
nassertr(p == s.size(), result);
result += base64_table[(word >> 18) & 0x3f];
result += base64_table[(word >> 12) & 0x3f];
result += base64_table[(word >> 6) & 0x3f];
result += '=';
} else {
result += base64_table[(word >> 18) & 0x3f];
result += base64_table[(word >> 12) & 0x3f];
result += '=';
result += '=';
}
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::scan_quoted_or_unquoted_string
// Access: Private, Static
// Description: Scans the string source beginning at character
// position start, to identify either the
// (spaced-delimited) unquoted string there, or the
// (quote-delimited) quoted string. In either case,
// fills the string found into result, and returns the
// next character position after the string (or after
// its closing quote mark).
////////////////////////////////////////////////////////////////////
size_t HTTPDocument::
scan_quoted_or_unquoted_string(string &result, const string &source,
size_t start) {
result = string();
if (start < source.length()) {
if (source[start] == '"') {
// Quoted string.
size_t p = start + 1;
while (p < source.length() && source[p] != '"') {
if (source[p] == '\\') {
// Backslash escapes.
++p;
if (p < source.length()) {
result += source[p];
++p;
}
} else {
result += source[p];
++p;
}
}
if (p < source.length()) {
++p;
}
return p;
}
// Unquoted string.
size_t p = start;
while (p < source.length() && source[p] != ',' && !isspace(source[p])) {
result += source[p];
++p;
}
return p;
}
// Empty string.
return start;
}
#ifndef NDEBUG
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::show_send
// Access: Private, Static
// Description: Writes the outgoing message, one line at a time, to
// the debugging log.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
show_send(const string &message) {
size_t start = 0;
size_t newline = message.find('\n', start);
while (newline != string::npos) {
downloader_cat.spam()
<< "send: " << message.substr(start, newline - start + 1);
start = newline + 1;
newline = message.find('\n', start);
}
if (start < message.length()) {
downloader_cat.spam()
<< "send: " << message.substr(start) << " (no newline)\n";
}
}
#endif // NDEBUG
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::parse_authentication_schemes
// Access: Private, Static
// Description: Decodes the text following a WWW-Authenticate: or
// Proxy-Authenticate: header field.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
parse_authentication_schemes(HTTPDocument::AuthenticationSchemes &schemes,
const string &field_value) {
// This string will consist of one or more records of the form:
//
// scheme token=value[,token=value[,...]]
//
// If there are multiple records, they will be comma-delimited,
// which makes parsing just a bit tricky.
// Start by skipping initial whitespace.
size_t p = 0;
while (p < field_value.length() && isspace(field_value[p])) {
++p;
}
if (p < field_value.length()) {
size_t q = p;
while (q < field_value.length() && !isspace(field_value[q])) {
++q;
}
// Here's our first scheme.
string scheme = downcase(field_value.substr(p, q - p));
Tokens *tokens = &(schemes[scheme]);
// Now pull off the tokens, one at a time.
p = q + 1;
while (p < field_value.length()) {
q = p;
while (q < field_value.length() && field_value[q] != '=' &&
field_value[q] != ',' && !isspace(field_value[q])) {
++q;
}
if (field_value[q] == '=') {
// This is a token.
string token = downcase(field_value.substr(p, q - p));
string value;
p = scan_quoted_or_unquoted_string(value, field_value, q + 1);
(*tokens)[token] = value;
// Skip trailing whitespace and extra commas.
while (p < field_value.length() &&
(field_value[p] == ',' || isspace(field_value[p]))) {
++p;
}
} else {
// This is not a token; it must be the start of a new scheme.
scheme = downcase(field_value.substr(p, q - p));
tokens = &(schemes[scheme]);
p = q + 1;
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::read_body
// Access: Private
// Description: Returns a newly-allocated istream suitable for
// reading the body of the document. If owns_source is
// true, the ownership of the _source pointer will be
// passed to the istream; otherwise, it will be
// retained. (owns_source must be true in order to read
// "identity" encoded documents.)
////////////////////////////////////////////////////////////////////
istream *HTTPDocument::
read_body(bool owns_source) {
if (_state != S_read_header || _source == (IBioStream *)NULL) {
return NULL;
}
string transfer_coding = downcase(get_header_value("Transfer-Encoding"));
string content_length = get_header_value("Content-Length");
istream *result;
if (transfer_coding == "chunked") {
// Chunked can be used directly.
_file_size = 0;
_state = S_started_body;
_read_index++;
result = new IChunkedStream(_source, owns_source, (HTTPDocument *)this);
if (owns_source) {
_source = (IBioStream *)NULL;
}
} else if (!content_length.empty()) {
// If we have a content length, we can use an IdentityStream.
_state = S_started_body;
_read_index++;
result = new IIdentityStream(_source, owns_source, (HTTPDocument *)this, _file_size);
if (owns_source) {
_source = (IBioStream *)NULL;
}
} else if (owns_source) {
// If we own the source, we can return it.
_state = S_started_body;
result = _source;
_source = (IBioStream *)NULL;
} else {
// Otherwise, we don't own the source; too bad.
result = NULL;
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::prepare_for_next
// Access: Private
// Description: Resets the state to prepare it for sending a new
// request to the server. This might mean closing the
// connection and opening a new one, or it might mean
// skipping past the unread body in the persistent
// connection, or it might do nothing at all if the body
// has already been completely read.
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
prepare_for_next() {
if (get_persistent_connection() && !will_close_connection() &&
_proxy == _client->get_proxy()) {
// See if we can reuse the current connection.
if (_state == S_read_header) {
// We have read the header; now skip past the body.
istream *body = read_body(false);
if (body != (istream *)NULL) {
string line;
getline(*body, line);
while (!body->fail() && !body->eof()) {
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "skip: " << line << "\n";
}
getline(*body, line);
}
nassertr(body != _source, false);
delete body;
}
}
if (_source == (IBioStream *)NULL) {
_source = new IBioStream(_bio, false);
}
if (_state == S_read_body) {
// We have read the body, but there's a trailer to read.
string line;
getline(*_source, line);
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "skip: " << line << "\n";
}
while (!_source->eof() && !_source->fail() && !line.empty()) {
getline(*_source, line);
if (!line.empty() && line[line.length() - 1] == '\r') {
line = line.substr(0, line.length() - 1);
}
if (downloader_cat.is_spam()) {
downloader_cat.spam() << "skip: " << line << "\n";
}
}
_state = S_read_trailer;
}
if (_state == S_read_trailer) {
// Great; this connection is ready to go!
return true;
}
}
if (_bio != (BIO *)NULL && _state == S_new) {
// If we have a BIO and the _state is S_new, then we haven't done
// anything with the BIO yet, so we can still use it.
if (_source == (IBioStream *)NULL) {
_source = new IBioStream(_bio, false);
}
return true;
}
// Either the client will close the connection after reading the
// body, or we were only partly through reading the body elsewhere;
// or possibly we don't have a connection yet at all. In any case,
// we must now get a new connection.
if (_bio != (BIO *)NULL && !_owns_bio) {
// We have a connection, but we don't own it, so we can't close
// it. Too bad.
return false;
}
// Go ahead and close the old BIO.
free_bio();
_proxy = _client->get_proxy();
_http_version = _client->get_http_version();
_http_version_string = _client->get_http_version_string();
_bio = _client->establish_connection(_url);
_owns_bio = true;
if (_bio != (BIO *)NULL) {
_source = new IBioStream(_bio, false);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::free_bio
// Access: Private
// Description: Frees the BIO and its IBioStream object, if
// allocated. This will close the connection if it is
// open.
////////////////////////////////////////////////////////////////////
void HTTPDocument::
free_bio() {
if (_source != (IBioStream *)NULL) {
delete _source;
_source = (IBioStream *)NULL;
}
if (_bio != (BIO *)NULL) {
if (_owns_bio) {
// TODO: We should be more careful here to manage reference
// counts so we don't free the bio out from under a BIOStreamBuf
// that's trying to read from it.
if (downloader_cat.is_debug()) {
const URLSpec &url = _proxy.empty() ? _url : _proxy;
downloader_cat.debug()
<< "Dropping connection to " << url.get_server() << "\n";
}
BIO_free_all(_bio);
}
_bio = (BIO *)NULL;
}
_read_index++;
_state = S_new;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPDocument::get_basic_authorization
// Access: Private
// Description: Looks for a username:password to satisfy the "Basic"
// scheme authorization request from the server or
// proxy.
////////////////////////////////////////////////////////////////////
bool HTTPDocument::
get_basic_authorization(string &authorization, const HTTPDocument::Tokens &tokens, const URLSpec &url, bool is_proxy) {
Tokens::const_iterator ti;
ti = tokens.find("realm");
if (ti != tokens.end()) {
_realm = (*ti).second;
}
string username;
// Look in several places in order to find the matching username.
// Fist, if there's a username on the URL, that always wins.
if (url.has_username()) {
username = url.get_username();
}
// Otherwise, start looking on the HTTPClient.
if (is_proxy) {
if (username.empty()) {
// Try the *proxy/realm.
username = _client->get_username("*proxy", _realm);
}
if (username.empty()) {
// Then, try *proxy/any realm.
username = _client->get_username("*proxy", string());
}
}
if (username.empty()) {
// Try the specific server/realm.
username = _client->get_username(url.get_server(), _realm);
}
if (username.empty()) {
// Then, try the specific server/any realm.
username = _client->get_username(url.get_server(), string());
}
if (username.empty()) {
// Then, try any server with this realm.
username = _client->get_username(string(), _realm);
}
if (username.empty()) {
// Then, take the general password.
username = _client->get_username(string(), string());
}
if (username.empty()) {
// No username:password available.
return false;
}
authorization = "Basic " + base64_encode(username);
return true;
}
#endif // HAVE_SSL

View File

@ -28,20 +28,28 @@
#ifdef HAVE_SSL
#include "httpClient.h"
#include "urlSpec.h"
#include "virtualFile.h"
#include "pmap.h"
#include <openssl/ssl.h>
class IBioStream;
class HTTPClient;
////////////////////////////////////////////////////////////////////
// Class : HTTPDocument
// Description : A single document retrieved from an HTTP server.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEXPRESS HTTPDocument : public VirtualFile {
private:
HTTPDocument(HTTPClient *client, BIO *bio = NULL);
bool send_request(const string &method, const URLSpec &url,
const string &body);
bool send_request(const string &header, const string &body);
public:
HTTPDocument(BIO *bio, bool owns_bio);
virtual ~HTTPDocument();
virtual VirtualFileSystem *get_file_system() const;
@ -50,32 +58,90 @@ public:
virtual bool is_regular_file() const;
virtual istream *open_read_file() const;
bool will_close_connection() const;
PUBLISHED:
INLINE bool is_valid() const;
INLINE const string &get_http_version() const;
INLINE const URLSpec &get_url() const;
INLINE HTTPClient::HTTPVersion get_http_version() const;
INLINE const string &get_http_version_string() const;
INLINE int get_status_code() const;
INLINE const string &get_status_string() const;
INLINE const string &get_realm() const;
INLINE const URLSpec &get_redirect() const;
string get_header_value(const string &key) const;
INLINE void set_persistent_connection(bool persistent_connection);
INLINE bool get_persistent_connection() const;
INLINE size_t get_file_size() const;
void write_headers(ostream &out) const;
INLINE bool get_document(const URLSpec &url, const string &body = string());
INLINE bool get_header(const URLSpec &url);
private:
void read_headers();
void determine_content_length();
void make_header(string &header, const string &method,
const URLSpec &url, const string &body);
void set_url(const URLSpec &url);
void issue_request(const string &header, const string &body);
void read_http_response();
void store_header_field(const string &field_name, const string &field_value);
bool get_authorization(string &authorization,
const string &authenticate_request,
const URLSpec &url, bool is_proxy);
static string downcase(const string &s);
static string base64_encode(const string &s);
static size_t scan_quoted_or_unquoted_string(string &result, const string &source, size_t start);
#ifndef NDEBUG
static void show_send(const string &message);
#endif
istream *read_body(bool owns_source);
bool prepare_for_next();
void free_bio();
HTTPClient *_client;
URLSpec _proxy;
BIO *_bio;
bool _owns_bio;
IBioStream *_source;
bool _persistent_connection;
string _http_version;
URLSpec _url;
string _method;
enum State {
S_new,
S_read_header,
S_started_body,
S_read_body,
S_read_trailer
};
State _state;
int _read_index;
HTTPClient::HTTPVersion _http_version;
string _http_version_string;
int _status_code;
string _status_string;
string _realm;
URLSpec _redirect;
typedef pmap<string, string> Headers;
Headers _headers;
size_t _file_size;
typedef pmap<string, string> Tokens;
typedef pmap<string, Tokens> AuthenticationSchemes;
static void parse_authentication_schemes(AuthenticationSchemes &schemes,
const string &field_value);
bool get_basic_authorization(string &authorization, const Tokens &tokens,
const URLSpec &url, bool is_proxy);
public:
virtual TypeHandle get_type() const {
@ -94,6 +160,8 @@ public:
private:
static TypeHandle _type_handle;
friend class ChunkedStreamBuf;
friend class IdentityStreamBuf;
friend class HTTPClient;
};
#include "httpDocument.I"

View File

@ -0,0 +1,63 @@
// Filename: identityStream.I
// Created by: drose (09Oct02)
//
////////////////////////////////////////////////////////////////////
//
// 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: IIdentityStream::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE IIdentityStream::
IIdentityStream() : istream(&_buf) {
}
////////////////////////////////////////////////////////////////////
// Function: IIdentityStream::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE IIdentityStream::
IIdentityStream(istream *source, bool owns_source,
HTTPDocument *doc, size_t content_length) : istream(&_buf) {
open(source, owns_source, doc, content_length);
}
////////////////////////////////////////////////////////////////////
// Function: IIdentityStream::open
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE IIdentityStream &IIdentityStream::
open(istream *source, bool owns_source, HTTPDocument *doc,
size_t content_length) {
clear(0);
_buf.open_read(source, owns_source, doc, content_length);
return *this;
}
////////////////////////////////////////////////////////////////////
// Function: IIdentityStream::close
// Access: Public
// Description: Resets the IdentityStream to empty, but does not actually
// close the source IDENTITY unless owns_source was true.
////////////////////////////////////////////////////////////////////
INLINE IIdentityStream &IIdentityStream::
close() {
_buf.close_read();
return *this;
}

View File

@ -0,0 +1,19 @@
// Filename: identityStream.cxx
// Created by: drose (09Oct02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "identityStream.h"

View File

@ -0,0 +1,60 @@
// Filename: identityStream.h
// Created by: drose (09Oct02)
//
////////////////////////////////////////////////////////////////////
//
// 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 IDENTITYSTREAM_H
#define IDENTITYSTREAM_H
#include "pandabase.h"
#include "identityStreamBuf.h"
class HTTPDocument;
////////////////////////////////////////////////////////////////////
// Class : IIdentityStream
// Description : An input stream object that reads data from a source
// istream, but automatically decodes the "identity"
// transfer-coding specified by an HTTP server.
//
// In practice, this just means it reads from the sub
// stream (like a SubStreamBuf) up to but not past the
// specified content-length. (If the content-length was
// unspecified, this class cannot be used.) It also
// updates the HTTPDocument when the stream is
// completely read.
////////////////////////////////////////////////////////////////////
// No need to export from DLL.
class IIdentityStream : public istream {
public:
INLINE IIdentityStream();
INLINE IIdentityStream(istream *source, bool owns_source,
HTTPDocument *doc, size_t content_length);
INLINE IIdentityStream &open(istream *source, bool owns_source,
HTTPDocument *doc, size_t content_length);
INLINE IIdentityStream &close();
private:
IdentityStreamBuf _buf;
};
#include "identityStream.I"
#endif

View File

@ -0,0 +1,160 @@
// Filename: identityStreamBuf.cxx
// Created by: drose (09Oct02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "identityStreamBuf.h"
#ifndef HAVE_STREAMSIZE
// Some compilers (notably SGI) don't define this for us
typedef int streamsize;
#endif /* HAVE_STREAMSIZE */
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
IdentityStreamBuf::
IdentityStreamBuf() {
_source = (istream *)NULL;
_owns_source = false;
_bytes_remaining = 0;
#ifdef WIN32_VC
// In spite of the claims of the MSDN Library to the contrary,
// Windows doesn't seem to provide an allocate() function, so we'll
// do it by hand.
char *buf = new char[4096];
char *ebuf = buf + 4096;
setg(buf, ebuf, ebuf);
setp(buf, ebuf);
#else
allocate();
setg(base(), ebuf(), ebuf());
setp(base(), ebuf());
#endif
}
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
IdentityStreamBuf::
~IdentityStreamBuf() {
close_read();
}
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::open_read
// Access: Public
// Description: If the document pointer is non-NULL, it will be
// updated with the length of the file as it is derived
// from the identity encoding.
////////////////////////////////////////////////////////////////////
void IdentityStreamBuf::
open_read(istream *source, bool owns_source, HTTPDocument *doc,
size_t content_length) {
_source = source;
_owns_source = owns_source;
_doc = doc;
_bytes_remaining = content_length;
if (_doc != (HTTPDocument *)NULL) {
_read_index = doc->_read_index;
}
}
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::close_read
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void IdentityStreamBuf::
close_read() {
if (_source != (istream *)NULL) {
if (_owns_source) {
delete _source;
_owns_source = false;
}
_source = (istream *)NULL;
}
}
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::underflow
// Access: Protected, Virtual
// Description: Called by the system istream implementation when its
// internal buffer needs more characters.
////////////////////////////////////////////////////////////////////
int IdentityStreamBuf::
underflow() {
// Sometimes underflow() is called even if the buffer is not empty.
if (gptr() >= egptr()) {
size_t buffer_size = egptr() - eback();
gbump(-(int)buffer_size);
size_t num_bytes = buffer_size;
size_t read_count = read_chars(gptr(), buffer_size);
if (read_count != num_bytes) {
// Oops, we didn't read what we thought we would.
if (read_count == 0) {
return EOF;
}
// Slide what we did read to the top of the buffer.
nassertr(read_count < num_bytes, EOF);
size_t delta = num_bytes - read_count;
memmove(gptr() + delta, gptr(), read_count);
gbump(delta);
}
}
return (unsigned char)*gptr();
}
////////////////////////////////////////////////////////////////////
// Function: IdentityStreamBuf::read_chars
// Access: Private
// Description: Gets some characters from the source stream.
////////////////////////////////////////////////////////////////////
size_t IdentityStreamBuf::
read_chars(char *start, size_t length) {
if (_bytes_remaining == 0) {
return 0;
}
// Extract some of the bytes remaining in the chunk.
length = min(length, _bytes_remaining);
_source->read(start, length);
length = _source->gcount();
_bytes_remaining -= length;
if (_bytes_remaining == 0) {
// We're done.
if (_doc != (HTTPDocument *)NULL && _read_index == _doc->_read_index) {
// An IdentityStreamBuf doesn't have a trailer, so we've already
// "read" it.
_doc->_state = HTTPDocument::S_read_trailer;
}
}
return length;
}

View File

@ -0,0 +1,54 @@
// Filename: identityStreamBuf.h
// Created by: drose (09Oct02)
//
////////////////////////////////////////////////////////////////////
//
// 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 IDENTITYSTREAMBUF_H
#define IDENTITYSTREAMBUF_H
#include "pandabase.h"
#include "httpDocument.h"
#include "pointerTo.h"
////////////////////////////////////////////////////////////////////
// Class : IdentityStreamBuf
// Description : The streambuf object that implements
// IIdentityStream.
////////////////////////////////////////////////////////////////////
class EXPCL_PANDAEXPRESS IdentityStreamBuf : public streambuf {
public:
IdentityStreamBuf();
virtual ~IdentityStreamBuf();
void open_read(istream *source, bool owns_source, HTTPDocument *doc,
size_t content_length);
void close_read();
protected:
virtual int underflow(void);
private:
size_t read_chars(char *start, size_t length);
istream *_source;
bool _owns_source;
size_t _bytes_remaining;
PT(HTTPDocument) _doc;
int _read_index;
};
#endif

View File

@ -47,6 +47,36 @@ operator = (const string &url) {
set_url(url);
}
////////////////////////////////////////////////////////////////////
// Function: URLSpec::Operator ==
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
INLINE bool URLSpec::
operator == (const URLSpec &other) const {
return _url == other._url;
}
////////////////////////////////////////////////////////////////////
// Function: URLSpec::Operator !=
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
INLINE bool URLSpec::
operator != (const URLSpec &other) const {
return !operator == (other);
}
////////////////////////////////////////////////////////////////////
// Function: URLSpec::Operator <
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
INLINE bool URLSpec::
operator < (const URLSpec &other) const {
return _url < other._url;
}
////////////////////////////////////////////////////////////////////
// Function: URLSpec::has_scheme
// Access: Published

View File

@ -38,6 +38,10 @@ PUBLISHED:
INLINE void operator = (const string &url);
void operator = (const URLSpec &copy);
INLINE bool operator == (const URLSpec &other) const;
INLINE bool operator != (const URLSpec &other) const;
INLINE bool operator < (const URLSpec &other) const;
INLINE bool has_scheme() const;
INLINE bool has_authority() const;
INLINE bool has_username() const;