]>
Commit | Line | Data |
---|---|---|
fb78cc23 | 1 | // Copyright (c) 2010 Satoshi Nakamoto |
f914f1a7 | 2 | // Copyright (c) 2009-2014 The Bitcoin Core developers |
72fb3d29 | 3 | // Distributed under the MIT software license, see the accompanying |
fb78cc23 WL |
4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | ||
6 | #include "rpcprotocol.h" | |
7 | ||
71697f97 | 8 | #include "clientversion.h" |
ad49c256 | 9 | #include "tinyformat.h" |
611116d4 | 10 | #include "util.h" |
ad49c256 WL |
11 | #include "utilstrencodings.h" |
12 | #include "utiltime.h" | |
6e5fd003 | 13 | #include "version.h" |
fb78cc23 WL |
14 | |
15 | #include <stdint.h> | |
16 | ||
17 | #include <boost/algorithm/string.hpp> | |
18 | #include <boost/asio.hpp> | |
19 | #include <boost/asio/ssl.hpp> | |
20 | #include <boost/bind.hpp> | |
21 | #include <boost/filesystem.hpp> | |
22 | #include <boost/foreach.hpp> | |
23 | #include <boost/iostreams/concepts.hpp> | |
24 | #include <boost/iostreams/stream.hpp> | |
fb78cc23 WL |
25 | #include <boost/shared_ptr.hpp> |
26 | #include "json/json_spirit_writer_template.h" | |
27 | ||
28 | using namespace std; | |
fb78cc23 WL |
29 | using namespace json_spirit; |
30 | ||
72fb3d29 | 31 | //! Number of bytes to allocate and read at most at once in post data |
2ec5a3d2 WL |
32 | const size_t POST_READ_SIZE = 256 * 1024; |
33 | ||
72fb3d29 MF |
34 | /** |
35 | * HTTP protocol | |
36 | * | |
37 | * This ain't Apache. We're just using HTTP header for the length field | |
38 | * and to be compatible with other JSON-RPC implementations. | |
39 | */ | |
fb78cc23 WL |
40 | |
41 | string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) | |
42 | { | |
43 | ostringstream s; | |
44 | s << "POST / HTTP/1.1\r\n" | |
45 | << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" | |
46 | << "Host: 127.0.0.1\r\n" | |
47 | << "Content-Type: application/json\r\n" | |
48 | << "Content-Length: " << strMsg.size() << "\r\n" | |
49 | << "Connection: close\r\n" | |
50 | << "Accept: application/json\r\n"; | |
51 | BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) | |
52 | s << item.first << ": " << item.second << "\r\n"; | |
53 | s << "\r\n" << strMsg; | |
54 | ||
55 | return s.str(); | |
56 | } | |
57 | ||
58 | static string rfc1123Time() | |
59 | { | |
3e8ac6af | 60 | return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime()); |
fb78cc23 WL |
61 | } |
62 | ||
16f33f16 | 63 | static const char *httpStatusDescription(int nStatus) |
64 | { | |
65 | switch (nStatus) { | |
66 | case HTTP_OK: return "OK"; | |
67 | case HTTP_BAD_REQUEST: return "Bad Request"; | |
68 | case HTTP_FORBIDDEN: return "Forbidden"; | |
69 | case HTTP_NOT_FOUND: return "Not Found"; | |
70 | case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error"; | |
71 | default: return ""; | |
72 | } | |
73 | } | |
74 | ||
75 | string HTTPError(int nStatus, bool keepalive, bool headersOnly) | |
fb78cc23 WL |
76 | { |
77 | if (nStatus == HTTP_UNAUTHORIZED) | |
78 | return strprintf("HTTP/1.0 401 Authorization Required\r\n" | |
79 | "Date: %s\r\n" | |
80 | "Server: bitcoin-json-rpc/%s\r\n" | |
81 | "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" | |
82 | "Content-Type: text/html\r\n" | |
83 | "Content-Length: 296\r\n" | |
84 | "\r\n" | |
85 | "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" | |
86 | "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" | |
87 | "<HTML>\r\n" | |
88 | "<HEAD>\r\n" | |
89 | "<TITLE>Error</TITLE>\r\n" | |
90 | "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" | |
91 | "</HEAD>\r\n" | |
92 | "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" | |
7d9d134b | 93 | "</HTML>\r\n", rfc1123Time(), FormatFullVersion()); |
c912e22d | 94 | |
16f33f16 | 95 | return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive, |
96 | headersOnly, "text/plain"); | |
97 | } | |
c912e22d | 98 | |
e17151ad | 99 | string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType) |
16f33f16 | 100 | { |
fb78cc23 WL |
101 | return strprintf( |
102 | "HTTP/1.1 %d %s\r\n" | |
103 | "Date: %s\r\n" | |
104 | "Connection: %s\r\n" | |
783b182c | 105 | "Content-Length: %u\r\n" |
c912e22d | 106 | "Content-Type: %s\r\n" |
fb78cc23 | 107 | "Server: bitcoin-json-rpc/%s\r\n" |
e17151ad | 108 | "\r\n", |
fb78cc23 | 109 | nStatus, |
16f33f16 | 110 | httpStatusDescription(nStatus), |
7d9d134b | 111 | rfc1123Time(), |
fb78cc23 | 112 | keepalive ? "keep-alive" : "close", |
e17151ad | 113 | contentLength, |
c912e22d | 114 | contentType, |
e17151ad WL |
115 | FormatFullVersion()); |
116 | } | |
117 | ||
118 | string HTTPReply(int nStatus, const string& strMsg, bool keepalive, | |
119 | bool headersOnly, const char *contentType) | |
120 | { | |
121 | if (headersOnly) | |
122 | { | |
123 | return HTTPReplyHeader(nStatus, keepalive, 0, contentType); | |
124 | } else { | |
125 | return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg; | |
126 | } | |
fb78cc23 WL |
127 | } |
128 | ||
129 | bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, | |
130 | string& http_method, string& http_uri) | |
131 | { | |
132 | string str; | |
133 | getline(stream, str); | |
134 | ||
135 | // HTTP request line is space-delimited | |
136 | vector<string> vWords; | |
137 | boost::split(vWords, str, boost::is_any_of(" ")); | |
138 | if (vWords.size() < 2) | |
139 | return false; | |
140 | ||
141 | // HTTP methods permitted: GET, POST | |
142 | http_method = vWords[0]; | |
143 | if (http_method != "GET" && http_method != "POST") | |
144 | return false; | |
145 | ||
146 | // HTTP URI must be an absolute path, relative to current host | |
147 | http_uri = vWords[1]; | |
148 | if (http_uri.size() == 0 || http_uri[0] != '/') | |
149 | return false; | |
150 | ||
151 | // parse proto, if present | |
152 | string strProto = ""; | |
153 | if (vWords.size() > 2) | |
154 | strProto = vWords[2]; | |
155 | ||
156 | proto = 0; | |
157 | const char *ver = strstr(strProto.c_str(), "HTTP/1."); | |
158 | if (ver != NULL) | |
159 | proto = atoi(ver+7); | |
160 | ||
161 | return true; | |
162 | } | |
163 | ||
164 | int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) | |
165 | { | |
166 | string str; | |
167 | getline(stream, str); | |
168 | vector<string> vWords; | |
169 | boost::split(vWords, str, boost::is_any_of(" ")); | |
170 | if (vWords.size() < 2) | |
171 | return HTTP_INTERNAL_SERVER_ERROR; | |
172 | proto = 0; | |
173 | const char *ver = strstr(str.c_str(), "HTTP/1."); | |
174 | if (ver != NULL) | |
175 | proto = atoi(ver+7); | |
176 | return atoi(vWords[1].c_str()); | |
177 | } | |
178 | ||
179 | int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) | |
180 | { | |
181 | int nLen = 0; | |
182 | while (true) | |
183 | { | |
184 | string str; | |
185 | std::getline(stream, str); | |
186 | if (str.empty() || str == "\r") | |
187 | break; | |
188 | string::size_type nColon = str.find(":"); | |
189 | if (nColon != string::npos) | |
190 | { | |
191 | string strHeader = str.substr(0, nColon); | |
192 | boost::trim(strHeader); | |
193 | boost::to_lower(strHeader); | |
194 | string strValue = str.substr(nColon+1); | |
195 | boost::trim(strValue); | |
196 | mapHeadersRet[strHeader] = strValue; | |
197 | if (strHeader == "content-length") | |
198 | nLen = atoi(strValue.c_str()); | |
199 | } | |
200 | } | |
201 | return nLen; | |
202 | } | |
203 | ||
204 | ||
205 | int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, | |
206 | string>& mapHeadersRet, string& strMessageRet, | |
733177eb | 207 | int nProto, size_t max_size) |
fb78cc23 WL |
208 | { |
209 | mapHeadersRet.clear(); | |
210 | strMessageRet = ""; | |
211 | ||
212 | // Read header | |
213 | int nLen = ReadHTTPHeaders(stream, mapHeadersRet); | |
733177eb | 214 | if (nLen < 0 || (size_t)nLen > max_size) |
fb78cc23 WL |
215 | return HTTP_INTERNAL_SERVER_ERROR; |
216 | ||
217 | // Read message | |
218 | if (nLen > 0) | |
219 | { | |
2ec5a3d2 WL |
220 | vector<char> vch; |
221 | size_t ptr = 0; | |
222 | while (ptr < (size_t)nLen) | |
223 | { | |
224 | size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE); | |
225 | vch.resize(ptr + bytes_to_read); | |
226 | stream.read(&vch[ptr], bytes_to_read); | |
227 | if (!stream) // Connection lost while reading | |
228 | return HTTP_INTERNAL_SERVER_ERROR; | |
229 | ptr += bytes_to_read; | |
230 | } | |
fb78cc23 WL |
231 | strMessageRet = string(vch.begin(), vch.end()); |
232 | } | |
233 | ||
234 | string sConHdr = mapHeadersRet["connection"]; | |
235 | ||
236 | if ((sConHdr != "close") && (sConHdr != "keep-alive")) | |
237 | { | |
238 | if (nProto >= 1) | |
239 | mapHeadersRet["connection"] = "keep-alive"; | |
240 | else | |
241 | mapHeadersRet["connection"] = "close"; | |
242 | } | |
243 | ||
244 | return HTTP_OK; | |
245 | } | |
246 | ||
72fb3d29 MF |
247 | /** |
248 | * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, | |
249 | * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were | |
250 | * unspecified (HTTP errors and contents of 'error'). | |
251 | * | |
252 | * 1.0 spec: http://json-rpc.org/wiki/specification | |
253 | * 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html | |
254 | * http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx | |
255 | */ | |
fb78cc23 WL |
256 | |
257 | string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id) | |
258 | { | |
259 | Object request; | |
260 | request.push_back(Pair("method", strMethod)); | |
261 | request.push_back(Pair("params", params)); | |
262 | request.push_back(Pair("id", id)); | |
263 | return write_string(Value(request), false) + "\n"; | |
264 | } | |
265 | ||
266 | Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) | |
267 | { | |
268 | Object reply; | |
269 | if (error.type() != null_type) | |
270 | reply.push_back(Pair("result", Value::null)); | |
271 | else | |
272 | reply.push_back(Pair("result", result)); | |
273 | reply.push_back(Pair("error", error)); | |
274 | reply.push_back(Pair("id", id)); | |
275 | return reply; | |
276 | } | |
277 | ||
278 | string JSONRPCReply(const Value& result, const Value& error, const Value& id) | |
279 | { | |
280 | Object reply = JSONRPCReplyObj(result, error, id); | |
281 | return write_string(Value(reply), false) + "\n"; | |
282 | } | |
283 | ||
284 | Object JSONRPCError(int code, const string& message) | |
285 | { | |
286 | Object error; | |
287 | error.push_back(Pair("code", code)); | |
288 | error.push_back(Pair("message", message)); | |
289 | return error; | |
290 | } |