Files
rippled/modules/ripple_net/basics/ripple_HttpsClient.cpp
2013-07-28 21:32:00 -07:00

503 lines
15 KiB
C++

//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
//
// Fetch a web page via https.
//
SETUP_LOG (HttpsClient)
// VFALCO NOTE Why use getConfig ().SSL_CONTEXT instead of just passing it?
// TODO Remove all theConfig deps from this file
//
HttpsClient::HttpsClient (boost::asio::io_service& io_service,
const unsigned short port,
std::size_t responseMax)
: mSocket (io_service, getConfig ().SSL_CONTEXT)
, mResolver (io_service)
, mHeader (maxClientHeaderBytes)
, mPort (port)
, mResponseMax (responseMax)
, mDeadline (io_service)
{
if (!getConfig ().SSL_VERIFY)
mSocket.SSLSocket ().set_verify_mode (boost::asio::ssl::verify_none);
}
void HttpsClient::makeGet (const std::string& strPath, boost::asio::streambuf& sb, const std::string& strHost)
{
std::ostream osRequest (&sb);
osRequest <<
"GET " << strPath << " HTTP/1.0\r\n"
"Host: " << strHost << "\r\n"
"Accept: */*\r\n" // YYY Do we need this line?
"Connection: close\r\n\r\n";
}
void HttpsClient::httpsRequest (
bool bSSL,
std::deque<std::string> deqSites,
FUNCTION_TYPE<void (boost::asio::streambuf& sb, const std::string& strHost)> build,
boost::posix_time::time_duration timeout,
FUNCTION_TYPE<bool (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)> complete)
{
mSSL = bSSL;
mDeqSites = deqSites;
mBuild = build;
mComplete = complete;
mTimeout = timeout;
httpsNext ();
}
void HttpsClient::httpsGet (
bool bSSL,
std::deque<std::string> deqSites,
const std::string& strPath,
boost::posix_time::time_duration timeout,
FUNCTION_TYPE<bool (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)> complete)
{
mComplete = complete;
mTimeout = timeout;
httpsRequest (
bSSL,
deqSites,
BIND_TYPE (&HttpsClient::makeGet, shared_from_this (), strPath, P_1, P_2),
timeout,
complete);
}
void HttpsClient::httpsNext ()
{
WriteLog (lsTRACE, HttpsClient) << "Fetch: " << mDeqSites[0];
boost::shared_ptr <boost::asio::ip::tcp::resolver::query> query (
new boost::asio::ip::tcp::resolver::query (
mDeqSites[0],
lexicalCast <std::string> (mPort),
boost::asio::ip::resolver_query_base::numeric_service));
mQuery = query;
mDeadline.expires_from_now (mTimeout, mShutdown);
WriteLog (lsTRACE, HttpsClient) << "expires_from_now: " << mShutdown.message ();
if (!mShutdown)
{
mDeadline.async_wait (
boost::bind (
&HttpsClient::handleDeadline,
shared_from_this (),
boost::asio::placeholders::error));
}
if (!mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Resolving: " << mDeqSites[0];
mResolver.async_resolve (*mQuery,
boost::bind (
&HttpsClient::handleResolve,
shared_from_this (),
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
if (mShutdown)
invokeComplete (mShutdown);
}
void HttpsClient::handleDeadline (const boost::system::error_code& ecResult)
{
if (ecResult == boost::asio::error::operation_aborted)
{
// Timer canceled because deadline no longer needed.
WriteLog (lsTRACE, HttpsClient) << "Deadline cancelled.";
nothing (); // Aborter is done.
}
else if (ecResult)
{
WriteLog (lsTRACE, HttpsClient) << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message ();
// Can't do anything sound.
abort ();
}
else
{
WriteLog (lsTRACE, HttpsClient) << "Deadline arrived.";
// Mark us as shutting down.
// XXX Use our own error code.
mShutdown = boost::system::error_code (boost::system::errc::bad_address, boost::system::system_category ());
// Cancel any resolving.
mResolver.cancel ();
// Stop the transaction.
mSocket.async_shutdown (boost::bind (
&HttpsClient::handleShutdown,
shared_from_this (),
boost::asio::placeholders::error));
}
}
void HttpsClient::handleShutdown (
const boost::system::error_code& ecResult
)
{
if (ecResult)
{
WriteLog (lsTRACE, HttpsClient) << "Shutdown error: " << mDeqSites[0] << ": " << ecResult.message ();
}
}
void HttpsClient::handleResolve (
const boost::system::error_code& ecResult,
boost::asio::ip::tcp::resolver::iterator itrEndpoint
)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message ();
invokeComplete (mShutdown);
}
else
{
WriteLog (lsTRACE, HttpsClient) << "Resolve complete.";
boost::asio::async_connect (
mSocket.lowest_layer (),
itrEndpoint,
boost::bind (
&HttpsClient::handleConnect,
shared_from_this (),
boost::asio::placeholders::error));
}
}
void HttpsClient::handleConnect (const boost::system::error_code& ecResult)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Connect error: " << mShutdown.message ();
}
if (!mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Connected.";
if (getConfig ().SSL_VERIFY)
{
mShutdown = mSocket.verify (mDeqSites[0]);
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "set_verify_callback: " << mDeqSites[0] << ": " << mShutdown.message ();
}
}
}
if (mShutdown)
{
invokeComplete (mShutdown);
}
else if (mSSL)
{
mSocket.async_handshake (
AutoSocket::ssl_socket::client,
boost::bind (
&HttpsClient::handleRequest,
shared_from_this (),
boost::asio::placeholders::error));
}
else
{
handleRequest (ecResult);
}
}
void HttpsClient::handleRequest (const boost::system::error_code& ecResult)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Handshake error:" << mShutdown.message ();
invokeComplete (mShutdown);
}
else
{
WriteLog (lsTRACE, HttpsClient) << "Session started.";
mBuild (mRequest, mDeqSites[0]);
mSocket.async_write (
mRequest,
boost::bind (&HttpsClient::handleWrite,
shared_from_this (),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void HttpsClient::handleWrite (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Write error: " << mShutdown.message ();
invokeComplete (mShutdown);
}
else
{
WriteLog (lsTRACE, HttpsClient) << "Wrote.";
mSocket.async_read_until (
mHeader,
"\r\n\r\n",
boost::bind (&HttpsClient::handleHeader,
shared_from_this (),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void HttpsClient::handleHeader (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
{
std::string strHeader ((std::istreambuf_iterator<char> (&mHeader)), std::istreambuf_iterator<char> ());
WriteLog (lsTRACE, HttpsClient) << "Header: \"" << strHeader << "\"";
static boost::regex reStatus ("\\`HTTP/1\\S+ (\\d{3}) .*\\'"); // HTTP/1.1 200 OK
static boost::regex reSize ("\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'");
static boost::regex reBody ("\\`.*\\r\\n\\r\\n(.*)\\'");
boost::smatch smMatch;
bool bMatch = boost::regex_match (strHeader, smMatch, reStatus); // Match status code.
if (!bMatch)
{
// XXX Use our own error code.
WriteLog (lsTRACE, HttpsClient) << "No status code";
invokeComplete (boost::system::error_code (boost::system::errc::bad_address, boost::system::system_category ()));
return;
}
mStatus = lexicalCastThrow <int> (std::string (smMatch[1]));
if (boost::regex_match (strHeader, smMatch, reBody)) // we got some body
mBody = smMatch[1];
if (boost::regex_match (strHeader, smMatch, reSize))
{
int size = lexicalCastThrow <int> (std::string(smMatch[1]));
if (size < mResponseMax)
mResponseMax = size;
}
if (mResponseMax == 0)
{
// no body wanted or available
invokeComplete (ecResult, mStatus);
}
else if (mBody.size () >= mResponseMax)
{
// we got the whole thing
invokeComplete (ecResult, mStatus, mBody);
}
else
{
mSocket.async_read (
mResponse.prepare (mResponseMax - mBody.size ()),
boost::asio::transfer_all (),
boost::bind (&HttpsClient::handleData,
shared_from_this (),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void HttpsClient::handleData (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown && mShutdown != boost::asio::error::eof)
{
WriteLog (lsTRACE, HttpsClient) << "Read error: " << mShutdown.message ();
invokeComplete (mShutdown);
}
else
{
if (mShutdown)
{
WriteLog (lsTRACE, HttpsClient) << "Complete.";
nothing ();
}
else
{
mResponse.commit (bytes_transferred);
std::string strBody ((std::istreambuf_iterator<char> (&mResponse)), std::istreambuf_iterator<char> ());
invokeComplete (ecResult, mStatus, mBody + strBody);
}
}
}
// Call cancel the deadline timer and invoke the completion routine.
void HttpsClient::invokeComplete (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)
{
boost::system::error_code ecCancel;
(void) mDeadline.cancel (ecCancel);
if (ecCancel)
{
WriteLog (lsTRACE, HttpsClient) << "HttpsClient::invokeComplete: Deadline cancel error: " << ecCancel.message ();
}
WriteLog (lsDEBUG, HttpsClient) << "HttpsClient::invokeComplete: Deadline popping: " << mDeqSites.size ();
if (!mDeqSites.empty ())
{
mDeqSites.pop_front ();
}
bool bAgain = true;
if (mDeqSites.empty () || !ecResult)
{
// ecResult: !0 = had an error, last entry
// iStatus: result, if no error
// strData: data, if no error
bAgain = mComplete && mComplete (ecResult ? ecResult : ecCancel, iStatus, strData);
}
if (!mDeqSites.empty () && bAgain)
{
httpsNext ();
}
}
void HttpsClient::httpsGet (
bool bSSL,
boost::asio::io_service& io_service,
std::deque<std::string> deqSites,
const unsigned short port,
const std::string& strPath,
std::size_t responseMax,
boost::posix_time::time_duration timeout,
FUNCTION_TYPE<bool (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)> complete)
{
boost::shared_ptr<HttpsClient> client (new HttpsClient (io_service, port, responseMax));
client->httpsGet (bSSL, deqSites, strPath, timeout, complete);
}
void HttpsClient::httpsGet (
bool bSSL,
boost::asio::io_service& io_service,
std::string strSite,
const unsigned short port,
const std::string& strPath,
std::size_t responseMax,
boost::posix_time::time_duration timeout,
FUNCTION_TYPE<bool (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)> complete)
{
std::deque<std::string> deqSites (1, strSite);
boost::shared_ptr<HttpsClient> client (new HttpsClient (io_service, port, responseMax));
client->httpsGet (bSSL, deqSites, strPath, timeout, complete);
}
void HttpsClient::httpsRequest (
bool bSSL,
boost::asio::io_service& io_service,
std::string strSite,
const unsigned short port,
FUNCTION_TYPE<void (boost::asio::streambuf& sb, const std::string& strHost)> setRequest,
std::size_t responseMax,
boost::posix_time::time_duration timeout,
FUNCTION_TYPE<bool (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)> complete)
{
std::deque<std::string> deqSites (1, strSite);
boost::shared_ptr<HttpsClient> client (new HttpsClient (io_service, port, responseMax));
client->httpsRequest (bSSL, deqSites, setRequest, timeout, complete);
}
#define SMS_TIMEOUT 30
bool responseSMS (const boost::system::error_code& ecResult, int iStatus, const std::string& strData)
{
WriteLog (lsINFO, HttpsClient) << "SMS: Response:" << iStatus << " :" << strData;
return true;
}
void HttpsClient::sendSMS (boost::asio::io_service& io_service, const std::string& strText)
{
std::string strScheme;
std::string strDomain;
int iPort;
std::string strPath;
if (getConfig ().SMS_URL == "" || !parseUrl (getConfig ().SMS_URL, strScheme, strDomain, iPort, strPath))
{
WriteLog (lsWARNING, HttpsClient) << "SMSRequest: Bad URL:" << getConfig ().SMS_URL;
}
else
{
bool bSSL = strScheme == "https";
std::deque<std::string> deqSites (1, strDomain);
std::string strURI =
boost::str (boost::format ("%s?from=%s&to=%s&api_key=%s&api_secret=%s&text=%s")
% (strPath.empty () ? "/" : strPath)
% getConfig ().SMS_FROM
% getConfig ().SMS_TO
% getConfig ().SMS_KEY
% getConfig ().SMS_SECRET
% urlEncode (strText));
// WriteLog (lsINFO) << "SMS: Request:" << strURI;
WriteLog (lsINFO, HttpsClient) << "SMS: Request: '" << strText << "'";
if (iPort < 0)
iPort = bSSL ? 443 : 80;
boost::shared_ptr<HttpsClient> client (new HttpsClient (io_service, iPort, maxClientHeaderBytes));
client->httpsGet (bSSL, deqSites, strURI, boost::posix_time::seconds (SMS_TIMEOUT),
BIND_TYPE (&responseSMS, P_1, P_2, P_3));
}
}
// vim:ts=4