mirror of
https://github.com/monero-project/monero.git
synced 2024-12-25 13:09:24 +00:00
9209880e9c
reported by m31007
759 lines
27 KiB
C++
759 lines
27 KiB
C++
// 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/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"
|
|
|
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
|
#define MONERO_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
|
|
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))
|
|
{
|
|
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_bytes_read(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;
|
|
m_bytes_read = 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;
|
|
|
|
m_bytes_read += buf.size();
|
|
if (m_bytes_read > m_config.m_max_content_length)
|
|
{
|
|
LOG_ERROR("simple_http_connection_handler::handle_buff_in: Too much data: got " << m_bytes_read);
|
|
m_state = http_state_error;
|
|
return false;
|
|
}
|
|
|
|
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(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");
|
|
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 (!analize_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 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)
|
|
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;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
template<class t_connection_context>
|
|
bool simple_http_connection_handler<t_connection_context>::handle_invoke_query_line()
|
|
{
|
|
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);
|
|
// 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;
|
|
}
|
|
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;
|
|
}
|
|
m_query_info.m_http_method_str = result[2];
|
|
m_query_info.m_full_request_str = result[0];
|
|
|
|
m_cache.erase(m_cache.begin(), 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);
|
|
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>::analize_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>::analize_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_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);
|
|
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;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
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)
|
|
{
|
|
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
|
|
"|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
|
|
//11 1213 14
|
|
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;
|
|
|
|
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];
|
|
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);
|
|
}
|
|
|
|
it_current_bound = result[(int)result.size()-1]. first;
|
|
}
|
|
return true;
|
|
}
|
|
//-----------------------------------------------------------------------------------
|
|
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)
|
|
{
|
|
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; }
|
|
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);
|
|
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";
|
|
}
|
|
|
|
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();
|
|
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);
|
|
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))
|
|
{
|
|
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_tipe = "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_tipe = get_file_mime_tipe(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 = "HTTP/1.1 ";
|
|
buf += boost::lexical_cast<std::string>(response.m_response_code) + " " + response.m_response_comment + "\r\n" +
|
|
"Server: Epee-based\r\n"
|
|
"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";
|
|
}
|
|
|
|
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(), "*") || 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_tipe(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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
//--------------------------------------------------------------------------------------------
|
|
//--------------------------------------------------------------------------------------------
|