Files
xahaud/src/ripple_net/rpc/RPCUtil.cpp
2013-11-20 10:16:46 -08:00

296 lines
9.4 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
// Used for logging
struct RPCLog;
SETUP_LOGN (RPCLog, "RPC")
unsigned int const gMaxHTTPHeaderSize = 0x02000000;
std::string gFormatStr ("v1");
// VFALCO TODO clean up this nonsense
std::string FormatFullVersion ()
{
return (gFormatStr);
}
Json::Value JSONRPCError (int code, const std::string& message)
{
Json::Value error (Json::objectValue);
error["code"] = Json::Value (code);
error["message"] = Json::Value (message);
return error;
}
//
// HTTP protocol
//
// This ain't Apache. We're just using HTTP header for the length field
// and to be compatible with other JSON-RPC implementations.
//
std::string createHTTPPost (const std::string& strHost, const std::string& strPath, const std::string& strMsg, const std::map<std::string, std::string>& mapRequestHeaders)
{
std::ostringstream s;
s << "POST "
<< (strPath.empty () ? "/" : strPath)
<< " HTTP/1.0\r\n"
<< "User-Agent: " SYSTEM_NAME "-json-rpc/" << FormatFullVersion () << "\r\n"
<< "Host: " << strHost << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size () << "\r\n"
<< "Accept: application/json\r\n";
typedef std::map<std::string, std::string>::value_type HeaderType;
BOOST_FOREACH (const HeaderType & item, mapRequestHeaders)
s << item.first << ": " << item.second << "\r\n";
s << "\r\n" << strMsg;
return s.str ();
}
std::string rfc1123Time ()
{
char buffer[64];
time_t now;
time (&now);
struct tm* now_gmt = gmtime (&now);
std::string locale (setlocale (LC_TIME, NULL));
setlocale (LC_TIME, "C"); // we want posix (aka "C") weekday/month strings
strftime (buffer, sizeof (buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt);
setlocale (LC_TIME, locale.c_str ());
return std::string (buffer);
}
std::string HTTPReply (int nStatus, const std::string& strMsg)
{
WriteLog (lsTRACE, RPCLog) << "HTTP Reply " << nStatus << " " << strMsg;
if (nStatus == 401)
return strprintf ("HTTP/1.0 401 Authorization Required\r\n"
"Date: %s\r\n"
"Server: " SYSTEM_NAME "-json-rpc/%s\r\n"
"WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 296\r\n"
"\r\n"
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
"\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
"<HTML>\r\n"
"<HEAD>\r\n"
"<TITLE>Error</TITLE>\r\n"
"<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n"
"</HEAD>\r\n"
"<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
"</HTML>\r\n", rfc1123Time ().c_str (), FormatFullVersion ().c_str ());
std::string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 403) strStatus = "Forbidden";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
std::string access;
if (getConfig ().RPC_ALLOW_REMOTE) access = "Access-Control-Allow-Origin: *\r\n";
else access = "";
return strprintf (
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Connection: Keep-Alive\r\n"
"%s"
"Content-Length: %d\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Server: " SYSTEM_NAME "-json-rpc/%s\r\n"
"\r\n"
"%s\r\n",
nStatus,
strStatus.c_str (),
rfc1123Time ().c_str (),
access.c_str (),
strMsg.size () + 2,
//SERVER_VERSION,
BuildInfo::getFullVersionString (),
strMsg.c_str ());
}
int ReadHTTPStatus (std::basic_istream<char>& stream)
{
std::string str;
getline (stream, str);
std::vector<std::string> vWords;
boost::split (vWords, str, boost::is_any_of (" "));
if (vWords.size () < 2)
return 500;
return atoi (vWords[1].c_str ());
}
int ReadHTTPHeader (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet)
{
int nLen = 0;
for (;;)
{
std::string str;
std::getline (stream, str);
if (str.empty () || str == "\r")
break;
std::string::size_type nColon = str.find (":");
if (nColon != std::string::npos)
{
std::string strHeader = str.substr (0, nColon);
boost::trim (strHeader);
boost::to_lower (strHeader);
std::string strValue = str.substr (nColon + 1);
boost::trim (strValue);
mapHeadersRet[strHeader] = strValue;
if (strHeader == "content-length")
nLen = atoi (strValue.c_str ());
}
}
return nLen;
}
int ReadHTTP (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet)
{
mapHeadersRet.clear ();
strMessageRet = "";
// Read status
int nStatus = ReadHTTPStatus (stream);
// Read header
int nLen = ReadHTTPHeader (stream, mapHeadersRet);
if (nLen < 0 || nLen > gMaxHTTPHeaderSize)
return 500;
// Read message
if (nLen > 0)
{
std::vector<char> vch (nLen);
stream.read (&vch[0], nLen);
strMessageRet = std::string (vch.begin (), vch.end ());
}
return nStatus;
}
std::string DecodeBase64 (std::string s)
{
// FIXME: This performs badly
BIO* b64, *bmem;
char* buffer = static_cast<char*> (calloc (s.size (), sizeof (char)));
b64 = BIO_new (BIO_f_base64 ());
BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new_mem_buf (const_cast<char*> (s.data ()), s.size ());
bmem = BIO_push (b64, bmem);
BIO_read (bmem, buffer, s.size ());
BIO_free_all (bmem);
std::string result (buffer);
free (buffer);
return result;
}
bool HTTPAuthorized (const std::map<std::string, std::string>& mapHeaders)
{
std::map<std::string, std::string>::const_iterator it = mapHeaders.find ("authorization");
if ((it == mapHeaders.end ()) || (it->second.substr (0, 6) != "Basic "))
return getConfig ().RPC_USER.empty () && getConfig ().RPC_PASSWORD.empty ();
std::string strUserPass64 = it->second.substr (6);
boost::trim (strUserPass64);
std::string strUserPass = DecodeBase64 (strUserPass64);
std::string::size_type nColon = strUserPass.find (":");
if (nColon == std::string::npos)
return false;
std::string strUser = strUserPass.substr (0, nColon);
std::string strPassword = strUserPass.substr (nColon + 1);
return (strUser == getConfig ().RPC_USER) && (strPassword == getConfig ().RPC_PASSWORD);
}
//
// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
// unspecified (HTTP errors and contents of 'error').
//
// 1.0 spec: http://json-rpc.org/wiki/specification
// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
//
std::string JSONRPCRequest (const std::string& strMethod, const Json::Value& params, const Json::Value& id)
{
Json::Value request;
request["method"] = strMethod;
request["params"] = params;
request["id"] = id;
Json::FastWriter writer;
return writer.write (request) + "\n";
}
std::string JSONRPCReply (const Json::Value& result, const Json::Value& error, const Json::Value& id)
{
Json::Value reply (Json::objectValue);
reply["result"] = result;
//reply["error"]=error;
//reply["id"]=id;
Json::FastWriter writer;
return writer.write (reply) + "\n";
}
void ErrorReply (std::ostream& stream, const Json::Value& objError, const Json::Value& id)
{
// Send error reply from json-rpc error object
int nStatus = 500;
int code = objError["code"].asInt ();
if (code == -32600) nStatus = 400;
else if (code == -32601) nStatus = 404;
std::string strReply = JSONRPCReply (Json::Value (), objError, id);
stream << HTTPReply (nStatus, strReply) << std::flush;
}
// vim:ts=4