Wallet RPC server modernization

- Replaces the wallet RPC classes with ones like the core RPC server
(with serialization code moved into a new .cpp file).
  - Restricted commands are now carried through the RPC serialization
    types (by inheriting from RESTRICTED instead of RPC_COMMAND) and
    restrictions are handled in one place rather than being handled in
    each of the 49 restricted endpoints.  This differs a little from how
    the core http server works (which has a PUBLIC base class) because
    for the wallet rpc server unrestricted really doesn't mean "public",
    it means something closer to view-only.
  - GET_TRANSFERS_CSV is now restricted (it looks like an oversight that
    it wasn't before since GET_TRANSFERS is restricted)
  - GET_ADDRESS_BOOK_ENTRY is now restricted.  Since restricted mode is
    meant to provide something like view-only access, it doesn't make
    much sense that address book entries were available.

- Use uWebSockets to provide the wallet RPC server HTTP functionality.
  This version is quite a bit simpler than the core RPC version since it
  doesn't support multithreaded (parallel) requests, and so we don't
  have to worry about queuing jobs.

- Converted all the numeric wallet rpc error codes defines to constexprs

- Changed how endpoints get called; previous this was called:

    bool on_some_endpoint(const wallet_rpc::COMMAND_RPC_SOME_ENDPOINT::request& req, wallet_rpc::COMMAND_RPC_SOME_ENDPOINT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL)

  This PR changes it similarly to how core_rpc_server's endpoints work:

    wallet_rpc::SOME_ENDPOINT invoke(wallet_rpc::COMMAND_RPC_SOME_ENDPOINT::request&& req);

  That is:
  - the response is now returned
  - the request is provided by mutable rvalue reference
  - the error object is gone (just throw instead)
  - the connection_context is gone (it was not used at all by any wallet
    rpc endpoint).

- Consolidated most of the (identical) exception handling to the RPC
  method invocation point rather than needing to repeat it in each
  individual endpoint.  This means each endpoint's `invoke` method can
  now just throw (or not catch) exceptions.  Some try/catches are still
  there because they are converting one type of exception into another,
  but the generic ones that return a generic error are gone.

- Removed all remaining epee http code.

- DRYed out some wallet rpc code.
This commit is contained in:
Jason Rhinelander 2020-07-29 14:15:40 -03:00
parent 8de0cc5553
commit 899d1e4eaf
23 changed files with 3099 additions and 7354 deletions

View File

@ -1,173 +0,0 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <optional>
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#include "wipeable_string.h"
#include "http_base.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
namespace epee
{
namespace net_utils
{
namespace http
{
struct login
{
login() : username(), password() {}
login(std::string username_, wipeable_string password_)
: username(std::move(username_)), password(std::move(password_))
{}
std::string username;
wipeable_string password;
};
//! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
class http_server_auth
{
public:
struct session
{
session(login credentials_)
: credentials(std::move(credentials_)), nonce(), counter(0)
{}
login credentials;
std::string nonce;
std::uint32_t counter;
};
http_server_auth() : user(), rng() {}
http_server_auth(login credentials, std::function<void(size_t, uint8_t*)> r);
//! \return Auth response, or `std::nullopt` iff `request` had valid auth.
std::optional<http_response_info> get_response(const http_request_info& request)
{
if (user)
return do_get_response(request);
return std::nullopt;
}
private:
std::optional<http_response_info> do_get_response(const http_request_info& request);
std::optional<session> user;
std::function<void(size_t, uint8_t*)> rng;
};
//! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
class http_client_auth
{
public:
enum status : std::uint8_t { kSuccess = 0, kBadPassword, kParseFailure };
struct session
{
session(login credentials_)
: credentials(std::move(credentials_)), server(), counter(0)
{}
struct keys
{
using algorithm =
std::function<std::string(const session&, std::string_view, std::string_view)>;
keys() : nonce(), opaque(), realm(), generator() {}
keys(std::string nonce_, std::string opaque_, std::string realm_, algorithm generator_)
: nonce(std::move(nonce_))
, opaque(std::move(opaque_))
, realm(std::move(realm_))
, generator(std::move(generator_))
{}
std::string nonce;
std::string opaque;
std::string realm;
algorithm generator;
};
login credentials;
keys server;
std::uint32_t counter;
};
http_client_auth() : user() {}
http_client_auth(login credentials);
/*!
Clients receiving a 401 response code from the server should call this
function to process the server auth. Then, before every client request,
`get_auth_field()` should be called to retrieve the newest
authorization request.
\return `kBadPassword` if client will never be able to authenticate,
`kParseFailure` if all server authentication responses were invalid,
and `kSuccess` if `get_auth_field` is ready to generate authorization
fields.
*/
status handle_401(const http_response_info& response)
{
if (user)
return do_handle_401(response);
return kBadPassword;
}
/*!
After calling `handle_401`, clients should call this function to
generate an authentication field for every request.
\return A HTTP "Authorization" field if `handle_401(...)` previously
returned `kSuccess`.
*/
std::optional<std::pair<std::string, std::string>> get_auth_field(
std::string_view method, std::string_view uri)
{
if (user)
return do_get_auth_field(method, uri);
return std::nullopt;
}
private:
status do_handle_401(const http_response_info&);
std::optional<std::pair<std::string, std::string>> do_get_auth_field(std::string_view, std::string_view);
std::optional<session> user;
};
}
}
}

View File

@ -1,190 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <string>
#include <string_view>
#include <utility>
#include <list>
#include "memwipe.h"
#include "string_tools.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
namespace epee::net_utils::http
{
enum http_method{
http_method_options,
http_method_get,
http_method_post,
http_method_put,
http_method_head,
http_method_etc,
http_method_unknown
};
enum http_content_type
{
http_content_type_text_html,
http_content_type_image_gif,
http_content_type_other,
http_content_type_not_set
};
typedef std::list<std::pair<std::string, std::string> > fields_list;
inline
std::string get_value_from_fields_list(const std::string& param_name, const net_utils::http::fields_list& fields)
{
fields_list::const_iterator it = fields.begin();
for(; it != fields.end(); it++)
if(!string_tools::compare_no_case(param_name, it->first))
break;
if(it==fields.end())
return std::string();
return it->second;
}
static inline void add_field(std::string& out, std::string_view name, std::string_view value)
{
out.append(name).append(": "sv).append(value).append("\r\n"sv);
}
static inline void add_field(std::string& out, const std::pair<std::string, std::string>& field)
{
add_field(out, field.first, field.second);
}
struct http_header_info
{
std::string m_connection; //"Connection:"
std::string m_referer; //"Referer:"
std::string m_content_length; //"Content-Length:"
std::string m_content_type; //"Content-Type:"
std::string m_transfer_encoding;//"Transfer-Encoding:"
std::string m_content_encoding; //"Content-Encoding:"
std::string m_host; //"Host:"
std::string m_cookie; //"Cookie:"
std::string m_user_agent; //"User-Agent:"
std::string m_origin; //"Origin:"
fields_list m_etc_fields;
void clear()
{
m_connection.clear();
m_referer.clear();
m_content_length.clear();
m_content_type.clear();
m_transfer_encoding.clear();
m_content_encoding.clear();
m_host.clear();
m_cookie.clear();
m_user_agent.clear();
m_origin.clear();
m_etc_fields.clear();
}
};
struct uri_content
{
std::string m_path;
std::string m_query;
std::string m_fragment;
std::list<std::pair<std::string, std::string> > m_query_params;
};
struct url_content
{
std::string schema;
std::string host;
std::string uri;
uint64_t port;
uri_content m_uri_content;
};
struct http_request_info
{
http_request_info():m_http_method(http_method_unknown),
m_http_ver_hi(0),
m_http_ver_lo(0),
m_have_to_block(false),
m_full_request_buf_size(0)
{}
http_method m_http_method;
std::string m_URI;
std::string m_http_method_str;
std::string m_full_request_str;
std::string m_replace_html;
std::string m_request_head;
int m_http_ver_hi;
int m_http_ver_lo;
bool m_have_to_block;
http_header_info m_header_info;
uri_content m_uri_content;
size_t m_full_request_buf_size;
std::string m_body;
void clear()
{
this->~http_request_info();
new(this) http_request_info();
}
};
struct http_response_info
{
int m_response_code;
std::string m_response_comment;
fields_list m_additional_fields;
std::string m_body;
std::vector<std::string> m_body_pieces; // For specifying outgoing responses in pieces (this takes priority over m_body when sending)
std::string m_mime_type;
http_header_info m_header_info;
int m_http_ver_hi;// OUT paramter only
int m_http_ver_lo;// OUT paramter only
void clear()
{
this->~http_response_info();
new(this) http_response_info();
}
void wipe()
{
memwipe(&m_body[0], m_body.size());
}
};
}

View File

@ -1,230 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#ifndef _HTTP_SERVER_H_
#define _HTTP_SERVER_H_
#include <optional>
#include <string>
#include "net_utils_base.h"
#include "to_nonconst_iterator.h"
#include "http_auth.h"
#include "http_base.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
namespace epee
{
namespace net_utils
{
namespace http
{
/************************************************************************/
/* */
/************************************************************************/
struct http_server_config
{
std::string m_folder;
std::vector<std::string> m_access_control_origins;
std::optional<login> m_user;
std::mutex m_config_mutex;
};
/************************************************************************/
/* */
/************************************************************************/
template<class t_connection_context = net_utils::connection_context_base>
class simple_http_connection_handler
{
public:
typedef t_connection_context connection_context;//t_connection_context net_utils::connection_context_base connection_context;
typedef http_server_config config_type;
simple_http_connection_handler(i_service_endpoint* psnd_hndlr, config_type& config, t_connection_context& conn_context);
virtual ~simple_http_connection_handler(){}
bool release_protocol()
{
return true;
}
virtual bool thread_init()
{
return true;
}
virtual bool thread_deinit()
{
return true;
}
bool after_init_connection()
{
return true;
}
virtual bool handle_recv(const void* ptr, size_t cb);
virtual bool handle_request(const http::http_request_info& query_info, http_response_info& response);
private:
enum machine_state{
http_state_retriving_comand_line,
http_state_retriving_header,
http_state_retriving_body,
http_state_connection_close,
http_state_error
};
enum body_transfer_type{
http_body_transfer_chunked,
http_body_transfer_measure,//mean "Content-Length" valid
http_body_transfer_chunked_instead_measure,
http_body_transfer_connection_close,
http_body_transfer_multipart,
http_body_transfer_undefined
};
bool handle_buff_in(std::string& buf);
bool analyze_cached_request_header_and_invoke_state(size_t pos);
bool handle_invoke_query_line();
bool parse_cached_header(http_header_info& body_info, const std::string& m_cache_to_process, size_t pos);
std::string::size_type match_end_of_header(const std::string& buf);
bool get_len_from_content_length(const std::string& str, size_t& len);
bool handle_retriving_query_body();
bool handle_query_measure();
bool set_ready_state();
bool slash_to_back_slash(std::string& str);
std::string get_file_mime_type(const std::string& path);
std::string get_response_header(const http_response_info& response);
//major function
inline bool handle_request_and_send_response(const http::http_request_info& query_info);
std::string get_not_found_response_body(const std::string& URI);
std::string m_root_path;
std::string m_cache;
machine_state m_state;
body_transfer_type m_body_transfer_type;
bool m_is_stop_handling;
http::http_request_info m_query_info;
size_t m_len_summary, m_len_remain;
config_type& m_config;
bool m_want_close;
size_t m_newlines;
protected:
i_service_endpoint* m_psnd_hndlr;
t_connection_context& m_conn_context;
};
template<class t_connection_context>
struct i_http_server_handler
{
virtual ~i_http_server_handler(){}
virtual bool handle_http_request(const http_request_info& query_info,
http_response_info& response,
t_connection_context& m_conn_context) = 0;
virtual bool init_server_thread(){return true;}
virtual bool deinit_server_thread(){return true;}
};
template<class t_connection_context>
struct custum_handler_config: public http_server_config
{
i_http_server_handler<t_connection_context>* m_phandler;
std::function<void(size_t, uint8_t*)> rng;
};
/************************************************************************/
/* */
/************************************************************************/
template<class t_connection_context = net_utils::connection_context_base>
class http_custom_handler: public simple_http_connection_handler<t_connection_context>
{
public:
typedef custum_handler_config<t_connection_context> config_type;
http_custom_handler(i_service_endpoint* psnd_hndlr, config_type& config, t_connection_context& conn_context)
: simple_http_connection_handler<t_connection_context>(psnd_hndlr, config, conn_context),
m_config(config),
m_auth(m_config.m_user ? http_server_auth{*m_config.m_user, config.rng} : http_server_auth{})
{}
inline bool handle_request(const http_request_info& query_info, http_response_info& response)
{
CHECK_AND_ASSERT_MES(m_config.m_phandler, false, "m_config.m_phandler is NULL!!!!");
const auto auth_response = m_auth.get_response(query_info);
if (auth_response)
{
response = std::move(*auth_response);
return true;
}
//fill with default values
response.m_mime_type = "text/plain";
response.m_response_code = 200;
response.m_response_comment = "OK";
response.m_body.clear();
return m_config.m_phandler->handle_http_request(query_info, response, this->m_conn_context);
}
virtual bool thread_init()
{
return m_config.m_phandler->init_server_thread();
}
virtual bool thread_deinit()
{
return m_config.m_phandler->deinit_server_thread();
}
void handle_qued_callback()
{}
bool after_init_connection()
{
return true;
}
private:
//simple_http_connection_handler::config_type m_stub_config;
config_type& m_config;
http_server_auth m_auth;
};
}
}
}
#include "http_protocol_handler.inl"
#endif //_HTTP_SERVER_H_

View File

@ -1,745 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <boost/lexical_cast.hpp>
#include <regex>
#include <cassert>
#include "http_protocol_handler.h"
#include "string_tools.h"
#include "file_io_utils.h"
#include "net_parse_helpers.h"
#include "time_helper.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
#define HTTP_MAX_URI_LEN 9000
#define HTTP_MAX_HEADER_LEN 100000
#define HTTP_MAX_STARTING_NEWLINES 8
namespace epee
{
namespace net_utils
{
namespace http
{
struct multipart_entry
{
std::list<std::pair<std::string, std::string> > m_etc_header_fields;
std::string m_content_disposition;
std::string m_content_type;
std::string m_body;
};
inline const std::regex rexp_boundary{R"(boundary=([^;\s,]*))", std::regex::icase};
inline
bool match_boundary(const std::string& content_type, std::string& boundary)
{
if(std::smatch result; std::regex_search(content_type, result, rexp_boundary))
{
boundary = result[1];
return true;
}
return false;
}
inline const std::regex rexp_content_header{R"(\n?(?:(Content-Disposition)|(Content-Type)|([\w-]+)) ?: ?(.*?)\r?\n[^\t ])", std::regex::icase};
inline
bool parse_header(std::string::const_iterator it_begin, std::string::const_iterator it_end, multipart_entry& entry)
{
std::string::const_iterator it_current_bound = it_begin;
std::string::const_iterator it_end_bound = it_end;
//lookup all fields and fill well-known fields
for (std::smatch result;
std::regex_search(it_current_bound, it_end_bound, result, rexp_content_header);
it_current_bound = result.suffix().first)
{
std::string value = result[4];
if(result[1].matched)//"Content-Disposition"
entry.m_content_disposition = result[4];
else if(result[2].matched)//"Content-Type"
entry.m_content_type = result[4];
else {
assert(result[3].matched); //e.t.c (HAVE TO BE MATCHED!)
entry.m_etc_header_fields.emplace_back(result[3], result[4]);
}
}
return true;
}
inline
bool handle_part_of_multipart(std::string::const_iterator it_begin, std::string::const_iterator it_end, multipart_entry& entry)
{
std::string end_str = "\r\n\r\n";
std::string::const_iterator end_header_it = std::search(it_begin, it_end, end_str.begin(), end_str.end());
if(end_header_it == it_end)
{
//header not matched
return false;
}
if(!parse_header(it_begin, end_header_it+4, entry))
{
LOG_ERROR("Failed to parse header:" << std::string(it_begin, end_header_it+2));
return false;
}
entry.m_body.assign(end_header_it+4, it_end);
return true;
}
inline
bool parse_multipart_body(const std::string& content_type, const std::string& body, std::list<multipart_entry>& out_values)
{
//bool res = file_io_utils::load_file_to_string("C:\\public\\multupart_data", body);
std::string boundary;
if(!match_boundary(content_type, boundary))
{
MERROR("Failed to match boundary in content type: " << content_type);
return false;
}
boundary+="\r\n";
bool is_stop = false;
bool first_step = true;
std::string::const_iterator it_begin = body.begin();
std::string::const_iterator it_end;
while(!is_stop)
{
std::string::size_type pos = body.find(boundary, std::distance(body.begin(), it_begin));
if(std::string::npos == pos)
{
is_stop = true;
boundary.erase(boundary.size()-2, 2);
boundary+= "--";
pos = body.find(boundary, std::distance(body.begin(), it_begin));
if(std::string::npos == pos)
{
MERROR("Error: Filed to match closing multipart tag");
it_end = body.end();
}else
{
it_end = body.begin() + pos;
}
}else
it_end = body.begin() + pos;
if(first_step && !is_stop)
{
first_step = false;
it_begin = it_end + boundary.size();
std::string temp = "\r\n--";
boundary = temp + boundary;
continue;
}
out_values.push_back(multipart_entry());
if(!handle_part_of_multipart(it_begin, it_end, out_values.back()))
{
MERROR("Failed to handle_part_of_multipart");
return false;
}
it_begin = it_end + boundary.size();
}
return true;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
simple_http_connection_handler<t_connection_context>::simple_http_connection_handler(i_service_endpoint* psnd_hndlr, config_type& config, t_connection_context& conn_context):
m_state(http_state_retriving_comand_line),
m_body_transfer_type(http_body_transfer_undefined),
m_is_stop_handling(false),
m_len_summary(0),
m_len_remain(0),
m_config(config),
m_want_close(false),
m_newlines(0),
m_psnd_hndlr(psnd_hndlr),
m_conn_context(conn_context)
{
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::set_ready_state()
{
m_is_stop_handling = false;
m_state = http_state_retriving_comand_line;
m_body_transfer_type = http_body_transfer_undefined;
m_query_info.clear();
m_len_summary = 0;
m_newlines = 0;
return true;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_recv(const void* ptr, size_t cb)
{
std::string buf((const char*)ptr, cb);
//LOG_PRINT_L0("HTTP_RECV: " << ptr << "\r\n" << buf);
//file_io_utils::save_string_to_file(string_tools::get_current_module_folder() + "/" + boost::lexical_cast<std::string>(ptr), std::string((const char*)ptr, cb));
bool res = handle_buff_in(buf);
if(m_want_close/*m_state == http_state_connection_close || m_state == http_state_error*/)
return false;
return res;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_buff_in(std::string& buf)
{
size_t ndel;
if(m_cache.size())
m_cache += buf;
else
m_cache.swap(buf);
m_is_stop_handling = false;
while(!m_is_stop_handling)
{
switch(m_state)
{
case http_state_retriving_comand_line:
//The HTTP protocol does not place any a priori limit on the length of a URI. (c)RFC2616
//but we forebly restirct it len to HTTP_MAX_URI_LEN to make it more safely
if(!m_cache.size())
break;
//check_and_handle_fake_response();
ndel = m_cache.find_first_not_of("\r\n");
if (ndel != 0)
{
//some times it could be that before query line cold be few line breaks
//so we have to be calm without panic with assers
m_newlines += std::string::npos == ndel ? m_cache.size() : ndel;
if (m_newlines > HTTP_MAX_STARTING_NEWLINES)
{
LOG_ERROR("simple_http_connection_handler::handle_buff_out: Too many starting newlines");
m_state = http_state_error;
return false;
}
m_cache.erase(0, ndel);
break;
}
if(m_cache.find('\n') != std::string::npos)
handle_invoke_query_line();
else
{
m_is_stop_handling = true;
if(m_cache.size() > HTTP_MAX_URI_LEN)
{
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::handle_buff_out: Too long URI line");
m_state = http_state_error;
return false;
}
}
break;
case http_state_retriving_header:
{
std::string::size_type pos = match_end_of_header(m_cache);
if(std::string::npos == pos)
{
m_is_stop_handling = true;
if(m_cache.size() > HTTP_MAX_HEADER_LEN)
{
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::handle_buff_in: Too long header area");
m_state = http_state_error;
return false;
}
break;
}
if (!analyze_cached_request_header_and_invoke_state(pos))
return false;
break;
}
case http_state_retriving_body:
return handle_retriving_query_body();
case http_state_connection_close:
return false;
default:
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::handle_char_out: Wrong state: " << m_state);
return false;
case http_state_error:
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::handle_char_out: Error state!!!");
return false;
}
if(!m_cache.size())
m_is_stop_handling = true;
}
return true;
}
inline const std::regex rexp_http_request{R"(^((OPTIONS)|(GET)|(HEAD)|(POST)|(PUT)|DELETE|TRACE) (\S+) HTTP/(\d+)\.(\d+)\r?\n)", std::regex::icase};
//--------------------------------------------------------------------------------------------
inline bool analyze_http_method(const std::smatch& result, http::http_method& method, int& http_ver_major, int& http_ver_minor)
{
if (!boost::conversion::try_lexical_convert<int>(result[8], http_ver_major))
return false;
if (!boost::conversion::try_lexical_convert<int>(result[9], http_ver_minor))
return false;
if(result[2].matched)
method = http::http_method_options;
else if(result[3].matched)
method = http::http_method_get;
else if(result[4].matched)
method = http::http_method_head;
else if(result[5].matched)
method = http::http_method_post;
else if(result[6].matched)
method = http::http_method_put;
else
method = http::http_method_etc;
return true;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_invoke_query_line()
{
std::smatch result;
if(std::regex_search(m_cache, result, rexp_http_request))
{
if (!analyze_http_method(result, m_query_info.m_http_method, m_query_info.m_http_ver_hi, m_query_info.m_http_ver_hi))
{
m_state = http_state_error;
MERROR("Failed to analyze method");
return false;
}
m_query_info.m_URI = result[7];
if (!parse_uri(m_query_info.m_URI, m_query_info.m_uri_content))
{
m_state = http_state_error;
MERROR("Failed to parse URI: m_query_info.m_URI");
return false;
}
m_query_info.m_http_method_str = result[1];
m_query_info.m_full_request_str = result[0];
m_cache = result.suffix();
m_state = http_state_retriving_header;
return true;
}else
{
m_state = http_state_error;
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::handle_invoke_query_line(): Failed to match first line: " << m_cache);
return false;
}
return false;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
std::string::size_type simple_http_connection_handler<t_connection_context>::match_end_of_header(const std::string& buf)
{
//Here we returning head size, including terminating sequence (\r\n\r\n or \n\n)
std::string::size_type res = buf.find("\r\n\r\n");
if(std::string::npos != res)
return res+4;
res = buf.find("\n\n");
if(std::string::npos != res)
return res+2;
return res;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::analyze_cached_request_header_and_invoke_state(size_t pos)
{
LOG_PRINT_L3("HTTP HEAD:\r\n" << m_cache.substr(0, pos));
m_query_info.m_full_request_buf_size = pos;
m_query_info.m_request_head.assign(m_cache.begin(), m_cache.begin()+pos);
if(!parse_cached_header(m_query_info.m_header_info, m_cache, pos))
{
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::analyze_cached_request_header_and_invoke_state(): failed to anilize request header: " << m_cache);
m_state = http_state_error;
return false;
}
m_cache.erase(0, pos);
std::string req_command_str = m_query_info.m_full_request_str;
//if we have POST or PUT command, it is very possible tha we will get body
//but now, we suppose than we have body only in case of we have "ContentLength"
if(m_query_info.m_header_info.m_content_length.size())
{
m_state = http_state_retriving_body;
m_body_transfer_type = http_body_transfer_measure;
if(!get_len_from_content_length(m_query_info.m_header_info.m_content_length, m_len_summary))
{
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::analyze_cached_request_header_and_invoke_state(): Failed to get_len_from_content_length();, m_query_info.m_content_length="<<m_query_info.m_header_info.m_content_length);
m_state = http_state_error;
return false;
}
if(0 == m_len_summary)
{ //current query finished, next will be next query
if(handle_request_and_send_response(m_query_info))
set_ready_state();
else
m_state = http_state_error;
}
m_len_remain = m_len_summary;
}else
{//current query finished, next will be next query
handle_request_and_send_response(m_query_info);
set_ready_state();
}
return true;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_retriving_query_body()
{
switch(m_body_transfer_type)
{
case http_body_transfer_measure:
return handle_query_measure();
case http_body_transfer_chunked:
case http_body_transfer_connection_close:
case http_body_transfer_multipart:
case http_body_transfer_undefined:
default:
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::handle_retriving_query_body(): Unexpected m_body_query_type state:" << m_body_transfer_type);
m_state = http_state_error;
return false;
}
return true;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_query_measure()
{
if(m_len_remain >= m_cache.size())
{
m_len_remain -= m_cache.size();
m_query_info.m_body += m_cache;
m_cache.clear();
}else
{
m_query_info.m_body.append(m_cache.begin(), m_cache.begin() + m_len_remain);
m_cache.erase(0, m_len_remain);
m_len_remain = 0;
}
if(!m_len_remain)
{
if(handle_request_and_send_response(m_query_info))
set_ready_state();
else
m_state = http_state_error;
}
return true;
}
inline const std::regex rexp_http_header{
"\n?(?:"
"(Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)|([\\w-]+?)"
") ?: ?(.*?)\r?\n(?=[^\t ])", std::regex::icase};
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
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)
{
std::string::const_iterator it_current_bound = m_cache_to_process.begin();
std::string::const_iterator it_end_bound = m_cache_to_process.begin()+pos;
body_info.clear();
constexpr size_t field_val = 12;
//lookup all fields and fill well-known fields
for (std::smatch result;
std::regex_search(it_current_bound, it_end_bound, result, rexp_http_header);
it_current_bound = result.suffix().first)
{
if(result[1].matched)//"Connection"
body_info.m_connection = result[field_val];
else if(result[2].matched)//"Referer"
body_info.m_referer = result[field_val];
else if(result[3].matched)//"Content-Length"
body_info.m_content_length = result[field_val];
else if(result[4].matched)//"Content-Type"
body_info.m_content_type = result[field_val];
else if(result[5].matched)//"Transfer-Encoding"
body_info.m_transfer_encoding = result[field_val];
else if(result[6].matched)//"Content-Encoding"
body_info.m_content_encoding = result[field_val];
else if(result[7].matched)//"Host"
body_info.m_host = result[field_val];
else if(result[8].matched)//"Cookie"
body_info.m_cookie = result[field_val];
else if(result[9].matched)//"User-Agent"
body_info.m_user_agent = result[field_val];
else if(result[10].matched)//"Origin"
body_info.m_origin = result[field_val];
else
{
assert(result[11].matched);
body_info.m_etc_fields.emplace_back(result[11], result[field_val]);
}
}
return true;
}
inline const std::regex rexp_digits{"\\d+"};
//-----------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::get_len_from_content_length(const std::string& str, size_t& len)
{
std::string res;
std::smatch result;
if(!std::regex_search(str, result, rexp_digits))
return false;
try { len = boost::lexical_cast<size_t>(result[0]); }
catch(...) { return false; }
return true;
}
//-----------------------------------------------------------------------------------
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)
{
http_response_info response{};
//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";
}
if (response.m_body_pieces.empty())
response.m_body_pieces.push_back(std::move(response.m_body));
std::string response_data = get_response_header(response);
//LOG_PRINT_L0("HTTP_SEND: << \r\n" << response_data + response.m_body);
LOG_PRINT_L3("HTTP_RESPONSE_HEAD: << \r\n" << response_data);
m_psnd_hndlr->do_send(shared_sv{std::move(response_data)});
if (query_info.m_http_method != http::http_method_head)
for (auto& body_piece : response.m_body_pieces)
m_psnd_hndlr->do_send(shared_sv{std::move(body_piece)});
m_psnd_hndlr->send_done();
return res;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_request(const http::http_request_info& query_info, http_response_info& response)
{
std::string uri_to_path = query_info.m_uri_content.m_path;
if("/" == uri_to_path)
uri_to_path = "/index.html";
//slash_to_back_slash(uri_to_path);
std::string destination_file_path;
{
std::lock_guard lock{m_config.m_config_mutex};
destination_file_path = m_config.m_folder + uri_to_path;
}
if(!file_io_utils::load_file_to_string(destination_file_path.c_str(), response.m_body))
{
MWARNING("URI \""<< query_info.m_full_request_str.substr(0, query_info.m_full_request_str.size()-2) << "\" [" << destination_file_path << "] Not Found (404 )");
response.m_body = get_not_found_response_body(query_info.m_URI);
response.m_response_code = 404;
response.m_response_comment = "Not found";
response.m_mime_type = "text/html";
return true;
}
MDEBUG(" -->> " << query_info.m_full_request_str << "\r\n<<--OK");
response.m_response_code = 200;
response.m_response_comment = "OK";
response.m_mime_type = get_file_mime_type(uri_to_path);
return true;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_response_header(const http_response_info& response)
{
std::string buf;
buf.reserve(200); // Typical length seems to be around 145 bytes
buf += "HTTP/1.1 ";
buf += std::to_string(response.m_response_code);
buf += ' ';
buf += response.m_response_comment;
buf += "\r\n"
"Server: Epee-based\r\n"
"Content-Length: ";
size_t size = 0;
for (auto& piece : response.m_body_pieces)
size += piece.size();
buf += std::to_string(size);
buf += "\r\n";
if(!response.m_mime_type.empty())
{
buf += "Content-Type: ";
buf += response.m_mime_type;
buf += "\r\n";
}
buf += "Last-Modified: "; //Wed, 01 Dec 2010 03:27:41 GMT"
buf += misc_utils::get_internet_time_str(std::time(nullptr));
buf += "\r\nAccept-Ranges: bytes\r\n";
string_tools::trim(m_query_info.m_header_info.m_connection);
if(m_query_info.m_header_info.m_connection.size())
{
if(!string_tools::compare_no_case("close", m_query_info.m_header_info.m_connection))
{
//closing connection after sending
buf += "Connection: close\r\n";
m_state = http_state_connection_close;
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-Expose-Headers: www-authenticate\r\n";
if (m_query_info.m_http_method == http::http_method_options)
buf += "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With\r\n";
buf += "Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS\r\n";
}
}
//add additional fields, if it is
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+="\r\n";
return buf;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_file_mime_type(const std::string& path)
{
std::string result;
std::string ext = string_tools::get_extension(path);
if(!string_tools::compare_no_case(ext, "gif"))
result = "image/gif";
else if(!string_tools::compare_no_case(ext, "jpg"))
result = "image/jpeg";
else if(!string_tools::compare_no_case(ext, "html"))
result = "text/html";
else if(!string_tools::compare_no_case(ext, "htm"))
result = "text/html";
else if(!string_tools::compare_no_case(ext, "js"))
result = "application/x-javascript";
else if(!string_tools::compare_no_case(ext, "css"))
result = "text/css";
else if(!string_tools::compare_no_case(ext, "xml"))
result = "application/xml";
else if(!string_tools::compare_no_case(ext, "svg"))
result = "image/svg+xml";
return result;
}
//-----------------------------------------------------------------------------------
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_not_found_response_body(const std::string& URI)
{
std::string body =
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
"<html><head>\r\n"
"<title>404 Not Found</title>\r\n"
"</head><body>\r\n"
"<h1>Not Found</h1>\r\n"
"<p>The requested URL \r\n";
body += URI;
body += "was not found on this server.</p>\r\n"
"</body></html>\r\n";
return body;
}
//--------------------------------------------------------------------------------------------
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::slash_to_back_slash(std::string& str)
{
for(std::string::iterator it = str.begin(); it!=str.end(); it++)
if('/' == *it)
*it = '\\';
return true;
}
}
}
}
//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------

View File

@ -1,240 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include "http_base.h"
#include "jsonrpc_structs.h"
#include "storages/portable_storage.h"
#include "storages/portable_storage_template_helper.h"
#include "misc_os_dependent.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
#define CHAIN_HTTP_TO_MAP2(context_type) bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, \
epee::net_utils::http::http_response_info& response, \
context_type& m_conn_context) \
{\
MINFO("HTTP [" << m_conn_context.m_remote_address.host_str() << "] " << query_info.m_http_method_str << " " << query_info.m_URI); \
response.m_response_code = 200; \
response.m_response_comment = "Ok"; \
if(!handle_http_request_map(query_info, response, m_conn_context)) \
{response.m_response_code = 404;response.m_response_comment = "Not found";} \
return true; \
}
#define BEGIN_URI_MAP2() template<class t_context> bool handle_http_request_map(const epee::net_utils::http::http_request_info& query_info, \
epee::net_utils::http::http_response_info& response_info, \
t_context& m_conn_context) { \
bool handled = false; \
if(false) return true; //just a stub to have "else if"
#define MAP_URI2(pattern, callback) else if(std::string::npos != query_info.m_URI.find(pattern)) return callback(query_info, response_info, &m_conn_context);
#define MAP_URI_AUTO_JON2_IF(s_pattern, callback_f, command_type, cond) \
else if((query_info.m_URI == s_pattern) && (cond)) \
{ \
response_info.m_mime_type = "application/json"; \
response_info.m_header_info.m_content_type = " application/json"; \
handled = true; \
uint64_t ticks = epee::misc_utils::get_tick_count(); \
command_type::request req{}; \
bool parse_res = epee::serialization::load_t_from_json(req, query_info.m_body); \
CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse json: \r\n" << query_info.m_body); \
uint64_t ticks1 = epee::misc_utils::get_tick_count(); \
command_type::response resp{};\
MINFO(m_conn_context << "calling " << s_pattern); \
if(!callback_f(req, resp, &m_conn_context)) \
{ \
LOG_ERROR("Failed to " << #callback_f << "()"); \
response_info.m_response_code = 500; \
response_info.m_response_comment = "Internal Server Error"; \
return true; \
} \
uint64_t ticks2 = epee::misc_utils::get_tick_count(); \
epee::serialization::store_t_to_json(resp, response_info.m_body); \
uint64_t ticks3 = epee::misc_utils::get_tick_count(); \
MDEBUG( s_pattern << " processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms"); \
}
#define MAP_URI_AUTO_JON2(s_pattern, callback_f, command_type) MAP_URI_AUTO_JON2_IF(s_pattern, callback_f, command_type, true)
#define MAP_URI_AUTO_BIN2(s_pattern, callback_f, command_type) \
else if(query_info.m_URI == s_pattern) \
{ \
response_info.m_mime_type = " application/octet-stream"; \
response_info.m_header_info.m_content_type = " application/octet-stream"; \
handled = true; \
uint64_t ticks = epee::misc_utils::get_tick_count(); \
command_type::request req{}; \
bool parse_res = epee::serialization::load_t_from_binary(req, epee::strspan<uint8_t>(query_info.m_body)); \
CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse bin body data, body size=" << query_info.m_body.size()); \
uint64_t ticks1 = epee::misc_utils::get_tick_count(); \
command_type::response resp{};\
MINFO(m_conn_context << "calling " << s_pattern); \
if(!callback_f(req, resp, &m_conn_context)) \
{ \
LOG_ERROR("Failed to " << #callback_f << "()"); \
response_info.m_response_code = 500; \
response_info.m_response_comment = "Internal Server Error"; \
return true; \
} \
uint64_t ticks2 = epee::misc_utils::get_tick_count(); \
epee::serialization::store_t_to_binary(resp, response_info.m_body); \
uint64_t ticks3 = epee::misc_utils::get_tick_count(); \
MDEBUG( s_pattern << "() processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms"); \
}
#define CHAIN_URI_MAP2(callback) else {callback(query_info, response_info, m_conn_context);handled = true;}
#define END_URI_MAP2() return handled;}
#define BEGIN_JSON_RPC_MAP(uri) else if(query_info.m_URI == uri) \
{ \
uint64_t ticks = epee::misc_utils::get_tick_count(); \
response_info.m_mime_type = "application/json"; \
epee::serialization::portable_storage ps; \
if(!ps.load_from_json(query_info.m_body)) \
{ \
epee::json_rpc::error_response rsp{}; \
rsp.jsonrpc = "2.0"; \
rsp.error.code = -32700; \
rsp.error.message = "Parse error"; \
epee::serialization::store_t_to_json(rsp, response_info.m_body); \
return true; \
} \
epee::serialization::storage_entry id_; \
id_ = epee::serialization::storage_entry(std::string()); \
ps.get_value("id", id_, nullptr); \
std::string callback_name; \
if(!ps.get_value("method", callback_name, nullptr)) \
{ \
epee::json_rpc::error_response rsp; \
rsp.jsonrpc = "2.0"; \
rsp.error.code = -32600; \
rsp.error.message = "Invalid Request"; \
epee::serialization::store_t_to_json(rsp, response_info.m_body); \
return true; \
} \
if(false) return true; //just a stub to have "else if"
#define PREPARE_OBJECTS_FROM_JSON(command_type) \
handled = true; \
epee::json_rpc::request<command_type::request> req{}; \
if(!req.load(ps)) \
{ \
epee::json_rpc::error_response fail_resp{}; \
fail_resp.jsonrpc = "2.0"; \
fail_resp.id = req.id; \
fail_resp.error.code = -32602; \
fail_resp.error.message = "Invalid params"; \
epee::serialization::store_t_to_json(fail_resp, response_info.m_body); \
return true; \
} \
uint64_t ticks1 = epee::misc_utils::get_tick_count(); \
epee::json_rpc::response<command_type::response> resp{}; \
resp.jsonrpc = "2.0"; \
resp.id = req.id;
#define FINALIZE_OBJECTS_TO_JSON(method_name) \
uint64_t ticks2 = epee::misc_utils::get_tick_count(); \
epee::serialization::store_t_to_json(resp, response_info.m_body); \
uint64_t ticks3 = epee::misc_utils::get_tick_count(); \
response_info.m_mime_type = "application/json"; \
response_info.m_header_info.m_content_type = " application/json"; \
MDEBUG( query_info.m_URI << "[" << method_name << "] processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms");
#define MAP_JON_RPC_WE_IF(method_name, callback_f, command_type, cond) \
else if((callback_name == method_name) && (cond)) \
{ \
PREPARE_OBJECTS_FROM_JSON(command_type) \
epee::json_rpc::error_response fail_resp{}; \
fail_resp.jsonrpc = "2.0"; \
fail_resp.id = req.id; \
MINFO(m_conn_context << "Calling RPC method " << method_name); \
if(!callback_f(req.params, resp.result, fail_resp.error, &m_conn_context)) \
{ \
epee::serialization::store_t_to_json(fail_resp, response_info.m_body); \
return true; \
} \
FINALIZE_OBJECTS_TO_JSON(method_name) \
return true;\
}
#define MAP_JON_RPC_WE(method_name, callback_f, command_type) MAP_JON_RPC_WE_IF(method_name, callback_f, command_type, true)
#define MAP_JON_RPC_WERI(method_name, callback_f, command_type) \
else if(callback_name == method_name) \
{ \
PREPARE_OBJECTS_FROM_JSON(command_type) \
epee::json_rpc::error_response fail_resp{}; \
fail_resp.jsonrpc = "2.0"; \
fail_resp.id = req.id; \
MINFO(m_conn_context << "calling RPC method " << method_name); \
if(!callback_f(req.params, resp.result, fail_resp.error, response_info, &m_conn_context)) \
{ \
epee::serialization::store_t_to_json(fail_resp, response_info.m_body); \
return true; \
} \
FINALIZE_OBJECTS_TO_JSON(method_name) \
return true;\
}
#define MAP_JON_RPC(method_name, callback_f, command_type) \
else if(callback_name == method_name) \
{ \
PREPARE_OBJECTS_FROM_JSON(command_type) \
MINFO(m_conn_context << "calling RPC method " << method_name); \
if(!callback_f(req.params, resp.result, &m_conn_context)) \
{ \
epee::json_rpc::error_response fail_resp{}; \
fail_resp.jsonrpc = "2.0"; \
fail_resp.id = req.id; \
fail_resp.error.code = -32603; \
fail_resp.error.message = "Internal error"; \
epee::serialization::store_t_to_json(fail_resp, response_info.m_body); \
return true; \
} \
FINALIZE_OBJECTS_TO_JSON(method_name) \
return true;\
}
#define END_JSON_RPC_MAP() \
epee::json_rpc::error_response rsp{}; \
rsp.id = id_; \
rsp.jsonrpc = "2.0"; \
rsp.error.code = -32601; \
rsp.error.message = "Method not found"; \
epee::serialization::store_t_to_json(rsp, response_info.m_body); \
return true; \
}

View File

@ -1,133 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <optional>
#include "net/abstract_tcp_server2.h"
#include "http_protocol_handler.h"
#include "net/http_server_handlers_map2.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.http"
namespace epee
{
template<class t_child_class, class t_connection_context = epee::net_utils::connection_context_base>
class http_server_impl_base: public net_utils::http::i_http_server_handler<t_connection_context>
{
public:
http_server_impl_base()
: m_net_server(epee::net_utils::e_connection_type_RPC)
{}
explicit http_server_impl_base(boost::asio::io_service& external_io_service)
: m_net_server(external_io_service)
{}
bool init(std::function<void(size_t, uint8_t*)> rng, const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0",
const std::string& bind_ipv6_address = "::", bool use_ipv6 = false, bool require_ipv4 = true,
std::vector<std::string> access_control_origins = std::vector<std::string>(),
std::optional<net_utils::http::login> user = std::nullopt)
{
//set self as callback handler
m_net_server.get_config_object().m_phandler = static_cast<t_child_class*>(this);
m_net_server.get_config_object().rng = std::move(rng);
//here set folder for hosting reqests
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);
MGINFO("Binding on " << bind_ip << " (IPv4):" << bind_port);
if (use_ipv6)
{
MGINFO("Binding on " << bind_ipv6_address << " (IPv6):" << bind_port);
}
bool res = m_net_server.init_server(bind_port, bind_ip, bind_port, bind_ipv6_address, use_ipv6, require_ipv4);
if(!res)
{
LOG_ERROR("Failed to bind server");
return false;
}
return true;
}
bool run(size_t threads_count, bool wait = true)
{
//go to loop
MINFO("Run net_service loop( " << threads_count << " threads)...");
if(!m_net_server.run_server(threads_count, wait))
{
LOG_ERROR("Failed to run net tcp server!");
}
if(wait)
MINFO("net_service loop stopped.");
return true;
}
bool deinit()
{
return m_net_server.deinit_server();
}
bool server_stop()
{
return m_net_server.server_stop();
}
bool send_stop_signal()
{
m_net_server.send_stop_signal();
return true;
}
int get_binded_port()
{
return m_net_server.get_binded_port();
}
long get_connections_count() const
{
return m_net_server.get_connections_count();
}
protected:
net_utils::boosted_tcp_server<net_utils::http::http_custom_handler<t_connection_context> > m_net_server;
};
}

View File

@ -1,142 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include "http_base.h"
#include <regex>
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net"
namespace epee::net_utils
{
inline bool parse_uri_query(const std::string& query, std::list<std::pair<std::string, std::string> >& params)
{
enum state
{
st_param_name,
st_param_val
};
state st = st_param_name;
std::string::const_iterator start_it = query.begin();
std::pair<std::string, std::string> e;
for(std::string::const_iterator it = query.begin(); it != query.end(); it++)
{
switch(st)
{
case st_param_name:
if(*it == '=')
{
e.first.assign(start_it, it);
start_it = it;++start_it;
st = st_param_val;
}
break;
case st_param_val:
if(*it == '&')
{
e.second.assign(start_it, it);
start_it = it;++start_it;
params.push_back(e);
e.first.clear();e.second.clear();
st = st_param_name;
}
break;
default:
LOG_ERROR("Unknown state " << (int)st);
return false;
}
}
if(st == st_param_name)
{
if(start_it != query.end())
{
e.first.assign(start_it, query.end());
params.push_back(e);
}
}else
{
if(start_it != query.end())
e.second.assign(start_it, query.end());
if(e.first.size())
params.push_back(e);
}
return true;
}
inline const std::regex rexp_match_uri{R"(([^?#]*)(?:\?([^#]*))?(?:#(.*))?)", std::regex::optimize};
inline
bool parse_uri(const std::string& uri, http::uri_content& content)
{
///iframe_test.html?api_url=http://api.vk.com/api.php&api_id=3289090&api_settings=1&viewer_id=562964060&viewer_type=0&sid=0aad8d1c5713130f9ca0076f2b7b47e532877424961367d81e7fa92455f069be7e21bc3193cbd0be11895&secret=368ebbc0ef&access_token=668bc03f43981d883f73876ffff4aa8564254b359cc745dfa1b3cde7bdab2e94105d8f6d8250717569c0a7&user_id=0&group_id=0&is_app_user=1&auth_key=d2f7a895ca5ff3fdb2a2a8ae23fe679a&language=0&parent_language=0&ad_info=ElsdCQBaQlxiAQRdFUVUXiN2AVBzBx5pU1BXIgZUJlIEAWcgAUoLQg==&referrer=unknown&lc_name=9834b6a3&hash=
content.m_query_params.clear();
std::smatch result;
if(!std::regex_match(uri, result, rexp_match_uri))
{
LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << uri);
content.m_path = uri;
return true;
}
content.m_path = result[1];
if(result[2].matched)
content.m_query = result[2];
if(result[3].matched)
content.m_fragment = result[3];
if(content.m_query.size())
parse_uri_query(content.m_query, content.m_query_params);
return true;
}
inline const std::regex rexp_match_url{R"(^(?:(.*?)://)?(?:\[([0-9a-fA-F:.]*)\]|([^\[/:]*))(?::(\d+))?)", std::regex::optimize};
// proto ipv6 ipv4/host port
inline
bool parse_url(const std::string& url_str, http::url_content& content)
{
content.port = 0;
std::smatch result;
if(!std::regex_search(url_str, result, rexp_match_url))
{
LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << url_str);
return false;
}
if(result[1].matched)
content.schema = result[1];
content.host = result[result[2].matched ? 2 : 3];
if(result[4].matched)
content.port = boost::lexical_cast<uint64_t>(result[4]);
content.uri = result.suffix().str();
return parse_uri(content.uri, content.m_uri_content);
}
}

View File

@ -1,139 +0,0 @@
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <string_view>
#include <chrono>
#include <string>
#include "portable_storage_template_helper.h"
#include "net/http_base.h"
#include "net/http_server_handlers_map2.h"
namespace epee
{
namespace net_utils
{
template<class t_request, class t_response, class t_transport>
bool invoke_http_json(const std::string_view uri, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = 15s, const std::string_view method = "POST"sv)
{
std::string req_param;
if(!serialization::store_t_to_json(out_struct, req_param))
return false;
http::fields_list additional_params;
additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8"));
const http::http_response_info* pri = NULL;
if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri), std::move(additional_params)))
{
LOG_PRINT_L1("Failed to invoke http request to " << uri);
return false;
}
if(!pri)
{
LOG_PRINT_L1("Failed to invoke http request to " << uri << ", internal error (null response ptr)");
return false;
}
if(pri->m_response_code != 200)
{
LOG_PRINT_L1("Failed to invoke http request to " << uri << ", wrong response code: " << pri->m_response_code);
return false;
}
return serialization::load_t_from_json(result_struct, pri->m_body);
}
template<class t_request, class t_response, class t_transport>
bool invoke_http_bin(const std::string_view uri, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = 15s, const std::string_view method = "POST"sv)
{
std::string req_param;
if(!serialization::store_t_to_binary(out_struct, req_param))
return false;
const http::http_response_info* pri = NULL;
if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri)))
{
LOG_PRINT_L1("Failed to invoke http request to " << uri);
return false;
}
if(!pri)
{
LOG_PRINT_L1("Failed to invoke http request to " << uri << ", internal error (null response ptr)");
return false;
}
if(pri->m_response_code != 200)
{
LOG_PRINT_L1("Failed to invoke http request to " << uri << ", wrong response code: " << pri->m_response_code);
return false;
}
return serialization::load_t_from_binary(result_struct, epee::strspan<uint8_t>(pri->m_body));
}
template<class t_request, class t_response, class t_transport>
bool invoke_http_json_rpc(const std::string_view uri, std::string method_name, const t_request& out_struct, t_response& result_struct, epee::json_rpc::error &error_struct, t_transport& transport, std::chrono::milliseconds timeout = 15s, const std::string_view http_method = "POST"sv, const std::string& req_id = "0"s)
{
epee::json_rpc::request<t_request> req_t{};
req_t.jsonrpc = "2.0";
req_t.id = req_id;
req_t.method = std::move(method_name);
req_t.params = out_struct;
epee::json_rpc::response<t_response, true> resp_t{};
if(!epee::net_utils::invoke_http_json(uri, req_t, resp_t, transport, timeout, http_method))
{
return false;
}
if(resp_t.error.code || resp_t.error.message.size())
{
LOG_ERROR("RPC call of \"" << req_t.method << "\" returned error: " << resp_t.error.code << ", message: " << resp_t.error.message);
return false;
}
result_struct = resp_t.result;
return true;
}
template<class t_request, class t_response, class t_transport>
bool invoke_http_json_rpc(const std::string_view uri, std::string method_name, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const std::string_view http_method = "POST", const std::string& req_id = "0")
{
epee::json_rpc::error error_struct;
return invoke_http_json_rpc(uri, method_name, out_struct, result_struct, error_struct, transport, timeout, http_method, req_id);
}
template<class t_command, class t_transport>
bool invoke_http_json_rpc(const std::string_view uri, typename t_command::request& out_struct, typename t_command::response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = 15s, const std::string_view http_method = "POST"sv, const std::string_view req_id = "0"sv)
{
return invoke_http_json_rpc(uri, t_command::methodname(), out_struct, result_struct, transport, timeout, http_method, req_id);
}
}
}

View File

@ -30,7 +30,6 @@ add_library(epee
buffer.cpp
connection_basic.cpp
hex.cpp
http_auth.cpp
levin_base.cpp
memwipe.c
mlocker.cpp

View File

@ -1,779 +0,0 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "net/http_auth.h"
#include <array>
#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/iteration/iter_fold.hpp>
#include <boost/fusion/algorithm/query/any.hpp>
#include <boost/fusion/iterator/distance.hpp>
#include <boost/fusion/iterator/value_of.hpp>
#include <boost/fusion/sequence/intrinsic/begin.hpp>
#include <boost/fusion/sequence/intrinsic/size.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/range/join.hpp>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <boost/spirit/include/qi_alternative.hpp>
#include <boost/spirit/include/qi_and_predicate.hpp>
#include <boost/spirit/include/qi_char.hpp>
#include <boost/spirit/include/qi_char_class.hpp>
#include <boost/spirit/include/qi_difference.hpp>
#include <boost/spirit/include/qi_kleene.hpp>
#include <boost/spirit/include/qi_parse.hpp>
#include <boost/spirit/include/qi_plus.hpp>
#include <boost/spirit/include/qi_no_case.hpp>
#include <boost/spirit/include/qi_not_predicate.hpp>
#include <boost/spirit/include/qi_raw.hpp>
#include <boost/spirit/include/qi_rule.hpp>
#include <boost/spirit/include/qi_sequence.hpp>
#include <boost/spirit/include/qi_string.hpp>
#include <boost/spirit/include/qi_symbols.hpp>
#include <boost/spirit/include/qi_uint.hpp>
#include <cassert>
#include <iterator>
#include <limits>
#include <tuple>
#include <type_traits>
#include <optional>
#include <string_view>
#include "hex.h"
#include "md5_l.h"
#include "string_coding.h"
/* This file uses the `u8` prefix for (a paranoid attempt at) maximum portability:
C++ does not actually specify ASCII
as the encoding type for unprefixed string literals, etc. Although rare, the
effort required to support rare compiler encoding types is low.
Also be careful of qi::ascii character classes (`qi::asci::alpha`, etc.) -
non-ASCII characters will cause undefined behavior in the table lookup until
boost 1.60. The expression `&qi::ascii::char_` will fail on non-ASCII
characters without "consuming" the input character. */
using namespace std::literals;
namespace
{
namespace http = epee::net_utils::http;
constexpr auto client_auth_field = u8"Authorization";
constexpr auto server_auth_field = u8"WWW-authenticate";
constexpr auto auth_realm = u8"monero-rpc";
constexpr auto sess_algo = u8"-sess"sv;
constexpr unsigned client_reserve_size = 512; //!< std::string::reserve size for clients
//// Digest Algorithms
struct md5_
{
static constexpr auto name = u8"MD5"sv;
struct update
{
void operator()(const std::string_view arg) const
{
md5::MD5Update(
std::addressof(ctx),
reinterpret_cast<const std::uint8_t*>(arg.data()),
arg.size()
);
}
void operator()(const boost::iterator_range<const char*> arg) const
{
operator()(std::string_view{arg.begin(), arg.size()});
}
template <size_t N>
void operator()(const std::array<char, N>& arg) const
{
operator()(std::string_view{arg.data(), arg.size()});
}
// Template indirection for wipeable_string to prevent implicit conversion from std::string/char.
template <typename T, std::enable_if_t<std::is_same_v<T, epee::wipeable_string>, int> = 0>
void operator()(const T& arg) const
{
operator()(std::string_view{arg.data(), arg.size()});
}
md5::MD5_CTX& ctx;
};
template<typename... T>
std::array<char, 32> operator()(const T&... args) const
{
md5::MD5_CTX ctx{};
md5::MD5Init(std::addressof(ctx));
update updater{ctx};
(updater(args), ...);
std::array<std::uint8_t, 16> digest{{}};
md5::MD5Final(digest.data(), std::addressof(ctx));
return epee::to_hex::array(digest);
}
};
//! Digest Algorithms available for HTTP Digest Auth. Sort better algos to the left
constexpr const std::tuple<md5_> digest_algorithms{};
//// Various String Utilities
struct ascii_tolower_
{
template<typename Char>
constexpr Char operator()(Char value) const noexcept
{
static_assert(std::is_integral<Char>::value, "only integral types supported");
return (u8'A' <= value && value <= u8'Z') ? (value + (u8'a' - u8'A')) : value;
}
};
constexpr const ascii_tolower_ ascii_tolower{};
struct ascii_iequal_
{
template<typename Char>
constexpr bool operator()(Char left, Char right) const noexcept
{
return ascii_tolower(left) == ascii_tolower(right);
}
};
constexpr const ascii_iequal_ ascii_iequal{};
struct http_list_separator_
{
template<typename Char>
bool operator()(Char value) const noexcept
{
static_assert(std::is_integral<Char>::value, "only integral types supported");
return boost::spirit::char_encoding::ascii::isascii_(value) &&
(value == u8',' || boost::spirit::char_encoding::ascii::isspace(value));
}
};
constexpr const http_list_separator_ http_list_separator{};
std::string to_string(boost::iterator_range<const char*> source)
{
return {source.begin(), source.size()};
}
void add_first_field(std::string& str, const std::string_view name, const std::string_view value, bool add_quotes = false)
{
str.append(name);
str.push_back(u8'=');
if (add_quotes) str.push_back(u8'"');
boost::copy(value, std::back_inserter(str));
if (add_quotes) str.push_back(u8'"');
}
void add_field(std::string& str, const std::string_view name, const std::string_view value, bool add_quotes = false)
{
str.push_back(u8',');
add_first_field(str, name, value, add_quotes);
}
template <size_t N>
void add_field(std::string& str, const std::string_view name, const std::array<char, N>& value, bool add_quotes = false)
{
add_field(str, name, std::string_view{value.data(), N}, add_quotes);
}
//// Digest Authentication
template<typename Digest>
std::result_of_t<Digest()> generate_a1(
Digest digest, const http::login& creds, const std::string_view realm)
{
return digest(creds.username, u8":", realm, u8":", creds.password);
}
template<typename Digest>
std::result_of_t<Digest()> generate_a1(
Digest digest, const http::http_client_auth::session& user)
{
return generate_a1(std::move(digest), user.credentials, user.server.realm);
}
template<typename T>
void init_client_value(std::string& str,
const std::string_view algorithm, const http::http_client_auth::session& user,
const std::string_view uri, const T& response)
{
str.append(u8"Digest ");
constexpr bool add_quotes = true;
add_first_field(str, u8"algorithm", algorithm);
add_field(str, u8"nonce", user.server.nonce, add_quotes);
add_field(str, u8"realm", user.server.realm, add_quotes);
add_field(str, u8"response", response, add_quotes);
add_field(str, u8"uri", uri, add_quotes);
add_field(str, u8"username", user.credentials.username, add_quotes);
if (!user.server.opaque.empty())
add_field(str, u8"opaque", user.server.opaque, add_quotes);
}
//! Implements superseded algorithm specified in RFC 2069
template<typename Digest>
struct old_algorithm
{
explicit old_algorithm(Digest digest_) : digest(std::move(digest_)) {}
std::string operator()(const http::http_client_auth::session& user,
const std::string_view method, const std::string_view uri) const
{
const auto response = digest(
generate_a1(digest, user), u8":", user.server.nonce, u8":", digest(method, u8":", uri)
);
std::string out{};
out.reserve(client_reserve_size);
init_client_value(out, Digest::name, user, uri, response);
return out;
}
private:
Digest digest;
};
//! Implements the `qop=auth` algorithm specified in RFC 2617
template<typename Digest>
struct auth_algorithm
{
explicit auth_algorithm(Digest digest_) : digest(std::move(digest_)) {}
std::string operator()(const http::http_client_auth::session& user,
const std::string_view method, const std::string_view uri) const
{
namespace karma = boost::spirit::karma;
using counter_type = decltype(user.counter);
static_assert(
std::numeric_limits<counter_type>::radix == 2, "unexpected radix for counter"
);
static_assert(
std::numeric_limits<counter_type>::digits <= 32,
"number of digits will cause underflow on padding below"
);
std::string out{};
out.reserve(client_reserve_size);
karma::generate(std::back_inserter(out), karma::hex(user.counter));
out.insert(out.begin(), 8 - out.size(), u8'0'); // zero left pad
if (out.size() != 8)
return {};
std::array<char, 8> nc{{}};
boost::copy(out, nc.data());
const auto response = digest(
generate_a1(digest, user), u8":", user.server.nonce, u8":", nc, u8"::auth:", digest(method, u8":", uri)
);
out.clear();
init_client_value(out, Digest::name, user, uri, response);
add_field(out, u8"qop", u8"auth"sv);
add_field(out, u8"nc", nc);
return out;
}
private:
Digest digest;
};
//! Processes client "Authorization" and server "WWW-authenticate" HTTP fields
struct auth_message
{
using iterator = const char*;
enum status{ kFail = 0, kStale, kPass };
//! \return Status of the `response` field from the client
static status verify(const std::string_view method, const std::string_view request,
const http::http_server_auth::session& user)
{
const auto parsed = parse(request);
if (parsed &&
boost::equals(parsed->username, user.credentials.username) &&
boost::fusion::any(digest_algorithms, has_valid_response{*parsed, user, method}))
{
if (boost::equals(parsed->nonce, user.nonce))
{
// RFC 2069 format does not verify nc value - allow just once
if (user.counter == 1 || (!parsed->qop.empty() && parsed->counter() == user.counter))
{
return kPass;
}
}
return kStale;
}
return kFail;
}
//! \return Information needed to generate client authentication `response`s.
static http::http_client_auth::session::keys extract(
const http::http_response_info& response, const bool is_first)
{
using field = std::pair<std::string, std::string>;
server_parameters best{};
const std::list<field>& fields = response.m_header_info.m_etc_fields;
auto current = fields.begin();
const auto end = fields.end();
while (true)
{
current = std::find_if(current, end, [] (const field& value) {
return boost::equals(server_auth_field, value.first, ascii_iequal);
});
if (current == end)
break;
const auto parsed = parse(current->second);
if (parsed)
{
server_parameters local_best = parsed->algorithm.empty() ?
server_parameters{*parsed, boost::fusion::find<md5_>(digest_algorithms)} :
boost::fusion::iter_fold(digest_algorithms, server_parameters{}, matches_algorithm{*parsed});
if (local_best.index < best.index)
best = std::move(local_best);
}
++current;
}
if (is_first || boost::equals(best.stale, u8"true"sv, ascii_iequal))
return best.take();
return {}; // authentication failed with bad user/pass
}
private:
explicit auth_message()
: algorithm()
, cnonce()
, nc()
, nonce()
, qop()
, realm()
, response()
, stale()
, uri()
, username() {
}
static std::optional<auth_message> parse(const std::string_view request)
{
struct parser
{
using field_parser = std::function<bool(const parser&, iterator&, iterator, auth_message&)>;
explicit parser() : field_table(), skip_whitespace(), header(), quoted_string(), token(), fields() {
using namespace std::placeholders;
namespace qi = boost::spirit::qi;
struct parse_nc
{
bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
{
return qi::parse(
current, end,
(qi::raw[qi::uint_parser<std::uint32_t, 16, 8, 8>{}]),
result.nc
);
}
};
struct parse_token
{
bool operator()(const parser& parse, iterator& current, const iterator end,
boost::iterator_range<iterator>& result) const
{
return qi::parse(current, end, parse.token, result);
}
};
struct parse_string
{
bool operator()(const parser& parse, iterator& current, const iterator end,
boost::iterator_range<iterator>& result) const
{
return qi::parse(current, end, parse.quoted_string, result);
}
bool operator()(const parser& parse, iterator& current, const iterator end) const
{
return qi::parse(current, end, parse.quoted_string);
}
};
struct parse_response
{
bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
{
using byte = qi::uint_parser<std::uint8_t, 16, 2, 2>;
return qi::parse(
current, end,
(qi::lit(u8'"') >> qi::raw[+(byte{})] >> qi::lit(u8'"')),
result.response
);
}
};
field_table.add
(u8"algorithm", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::algorithm, _4)))
(u8"cnonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::cnonce, _4)))
(u8"domain", std::bind(parse_string{}, _1, _2, _3)) // ignore field
(u8"nc", parse_nc{})
(u8"nonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::nonce, _4)))
(u8"opaque", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::opaque, _4)))
(u8"qop", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::qop, _4)))
(u8"realm", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::realm, _4)))
(u8"response", parse_response{})
(u8"stale", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::stale, _4)))
(u8"uri", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::uri, _4)))
(u8"username", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::username, _4)));
skip_whitespace = *(&qi::ascii::char_ >> qi::ascii::space);
header = skip_whitespace >> qi::ascii::no_case[u8"digest"] >> skip_whitespace;
quoted_string = (qi::lit(u8'"') >> qi::raw[+(u8"\\\"" | (qi::ascii::char_ - u8'"'))] >> qi::lit(u8'"'));
token =
(!qi::lit(u8'"') >> qi::raw[+(&qi::ascii::char_ >> (qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}")))]) |
quoted_string;
fields = field_table >> skip_whitespace >> u8'=' >> skip_whitespace;
}
std::optional<auth_message> operator()(const std::string_view request) const
{
namespace qi = boost::spirit::qi;
iterator current = request.begin();
const iterator end = request.end();
if (!qi::parse(current, end, header))
{
return std::nullopt;
}
auth_message info{};
field_parser null_parser{};
std::reference_wrapper<const field_parser> field = null_parser;
do // require at least one field; require field after `,` character
{
if (!qi::parse(current, end, fields, field) || !field(*this, current, end, info))
{
return std::nullopt;
}
qi::parse(current, end, skip_whitespace);
} while (qi::parse(current, end, qi::char_(u8',') >> skip_whitespace));
if (current == end)
return info;
return std::nullopt;
}
private:
boost::spirit::qi::symbols<
char, field_parser, boost::spirit::qi::tst<char, field_parser>, ascii_tolower_
> field_table;
boost::spirit::qi::rule<iterator> skip_whitespace;
boost::spirit::qi::rule<iterator> header;
boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> quoted_string;
boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> token;
boost::spirit::qi::rule<iterator, std::reference_wrapper<const field_parser>()> fields;
}; // parser
static const parser parse_;
return parse_(request);
}
struct has_valid_response
{
template<typename Digest, typename Result>
Result generate_old_response(Digest digest, const Result& key, const Result& auth) const
{
return digest(key, u8":", request.nonce, u8":", auth);
}
template<typename Digest, typename Result>
Result generate_new_response(Digest digest, const Result& key, const Result& auth) const
{
return digest(
key, u8":", request.nonce, u8":", request.nc, u8":", request.cnonce, u8":", request.qop, u8":", auth
);
}
template<typename Result>
bool check(const Result& result) const
{
return boost::equals(request.response, result, ascii_iequal);
}
template<typename Digest>
bool operator()(const Digest& digest) const
{
if (boost::starts_with(request.algorithm, Digest::name, ascii_iequal) ||
(request.algorithm.empty() && std::is_same<md5_, Digest>::value))
{
auto key = generate_a1(digest, user.credentials, auth_realm);
if (boost::ends_with(request.algorithm, sess_algo, ascii_iequal))
{
key = digest(key, u8":", request.nonce, u8":", request.cnonce);
}
auto auth = digest(method, u8":", request.uri);
if (request.qop.empty())
{
return check(generate_old_response(std::move(digest), std::move(key), std::move(auth)));
}
else if (boost::equals(u8"auth"sv, request.qop, ascii_iequal))
{
return check(generate_new_response(std::move(digest), std::move(key), std::move(auth)));
}
}
return false;
}
const auth_message& request;
const http::http_server_auth::session& user;
const std::string_view method;
};
std::optional<std::uint32_t> counter() const
{
namespace qi = boost::spirit::qi;
using hex = qi::uint_parser<std::uint32_t, 16>;
std::uint32_t value = 0;
const bool converted = qi::parse(nc.begin(), nc.end(), hex{}, value);
if (converted) return value;
return std::nullopt;
}
struct server_parameters
{
server_parameters()
: nonce(), opaque(), realm(), stale(), value_generator()
, index(boost::fusion::size(digest_algorithms))
{}
template<typename DigestIter>
explicit server_parameters(const auth_message& request, const DigestIter& digest)
: nonce(request.nonce)
, opaque(request.opaque)
, realm(request.realm)
, stale(request.stale)
, value_generator()
, index(boost::fusion::distance(boost::fusion::begin(digest_algorithms), digest))
{
using digest_type = typename boost::fusion::result_of::value_of<DigestIter>::type;
// debug check internal state of the auth_message class
assert(
(std::is_same<digest_type, md5_>::value) ||
boost::equals((*digest).name, request.algorithm, ascii_iequal)
);
if (request.qop.empty())
value_generator = old_algorithm<digest_type>{*digest};
else
{
for (auto elem = boost::make_split_iterator(request.qop, boost::token_finder(http_list_separator));
!elem.eof();
++elem)
{
if (boost::equals(u8"auth"sv, *elem, ascii_iequal))
{
value_generator = auth_algorithm<digest_type>{*digest};
break;
}
}
if (!value_generator) // no supported qop mode
index = boost::fusion::size(digest_algorithms);
}
}
http::http_client_auth::session::keys take()
{
return {to_string(nonce), to_string(opaque), to_string(realm), std::move(value_generator)};
}
boost::iterator_range<iterator> nonce;
boost::iterator_range<iterator> opaque;
boost::iterator_range<iterator> realm;
boost::iterator_range<iterator> stale;
http::http_client_auth::session::keys::algorithm value_generator;
unsigned index;
};
struct matches_algorithm
{
template<typename DigestIter>
server_parameters operator()(server_parameters current, const DigestIter& digest) const
{
if (!current.value_generator)
{
if (boost::equals(response.algorithm, (*digest).name, ascii_iequal))
{
current = server_parameters{response, digest};
}
}
return current;
}
const auth_message& response;
};
boost::iterator_range<iterator> algorithm;
boost::iterator_range<iterator> cnonce;
boost::iterator_range<iterator> nc;
boost::iterator_range<iterator> nonce;
boost::iterator_range<iterator> opaque;
boost::iterator_range<iterator> qop;
boost::iterator_range<iterator> realm;
boost::iterator_range<iterator> response;
boost::iterator_range<iterator> stale;
boost::iterator_range<iterator> uri;
boost::iterator_range<iterator> username;
}; // auth_message
struct add_challenge
{
template<typename Digest>
void operator()(const Digest& digest) const
{
static constexpr auto fvalue = u8"Digest qop=\"auth\""sv;
for (unsigned i = 0; i < 2; ++i)
{
std::string out(fvalue);
std::string algorithm{Digest::name};
if (i > 0) algorithm += sess_algo;
add_field(out, u8"algorithm", algorithm);
constexpr bool add_quotes = true;
add_field(out, u8"realm", auth_realm, add_quotes);
add_field(out, u8"nonce", nonce, add_quotes);
add_field(out, u8"stale", is_stale ? "true"sv : "false"sv);
fields.push_back(std::make_pair(std::string(server_auth_field), std::move(out)));
}
}
const std::string_view nonce;
std::list<std::pair<std::string, std::string>>& fields;
const bool is_stale;
};
http::http_response_info create_digest_response(const std::string_view nonce, const bool is_stale)
{
epee::net_utils::http::http_response_info rc{};
rc.m_response_code = 401;
rc.m_response_comment = u8"Unauthorized";
rc.m_mime_type = u8"text/html";
rc.m_body =
u8"<html><head><title>Unauthorized Access</title></head><body><h1>401 Unauthorized</h1></body></html>";
boost::fusion::for_each(
digest_algorithms, add_challenge{nonce, rc.m_additional_fields, is_stale}
);
return rc;
}
}
namespace epee
{
namespace net_utils
{
namespace http
{
http_server_auth::http_server_auth(login credentials, std::function<void(size_t, uint8_t*)> r)
: user(session{std::move(credentials)}), rng(std::move(r)) {
}
std::optional<http_response_info> http_server_auth::do_get_response(const http_request_info& request)
{
assert(user);
using field = std::pair<std::string, std::string>;
const std::list<field>& fields = request.m_header_info.m_etc_fields;
const auto auth = boost::find_if(fields, [] (const field& value) {
return boost::equals(client_auth_field, value.first, ascii_iequal);
});
bool is_stale = false;
if (auth != fields.end())
{
++(user->counter);
switch (auth_message::verify(request.m_http_method_str, auth->second, *user))
{
case auth_message::kPass:
return std::nullopt;
case auth_message::kStale:
is_stale = true;
break;
default:
case auth_message::kFail:
break;
}
}
user->counter = 0;
{
std::array<std::uint8_t, 16> rand_128bit{{}};
rng(rand_128bit.size(), rand_128bit.data());
user->nonce = string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size());
}
return create_digest_response(user->nonce, is_stale);
}
http_client_auth::http_client_auth(login credentials)
: user(session{std::move(credentials)}) {
}
http_client_auth::status http_client_auth::do_handle_401(const http_response_info& response)
{
assert(user);
const bool first_auth = (user->counter == 0);
user->server = auth_message::extract(response, first_auth);
if (user->server.generator)
{
user->counter = 0;
return kSuccess;
}
return first_auth ? kParseFailure : kBadPassword;
}
std::optional<std::pair<std::string, std::string>> http_client_auth::do_get_auth_field(
const std::string_view method, const std::string_view uri)
{
assert(user);
if (user->server.generator)
{
++(user->counter);
return std::make_pair(std::string(client_auth_field), user->server.generator(*user, method, uri));
}
return std::nullopt;
}
}
}
}

View File

@ -33,6 +33,7 @@
#include "cryptonote_core/blockchain.h"
#include "common/command_line.h"
#include "loki_economy.h"
#include "common/hex.h"
#include "version.h"
#include <lokimq/hex.h>

View File

@ -52,7 +52,6 @@
#include "cryptonote_core/tx_sanity_check.h"
#include "misc_language.h"
#include "net/parse.h"
#include "storages/http_abstract_invoke.h"
#include "crypto/hash.h"
#include "rpc/rpc_args.h"
#include "rpc/rpc_handler.h"

View File

@ -68,7 +68,6 @@
#include "cryptonote_core/loki_name_system.h"
#include "simplewallet.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "storages/http_abstract_invoke.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "crypto/crypto.h" // for crypto::secret_key definition
#include "mnemonics/electrum-words.h"
@ -3170,12 +3169,12 @@ Pending or Failed: "failed"|"pending", "out", Lock, Checkpointed, Time, Amount*
m_cmd_binder.set_handler("lns_buy_mapping",
[this](const auto& x) { return lns_buy_mapping(x); },
tr(USAGE_LNS_BUY_MAPPING),
tr(tools::wallet_rpc::COMMAND_RPC_LNS_BUY_MAPPING::description));
tr(tools::wallet_rpc::LNS_BUY_MAPPING::description));
m_cmd_binder.set_handler("lns_update_mapping",
[this](const auto& x) { return lns_update_mapping(x); },
tr(USAGE_LNS_UPDATE_MAPPING),
tr(tools::wallet_rpc::COMMAND_RPC_LNS_UPDATE_MAPPING::description));
tr(tools::wallet_rpc::LNS_UPDATE_MAPPING::description));
m_cmd_binder.set_handler("lns_print_owners_to_names",
[this](const auto& x) { return lns_print_owners_to_names(x); },
@ -3190,7 +3189,7 @@ Pending or Failed: "failed"|"pending", "out", Lock, Checkpointed, Time, Amount*
m_cmd_binder.set_handler("lns_make_update_mapping_signature",
[this](const auto& x) { return lns_make_update_mapping_signature(x); },
tr(USAGE_LNS_MAKE_UPDATE_MAPPING_SIGNATURE),
tr(tools::wallet_rpc::COMMAND_RPC_LNS_MAKE_UPDATE_SIGNATURE::description));
tr(tools::wallet_rpc::LNS_MAKE_UPDATE_SIGNATURE::description));
}
simple_wallet::~simple_wallet()

View File

@ -41,7 +41,7 @@ target_link_libraries(wallet
PUBLIC
multisig
rpc_commands
rpc_args
rpc_server_base
cryptonote_core
mnemonics
device_trezor
@ -58,10 +58,12 @@ target_link_libraries(wallet
loki_add_executable(wallet_rpc_server "loki-wallet-rpc"
wallet_rpc_server.cpp
wallet_rpc_server_commands_defs.cpp
)
target_link_libraries(wallet_rpc_server
PRIVATE
rpc_server_base
wallet
daemonizer
Boost::program_options

File diff suppressed because it is too large Load Diff

View File

@ -34,263 +34,170 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <string>
#include <uWebSockets/App.h>
#include "common/util.h"
#include "net/http_server_impl_base.h"
#include "common/periodic_task.h"
#include "wallet_rpc_server_commands_defs.h"
#include "wallet2.h"
#include "rpc/http_server_base.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "wallet.rpc"
namespace tools
{
using HttpRequest = uWS::HttpRequest;
using HttpResponse = uWS::HttpResponse<false/*SSL*/>;
/************************************************************************/
/* */
/************************************************************************/
class wallet_rpc_server: public epee::http_server_impl_base<wallet_rpc_server>
class wallet_rpc_server final : public cryptonote::rpc::http_server_base
{
public:
typedef epee::net_utils::connection_context_base connection_context;
static const char* tr(const char* str);
wallet_rpc_server(boost::program_options::variables_map vm);
bool init();
bool run(bool /*interactive - ignored (rpc wallet is always non-interactive) */);
void stop();
void set_wallet(std::unique_ptr<wallet2> cr);
std::atomic<bool> m_long_poll_disabled;
/// Thrown if we get invalid/unparseable JSON data.
struct parse_error : std::runtime_error { using std::runtime_error::runtime_error; };
//json_rpc
wallet_rpc::GET_BALANCE::response invoke(wallet_rpc::GET_BALANCE::request&& req);
wallet_rpc::GET_ADDRESS::response invoke(wallet_rpc::GET_ADDRESS::request&& req);
wallet_rpc::GET_ADDRESS_INDEX::response invoke(wallet_rpc::GET_ADDRESS_INDEX::request&& req);
wallet_rpc::CREATE_ADDRESS::response invoke(wallet_rpc::CREATE_ADDRESS::request&& req);
wallet_rpc::LABEL_ADDRESS::response invoke(wallet_rpc::LABEL_ADDRESS::request&& req);
wallet_rpc::GET_ACCOUNTS::response invoke(wallet_rpc::GET_ACCOUNTS::request&& req);
wallet_rpc::CREATE_ACCOUNT::response invoke(wallet_rpc::CREATE_ACCOUNT::request&& req);
wallet_rpc::LABEL_ACCOUNT::response invoke(wallet_rpc::LABEL_ACCOUNT::request&& req);
wallet_rpc::GET_ACCOUNT_TAGS::response invoke(wallet_rpc::GET_ACCOUNT_TAGS::request&& req);
wallet_rpc::TAG_ACCOUNTS::response invoke(wallet_rpc::TAG_ACCOUNTS::request&& req);
wallet_rpc::UNTAG_ACCOUNTS::response invoke(wallet_rpc::UNTAG_ACCOUNTS::request&& req);
wallet_rpc::SET_ACCOUNT_TAG_DESCRIPTION::response invoke(wallet_rpc::SET_ACCOUNT_TAG_DESCRIPTION::request&& req);
wallet_rpc::GET_HEIGHT::response invoke(wallet_rpc::GET_HEIGHT::request&& req);
wallet_rpc::TRANSFER::response invoke(wallet_rpc::TRANSFER::request&& req);
wallet_rpc::TRANSFER_SPLIT::response invoke(wallet_rpc::TRANSFER_SPLIT::request&& req);
wallet_rpc::SIGN_TRANSFER::response invoke(wallet_rpc::SIGN_TRANSFER::request&& req);
wallet_rpc::DESCRIBE_TRANSFER::response invoke(wallet_rpc::DESCRIBE_TRANSFER::request&& req);
wallet_rpc::SUBMIT_TRANSFER::response invoke(wallet_rpc::SUBMIT_TRANSFER::request&& req);
wallet_rpc::SWEEP_DUST::response invoke(wallet_rpc::SWEEP_DUST::request&& req);
wallet_rpc::SWEEP_ALL::response invoke(wallet_rpc::SWEEP_ALL::request&& req);
wallet_rpc::SWEEP_SINGLE::response invoke(wallet_rpc::SWEEP_SINGLE::request&& req);
wallet_rpc::RELAY_TX::response invoke(wallet_rpc::RELAY_TX::request&& req);
wallet_rpc::MAKE_INTEGRATED_ADDRESS::response invoke(wallet_rpc::MAKE_INTEGRATED_ADDRESS::request&& req);
wallet_rpc::SPLIT_INTEGRATED_ADDRESS::response invoke(wallet_rpc::SPLIT_INTEGRATED_ADDRESS::request&& req);
wallet_rpc::STORE::response invoke(wallet_rpc::STORE::request&& req);
wallet_rpc::GET_PAYMENTS::response invoke(wallet_rpc::GET_PAYMENTS::request&& req);
wallet_rpc::GET_BULK_PAYMENTS::response invoke(wallet_rpc::GET_BULK_PAYMENTS::request&& req);
wallet_rpc::INCOMING_TRANSFERS::response invoke(wallet_rpc::INCOMING_TRANSFERS::request&& req);
wallet_rpc::STOP_WALLET::response invoke(wallet_rpc::STOP_WALLET::request&& req);
wallet_rpc::RESCAN_BLOCKCHAIN::response invoke(wallet_rpc::RESCAN_BLOCKCHAIN::request&& req);
wallet_rpc::SET_TX_NOTES::response invoke(wallet_rpc::SET_TX_NOTES::request&& req);
wallet_rpc::GET_TX_NOTES::response invoke(wallet_rpc::GET_TX_NOTES::request&& req);
wallet_rpc::SET_ATTRIBUTE::response invoke(wallet_rpc::SET_ATTRIBUTE::request&& req);
wallet_rpc::GET_ATTRIBUTE::response invoke(wallet_rpc::GET_ATTRIBUTE::request&& req);
wallet_rpc::GET_TX_KEY::response invoke(wallet_rpc::GET_TX_KEY::request&& req);
wallet_rpc::CHECK_TX_KEY::response invoke(wallet_rpc::CHECK_TX_KEY::request&& req);
wallet_rpc::GET_TX_PROOF::response invoke(wallet_rpc::GET_TX_PROOF::request&& req);
wallet_rpc::CHECK_TX_PROOF::response invoke(wallet_rpc::CHECK_TX_PROOF::request&& req);
wallet_rpc::GET_SPEND_PROOF::response invoke(wallet_rpc::GET_SPEND_PROOF::request&& req);
wallet_rpc::CHECK_SPEND_PROOF::response invoke(wallet_rpc::CHECK_SPEND_PROOF::request&& req);
wallet_rpc::GET_RESERVE_PROOF::response invoke(wallet_rpc::GET_RESERVE_PROOF::request&& req);
wallet_rpc::CHECK_RESERVE_PROOF::response invoke(wallet_rpc::CHECK_RESERVE_PROOF::request&& req);
wallet_rpc::GET_TRANSFERS::response invoke(wallet_rpc::GET_TRANSFERS::request&& req);
wallet_rpc::GET_TRANSFERS_CSV::response invoke(wallet_rpc::GET_TRANSFERS_CSV::request&& req);
wallet_rpc::GET_TRANSFER_BY_TXID::response invoke(wallet_rpc::GET_TRANSFER_BY_TXID::request&& req);
wallet_rpc::SIGN::response invoke(wallet_rpc::SIGN::request&& req);
wallet_rpc::VERIFY::response invoke(wallet_rpc::VERIFY::request&& req);
wallet_rpc::EXPORT_OUTPUTS::response invoke(wallet_rpc::EXPORT_OUTPUTS::request&& req);
wallet_rpc::IMPORT_OUTPUTS::response invoke(wallet_rpc::IMPORT_OUTPUTS::request&& req);
wallet_rpc::EXPORT_KEY_IMAGES::response invoke(wallet_rpc::EXPORT_KEY_IMAGES::request&& req);
wallet_rpc::IMPORT_KEY_IMAGES::response invoke(wallet_rpc::IMPORT_KEY_IMAGES::request&& req);
wallet_rpc::MAKE_URI::response invoke(wallet_rpc::MAKE_URI::request&& req);
wallet_rpc::PARSE_URI::response invoke(wallet_rpc::PARSE_URI::request&& req);
wallet_rpc::GET_ADDRESS_BOOK_ENTRY::response invoke(wallet_rpc::GET_ADDRESS_BOOK_ENTRY::request&& req);
wallet_rpc::ADD_ADDRESS_BOOK_ENTRY::response invoke(wallet_rpc::ADD_ADDRESS_BOOK_ENTRY::request&& req);
wallet_rpc::EDIT_ADDRESS_BOOK_ENTRY::response invoke(wallet_rpc::EDIT_ADDRESS_BOOK_ENTRY::request&& req);
wallet_rpc::DELETE_ADDRESS_BOOK_ENTRY::response invoke(wallet_rpc::DELETE_ADDRESS_BOOK_ENTRY::request&& req);
wallet_rpc::REFRESH::response invoke(wallet_rpc::REFRESH::request&& req);
wallet_rpc::AUTO_REFRESH::response invoke(wallet_rpc::AUTO_REFRESH::request&& req);
wallet_rpc::RESCAN_SPENT::response invoke(wallet_rpc::RESCAN_SPENT::request&& req);
wallet_rpc::START_MINING::response invoke(wallet_rpc::START_MINING::request&& req);
wallet_rpc::STOP_MINING::response invoke(wallet_rpc::STOP_MINING::request&& req);
wallet_rpc::GET_LANGUAGES::response invoke(wallet_rpc::GET_LANGUAGES::request&& req);
wallet_rpc::CREATE_WALLET::response invoke(wallet_rpc::CREATE_WALLET::request&& req);
wallet_rpc::OPEN_WALLET::response invoke(wallet_rpc::OPEN_WALLET::request&& req);
wallet_rpc::CLOSE_WALLET::response invoke(wallet_rpc::CLOSE_WALLET::request&& req);
wallet_rpc::CHANGE_WALLET_PASSWORD::response invoke(wallet_rpc::CHANGE_WALLET_PASSWORD::request&& req);
wallet_rpc::GENERATE_FROM_KEYS::response invoke(wallet_rpc::GENERATE_FROM_KEYS::request&& req);
wallet_rpc::RESTORE_DETERMINISTIC_WALLET::response invoke(wallet_rpc::RESTORE_DETERMINISTIC_WALLET::request&& req);
wallet_rpc::IS_MULTISIG::response invoke(wallet_rpc::IS_MULTISIG::request&& req);
wallet_rpc::PREPARE_MULTISIG::response invoke(wallet_rpc::PREPARE_MULTISIG::request&& req);
wallet_rpc::MAKE_MULTISIG::response invoke(wallet_rpc::MAKE_MULTISIG::request&& req);
wallet_rpc::EXPORT_MULTISIG::response invoke(wallet_rpc::EXPORT_MULTISIG::request&& req);
wallet_rpc::IMPORT_MULTISIG::response invoke(wallet_rpc::IMPORT_MULTISIG::request&& req);
wallet_rpc::FINALIZE_MULTISIG::response invoke(wallet_rpc::FINALIZE_MULTISIG::request&& req);
wallet_rpc::EXCHANGE_MULTISIG_KEYS::response invoke(wallet_rpc::EXCHANGE_MULTISIG_KEYS::request&& req);
wallet_rpc::SIGN_MULTISIG::response invoke(wallet_rpc::SIGN_MULTISIG::request&& req);
wallet_rpc::SUBMIT_MULTISIG::response invoke(wallet_rpc::SUBMIT_MULTISIG::request&& req);
wallet_rpc::VALIDATE_ADDRESS::response invoke(wallet_rpc::VALIDATE_ADDRESS::request&& req);
wallet_rpc::SET_DAEMON::response invoke(wallet_rpc::SET_DAEMON::request&& req);
wallet_rpc::SET_LOG_LEVEL::response invoke(wallet_rpc::SET_LOG_LEVEL::request&& req);
wallet_rpc::SET_LOG_CATEGORIES::response invoke(wallet_rpc::SET_LOG_CATEGORIES::request&& req);
wallet_rpc::GET_VERSION::response invoke(wallet_rpc::GET_VERSION::request&& req);
wallet_rpc::STAKE::response invoke(wallet_rpc::STAKE::request&& req);
wallet_rpc::REGISTER_SERVICE_NODE::response invoke(wallet_rpc::REGISTER_SERVICE_NODE::request&& req);
wallet_rpc::CAN_REQUEST_STAKE_UNLOCK::response invoke(wallet_rpc::CAN_REQUEST_STAKE_UNLOCK::request&& req);
wallet_rpc::REQUEST_STAKE_UNLOCK::response invoke(wallet_rpc::REQUEST_STAKE_UNLOCK::request&& req);
wallet_rpc::LNS_BUY_MAPPING::response invoke(wallet_rpc::LNS_BUY_MAPPING::request&& req);
wallet_rpc::LNS_UPDATE_MAPPING::response invoke(wallet_rpc::LNS_UPDATE_MAPPING::request&& req);
wallet_rpc::LNS_MAKE_UPDATE_SIGNATURE::response invoke(wallet_rpc::LNS_MAKE_UPDATE_SIGNATURE::request&& req);
wallet_rpc::LNS_HASH_NAME::response invoke(wallet_rpc::LNS_HASH_NAME::request&& req);
wallet_rpc::LNS_DECRYPT_VALUE::response invoke(wallet_rpc::LNS_DECRYPT_VALUE::request&& req);
wallet_rpc::QUERY_KEY::response invoke(wallet_rpc::QUERY_KEY::request&& req);
private:
bool run_server_threads();
CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map
/// Handles a POST request to /json_rpc.
void handle_json_rpc_request(HttpResponse& res, HttpRequest& req);
BEGIN_URI_MAP2()
BEGIN_JSON_RPC_MAP("/json_rpc")
MAP_JON_RPC_WE("get_balance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE)
MAP_JON_RPC_WE("get_address", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS)
MAP_JON_RPC_WE("get_address_index", on_getaddress_index, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX)
MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE)
MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS)
MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS)
MAP_JON_RPC_WE("label_address", on_label_address, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS)
MAP_JON_RPC_WE("get_accounts", on_get_accounts, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS)
MAP_JON_RPC_WE("create_account", on_create_account, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT)
MAP_JON_RPC_WE("label_account", on_label_account, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT)
MAP_JON_RPC_WE("get_account_tags", on_get_account_tags, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS)
MAP_JON_RPC_WE("tag_accounts", on_tag_accounts, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS)
MAP_JON_RPC_WE("untag_accounts", on_untag_accounts, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS)
MAP_JON_RPC_WE("set_account_tag_description", on_set_account_tag_description, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION)
MAP_JON_RPC_WE("get_height", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT)
MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT)
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER)
MAP_JON_RPC_WE("describe_transfer", on_describe_transfer, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER)
MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER)
MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL)
MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE)
MAP_JON_RPC_WE("relay_tx", on_relay_tx, wallet_rpc::COMMAND_RPC_RELAY_TX)
MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE)
MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS)
MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS)
MAP_JON_RPC_WE("incoming_transfers", on_incoming_transfers, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS)
MAP_JON_RPC_WE("query_key", on_query_key, wallet_rpc::COMMAND_RPC_QUERY_KEY)
MAP_JON_RPC_WE("make_integrated_address", on_make_integrated_address, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS)
MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS)
MAP_JON_RPC_WE("stop_wallet", on_stop_wallet, wallet_rpc::COMMAND_RPC_STOP_WALLET)
MAP_JON_RPC_WE("rescan_blockchain", on_rescan_blockchain, wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN)
MAP_JON_RPC_WE("set_tx_notes", on_set_tx_notes, wallet_rpc::COMMAND_RPC_SET_TX_NOTES)
MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES)
MAP_JON_RPC_WE("set_attribute", on_set_attribute, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE)
MAP_JON_RPC_WE("get_attribute", on_get_attribute, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE)
MAP_JON_RPC_WE("get_tx_key", on_get_tx_key, wallet_rpc::COMMAND_RPC_GET_TX_KEY)
MAP_JON_RPC_WE("check_tx_key", on_check_tx_key, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY)
MAP_JON_RPC_WE("get_tx_proof", on_get_tx_proof, wallet_rpc::COMMAND_RPC_GET_TX_PROOF)
MAP_JON_RPC_WE("check_tx_proof", on_check_tx_proof, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF)
MAP_JON_RPC_WE("get_spend_proof", on_get_spend_proof, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF)
MAP_JON_RPC_WE("check_spend_proof", on_check_spend_proof, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF)
MAP_JON_RPC_WE("get_reserve_proof", on_get_reserve_proof, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF)
MAP_JON_RPC_WE("check_reserve_proof", on_check_reserve_proof, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF)
MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS)
MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID)
MAP_JON_RPC_WE("get_transfers_csv", on_get_transfers_csv, wallet_rpc::COMMAND_RPC_GET_TRANSFERS_CSV)
MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN)
MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY)
MAP_JON_RPC_WE("export_outputs", on_export_outputs, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS)
MAP_JON_RPC_WE("import_outputs", on_import_outputs, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS)
MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES)
MAP_JON_RPC_WE("import_key_images", on_import_key_images, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES)
MAP_JON_RPC_WE("make_uri", on_make_uri, wallet_rpc::COMMAND_RPC_MAKE_URI)
MAP_JON_RPC_WE("parse_uri", on_parse_uri, wallet_rpc::COMMAND_RPC_PARSE_URI)
MAP_JON_RPC_WE("get_address_book", on_get_address_book, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("edit_address_book", on_edit_address_book, wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH)
MAP_JON_RPC_WE("auto_refresh", on_auto_refresh, wallet_rpc::COMMAND_RPC_AUTO_REFRESH)
MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT)
MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING)
MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING)
MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES)
MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET)
MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET)
MAP_JON_RPC_WE("close_wallet", on_close_wallet, wallet_rpc::COMMAND_RPC_CLOSE_WALLET)
MAP_JON_RPC_WE("change_wallet_password", on_change_wallet_password, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD)
MAP_JON_RPC_WE("generate_from_keys", on_generate_from_keys, wallet_rpc::COMMAND_RPC_GENERATE_FROM_KEYS)
MAP_JON_RPC_WE("restore_deterministic_wallet", on_restore_deterministic_wallet, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET)
MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG)
MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG)
MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG)
MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
MAP_JON_RPC_WE("exchange_multisig_keys", on_exchange_multisig_keys, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS)
MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
MAP_JON_RPC_WE("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS)
MAP_JON_RPC_WE("set_daemon", on_set_daemon, wallet_rpc::COMMAND_RPC_SET_DAEMON)
MAP_JON_RPC_WE("set_log_level", on_set_log_level, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL)
MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES)
MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION)
//
// Loki
//
MAP_JON_RPC_WE("stake", on_stake, wallet_rpc::COMMAND_RPC_STAKE)
MAP_JON_RPC_WE("register_service_node", on_register_service_node, wallet_rpc::COMMAND_RPC_REGISTER_SERVICE_NODE)
MAP_JON_RPC_WE("can_request_stake_unlock", on_can_request_stake_unlock, wallet_rpc::COMMAND_RPC_CAN_REQUEST_STAKE_UNLOCK)
MAP_JON_RPC_WE("request_stake_unlock", on_request_stake_unlock, wallet_rpc::COMMAND_RPC_REQUEST_STAKE_UNLOCK)
MAP_JON_RPC_WE("lns_buy_mapping", on_lns_buy_mapping, wallet_rpc::COMMAND_RPC_LNS_BUY_MAPPING)
MAP_JON_RPC_WE("lns_update_mapping", on_lns_update_mapping, wallet_rpc::COMMAND_RPC_LNS_UPDATE_MAPPING)
MAP_JON_RPC_WE("lns_make_update_mapping_signature", on_lns_make_update_mapping_signature, wallet_rpc::COMMAND_RPC_LNS_MAKE_UPDATE_SIGNATURE)
MAP_JON_RPC_WE("lns_hash_name", on_lns_hash_name, wallet_rpc::COMMAND_RPC_LNS_HASH_NAME)
MAP_JON_RPC_WE("lns_decrypt_value", on_lns_decrypt_value, wallet_rpc::COMMAND_RPC_LNS_DECRYPT_VALUE)
END_JSON_RPC_MAP()
END_URI_MAP2()
//json_rpc
bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_getaddress_index(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_incoming_transfers(const wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_stop_wallet(const wallet_rpc::COMMAND_RPC_STOP_WALLET::request& req, wallet_rpc::COMMAND_RPC_STOP_WALLET::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_rescan_blockchain(const wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::request& req, wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_tx_notes(const wallet_rpc::COMMAND_RPC_SET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_SET_TX_NOTES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_attribute(const wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_attribute(const wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_check_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_transfers_csv(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS_CSV::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS_CSV::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_parse_uri(const wallet_rpc::COMMAND_RPC_PARSE_URI::request& req, wallet_rpc::COMMAND_RPC_PARSE_URI::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_edit_address_book(const wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_close_wallet(const wallet_rpc::COMMAND_RPC_CLOSE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CLOSE_WALLET::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_change_wallet_password(const wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::request& req, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_generate_from_keys(const wallet_rpc::COMMAND_RPC_GENERATE_FROM_KEYS::request& req, wallet_rpc::COMMAND_RPC_GENERATE_FROM_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_restore_deterministic_wallet(const wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::request& req, wallet_rpc::COMMAND_RPC_RESTORE_DETERMINISTIC_WALLET::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_log_level(const wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_stake(const wallet_rpc::COMMAND_RPC_STAKE::request& req, wallet_rpc::COMMAND_RPC_STAKE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_register_service_node(const wallet_rpc::COMMAND_RPC_REGISTER_SERVICE_NODE::request& req, wallet_rpc::COMMAND_RPC_REGISTER_SERVICE_NODE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_can_request_stake_unlock(const wallet_rpc::COMMAND_RPC_CAN_REQUEST_STAKE_UNLOCK::request& req, wallet_rpc::COMMAND_RPC_CAN_REQUEST_STAKE_UNLOCK::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_request_stake_unlock(const wallet_rpc::COMMAND_RPC_REQUEST_STAKE_UNLOCK::request& req, wallet_rpc::COMMAND_RPC_REQUEST_STAKE_UNLOCK::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_lns_buy_mapping(const wallet_rpc::COMMAND_RPC_LNS_BUY_MAPPING::request& req, wallet_rpc::COMMAND_RPC_LNS_BUY_MAPPING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_lns_update_mapping(const wallet_rpc::COMMAND_RPC_LNS_UPDATE_MAPPING::request& req, wallet_rpc::COMMAND_RPC_LNS_UPDATE_MAPPING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_lns_make_update_mapping_signature(const wallet_rpc::COMMAND_RPC_LNS_MAKE_UPDATE_SIGNATURE::request& req, wallet_rpc::COMMAND_RPC_LNS_MAKE_UPDATE_SIGNATURE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_lns_hash_name(const wallet_rpc::COMMAND_RPC_LNS_HASH_NAME::request& req, wallet_rpc::COMMAND_RPC_LNS_HASH_NAME::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_lns_decrypt_value(const wallet_rpc::COMMAND_RPC_LNS_DECRYPT_VALUE::request& req, wallet_rpc::COMMAND_RPC_LNS_DECRYPT_VALUE::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
//json rpc v2
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
// helpers
bool not_open(epee::json_rpc::error& er);
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
// Checks that a wallet is open; if not, throws an error.
void require_open();
template<typename Ts, typename Tu>
bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
void fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool blink,
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er);
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata);
bool validate_transfer(const std::list<wallet::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er);
void validate_transfer(const std::list<wallet::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination);
// Parse options and opens the wallet. Returns nullptr if in directory mode (i.e. no wallet
// gets opened). Throws on error.
std::unique_ptr<tools::wallet2> load_wallet();
// Sets up the RPC endpoints (called before listening).
void create_rpc_endpoints(uWS::App& http);
// Runs the server event loop; does not return until the server is shut down (by a signal or a
// remote STOP command).
void run_loop();
std::unique_ptr<wallet2> m_wallet;
std::string m_wallet_dir;
std::vector<std::tuple<std::string /*ip*/, uint16_t /*port*/, bool /*required*/>> m_bind;
tools::private_file rpc_login_file;
std::atomic<bool> m_stop;
bool m_restricted;
boost::program_options::variables_map m_vm;
std::chrono::milliseconds m_auto_refresh_period;
std::chrono::steady_clock::time_point m_last_auto_refresh_time;
std::thread m_long_poll_thread;
std::atomic<bool> m_long_poll_new_changes;
std::atomic<bool> m_long_poll_disabled;
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,60 +29,64 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include <cstdint>
namespace tools::wallet_rpc::error_code {
#define WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR -1
#define WALLET_RPC_ERROR_CODE_WRONG_ADDRESS -2
#define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3
#define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4
#define WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID -5
#define WALLET_RPC_ERROR_CODE_TRANSFER_TYPE -6
#define WALLET_RPC_ERROR_CODE_DENIED -7
#define WALLET_RPC_ERROR_CODE_WRONG_TXID -8
#define WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE -9
#define WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE -10
#define WALLET_RPC_ERROR_CODE_WRONG_URI -11
#define WALLET_RPC_ERROR_CODE_WRONG_INDEX -12
#define WALLET_RPC_ERROR_CODE_NOT_OPEN -13
#define WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUT_OF_BOUNDS -14
#define WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUT_OF_BOUNDS -15
#define WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE -16
#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY -17
#define WALLET_RPC_ERROR_CODE_TX_TOO_LARGE -18
#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_OUTS_TO_MIX -19
#define WALLET_RPC_ERROR_CODE_ZERO_DESTINATION -20
#define WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS -21
#define WALLET_RPC_ERROR_CODE_INVALID_PASSWORD -22
#define WALLET_RPC_ERROR_CODE_NO_WALLET_DIR -23
#define WALLET_RPC_ERROR_CODE_NO_TXKEY -24
#define WALLET_RPC_ERROR_CODE_WRONG_KEY -25
#define WALLET_RPC_ERROR_CODE_BAD_HEX -26
#define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27
#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28
#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29
#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30
#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31
#define WALLET_RPC_ERROR_CODE_WRONG_LR -32
#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33
#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA -34
#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE -35
#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36
#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_UNLOCKED_MONEY -37
#define WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION -38
#define WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA -39
#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40
#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41
#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42
#define WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC -43
#define WALLET_RPC_ERROR_CODE_INVALID_LOG_LEVEL -44
#define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45
constexpr int16_t UNKNOWN_ERROR = -1;
constexpr int16_t WRONG_ADDRESS = -2;
constexpr int16_t DAEMON_IS_BUSY = -3;
constexpr int16_t GENERIC_TRANSFER_ERROR = -4;
constexpr int16_t WRONG_PAYMENT_ID = -5;
constexpr int16_t TRANSFER_TYPE = -6;
constexpr int16_t DENIED = -7;
constexpr int16_t WRONG_TXID = -8;
constexpr int16_t WRONG_SIGNATURE = -9;
constexpr int16_t WRONG_KEY_IMAGE = -10;
constexpr int16_t WRONG_URI = -11;
constexpr int16_t WRONG_INDEX = -12;
constexpr int16_t NOT_OPEN = -13;
constexpr int16_t ACCOUNT_INDEX_OUT_OF_BOUNDS = -14;
constexpr int16_t ADDRESS_INDEX_OUT_OF_BOUNDS = -15;
constexpr int16_t TX_NOT_POSSIBLE = -16;
constexpr int16_t NOT_ENOUGH_MONEY = -17;
constexpr int16_t TX_TOO_LARGE = -18;
constexpr int16_t NOT_ENOUGH_OUTS_TO_MIX = -19;
constexpr int16_t ZERO_DESTINATION = -20;
constexpr int16_t WALLET_ALREADY_EXISTS = -21;
constexpr int16_t INVALID_PASSWORD = -22;
constexpr int16_t NO_WALLET_DIR = -23;
constexpr int16_t NO_TXKEY = -24;
constexpr int16_t WRONG_KEY = -25;
constexpr int16_t BAD_HEX = -26;
constexpr int16_t BAD_TX_METADATA = -27;
constexpr int16_t ALREADY_MULTISIG = -28;
constexpr int16_t WATCH_ONLY = -29;
constexpr int16_t BAD_MULTISIG_INFO = -30;
constexpr int16_t NOT_MULTISIG = -31;
constexpr int16_t WRONG_LR = -32;
constexpr int16_t THRESHOLD_NOT_REACHED = -33;
constexpr int16_t BAD_MULTISIG_TX_DATA = -34;
constexpr int16_t MULTISIG_SIGNATURE = -35;
constexpr int16_t MULTISIG_SUBMISSION = -36;
constexpr int16_t NOT_ENOUGH_UNLOCKED_MONEY = -37;
constexpr int16_t NO_DAEMON_CONNECTION = -38;
constexpr int16_t BAD_UNSIGNED_TX_DATA = -39;
constexpr int16_t BAD_SIGNED_TX_DATA = -40;
constexpr int16_t SIGNED_SUBMISSION = -41;
constexpr int16_t SIGN_UNSIGNED = -42;
constexpr int16_t NON_DETERMINISTIC = -43;
constexpr int16_t INVALID_LOG_LEVEL = -44;
constexpr int16_t ATTRIBUTE_NOT_FOUND = -45;
// Loki:
#define WALLET_RPC_ERROR_CODE_BLINK_FAILED -1000
#define WALLET_RPC_ERROR_CODE_HF_QUERY_FAILED -1001
#define WALLET_RPC_ERROR_CODE_WRONG_LNS_TYPE -1002
#define WALLET_RPC_ERROR_CODE_LNS_BAD_NAME -1003
#define WALLET_RPC_ERROR_CODE_LNS_VALUE_TOO_LONG -1004
#define WALLET_RPC_ERROR_CODE_LNS_VALUE_NOT_HEX -1005
#define WALLET_RPC_ERROR_CODE_LNS_VALUE_LENGTH_NOT_EVEN -1006
#define WALLET_RPC_ERROR_CODE_LNS_VALUE_DECRYPT_FAILED -1007
constexpr int16_t BLINK_FAILED = -1000;
constexpr int16_t HF_QUERY_FAILED = -1001;
constexpr int16_t WRONG_LNS_TYPE = -1002;
constexpr int16_t LNS_BAD_NAME = -1003;
constexpr int16_t LNS_VALUE_TOO_LONG = -1004;
constexpr int16_t LNS_VALUE_NOT_HEX = -1005;
constexpr int16_t LNS_VALUE_LENGTH_NOT_EVEN = -1006;
constexpr int16_t LNS_VALUE_DECRYPT_FAILED = -1007;
}

View File

@ -122,18 +122,6 @@ set_property(TARGET base58_fuzz_tests
PROPERTY
FOLDER "tests")
add_executable(parse-url_fuzz_tests parse_url.cpp fuzzer.cpp)
target_link_libraries(parse-url_fuzz_tests
PRIVATE
common
epee
Boost::program_options
Boost::system
extra)
set_property(TARGET parse-url_fuzz_tests
PROPERTY
FOLDER "tests")
add_executable(levin_fuzz_tests levin.cpp fuzzer.cpp)
target_link_libraries(levin_fuzz_tests
PRIVATE

View File

@ -1,76 +0,0 @@
// Copyright (c) 2017-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "include_base_utils.h"
#include "file_io_utils.h"
#include "net/net_parse_helpers.h"
#include "fuzzer.h"
class ParseURLFuzzer: public Fuzzer
{
public:
ParseURLFuzzer() {}
virtual int init();
virtual int run(const std::string &filename);
};
int ParseURLFuzzer::init()
{
return 0;
}
int ParseURLFuzzer::run(const std::string &filename)
{
std::string s;
if (!epee::file_io_utils::load_file_to_string(filename, s))
{
std::cout << "Error: failed to load file " << filename << std::endl;
return 1;
}
try
{
epee::net_utils::http::url_content url;
epee::net_utils::parse_url(s, url);
}
catch (const std::exception &e)
{
std::cerr << "Failed to load from binary: " << e.what() << std::endl;
return 1;
}
return 0;
}
int main(int argc, const char **argv)
{
TRY_ENTRY();
ParseURLFuzzer fuzzer;
return run_fuzzer(argc, argv, fuzzer);
CATCH_ENTRY_L0("main", 1);
}

View File

@ -49,7 +49,6 @@ add_executable(unit_tests
get_xtype_from_string.cpp
hashchain.cpp
hmac_keccak.cpp
http.cpp
keccak.cpp
levin.cpp
logging.cpp

View File

@ -1,745 +0,0 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "gtest/gtest.h"
#include "net/http_auth.h"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/spirit/include/karma_char.hpp>
#include <boost/spirit/include/karma_list.hpp>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/spirit/include/karma_right_alignment.hpp>
#include <boost/spirit/include/karma_sequence.hpp>
#include <boost/spirit/include/karma_string.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <boost/spirit/include/qi_alternative.hpp>
#include <boost/spirit/include/qi_char.hpp>
#include <boost/spirit/include/qi_char_class.hpp>
#include <boost/spirit/include/qi_difference.hpp>
#include <boost/spirit/include/qi_eoi.hpp>
#include <boost/spirit/include/qi_list.hpp>
#include <boost/spirit/include/qi_parse.hpp>
#include <boost/spirit/include/qi_plus.hpp>
#include <boost/spirit/include/qi_sequence.hpp>
#include <boost/spirit/include/qi_string.hpp>
#include <cstdint>
#include <iterator>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "md5_l.h"
#include "string_tools.h"
#include "crypto/crypto.h"
namespace {
namespace http = epee::net_utils::http;
using fields = std::unordered_map<std::string, std::string>;
using auth_responses = std::vector<fields>;
void rng(size_t len, uint8_t *ptr)
{
crypto::rand(len, ptr);
}
std::string quoted(std::string str)
{
str.insert(str.begin(), '"');
str.push_back('"');
return str;
}
void write_fields(std::string& out, const fields& args)
{
namespace karma = boost::spirit::karma;
karma::generate(
std::back_inserter(out),
(karma::string << " = " << karma::string) % " , ",
args);
}
std::string write_fields(const fields& args)
{
std::string out{};
write_fields(out, args);
return out;
}
http::http_request_info make_request(const fields& args)
{
std::string out{" DIGEST "};
write_fields(out, args);
http::http_request_info request{};
request.m_http_method_str = "NOP";
request.m_header_info.m_etc_fields.push_back(
std::make_pair(u8"authorization", std::move(out))
);
return request;
}
http::http_response_info make_response(const auth_responses& choices)
{
http::http_response_info response{};
for (const auto& choice : choices)
{
std::string out{" DIGEST "};
write_fields(out, choice);
response.m_header_info.m_etc_fields.push_back(
std::make_pair(u8"WWW-authenticate", std::move(out))
);
}
return response;
}
bool has_same_fields(const auth_responses& in)
{
const std::vector<std::string> check{u8"nonce", u8"qop", u8"realm", u8"stale"};
auto current = in.begin();
const auto end = in.end();
if (current == end)
return true;
++current;
for ( ; current != end; ++current )
{
for (const auto& field : check)
{
const std::string& expected = in[0].at(field);
const std::string& actual = current->at(field);
EXPECT_EQ(expected, actual);
if (expected != actual)
return false;
}
}
return true;
}
bool is_unauthorized(const http::http_response_info& response)
{
EXPECT_EQ(401, response.m_response_code);
EXPECT_STREQ(u8"Unauthorized", response.m_response_comment.c_str());
EXPECT_STREQ(u8"text/html", response.m_mime_type.c_str());
return response.m_response_code == 401 &&
response.m_response_comment == u8"Unauthorized" &&
response.m_mime_type == u8"text/html";
}
fields parse_fields(const std::string& value)
{
namespace qi = boost::spirit::qi;
fields out{};
const bool rc = qi::parse(
value.begin(), value.end(),
qi::lit(u8"Digest ") >> ((
+qi::ascii::alpha >>
qi::lit('=') >> (
(qi::lit('"') >> +(qi::ascii::char_ - '"') >> qi::lit('"')) |
+(qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}"))
)
) % ','
) >> qi::eoi,
out
);
if (!rc)
throw std::runtime_error{"Bad field given in HTTP header"};
return out;
}
auth_responses parse_response(const http::http_response_info& response)
{
auth_responses result{};
const auto end = response.m_additional_fields.end();
for (auto current = response.m_additional_fields.begin();; ++current)
{
current = std::find_if(current, end, [] (const std::pair<std::string, std::string>& field) {
return boost::equals(u8"WWW-authenticate", field.first);
});
if (current == end)
return result;
result.push_back(parse_fields(current->second));
}
return result;
}
std::string md5_hex(const std::string& in)
{
md5::MD5_CTX ctx{};
md5::MD5Init(std::addressof(ctx));
md5::MD5Update(
std::addressof(ctx),
reinterpret_cast<const std::uint8_t*>(in.data()),
in.size()
);
std::array<std::uint8_t, 16> digest{{}};
md5::MD5Final(digest.data(), std::addressof(ctx));
return epee::string_tools::pod_to_hex(digest);
}
std::string get_a1(const http::login& user, const fields& src)
{
const std::string& realm = src.at(u8"realm");
return boost::join(
std::vector<std::string>{user.username, realm, std::string(user.password.data(), user.password.size())}, u8":"
);
}
std::string get_a1(const http::login& user, const auth_responses& responses)
{
return get_a1(user, responses.at(0));
}
std::string get_a1_sess(const http::login& user, const std::string& cnonce, const auth_responses& responses)
{
const std::string& nonce = responses.at(0).at(u8"nonce");
return boost::join(
std::vector<std::string>{md5_hex(get_a1(user, responses)), nonce, cnonce}, u8":"
);
}
std::string get_a2(const std::string& uri)
{
return boost::join(std::vector<std::string>{"NOP", uri}, u8":");
}
std::string get_nc(std::uint32_t count)
{
namespace karma = boost::spirit::karma;
std::string out;
karma::generate(
std::back_inserter(out),
karma::right_align(8, '0')[karma::uint_generator<std::uint32_t, 16>{}],
count
);
return out;
}
}
TEST(HTTP_Server_Auth, NotRequired)
{
http::http_server_auth auth{}; // no rng here
EXPECT_FALSE(auth.get_response(http::http_request_info{}));
}
TEST(HTTP_Server_Auth, MissingAuth)
{
http::http_server_auth auth{{"foo", "bar"}, rng};
EXPECT_TRUE(bool(auth.get_response(http::http_request_info{})));
{
http::http_request_info request{};
request.m_header_info.m_etc_fields.push_back({"\xFF", "\xFF"});
EXPECT_TRUE(bool(auth.get_response(request)));
}
}
TEST(HTTP_Server_Auth, BadSyntax)
{
http::http_server_auth auth{{"foo", "bar"}, rng};
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"algorithm", "fo\xFF"}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce \xFF =", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8" \xFF cnonce", "\"000\xFF\""}}))));
}
TEST(HTTP_Server_Auth, MD5)
{
http::login user{"foo", "bar"};
http::http_server_auth auth{user, rng};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
EXPECT_TRUE(is_unauthorized(*response));
const auto fields = parse_response(*response);
ASSERT_LE(2u, fields.size());
EXPECT_TRUE(has_same_fields(fields));
const std::string& nonce = fields[0].at(u8"nonce");
EXPECT_EQ(24, nonce.size());
const std::string uri{"/some_foo_thing"};
const std::string a1 = get_a1(user, fields);
const std::string a2 = get_a2(uri);
const std::string auth_code = md5_hex(
boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)}, u8":")
);
const auto request = make_request({
{u8"nonce", quoted(nonce)},
{u8"realm", quoted(fields[0].at(u8"realm"))},
{u8"response", quoted(auth_code)},
{u8"uri", quoted(uri)},
{u8"username", quoted(user.username)}
});
EXPECT_FALSE(bool(auth.get_response(request)));
const auto response2 = auth.get_response(request);
ASSERT_TRUE(bool(response2));
EXPECT_TRUE(is_unauthorized(*response2));
const auto fields2 = parse_response(*response2);
ASSERT_LE(2u, fields2.size());
EXPECT_TRUE(has_same_fields(fields2));
EXPECT_NE(nonce, fields2[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
TEST(HTTP_Server_Auth, MD5_sess)
{
constexpr const char cnonce[] = "not a good cnonce";
http::login user{"foo", "bar"};
http::http_server_auth auth{user, rng};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
EXPECT_TRUE(is_unauthorized(*response));
const auto fields = parse_response(*response);
ASSERT_LE(2u, fields.size());
EXPECT_TRUE(has_same_fields(fields));
const std::string& nonce = fields[0].at(u8"nonce");
EXPECT_EQ(24, nonce.size());
const std::string uri{"/some_foo_thing"};
const std::string a1 = get_a1_sess(user, cnonce, fields);
const std::string a2 = get_a2(uri);
const std::string auth_code = md5_hex(
boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)}, u8":")
);
const auto request = make_request({
{u8"algorithm", u8"md5-sess"},
{u8"cnonce", quoted(cnonce)},
{u8"nonce", quoted(nonce)},
{u8"realm", quoted(fields[0].at(u8"realm"))},
{u8"response", quoted(auth_code)},
{u8"uri", quoted(uri)},
{u8"username", quoted(user.username)}
});
EXPECT_FALSE(bool(auth.get_response(request)));
const auto response2 = auth.get_response(request);
ASSERT_TRUE(bool(response2));
EXPECT_TRUE(is_unauthorized(*response2));
const auto fields2 = parse_response(*response2);
ASSERT_LE(2u, fields2.size());
EXPECT_TRUE(has_same_fields(fields2));
EXPECT_NE(nonce, fields2[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
TEST(HTTP_Server_Auth, MD5_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
http::login user{"foo", "bar"};
http::http_server_auth auth{user, rng};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
EXPECT_TRUE(is_unauthorized(*response));
const auto parsed = parse_response(*response);
ASSERT_LE(2u, parsed.size());
EXPECT_TRUE(has_same_fields(parsed));
const std::string& nonce = parsed[0].at(u8"nonce");
EXPECT_EQ(24, nonce.size());
const std::string uri{"/some_foo_thing"};
const std::string a1 = get_a1(user, parsed);
const std::string a2 = get_a2(uri);
std::string nc = get_nc(1);
const auto generate_auth = [&] {
return md5_hex(
boost::join(
std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)}, u8":"
)
);
};
fields args{
{u8"algorithm", quoted(u8"md5")},
{u8"cnonce", quoted(cnonce)},
{u8"nc", nc},
{u8"nonce", quoted(nonce)},
{u8"qop", quoted(qop)},
{u8"realm", quoted(parsed[0].at(u8"realm"))},
{u8"response", quoted(generate_auth())},
{u8"uri", quoted(uri)},
{u8"username", quoted(user.username)}
};
const auto request = make_request(args);
EXPECT_FALSE(bool(auth.get_response(request)));
for (unsigned i = 2; i < 20; ++i)
{
nc = get_nc(i);
args.at(u8"nc") = nc;
args.at(u8"response") = quoted(generate_auth());
EXPECT_FALSE(auth.get_response(make_request(args)));
}
const auto replay = auth.get_response(request);
ASSERT_TRUE(bool(replay));
EXPECT_TRUE(is_unauthorized(*replay));
const auto parsed_replay = parse_response(*replay);
ASSERT_LE(2u, parsed_replay.size());
EXPECT_TRUE(has_same_fields(parsed_replay));
EXPECT_NE(nonce, parsed_replay[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
TEST(HTTP_Server_Auth, MD5_sess_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
http::login user{"foo", "bar"};
http::http_server_auth auth{user, rng};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
EXPECT_TRUE(is_unauthorized(*response));
const auto parsed = parse_response(*response);
ASSERT_LE(2u, parsed.size());
EXPECT_TRUE(has_same_fields(parsed));
const std::string& nonce = parsed[0].at(u8"nonce");
EXPECT_EQ(24, nonce.size());
const std::string uri{"/some_foo_thing"};
const std::string a1 = get_a1_sess(user, cnonce, parsed);
const std::string a2 = get_a2(uri);
std::string nc = get_nc(1);
const auto generate_auth = [&] {
return md5_hex(
boost::join(
std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)}, u8":"
)
);
};
fields args{
{u8"algorithm", u8"md5-sess"},
{u8"cnonce", quoted(cnonce)},
{u8"nc", nc},
{u8"nonce", quoted(nonce)},
{u8"qop", qop},
{u8"realm", quoted(parsed[0].at(u8"realm"))},
{u8"response", quoted(generate_auth())},
{u8"uri", quoted(uri)},
{u8"username", quoted(user.username)}
};
const auto request = make_request(args);
EXPECT_FALSE(bool(auth.get_response(request)));
for (unsigned i = 2; i < 20; ++i)
{
nc = get_nc(i);
args.at(u8"nc") = nc;
args.at(u8"response") = quoted(generate_auth());
EXPECT_FALSE(auth.get_response(make_request(args)));
}
const auto replay = auth.get_response(request);
ASSERT_TRUE(bool(replay));
EXPECT_TRUE(is_unauthorized(*replay));
const auto parsed_replay = parse_response(*replay);
ASSERT_LE(2u, parsed_replay.size());
EXPECT_TRUE(has_same_fields(parsed_replay));
EXPECT_NE(nonce, parsed_replay[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
TEST(HTTP_Auth, DogFood)
{
const auto add_auth_field = [] (http::http_request_info& request, http::http_client_auth& client)
{
auto field = client.get_auth_field(request.m_http_method_str, request.m_URI);
EXPECT_TRUE(bool(field));
if (!field)
return false;
request.m_header_info.m_etc_fields.push_back(std::move(*field));
return true;
};
const http::login user{"some_user", "ultimate password"};
http::http_server_auth server{user, rng};
http::http_client_auth client{user};
http::http_request_info request{};
request.m_http_method_str = "GET";
request.m_URI = "/FOO";
auto response = server.get_response(request);
ASSERT_TRUE(bool(response));
EXPECT_TRUE(is_unauthorized(*response));
EXPECT_TRUE(response->m_header_info.m_etc_fields.empty());
response->m_header_info.m_etc_fields = response->m_additional_fields;
EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
EXPECT_TRUE(add_auth_field(request, client));
EXPECT_FALSE(bool(server.get_response(request)));
for (unsigned i = 0; i < 1000; ++i)
{
request.m_http_method_str += std::to_string(i);
request.m_header_info.m_etc_fields.clear();
EXPECT_TRUE(add_auth_field(request, client));
EXPECT_FALSE(bool(server.get_response(request)));
}
// resetting counter should be rejected by server
request.m_header_info.m_etc_fields.clear();
client = http::http_client_auth{user};
EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
EXPECT_TRUE(add_auth_field(request, client));
auto response2 = server.get_response(request);
ASSERT_TRUE(bool(response2));
EXPECT_TRUE(is_unauthorized(*response2));
EXPECT_TRUE(response2->m_header_info.m_etc_fields.empty());
response2->m_header_info.m_etc_fields = response2->m_additional_fields;
const auth_responses parsed1 = parse_response(*response);
const auth_responses parsed2 = parse_response(*response2);
ASSERT_LE(1u, parsed1.size());
ASSERT_LE(1u, parsed2.size());
EXPECT_NE(parsed1[0].at(u8"nonce"), parsed2[0].at(u8"nonce"));
// with stale=true client should reset
request.m_header_info.m_etc_fields.clear();
EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response2));
EXPECT_TRUE(add_auth_field(request, client));
EXPECT_FALSE(bool(server.get_response(request)));
// client should give up if stale=false
EXPECT_EQ(http::http_client_auth::kBadPassword, client.handle_401(*response));
}
TEST(HTTP_Client_Auth, Unavailable)
{
http::http_client_auth auth{};
EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(http::http_response_info{}));
EXPECT_FALSE(bool(auth.get_auth_field("GET", "/file")));
}
TEST(HTTP_Client_Auth, MissingAuthenticate)
{
http::http_client_auth auth{{"foo", "bar"}};
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(http::http_response_info{}));
EXPECT_FALSE(bool(auth.get_auth_field("POST", "/\xFFname")));
{
http::http_response_info response{};
response.m_additional_fields.push_back({"\xFF", "\xFF"});
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(response));
}
EXPECT_FALSE(bool(auth.get_auth_field("DELETE", "/file/does/not/exist")));
}
TEST(HTTP_Client_Auth, BadSyntax)
{
http::http_client_auth auth{{"foo", "bar"}};
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"realm", "fo\xFF"}}})));
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"domain", "fo\xFF"}}})));
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce", "fo\xFF"}}})));
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce \xFF =", "fo\xFF"}}})));
EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8" \xFF nonce", "fo\xFF"}}})));
}
TEST(HTTP_Client_Auth, MD5)
{
constexpr char method[] = "NOP";
constexpr char nonce[] = "some crazy nonce";
constexpr char realm[] = "the only realm";
constexpr char uri[] = "/some_file";
const http::login user{"foo", "bar"};
http::http_client_auth auth{user};
auto response = make_response({
{
{u8"domain", quoted("ignored")},
{u8"nonce", quoted(nonce)},
{u8"REALM", quoted(realm)}
},
{
{u8"algorithm", "null"},
{u8"domain", quoted("ignored")},
{u8"nonce", quoted(std::string{"e"} + nonce)},
{u8"realm", quoted(std::string{"e"} + realm)}
},
});
EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
const auto auth_field = auth.get_auth_field(method, uri);
ASSERT_TRUE(bool(auth_field));
const auto parsed = parse_fields(auth_field->second);
EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
EXPECT_EQ(parsed.end(), parsed.find(u8"opaque"));
EXPECT_EQ(parsed.end(), parsed.find(u8"qop"));
EXPECT_EQ(parsed.end(), parsed.find(u8"nc"));
EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
EXPECT_EQ(user.username, parsed.at(u8"username"));
EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
const std::string a1 = get_a1(user, parsed);
const std::string a2 = get_a2(uri);
const std::string auth_code = md5_hex(
boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)}, u8":")
);
EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
{
const auto auth_field_dup = auth.get_auth_field(method, uri);
ASSERT_TRUE(bool(auth_field_dup));
EXPECT_EQ(*auth_field, *auth_field_dup);
}
EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
response.m_header_info.m_etc_fields.front().second.append(u8"," + write_fields({{u8"stale", u8"TRUE"}}));
EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
}
TEST(HTTP_Client_Auth, MD5_auth)
{
constexpr char cnonce[] = "";
constexpr char method[] = "NOP";
constexpr char nonce[] = "some crazy nonce";
constexpr char opaque[] = "this is the opaque";
constexpr char qop[] = u8"ignore,auth,ignore";
constexpr char realm[] = "the only realm";
constexpr char uri[] = "/some_file";
const http::login user{"foo", "bar"};
http::http_client_auth auth{user};
auto response = make_response({
{
{u8"algorithm", u8"MD5"},
{u8"domain", quoted("ignored")},
{u8"nonce", quoted(std::string{"e"} + nonce)},
{u8"realm", quoted(std::string{"e"} + realm)},
{u8"qop", quoted("some,thing,to,ignore")}
},
{
{u8"algorIthm", quoted(u8"md5")},
{u8"domain", quoted("ignored")},
{u8"noNce", quoted(nonce)},
{u8"opaque", quoted(opaque)},
{u8"realm", quoted(realm)},
{u8"QoP", quoted(qop)}
}
});
EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
for (unsigned i = 1; i < 1000; ++i)
{
const std::string nc = get_nc(i);
const auto auth_field = auth.get_auth_field(method, uri);
ASSERT_TRUE(bool(auth_field));
const auto parsed = parse_fields(auth_field->second);
EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
EXPECT_STREQ(opaque, parsed.at(u8"opaque").c_str());
EXPECT_STREQ(u8"auth", parsed.at(u8"qop").c_str());
EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
EXPECT_EQ(user.username, parsed.at(u8"username"));
EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
EXPECT_EQ(nc, parsed.at(u8"nc"));
const std::string a1 = get_a1(user, parsed);
const std::string a2 = get_a2(uri);
const std::string auth_code = md5_hex(
boost::join(std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, u8"auth", md5_hex(a2)}, u8":")
);
EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
}
EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
response.m_header_info.m_etc_fields.back().second.append(u8"," + write_fields({{u8"stale", u8"trUe"}}));
EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
}
TEST(HTTP, Add_Field)
{
std::string str{"leading text"};
epee::net_utils::http::add_field(str, "foo", "bar");
epee::net_utils::http::add_field(str, std::string("bar"), std::string("foo"));
epee::net_utils::http::add_field(str, {"moarbars", "moarfoo"});
EXPECT_STREQ("leading textfoo: bar\r\nbar: foo\r\nmoarbars: moarfoo\r\n", str.c_str());
}