diff --git a/programs/cleos/httpc.cpp b/programs/cleos/httpc.cpp index 591558ebcccd3fde7b1aa5962d190e8d18c4088e..f139e7b8b712a8e88ca85e0ebbddde4df6f0b566 100644 --- a/programs/cleos/httpc.cpp +++ b/programs/cleos/httpc.cpp @@ -77,6 +77,29 @@ namespace eosio { namespace client { namespace http { return re.str(); } + parsed_url parse_url( const string& server_url ) { + parsed_url res; + + //via rfc3986 and modified a bit to suck out the port number + //Sadly this doesn't work for ipv6 addresses + std::regex rgx(R"xx(^(([^:/?#]+):)?(//([^:/?#]*)(:(\d+))?)?([^?#]*)(\?([^#]*))?(#(.*))?)xx"); + std::smatch match; + if(std::regex_search(server_url.begin(), server_url.end(), match, rgx)) { + res.scheme = match[2]; + res.server = match[4]; + res.port = match[6]; + res.path_prefix = match[7]; + } + if(res.scheme != "http" && res.scheme != "https") + FC_THROW("Unrecognized URL scheme (${s}) in URL \"${u}\"", ("s", res.scheme)("u", server_url)); + if(res.server.empty()) + FC_THROW("No server parsed from URL \"${u}\"", ("u", server_url)); + if(res.port.empty()) + res.port = res.scheme == "http" ? "8888" : "443"; + boost::trim_right_if(res.path_prefix, boost::is_any_of("/")); + return res; + } + fc::variant call( const std::string& server_url, const std::string& path, const fc::variant& postdata ) { @@ -86,30 +109,12 @@ namespace eosio { namespace client { namespace http { boost::asio::io_service io_service; - string scheme, server, port, path_prefix; - - //via rfc3986 and modified a bit to suck out the port number - //Sadly this doesn't work for ipv6 addresses - std::regex rgx(R"xx(^(([^:/?#]+):)?(//([^:/?#]*)(:(\d+))?)?([^?#]*)(\?([^#]*))?(#(.*))?)xx"); - std::smatch match; - if(std::regex_search(server_url.begin(), server_url.end(), match, rgx)) { - scheme = match[2]; - server = match[4]; - port = match[6]; - path_prefix = match[7]; - } - if(scheme != "http" && scheme != "https") - FC_THROW("Unrecognized URL scheme (${s}) in URL \"${u}\"", ("s", scheme)("u", server_url)); - if(server.empty()) - FC_THROW("No server parsed from URL \"${u}\"", ("u", server_url)); - if(port.empty()) - port = scheme == "http" ? "8888" : "443"; - boost::trim_right_if(path_prefix, boost::is_any_of("/")); - + auto url = parse_url( server_url ); + boost::asio::streambuf request; std::ostream request_stream(&request); - request_stream << "POST " << path_prefix + path << " HTTP/1.0\r\n"; - request_stream << "Host: " << server << "\r\n"; + request_stream << "POST " << url.path_prefix + path << " HTTP/1.0\r\n"; + request_stream << "Host: " << url.server << "\r\n"; request_stream << "content-length: " << postjson.size() << "\r\n"; request_stream << "Accept: */*\r\n"; request_stream << "Connection: close\r\n\r\n"; @@ -118,9 +123,9 @@ namespace eosio { namespace client { namespace http { unsigned int status_code; std::string re; - if(scheme == "http") { + if(url.scheme == "http") { tcp::socket socket(io_service); - do_connect(socket, server, port); + do_connect(socket, url.server, url.port); re = do_txrx(socket, request, status_code); } else { //https @@ -137,7 +142,7 @@ namespace eosio { namespace client { namespace http { boost::asio::ssl::stream socket(io_service, ssl_context); socket.set_verify_mode(boost::asio::ssl::verify_peer); - do_connect(socket.next_layer(), server, port); + do_connect(socket.next_layer(), url.server, url.port); socket.handshake(boost::asio::ssl::stream_base::client); re = do_txrx(socket, request, status_code); //try and do a clean shutdown; but swallow if this fails (other side could have already gave TCP the ax) diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index 8518245bb8938e5762b08beab8bf519323d01f47..9886fab72a3aea24ca9edbaaab3c62a927eb1f7b 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -5,6 +5,16 @@ #pragma once namespace eosio { namespace client { namespace http { + + struct parsed_url { + string scheme; + string server; + string port; + string path_prefix; + }; + + parsed_url parse_url( const string& server_url ); + fc::variant call( const std::string& server_url, const std::string& path, const fc::variant& postdata = fc::variant() ); @@ -50,6 +60,7 @@ namespace eosio { namespace client { namespace http { const string wallet_unlock = wallet_func_base + "/unlock"; const string wallet_import_key = wallet_func_base + "/import_key"; const string wallet_sign_trx = wallet_func_base + "/sign_transaction"; + const string keosd_stop = "/v1/keosd/stop"; FC_DECLARE_EXCEPTION( connection_exception, 1100000, "Connection Exception" ); }}} diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 1a7e2ea95fada66b1f84461507941fd47cae9b42..2413cb814ba94294c6357361e247af449a99ca4e 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -69,11 +69,10 @@ Options: -p,--permission TEXT ... An account and permission level to authorize, as in 'account@permission' (defaults to 'creator@active') ``` */ + #include #include #include -#include -#include #include #include #include @@ -84,12 +83,22 @@ Options: #include #include +#include #include #include #include #include #include +#pragma push_macro("N") +#undef N + +#include +#include +#include +#include +#include +#include #include #include #include @@ -98,6 +107,8 @@ Options: #include #include +#pragma pop_macro("N") + #include #include #include @@ -633,6 +644,44 @@ struct set_action_permission_subcommand { } }; +bool port_busy( uint16_t port ) { + using namespace boost::asio; + + io_service ios; + boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), port); + boost::asio::ip::tcp::socket socket(ios); + boost::system::error_code ec = error::would_block; + //connecting/failing to connect to localhost should be always fast - don't care about timeouts + socket.async_connect(endpoint, [&](const boost::system::error_code& error) { ec = error; } ); + do { + ios.run_one(); + } while (ec == error::would_block); + return !ec; +} + +void start_keosd( uint16_t wallet_port ) { + auto binPath = boost::dll::program_location(); + boost::filesystem::path keosPath = binPath.parent_path().append("keosd"); //cleos and keosd are in the same installation directory + if ( !boost::filesystem::exists(keosPath) ) { + keosPath = binPath.parent_path().parent_path().append("keosd").append("keosd"); //build directory + } + if ( boost::filesystem::exists( keosPath ) ) { + namespace bp = boost::process; + keosPath = boost::filesystem::canonical( keosPath ); + ::boost::process::child keos( keosPath, "--http-server-address=127.0.0.1:"+std::to_string(wallet_port), + bp::std_in.close(), + bp::std_out > bp::null, + bp::std_err > bp::null); + if (keos.running()) { + std::cerr << keosPath << " launched" << std::endl; + keos.detach(); + } else { + std::cerr << "No wallet service listening on 127.0.0.1:" << std::to_string(wallet_port) << ". Failed to launch " << keosPath << std::endl; + } + } else { + std::cerr << "No wallet service listening on 127.0.0.1: " << std::to_string(wallet_port) << ". Cannot automatically start keosd because keosd was not found." << std::endl; + } +} CLI::callback_t old_host_port = [](CLI::results_t) { std::cerr << localized("Host and port options (-H, --wallet-host, etc.) have been replaced with -u/--url and --wallet-url\n" @@ -1100,11 +1149,6 @@ void get_account( const string& accountName, bool json_format ) { } int main( int argc, char** argv ) { - fc::path binPath = argv[0]; - if (binPath.is_relative()) { - binPath = relative(binPath, fc::current_path()); - } - setlocale(LC_ALL, ""); bindtextdomain(locale_domain, locale_path); textdomain(locale_domain); @@ -1119,6 +1163,15 @@ int main( int argc, char** argv ) { app.add_option( "-u,--url", url, localized("the http/https URL where nodeos is running"), true ); app.add_option( "--wallet-url", wallet_url, localized("the http/https URL where keosd is running"), true ); + auto parsed_url = parse_url( wallet_url ); + auto wallet_port_uint = std::stoi(parsed_url.port); + if ( wallet_port_uint < 0 || 65535 < wallet_port_uint ) { + FC_THROW("port is not in valid range"); + } + if ( ( parsed_url.server == "localhost" || parsed_url.server == "127.0.0.1" ) && !port_busy( uint16_t(wallet_port_uint) ) ) { + start_keosd( uint16_t(wallet_port_uint) ); + } + bool verbose_errors = false; app.add_flag( "-v,--verbose", verbose_errors, localized("output verbose actions on error")); @@ -1688,6 +1741,16 @@ int main( int argc, char** argv ) { std::cout << fc::json::to_pretty_string(v) << std::endl; }); + auto stopKeosd = wallet->add_subcommand("stop", localized("Stop keosd (doesn't work with nodeos)."), false); + stopKeosd->set_callback([] { + const auto& v = call(wallet_url, keosd_stop); + if ( !v.is_object() || v.get_object().size() != 0 ) { //on success keosd responds with empty object + std::cerr << fc::json::to_pretty_string(v) << std::endl; + } else { + std::cout << "OK" << std::endl; + } + }); + // sign subcommand string trx_json_to_sign; string str_private_key; diff --git a/programs/keosd/main.cpp b/programs/keosd/main.cpp index a115f04f3443f84e59b1c260db1add43091b609a..ce1588fccd68ca02c596618ce9494cddd025575f 100644 --- a/programs/keosd/main.cpp +++ b/programs/keosd/main.cpp @@ -42,6 +42,8 @@ int main(int argc, char** argv) app().register_plugin(); if(!app().initialize(argc, argv)) return -1; + auto& http = app().get_plugin(); + http.add_handler("/v1/keosd/stop", [](string, string, url_response_callback cb) { cb(200, "{}"); std::raise(SIGTERM); } ); app().startup(); app().exec(); } catch (const fc::exception& e) {