monero/contrib/epee/include/net/http_protocol_handler.inl

750 lines
26 KiB
Plaintext
Raw Normal View History

// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
2014-03-03 15:07:58 -07:00
// 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.
2014-07-23 07:03:52 -06:00
//
// 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.
2014-07-23 07:03:52 -06:00
//
2014-03-03 15:07:58 -07:00
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include "http_protocol_handler.h"
#include "reg_exp_definer.h"
#include "string_tools.h"
#include "file_io_utils.h"
#include "net_parse_helpers.h"
#include "time_helper.h"
2014-03-03 15:07:58 -07:00
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.http"
2014-03-03 15:07:58 -07:00
#define HTTP_MAX_URI_LEN 9000
#define HTTP_MAX_HEADER_LEN 100000
#define HTTP_MAX_STARTING_NEWLINES 8
2014-03-03 15:07:58 -07:00
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
bool match_boundary(const std::string& content_type, std::string& boundary)
{
STATIC_REGEXP_EXPR_1(rexp_match_boundary, "boundary=(.*?)(($)|([;\\s,]))", boost::regex::icase | boost::regex::normal);
// 1
boost::smatch result;
if(boost::regex_search(content_type, result, rexp_match_boundary, boost::match_default) && result[0].matched)
{
boundary = result[1];
return true;
}
return false;
}
inline
bool parse_header(std::string::const_iterator it_begin, std::string::const_iterator it_end, multipart_entry& entry)
{
STATIC_REGEXP_EXPR_1(rexp_mach_field,
"\n?((Content-Disposition)|(Content-Type)"
// 12 3
"|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
//4 56 7
boost::regex::icase | boost::regex::normal);
boost::smatch result;
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
while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched)
{
const size_t field_val = 6;
const size_t field_etc_name = 4;
int i = 2; //start position = 2
if(result[i++].matched)//"Content-Disposition"
entry.m_content_disposition = result[field_val];
else if(result[i++].matched)//"Content-Type"
entry.m_content_type = result[field_val];
else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!)
entry.m_etc_header_fields.push_back(std::pair<std::string, std::string>(result[field_etc_name], result[field_val]));
else
{
LOG_ERROR("simple_http_connection_handler::parse_header() not matched last entry in:"<<std::string(it_current_bound, it_end));
}
it_current_bound = result[(int)result.size()-1].first;
}
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))
{
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
MERROR("Failed to match boundary in content type: " << content_type);
2014-03-03 15:07:58 -07:00
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)
{
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
MERROR("Error: Filed to match closing multipart tag");
2014-03-03 15:07:58 -07:00
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()))
{
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
MERROR("Failed to handle_part_of_multipart");
2014-03-03 15:07:58 -07:00
return false;
}
it_begin = it_end + boundary.size();
}
return true;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
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):
2014-03-03 15:07:58 -07:00
m_state(http_state_retriving_comand_line),
m_body_transfer_type(http_body_transfer_undefined),
m_is_stop_handling(false),
2014-03-03 15:07:58 -07:00
m_len_summary(0),
m_len_remain(0),
m_config(config),
2014-03-03 15:07:58 -07:00
m_want_close(false),
m_newlines(0),
m_psnd_hndlr(psnd_hndlr),
m_conn_context(conn_context)
2014-03-03 15:07:58 -07:00
{
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::set_ready_state()
2014-03-03 15:07:58 -07:00
{
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;
2014-03-03 15:07:58 -07:00
return true;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_recv(const void* ptr, size_t cb)
2014-03-03 15:07:58 -07:00
{
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;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_buff_in(std::string& buf)
2014-03-03 15:07:58 -07:00
{
size_t ndel;
2014-03-03 15:07:58 -07:00
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)
2014-03-03 15:07:58 -07:00
{
//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);
2014-03-03 15:07:58 -07:00
break;
}
if(std::string::npos != m_cache.find('\n', 0))
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");
2014-03-03 15:07:58 -07:00
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");
2014-03-03 15:07:58 -07:00
m_state = http_state_error;
return false;
}
break;
}
if (!analize_cached_request_header_and_invoke_state(pos))
return false;
2014-03-03 15:07:58 -07:00
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);
2014-03-03 15:07:58 -07:00
return false;
case http_state_error:
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::handle_char_out: Error state!!!");
2014-03-03 15:07:58 -07:00
return false;
}
if(!m_cache.size())
m_is_stop_handling = true;
}
return true;
}
//--------------------------------------------------------------------------------------------
inline bool analize_http_method(const boost::smatch& result, http::http_method& method, int& http_ver_major, int& http_ver_minor)
{
CHECK_AND_ASSERT_MES(result[0].matched, false, "simple_http_connection_handler::analize_http_method() assert failed...");
if (!boost::conversion::try_lexical_convert<int>(result[11], http_ver_major))
return false;
if (!boost::conversion::try_lexical_convert<int>(result[12], http_ver_minor))
return false;
if(result[3].matched)
method = http::http_method_options;
else if(result[4].matched)
2014-03-03 15:07:58 -07:00
method = http::http_method_get;
else if(result[5].matched)
method = http::http_method_head;
else if(result[6].matched)
method = http::http_method_post;
else if(result[7].matched)
method = http::http_method_put;
else
method = http::http_method_etc;
return true;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_invoke_query_line()
2014-03-03 15:07:58 -07:00
{
STATIC_REGEXP_EXPR_1(rexp_match_command_line, "^(((OPTIONS)|(GET)|(HEAD)|(POST)|(PUT)|(DELETE)|(TRACE)) (\\S+) HTTP/(\\d+)\\.(\\d+))\r?\n", boost::regex::icase | boost::regex::normal);
2014-03-03 15:07:58 -07:00
// 123 4 5 6 7 8 9 10 11 12
//size_t match_len = 0;
boost::smatch result;
if(boost::regex_search(m_cache, result, rexp_match_command_line, boost::match_default) && result[0].matched)
{
if (!analize_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;
}
2014-03-03 15:07:58 -07:00
m_query_info.m_URI = result[10];
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;
}
2014-03-03 15:07:58 -07:00
m_query_info.m_http_method_str = result[2];
m_query_info.m_full_request_str = result[0];
m_cache.erase(m_cache.begin(), to_nonsonst_iterator(m_cache, result[0].second));
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);
2014-03-03 15:07:58 -07:00
return false;
}
return false;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
std::string::size_type simple_http_connection_handler<t_connection_context>::match_end_of_header(const std::string& buf)
2014-03-03 15:07:58 -07:00
{
//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;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::analize_cached_request_header_and_invoke_state(size_t pos)
2014-03-03 15:07:58 -07:00
{
LOG_PRINT_L3("HTTP HEAD:\r\n" << m_cache.substr(0, pos));
2014-03-03 15:07:58 -07:00
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>::analize_cached_request_header_and_invoke_state(): failed to anilize request header: " << m_cache);
2014-03-03 15:07:58 -07:00
m_state = http_state_error;
return false;
2014-03-03 15:07:58 -07:00
}
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_lenght(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>::analize_cached_request_header_and_invoke_state(): Failed to get_len_from_content_lenght();, m_query_info.m_content_length="<<m_query_info.m_header_info.m_content_length);
2014-03-03 15:07:58 -07:00
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;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_retriving_query_body()
2014-03-03 15:07:58 -07:00
{
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);
2014-03-03 15:07:58 -07:00
m_state = http_state_error;
return false;
}
return true;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::handle_query_measure()
2014-03-03 15:07:58 -07:00
{
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;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
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)
2014-03-03 15:07:58 -07:00
{
STATIC_REGEXP_EXPR_1(rexp_mach_field,
"\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)"
// 12 3 4 5 6 7 8 9 10 11
2014-03-03 15:07:58 -07:00
"|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
//11 1213 14
2014-03-03 15:07:58 -07:00
boost::regex::icase | boost::regex::normal);
boost::smatch result;
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();
//lookup all fields and fill well-known fields
while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched)
{
const size_t field_val = 14;
const size_t field_etc_name = 12;
2014-03-03 15:07:58 -07:00
int i = 2; //start position = 2
if(result[i++].matched)//"Connection"
body_info.m_connection = result[field_val];
else if(result[i++].matched)//"Referer"
body_info.m_referer = result[field_val];
else if(result[i++].matched)//"Content-Length"
body_info.m_content_length = result[field_val];
else if(result[i++].matched)//"Content-Type"
body_info.m_content_type = result[field_val];
else if(result[i++].matched)//"Transfer-Encoding"
body_info.m_transfer_encoding = result[field_val];
else if(result[i++].matched)//"Content-Encoding"
body_info.m_content_encoding = result[field_val];
else if(result[i++].matched)//"Host"
body_info.m_host = result[field_val];
else if(result[i++].matched)//"Cookie"
body_info.m_cookie = result[field_val];
else if(result[i++].matched)//"User-Agent"
body_info.m_user_agent = result[field_val];
else if(result[i++].matched)//"Origin"
body_info.m_origin = result[field_val];
2014-03-03 15:07:58 -07:00
else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!)
body_info.m_etc_fields.push_back(std::pair<std::string, std::string>(result[field_etc_name], result[field_val]));
else
{
LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::parse_cached_header() not matched last entry in:" << m_cache_to_process);
2014-03-03 15:07:58 -07:00
}
it_current_bound = result[(int)result.size()-1]. first;
}
return true;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::get_len_from_content_lenght(const std::string& str, size_t& OUT len)
2014-03-03 15:07:58 -07:00
{
STATIC_REGEXP_EXPR_1(rexp_mach_field, "\\d+", boost::regex::normal);
std::string res;
boost::smatch result;
if(!(boost::regex_search( str, result, rexp_mach_field, boost::match_default) && result[0].matched))
return false;
try { len = boost::lexical_cast<size_t>(result[0]); }
catch(...) { return false; }
2014-03-03 15:07:58 -07:00
return true;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
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)
2014-03-03 15:07:58 -07:00
{
http_response_info response{};
2014-03-03 15:07:58 -07:00
//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);
if (response.m_response_code == 500)
{
m_want_close = true; // close on all "Internal server error"s
}
}
else
{
response.m_response_code = 200;
response.m_response_comment = "OK";
}
2014-03-03 15:07:58 -07:00
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);
if ((response.m_body.size() && (query_info.m_http_method != http::http_method_head)) || (query_info.m_http_method == http::http_method_options))
response_data += response.m_body;
m_psnd_hndlr->do_send(byte_slice{std::move(response_data)});
m_psnd_hndlr->send_done();
2014-03-03 15:07:58 -07:00
return res;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
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)
2014-03-03 15:07:58 -07:00
{
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);
m_config.m_lock.lock();
std::string destination_file_path = m_config.m_folder + uri_to_path;
m_config.m_lock.unlock();
if(!file_io_utils::load_file_to_string(destination_file_path.c_str(), response.m_body))
{
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
MWARNING("URI \""<< query_info.m_full_request_str.substr(0, query_info.m_full_request_str.size()-2) << "\" [" << destination_file_path << "] Not Found (404 )");
2014-03-03 15:07:58 -07:00
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_tipe = "text/html";
return true;
}
Change logging to easylogging++ This replaces the epee and data_loggers logging systems with a single one, and also adds filename:line and explicit severity levels. Categories may be defined, and logging severity set by category (or set of categories). epee style 0-4 log level maps to a sensible severity configuration. Log files now also rotate when reaching 100 MB. To select which logs to output, use the MONERO_LOGS environment variable, with a comma separated list of categories (globs are supported), with their requested severity level after a colon. If a log matches more than one such setting, the last one in the configuration string applies. A few examples: This one is (mostly) silent, only outputting fatal errors: MONERO_LOGS=*:FATAL This one is very verbose: MONERO_LOGS=*:TRACE This one is totally silent (logwise): MONERO_LOGS="" This one outputs all errors and warnings, except for the "verify" category, which prints just fatal errors (the verify category is used for logs about incoming transactions and blocks, and it is expected that some/many will fail to verify, hence we don't want the spam): MONERO_LOGS=*:WARNING,verify:FATAL Log levels are, in decreasing order of priority: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE Subcategories may be added using prefixes and globs. This example will output net.p2p logs at the TRACE level, but all other net* logs only at INFO: MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE Logs which are intended for the user (which Monero was using a lot through epee, but really isn't a nice way to go things) should use the "global" category. There are a few helper macros for using this category, eg: MGINFO("this shows up by default") or MGINFO_RED("this is red"), to try to keep a similar look and feel for now. Existing epee log macros still exist, and map to the new log levels, but since they're used as a "user facing" UI element as much as a logging system, they often don't map well to log severities (ie, a log level 0 log may be an error, or may be something we want the user to see, such as an important info). In those cases, I tried to use the new macros. In other cases, I left the existing macros in. When modifying logs, it is probably best to switch to the new macros with explicit levels. The --log-level options and set_log commands now also accept category settings, in addition to the epee style log levels.
2017-01-01 09:34:23 -07:00
MDEBUG(" -->> " << query_info.m_full_request_str << "\r\n<<--OK");
2014-03-03 15:07:58 -07:00
response.m_response_code = 200;
response.m_response_comment = "OK";
response.m_mime_tipe = get_file_mime_tipe(uri_to_path);
return true;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_response_header(const http_response_info& response)
2014-03-03 15:07:58 -07:00
{
std::string buf = "HTTP/1.1 ";
buf += boost::lexical_cast<std::string>(response.m_response_code) + " " + response.m_response_comment + "\r\n" +
2014-04-07 09:02:15 -06:00
"Server: Epee-based\r\n"
2014-03-03 15:07:58 -07:00
"Content-Length: ";
buf += boost::lexical_cast<std::string>(response.m_body.size()) + "\r\n";
if(!response.m_mime_tipe.empty())
{
buf += "Content-Type: ";
buf += response.m_mime_tipe + "\r\n";
}
2014-03-03 15:07:58 -07:00
buf += "Last-Modified: ";
time_t tm;
time(&tm);
buf += misc_utils::get_internet_time_str(tm) + "\r\n";
buf += "Accept-Ranges: bytes\r\n";
//Wed, 01 Dec 2010 03:27:41 GMT"
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";
}
}
2014-03-03 15:07:58 -07:00
//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";
2014-03-03 15:07:58 -07:00
buf+="\r\n";
return buf;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_file_mime_tipe(const std::string& path)
2014-03-03 15:07:58 -07:00
{
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;
}
//-----------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
std::string simple_http_connection_handler<t_connection_context>::get_not_found_response_body(const std::string& URI)
2014-03-03 15:07:58 -07:00
{
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;
}
//--------------------------------------------------------------------------------------------
2014-03-20 05:46:11 -06:00
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::slash_to_back_slash(std::string& str)
2014-03-03 15:07:58 -07:00
{
for(std::string::iterator it = str.begin(); it!=str.end(); it++)
if('/' == *it)
*it = '\\';
return true;
}
}
}
}
//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------
2015-01-02 09:52:46 -07:00
//--------------------------------------------------------------------------------------------