// 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 #include #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 > 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(result[field_etc_name], result[field_val])); else { LOG_ERROR("simple_http_connection_handler::parse_header() not matched last entry in:"<& 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 simple_http_connection_handler::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 bool simple_http_connection_handler::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 bool simple_http_connection_handler::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(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 bool simple_http_connection_handler::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(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(result[11], http_ver_major)) return false; if (!boost::conversion::try_lexical_convert(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 bool simple_http_connection_handler::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::handle_invoke_query_line(): Failed to match first line: " << m_cache); return false; } return false; } //-------------------------------------------------------------------------------------------- template std::string::size_type simple_http_connection_handler::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 bool simple_http_connection_handler::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::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::analize_cached_request_header_and_invoke_state(): Failed to get_len_from_content_lenght();, m_query_info.m_content_length="< bool simple_http_connection_handler::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::handle_retriving_query_body(): Unexpected m_body_query_type state:" << m_body_transfer_type); m_state = http_state_error; return false; } return true; } //----------------------------------------------------------------------------------- template bool simple_http_connection_handler::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 bool simple_http_connection_handler::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(result[field_etc_name], result[field_val])); else { LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler::parse_cached_header() not matched last entry in:" << m_cache_to_process); } it_current_bound = result[(int)result.size()-1]. first; } return true; } //----------------------------------------------------------------------------------- template bool simple_http_connection_handler::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(result[0]); } catch(...) { return false; } return true; } //----------------------------------------------------------------------------------- template bool simple_http_connection_handler::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 bool simple_http_connection_handler::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 std::string simple_http_connection_handler::get_response_header(const http_response_info& response) { std::string buf = "HTTP/1.1 "; buf += boost::lexical_cast(response.m_response_code) + " " + response.m_response_comment + "\r\n" + "Server: Epee-based\r\n" "Content-Length: "; buf += boost::lexical_cast(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 std::string simple_http_connection_handler::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 std::string simple_http_connection_handler::get_not_found_response_body(const std::string& URI) { std::string body = "\r\n" "\r\n" "404 Not Found\r\n" "\r\n" "

Not Found

\r\n" "

The requested URL \r\n"; body += URI; body += "was not found on this server.

\r\n" "\r\n"; return body; } //-------------------------------------------------------------------------------------------- template bool simple_http_connection_handler::slash_to_back_slash(std::string& str) { for(std::string::iterator it = str.begin(); it!=str.end(); it++) if('/' == *it) *it = '\\'; return true; } } } } //-------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------