RPC Add cross origin resource sharing support

This commit is contained in:
Tim L 2017-10-24 09:26:05 -04:00
parent 8d511f3c24
commit 69c37200aa
10 changed files with 82 additions and 19 deletions

3
contrib/epee/include/net/http_base.h Normal file → Executable file
View File

@ -46,6 +46,7 @@ namespace net_utils
{ {
enum http_method{ enum http_method{
http_method_options,
http_method_get, http_method_get,
http_method_post, http_method_post,
http_method_put, http_method_put,
@ -115,6 +116,7 @@ namespace net_utils
std::string m_host; //"Host:" std::string m_host; //"Host:"
std::string m_cookie; //"Cookie:" std::string m_cookie; //"Cookie:"
std::string m_user_agent; //"User-Agent:" std::string m_user_agent; //"User-Agent:"
std::string m_origin; //"Origin:"
fields_list m_etc_fields; fields_list m_etc_fields;
void clear() void clear()
@ -128,6 +130,7 @@ namespace net_utils
m_host.clear(); m_host.clear();
m_cookie.clear(); m_cookie.clear();
m_user_agent.clear(); m_user_agent.clear();
m_origin.clear();
m_etc_fields.clear(); m_etc_fields.clear();
} }
}; };

12
contrib/epee/include/net/http_client.h Normal file → Executable file
View File

@ -749,10 +749,10 @@ using namespace std;
MTRACE("http_stream_filter::parse_cached_header(*)"); MTRACE("http_stream_filter::parse_cached_header(*)");
STATIC_REGEXP_EXPR_1(rexp_mach_field, STATIC_REGEXP_EXPR_1(rexp_mach_field,
"\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)"
// 12 3 4 5 6 7 8 9 10 // 12 3 4 5 6 7 8 9 10 11
"|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
//11 1213 14 //12 13 14 15
boost::regex::icase | boost::regex::normal); boost::regex::icase | boost::regex::normal);
boost::smatch result; boost::smatch result;
@ -764,7 +764,7 @@ using namespace std;
//lookup all fields and fill well-known fields //lookup all fields and fill well-known fields
while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched)
{ {
const size_t field_val = 13; const size_t field_val = 14;
//const size_t field_etc_name = 11; //const size_t field_etc_name = 11;
int i = 2; //start position = 2 int i = 2; //start position = 2
@ -788,8 +788,10 @@ using namespace std;
body_info.m_cookie = result[field_val]; body_info.m_cookie = result[field_val];
else if(result[i++].matched)//"User-Agent" else if(result[i++].matched)//"User-Agent"
body_info.m_user_agent = result[field_val]; body_info.m_user_agent = result[field_val];
else if(result[i++].matched)//"Origin"
body_info.m_origin = result[field_val];
else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!)
body_info.m_etc_fields.emplace_back(result[11], result[field_val]); body_info.m_etc_fields.emplace_back(result[12], result[field_val]);
else else
{CHECK_AND_ASSERT_MES(false, false, "http_stream_filter::parse_cached_header() not matched last entry in:"<<m_cache_to_process);} {CHECK_AND_ASSERT_MES(false, false, "http_stream_filter::parse_cached_header() not matched last entry in:"<<m_cache_to_process);}

2
contrib/epee/include/net/http_protocol_handler.h Normal file → Executable file
View File

@ -54,6 +54,7 @@ namespace net_utils
struct http_server_config struct http_server_config
{ {
std::string m_folder; std::string m_folder;
std::vector<std::string> m_access_control_origins;
boost::optional<login> m_user; boost::optional<login> m_user;
critical_section m_lock; critical_section m_lock;
}; };
@ -193,6 +194,7 @@ namespace net_utils
response.m_response_code = 200; response.m_response_code = 200;
response.m_response_comment = "OK"; response.m_response_comment = "OK";
response.m_body.clear(); response.m_body.clear();
return m_config.m_phandler->handle_http_request(query_info, response, m_conn_context); return m_config.m_phandler->handle_http_request(query_info, response, m_conn_context);
} }

51
contrib/epee/include/net/http_protocol_handler.inl Normal file → Executable file
View File

@ -316,7 +316,10 @@ namespace net_utils
CHECK_AND_ASSERT_MES(result[0].matched, false, "simple_http_connection_handler::analize_http_method() assert failed..."); CHECK_AND_ASSERT_MES(result[0].matched, false, "simple_http_connection_handler::analize_http_method() assert failed...");
http_ver_major = boost::lexical_cast<int>(result[11]); http_ver_major = boost::lexical_cast<int>(result[11]);
http_ver_minor = boost::lexical_cast<int>(result[12]); http_ver_minor = boost::lexical_cast<int>(result[12]);
if(result[4].matched)
if(result[3].matched)
method = http::http_method_options;
else if(result[4].matched)
method = http::http_method_get; method = http::http_method_get;
else if(result[5].matched) else if(result[5].matched)
method = http::http_method_head; method = http::http_method_head;
@ -472,8 +475,8 @@ namespace net_utils
bool simple_http_connection_handler<t_connection_context>::parse_cached_header(http_header_info& body_info, const std::string& m_cache_to_process, size_t pos) bool simple_http_connection_handler<t_connection_context>::parse_cached_header(http_header_info& body_info, const std::string& m_cache_to_process, size_t pos)
{ {
STATIC_REGEXP_EXPR_1(rexp_mach_field, STATIC_REGEXP_EXPR_1(rexp_mach_field,
"\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)"
// 12 3 4 5 6 7 8 9 10 // 12 3 4 5 6 7 8 9 10 11
"|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
//11 1213 14 //11 1213 14
boost::regex::icase | boost::regex::normal); boost::regex::icase | boost::regex::normal);
@ -487,8 +490,8 @@ namespace net_utils
//lookup all fields and fill well-known fields //lookup all fields and fill well-known fields
while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched)
{ {
const size_t field_val = 13; const size_t field_val = 14;
const size_t field_etc_name = 11; const size_t field_etc_name = 12;
int i = 2; //start position = 2 int i = 2; //start position = 2
if(result[i++].matched)//"Connection" if(result[i++].matched)//"Connection"
@ -509,6 +512,8 @@ namespace net_utils
body_info.m_cookie = result[field_val]; body_info.m_cookie = result[field_val];
else if(result[i++].matched)//"User-Agent" else if(result[i++].matched)//"User-Agent"
body_info.m_user_agent = result[field_val]; body_info.m_user_agent = result[field_val];
else if(result[i++].matched)//"Origin"
body_info.m_origin = result[field_val];
else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!)
body_info.m_etc_fields.push_back(std::pair<std::string, std::string>(result[field_etc_name], result[field_val])); body_info.m_etc_fields.push_back(std::pair<std::string, std::string>(result[field_etc_name], result[field_val]));
else else
@ -537,17 +542,27 @@ namespace net_utils
template<class t_connection_context> template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_request_and_send_response(const http::http_request_info& query_info) bool simple_http_connection_handler<t_connection_context>::handle_request_and_send_response(const http::http_request_info& query_info)
{ {
http_response_info response; http_response_info response{};
bool res = handle_request(query_info, response);
//CHECK_AND_ASSERT_MES(res, res, "handle_request(query_info, response) returned false" ); //CHECK_AND_ASSERT_MES(res, res, "handle_request(query_info, response) returned false" );
bool res = true;
if (query_info.m_http_method != http::http_method_options)
{
res = handle_request(query_info, response);
}
else
{
response.m_response_code = 200;
response.m_response_comment = "OK";
}
std::string response_data = get_response_header(response); std::string response_data = get_response_header(response);
//LOG_PRINT_L0("HTTP_SEND: << \r\n" << response_data + response.m_body); //LOG_PRINT_L0("HTTP_SEND: << \r\n" << response_data + response.m_body);
LOG_PRINT_L3("HTTP_RESPONSE_HEAD: << \r\n" << response_data); LOG_PRINT_L3("HTTP_RESPONSE_HEAD: << \r\n" << response_data);
m_psnd_hndlr->do_send((void*)response_data.data(), response_data.size()); m_psnd_hndlr->do_send((void*)response_data.data(), response_data.size());
if(response.m_body.size() && (query_info.m_http_method != http::http_method_head)) if ((response.m_body.size() && (query_info.m_http_method != http::http_method_head)) || (query_info.m_http_method == http::http_method_options))
m_psnd_hndlr->do_send((void*)response.m_body.data(), response.m_body.size()); m_psnd_hndlr->do_send((void*)response.m_body.data(), response.m_body.size());
return res; return res;
} }
@ -579,7 +594,6 @@ namespace net_utils
response.m_response_comment = "OK"; response.m_response_comment = "OK";
response.m_mime_tipe = get_file_mime_tipe(uri_to_path); response.m_mime_tipe = get_file_mime_tipe(uri_to_path);
return true; return true;
} }
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
@ -591,8 +605,12 @@ namespace net_utils
"Server: Epee-based\r\n" "Server: Epee-based\r\n"
"Content-Length: "; "Content-Length: ";
buf += boost::lexical_cast<std::string>(response.m_body.size()) + "\r\n"; buf += boost::lexical_cast<std::string>(response.m_body.size()) + "\r\n";
if(!response.m_mime_tipe.empty())
{
buf += "Content-Type: "; buf += "Content-Type: ";
buf += response.m_mime_tipe + "\r\n"; buf += response.m_mime_tipe + "\r\n";
}
buf += "Last-Modified: "; buf += "Last-Modified: ";
time_t tm; time_t tm;
@ -612,6 +630,19 @@ namespace net_utils
m_want_close = true; m_want_close = true;
} }
} }
// Cross-origin resource sharing
if(m_query_info.m_header_info.m_origin.size())
{
if (std::binary_search(m_config.m_access_control_origins.begin(), m_config.m_access_control_origins.end(), m_query_info.m_header_info.m_origin))
{
buf += "Access-Control-Allow-Origin: ";
buf += m_query_info.m_header_info.m_origin;
buf += "\r\n";
buf += "Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS\r\n";
}
}
//add additional fields, if it is //add additional fields, if it is
for(fields_list::const_iterator it = response.m_additional_fields.begin(); it!=response.m_additional_fields.end(); it++) for(fields_list::const_iterator it = response.m_additional_fields.begin(); it!=response.m_additional_fields.end(); it++)
buf += it->first + ":" + it->second + "\r\n"; buf += it->first + ":" + it->second + "\r\n";

0
contrib/epee/include/net/http_server_handlers_map2.h Normal file → Executable file
View File

5
contrib/epee/include/net/http_server_impl_base.h Normal file → Executable file
View File

@ -56,6 +56,7 @@ namespace epee
{} {}
bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0",
std::vector<std::string> access_control_origins = std::vector<std::string>(),
boost::optional<net_utils::http::login> user = boost::none) boost::optional<net_utils::http::login> user = boost::none)
{ {
@ -65,6 +66,10 @@ namespace epee
//here set folder for hosting reqests //here set folder for hosting reqests
m_net_server.get_config_object().m_folder = ""; m_net_server.get_config_object().m_folder = "";
//set access control allow origins if configured
std::sort(access_control_origins.begin(), access_control_origins.end());
m_net_server.get_config_object().m_access_control_origins = std::move(access_control_origins);
m_net_server.get_config_object().m_user = std::move(user); m_net_server.get_config_object().m_user = std::move(user);
MGINFO("Binding on " << bind_ip << ":" << bind_port); MGINFO("Binding on " << bind_ip << ":" << bind_port);

2
src/rpc/core_rpc_server.cpp Normal file → Executable file
View File

@ -101,7 +101,7 @@ namespace cryptonote
http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password());
return epee::http_server_impl_base<core_rpc_server, connection_context>::init( return epee::http_server_impl_base<core_rpc_server, connection_context>::init(
std::move(port), std::move(rpc_config->bind_ip), std::move(http_login) std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login)
); );
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------

18
src/rpc/rpc_args.cpp Normal file → Executable file
View File

@ -28,6 +28,7 @@
// //
#include "rpc_args.h" #include "rpc_args.h"
#include <boost/algorithm/string.hpp>
#include <boost/asio/ip/address.hpp> #include <boost/asio/ip/address.hpp>
#include "common/command_line.h" #include "common/command_line.h"
#include "common/i18n.h" #include "common/i18n.h"
@ -38,6 +39,7 @@ namespace cryptonote
: rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify ip to bind rpc server"), "127.0.0.1"}) : rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify ip to bind rpc server"), "127.0.0.1"})
, rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true}) , rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true})
, confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")}) , confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")})
, rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""})
{} {}
const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); } const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); }
@ -48,6 +50,7 @@ namespace cryptonote
command_line::add_arg(desc, arg.rpc_bind_ip); command_line::add_arg(desc, arg.rpc_bind_ip);
command_line::add_arg(desc, arg.rpc_login); command_line::add_arg(desc, arg.rpc_login);
command_line::add_arg(desc, arg.confirm_external_bind); command_line::add_arg(desc, arg.confirm_external_bind);
command_line::add_arg(desc, arg.rpc_access_control_origins);
} }
boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm) boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm)
@ -91,6 +94,21 @@ namespace cryptonote
} }
} }
auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins);
if (!access_control_origins_input.empty())
{
if (!config.login)
{
LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RFC server password --") << arg.rpc_login.name << tr(" cannot be empty"));
return boost::none;
}
std::vector<std::string> access_control_origins;
boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(","));
std::for_each(access_control_origins.begin(), access_control_origins.end(), boost::bind(&boost::trim<std::string>, _1, std::locale::classic()));
config.access_control_origins = std::move(access_control_origins);
}
return {std::move(config)}; return {std::move(config)};
} }
} }

2
src/rpc/rpc_args.h Normal file → Executable file
View File

@ -53,6 +53,7 @@ namespace cryptonote
const command_line::arg_descriptor<std::string> rpc_bind_ip; const command_line::arg_descriptor<std::string> rpc_bind_ip;
const command_line::arg_descriptor<std::string> rpc_login; const command_line::arg_descriptor<std::string> rpc_login;
const command_line::arg_descriptor<bool> confirm_external_bind; const command_line::arg_descriptor<bool> confirm_external_bind;
const command_line::arg_descriptor<std::string> rpc_access_control_origins;
}; };
static const char* tr(const char* str); static const char* tr(const char* str);
@ -62,6 +63,7 @@ namespace cryptonote
static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm); static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm);
std::string bind_ip; std::string bind_ip;
std::vector<std::string> access_control_origins;
boost::optional<tools::login> login; // currently `boost::none` if unspecified by user boost::optional<tools::login> login; // currently `boost::none` if unspecified by user
}; };
} }

2
src/wallet/wallet_rpc_server.cpp Normal file → Executable file
View File

@ -216,7 +216,7 @@ namespace tools
m_net_server.set_threads_prefix("RPC"); m_net_server.set_threads_prefix("RPC");
return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init(
std::move(bind_port), std::move(rpc_config->bind_ip), std::move(http_login) std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login)
); );
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------