Original HUSH source code based on ZEC 1.0.8 . For historical purposes only! https://hush.is
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

357 lines
10 KiB

// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "rpcprotocol.h"
#include "clientversion.h"
#include "random.h"
#include "tinyformat.h"
#include "util.h"
#include "utilstrencodings.h"
#include "utiltime.h"
#include "version.h"
#include <stdint.h>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/shared_ptr.hpp>
#include <univalue.h>
using namespace std;
//! Number of bytes to allocate and read at most at once in post data
const size_t POST_READ_SIZE = 256 * 1024;
/**
* 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.
*/
string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders)
{
ostringstream s;
s << "POST / HTTP/1.1\r\n"
<< "User-Agent: zcash-json-rpc/" << FormatFullVersion() << "\r\n"
<< "Host: 127.0.0.1\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Connection: close\r\n"
<< "Accept: application/json\r\n";
BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders)
s << item.first << ": " << item.second << "\r\n";
s << "\r\n" << strMsg;
return s.str();
}
static string rfc1123Time()
{
return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime());
}
static const char *httpStatusDescription(int nStatus)
{
switch (nStatus) {
case HTTP_OK: return "OK";
case HTTP_BAD_REQUEST: return "Bad Request";
case HTTP_FORBIDDEN: return "Forbidden";
case HTTP_NOT_FOUND: return "Not Found";
case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error";
default: return "";
}
}
string HTTPError(int nStatus, bool keepalive, bool headersOnly)
{
if (nStatus == HTTP_UNAUTHORIZED)
return strprintf("HTTP/1.0 401 Authorization Required\r\n"
"Date: %s\r\n"
"Server: zcash-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(), FormatFullVersion());
return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive,
headersOnly, "text/plain");
}
string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType)
{
return strprintf(
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Connection: %s\r\n"
"Content-Length: %u\r\n"
"Content-Type: %s\r\n"
"Server: zcash-json-rpc/%s\r\n"
"\r\n",
nStatus,
httpStatusDescription(nStatus),
rfc1123Time(),
keepalive ? "keep-alive" : "close",
contentLength,
contentType,
FormatFullVersion());
}
string HTTPReply(int nStatus, const string& strMsg, bool keepalive,
bool headersOnly, const char *contentType)
{
if (headersOnly)
{
return HTTPReplyHeader(nStatus, keepalive, 0, contentType);
} else {
return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg;
}
}
bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
string& http_method, string& http_uri)
{
string str;
getline(stream, str);
// HTTP request line is space-delimited
vector<string> vWords;
boost::split(vWords, str, boost::is_any_of(" "));
if (vWords.size() < 2)
return false;
// HTTP methods permitted: GET, POST
http_method = vWords[0];
if (http_method != "GET" && http_method != "POST")
return false;
// HTTP URI must be an absolute path, relative to current host
http_uri = vWords[1];
if (http_uri.size() == 0 || http_uri[0] != '/')
return false;
// parse proto, if present
string strProto = "";
if (vWords.size() > 2)
strProto = vWords[2];
proto = 0;
const char *ver = strstr(strProto.c_str(), "HTTP/1.");
if (ver != NULL)
proto = atoi(ver+7);
return true;
}
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto)
{
string str;
getline(stream, str);
vector<string> vWords;
boost::split(vWords, str, boost::is_any_of(" "));
if (vWords.size() < 2)
return HTTP_INTERNAL_SERVER_ERROR;
proto = 0;
const char *ver = strstr(str.c_str(), "HTTP/1.");
if (ver != NULL)
proto = atoi(ver+7);
return atoi(vWords[1].c_str());
}
int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
{
int nLen = 0;
while (true)
{
string str;
std::getline(stream, str);
if (str.empty() || str == "\r")
break;
string::size_type nColon = str.find(":");
if (nColon != string::npos)
{
string strHeader = str.substr(0, nColon);
boost::trim(strHeader);
boost::to_lower(strHeader);
string strValue = str.substr(nColon+1);
boost::trim(strValue);
mapHeadersRet[strHeader] = strValue;
if (strHeader == "content-length")
nLen = atoi(strValue.c_str());
}
}
return nLen;
}
int ReadHTTPMessage(std::basic_istream<char>& stream, map<string,
string>& mapHeadersRet, string& strMessageRet,
int nProto, size_t max_size)
{
mapHeadersRet.clear();
strMessageRet = "";
// Read header
int nLen = ReadHTTPHeaders(stream, mapHeadersRet);
if (nLen < 0 || (size_t)nLen > max_size)
return HTTP_INTERNAL_SERVER_ERROR;
// Read message
if (nLen > 0)
{
vector<char> vch;
size_t ptr = 0;
while (ptr < (size_t)nLen)
{
size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE);
vch.resize(ptr + bytes_to_read);
stream.read(&vch[ptr], bytes_to_read);
if (!stream) // Connection lost while reading
return HTTP_INTERNAL_SERVER_ERROR;
ptr += bytes_to_read;
}
strMessageRet = string(vch.begin(), vch.end());
}
string sConHdr = mapHeadersRet["connection"];
if ((sConHdr != "close") && (sConHdr != "keep-alive"))
{
if (nProto >= 1)
mapHeadersRet["connection"] = "keep-alive";
else
mapHeadersRet["connection"] = "close";
}
return HTTP_OK;
}
/**
* 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://jsonrpc.org/historical/json-rpc-over-http.html
* http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx
*/
string JSONRPCRequest(const string& strMethod, const UniValue& params, const UniValue& id)
{
UniValue request(UniValue::VOBJ);
request.push_back(Pair("method", strMethod));
request.push_back(Pair("params", params));
request.push_back(Pair("id", id));
return request.write() + "\n";
}
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id)
{
UniValue reply(UniValue::VOBJ);
if (!error.isNull())
reply.push_back(Pair("result", NullUniValue));
else
reply.push_back(Pair("result", result));
reply.push_back(Pair("error", error));
reply.push_back(Pair("id", id));
return reply;
}
string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id)
{
UniValue reply = JSONRPCReplyObj(result, error, id);
return reply.write() + "\n";
}
UniValue JSONRPCError(int code, const string& message)
{
UniValue error(UniValue::VOBJ);
error.push_back(Pair("code", code));
error.push_back(Pair("message", message));
return error;
}
/** Username used when cookie authentication is in use (arbitrary, only for
* recognizability in debugging/logging purposes)
*/
static const std::string COOKIEAUTH_USER = "__cookie__";
/** Default name for auth cookie file */
static const std::string COOKIEAUTH_FILE = ".cookie";
boost::filesystem::path GetAuthCookieFile()
{
boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
if (!path.is_complete()) path = GetDataDir() / path;
return path;
}
bool GenerateAuthCookie(std::string *cookie_out)
{
unsigned char rand_pwd[32];
GetRandBytes(rand_pwd, 32);
std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
/** the umask determines what permissions are used to create this file -
* these are set to 077 in init.cpp unless overridden with -sysperms.
*/
std::ofstream file;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open()) {
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
return false;
}
file << cookie;
file.close();
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
if (cookie_out)
*cookie_out = cookie;
return true;
}
bool GetAuthCookie(std::string *cookie_out)
{
std::ifstream file;
std::string cookie;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open())
return false;
std::getline(file, cookie);
file.close();
if (cookie_out)
*cookie_out = cookie;
return true;
}
void DeleteAuthCookie()
{
try {
boost::filesystem::remove(GetAuthCookieFile());
} catch (const boost::filesystem::filesystem_error& e) {
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
}
}