/////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) // // See accompanying file COPYING.TXT file for licensing details. // /////////////////////////////////////////////////////////////////////////////// #define CPPCMS_SOURCE //#define DEBUG_HTTP_PARSER #include "cgi_api.h" #include "cgi_acceptor.h" #include #include "service_impl.h" #include "cppcms_error_category.h" #include #include "http_parser.h" #include #include #include #include #include #include #include #include #include "binder.h" // for testing only #if !defined(__linux) && !defined(CPPCMS_WIN32) # define CPPCMS_NO_SO_SNDTIMO #endif #ifdef CPPCMS_NO_SO_SNDTIMO #include #include #endif #include #include #include #include #include "cached_settings.h" #include "format_number.h" #include "string_map.h" #include "send_timeout.h" #include "rewrite.h" namespace io = booster::aio; namespace cppcms { namespace impl { namespace cgi { class http; class http_watchdog : public booster::enable_shared_from_this { public: typedef booster::shared_ptr http_ptr; typedef booster::weak_ptr weak_http_ptr; http_watchdog(booster::aio::io_service &srv) : timer_(srv) { } void check(booster::system::error_code const &e=booster::system::error_code()); void add(weak_http_ptr p) { connections_.insert(p); } void remove(weak_http_ptr p) { connections_.erase(p); } private: typedef std::set connections_type; connections_type connections_; booster::aio::deadline_timer timer_; }; class http_creator { public: http_creator() {} // for concept http_creator(booster::aio::io_service &srv,json::value const &settings,std::string const &ip="0.0.0.0",int port = 8080) : ip_(ip),port_(port),watchdog_(new http_watchdog(srv)) { if(settings.find("http.rewrite").type()==json::is_array) { rewrite_.reset(new url_rewriter(settings.find("http.rewrite").array())); } watchdog_->check(); } http *operator()(cppcms::service &srv) const; private: std::string ip_; int port_; booster::shared_ptr watchdog_; booster::shared_ptr rewrite_; }; namespace { char non_const_empty_string[1]={0}; } class http : public connection { public: http( cppcms::service &srv, std::string const &ip, int port, booster::shared_ptr wd, booster::shared_ptr rw ) : connection(srv), socket_(srv.impl().get_io_service()), input_body_ptr_(0), input_parser_(input_body_,input_body_ptr_), output_pbase_(0), output_pptr_(0), output_epptr_(0), output_parser_(output_pbase_,output_pptr_,output_epptr_), request_method_(non_const_empty_string), request_uri_(non_const_empty_string), headers_done_(false), first_header_observerd_(false), total_read_(0), time_to_die_(0), timeout_(0), sync_option_is_set_(false), in_watchdog_(false), watchdog_(wd), rewrite_(rw), eof_callback_(false) { env_.add("SERVER_SOFTWARE",CPPCMS_PACKAGE_NAME "/" CPPCMS_PACKAGE_VERSION); env_.add("SERVER_NAME",pool_.add(ip)); char *sport = pool_.alloc(10); format_number(port,sport,10); env_.add("SERVER_PORT",sport); env_.add("GATEWAY_INTERFACE","CGI/1.0"); env_.add("SERVER_PROTOCOL","HTTP/1.0"); timeout_ = srv.cached_settings().http.timeout; } ~http() { if(socket_.native()!=io::invalid_socket) { booster::system::error_code e; socket_.shutdown(io::stream_socket::shut_rdwr,e); } } void update_time() { time_to_die_ = time(0) + timeout_; } void log_timeout() { char const *uri = request_uri_; if(!uri || *uri==0) uri = "unknown"; BOOSTER_INFO("cppcms_http") << "Timeout on connection for URI: " << uri << " from " << cgetenv("REMOTE_ADDR"); } void die() { log_timeout(); close(); } void async_die() { socket_.cancel(); die(); } time_t time_to_die() { return time_to_die_; } void async_read_some_headers(handler const &h) { socket_.on_readable(mfunc_to_event_handler(&http::some_headers_data_read,self(),h)); update_time(); } virtual void async_read_headers(handler const &h) { #ifdef CPPCMS_NO_SO_SNDTIMO booster::system::error_code e; socket_.set_non_blocking_if_needed(true,e); if(e) { h(e); return; } #endif update_time(); add_to_watchdog(); async_read_some_headers(h); } void some_headers_data_read(booster::system::error_code const &er,handler const &h) { if(er) { h(er); return; } booster::system::error_code e; size_t n = socket_.bytes_readable(e); if(e) { h(e); return ; } if(n == 0) { h(booster::system::error_code(booster::aio::aio_error::eof,booster::aio::aio_error_cat)); return; } if(n > 16384) n=16384; if(input_body_.capacity() < n) { input_body_.reserve(n); } input_body_.resize(input_body_.capacity(),0); input_body_ptr_=0; n = socket_.read_some(booster::aio::buffer(input_body_),e); total_read_+=n; input_body_.resize(n); for(;;) { using ::cppcms::http::impl::parser; switch(input_parser_.step()) { case parser::more_data: if(total_read_ > 16384) { h(booster::system::error_code(errc::protocol_violation,cppcms_category)); return; } // Assuming body_ptr == body.size() async_read_some_headers(h); return; case parser::got_header: if(!first_header_observerd_) { first_header_observerd_=true; char const *header_begin = input_parser_.header_.c_str(); char const *header_end = header_begin + input_parser_.header_.size(); char const *query=header_end; char const *rmethod=std::find( header_begin, header_end, ' '); if(rmethod!=header_end) query=std::find(rmethod+1,header_end,' '); if(query!=header_end) { request_method_ = pool_.add(header_begin,rmethod); request_uri_ = pool_.add(rmethod+1,query); first_header_observerd_=true; BOOSTER_INFO("cppcms_http") << request_method_ <<" " << request_uri_; } else { h(booster::system::error_code(errc::protocol_violation,cppcms_category)); return; } } else { // Any other header char const *name = ""; char const *value = ""; if(!parse_single_header(input_parser_.header_,name,value)) { h(booster::system::error_code(errc::protocol_violation,cppcms_category)); return; } if(strcmp(name,"CONTENT_LENGTH")==0 || strcmp(name,"CONTENT_TYPE")==0) env_.add(name,value); else { char *updated_name =pool_.alloc(strlen(name) + 5 + 1); strcpy(updated_name,"HTTP_"); strcat(updated_name,name); env_.add(updated_name,value); } } break; case parser::end_of_headers: process_request(h); return; case parser::error_observerd: h(booster::system::error_code(errc::protocol_violation,cppcms_category)); return; } } } virtual void async_read_some(void *p,size_t s,io_handler const &h) { update_time(); if(input_body_ptr_==input_body_.size()) { input_body_.clear(); input_body_ptr_=0; } if(!input_body_.empty()) { if(input_body_.size() - input_body_ptr_ < s) { s=input_body_.size() - input_body_ptr_; } memcpy(p,&input_body_[input_body_ptr_],s); input_body_ptr_+=s; if(input_body_ptr_==input_body_.size()) { input_body_.clear(); input_body_ptr_=0; } socket_.get_io_service().post(h,booster::system::error_code(),s); return; } if(input_body_.capacity()!=0) { std::vector v; input_body_.swap(v); } socket_.async_read_some(io::buffer(p,s),h); } virtual void do_eof() { if(eof_callback_) socket_.cancel(); eof_callback_ = false; booster::system::error_code e; socket_.shutdown(io::stream_socket::shut_wr,e); socket_.close(e); } #ifndef CPPCMS_NO_SO_SNDTIMO size_t timed_write_some(booster::aio::const_buffer const &buf,booster::system::error_code &e) { socket_.set_non_blocking_if_needed(false,e); if(e) return 0; if(!sync_option_is_set_) { cppcms::impl::set_send_timeout(socket_,timeout_,e); if(e) return 0; sync_option_is_set_ = true; } booster::ptime start = booster::ptime::now(); size_t n = socket_.write_some(buf,e); booster::ptime end = booster::ptime::now(); // it may actually return with success but return small // a small buffer if(booster::ptime::to_number(end - start) >= timeout_ - 0.1) { e=booster::system::error_code(errc::protocol_violation,cppcms_category); die(); return n; } if(e && (io::basic_socket::would_block(e) #ifdef CPPCMS_WIN32 || e.value() == 10060 // WSAETIMEDOUT - do not want to include windows.h #endif ) ) { // handle timeout with SO_SNDTIMEO // that responds with EAGIAN or EWOULDBLOCK die(); } return n; } #else size_t timed_write_some(booster::aio::const_buffer const &buf,booster::system::error_code &e) { socket_.set_non_blocking_if_needed(true,e); booster::ptime start = booster::ptime::now(); pollfd pfd=pollfd(); pfd.fd = socket_.native(); pfd.events = POLLOUT; pfd.revents = 0; int msec = timeout_ * 1000; int r = 0; while((r=poll(&pfd,1,msec))<0 && errno==EINTR) { msec -= booster::ptime::milliseconds(booster::ptime::now() - start); if(msec <= 0) { r = 0; break; } } if(r < 0) { e=booster::system::error_code(errno,booster::system::system_category); return 0; } if(r==1 && pfd.revents & POLLOUT) return socket_.write_some(buf,e); e=booster::system::error_code(errc::protocol_violation,cppcms_category); die(); return 0; } #endif bool write_to_socket(booster::aio::const_buffer const &bufin,booster::system::error_code &e) { booster::aio::const_buffer buf = bufin; size_t total = 0; while(!buf.empty()) { size_t n = timed_write_some(buf,e); total += n; buf += n; if(e) { close(); break; } } return total == bufin.bytes_count(); } virtual booster::aio::io_service &get_io_service() { return socket_.get_io_service(); } virtual bool keep_alive() { return false; } void close() { booster::system::error_code e; socket_.shutdown(io::stream_socket::shut_rdwr,e); socket_.close(e); } struct ignore_binder { callback h; void operator()(booster::system::error_code const &,size_t) { h(); } }; virtual void async_read_eof(callback const &h) { watchdog_->add(self()); static char a; ignore_binder cb = { h }; socket_.async_read_some(io::buffer(&a,1),cb); } void on_async_write_start() { update_time(); watchdog_->add(self()); } void on_async_write_progress(bool completed) { update_time(); if(completed) watchdog_->remove(self()); } virtual booster::aio::stream_socket &socket() { return socket_; } virtual booster::aio::const_buffer format_output(booster::aio::const_buffer const &in,bool /*completed*/,booster::system::error_code &e) { if(headers_done_) return in; booster::aio::const_buffer packet; std::pair tmp = in.get(); booster::aio::const_buffer::entry const *entry = tmp.first; size_t parts = tmp.second; output_pbase_=output_pptr_=output_epptr_=0; bool r=false; while(parts > 0) { output_pbase_=static_cast(entry->ptr); output_pptr_ = output_pbase_; output_epptr_ = output_pbase_ + entry->size; r =process_output_headers(e); parts--; entry++; if(r) { if(!e) headers_done_ = true; break; } } if(e) return packet; if(!r) { tmp = in.get(); entry = tmp.first; parts = tmp.second; output_body_.reserve(output_body_.size() + in.bytes_count()); while(parts > 0) { output_body_.insert(output_body_.end(),entry->ptr,entry->ptr+entry->size); parts--; entry++; } return packet; } packet+= booster::aio::buffer(response_line_); if(!output_body_.empty()) packet+= booster::aio::buffer(output_body_); packet+= in; return packet; } private: bool process_output_headers(booster::system::error_code &e) { static char const *addon = "Server: CppCMS-Embedded/" CPPCMS_PACKAGE_VERSION "\r\n" "Connection: close\r\n"; using cppcms::http::impl::parser; for(;;) { switch(output_parser_.step()) { case parser::more_data: return false; case parser::got_header: { char const *name=""; char const *value=""; if(!parse_single_header(output_parser_.header_,name,value)) { e=booster::system::error_code(errc::protocol_violation,cppcms_category); return true; } if(strcmp(name,"STATUS")==0) { response_line_ = "HTTP/1.0 "; response_line_ +=value; response_line_ +="\r\n"; response_line_.append(addon); return true; } } break; case parser::end_of_headers: response_line_ = "HTTP/1.0 200 Ok\r\n"; response_line_.append(addon); return true; case parser::error_observerd: e=booster::system::error_code(errc::protocol_violation,cppcms_category); return true; } } } void process_request(handler const &h) { char const *rm = request_method_; char const *rm_end = request_method_ + strlen(request_method_); if(rm==rm_end || cppcms::http::protocol::tocken(rm,rm_end)!=rm_end) { error_response("HTTP/1.0 400 Bad Request\r\n\r\n",h); return; } env_.add("REQUEST_METHOD",request_method_); if(rewrite_) request_uri_ = rewrite_->rewrite(request_uri_,pool_); char const *remote_addr=0; if(service().cached_settings().http.proxy.behind==true) { std::vector const &variables = service().cached_settings().http.proxy.remote_addr_cgi_variables; for(unsigned i=0;remote_addr == 0 && i const &script_names = service().cached_settings().http.script_names; size_t path_size = strlen(path); for(unsigned i=0;i= name_size && memcmp(path,name.c_str(),name_size) == 0 && (path_size == name_size || path[name_size]=='/')) { env_.add("SCRIPT_NAME",pool_.add(name)); path = path + name_size; break; } } env_.add("PATH_INFO",pool_.add(util::urldecode(path,path+strlen(path)))); update_time(); h(booster::system::error_code()); } void on_async_read_complete() { remove_from_watchdog(); } void error_response(char const *message,handler const &h) { socket_.async_write(io::buffer(message,strlen(message)), mfunc_to_io_handler(&http::on_error_response_written,self(),h)); } void on_error_response_written(booster::system::error_code const &e,size_t,handler const &h) { if(e) { h(e); return; } close(); h(booster::system::error_code(errc::protocol_violation,cppcms_category)); } bool parse_single_header(std::string const &header,char const *&o_name,char const *&o_value) { char const *p=header.c_str(); char const *e=p + header.size(); char const *name_end = p; p=cppcms::http::protocol::skip_ws(p,e); name_end=cppcms::http::protocol::tocken(p,e); if(name_end==p) return false; size_t name_size = name_end - p; char *name = pool_.alloc(name_size + 1); *std::copy(p,name_end,name) = 0; p=name_end; p=cppcms::http::protocol::skip_ws(p,e); if(p==e || *p!=':') return false; ++p; p=cppcms::http::protocol::skip_ws(p,e); char *value = pool_.alloc(e-p+1); *std::copy(p,e,value) = 0; for(unsigned i=0;i self() { return booster::static_pointer_cast(shared_from_this()); } friend class socket_acceptor; booster::aio::stream_socket socket_; std::vector input_body_; unsigned input_body_ptr_; ::cppcms::http::impl::parser input_parser_; std::vector output_body_; char const *output_pbase_; char const *output_pptr_; char const *output_epptr_; ::cppcms::http::impl::parser output_parser_; std::string response_line_; char *request_method_; char *request_uri_; bool headers_done_; bool first_header_observerd_; unsigned total_read_; time_t time_to_die_; int timeout_; bool sync_option_is_set_; bool in_watchdog_; void add_to_watchdog() { if(!in_watchdog_) { watchdog_->add(self()); in_watchdog_ = true; } } void remove_from_watchdog() { if(in_watchdog_) { watchdog_->remove(self()); in_watchdog_ = false; } } booster::shared_ptr watchdog_; booster::shared_ptr rewrite_; bool eof_callback_; }; void http_watchdog::check(booster::system::error_code const &e) { if(e) return; std::list kill; time_t now = time(0); for(connections_type::iterator p=connections_.begin(),e=connections_.end();p!=e;) { booster::shared_ptr ptr = p->lock(); if(!ptr) { connections_type::iterator tmp = p; ++p; connections_.erase(tmp); } else { if(ptr->time_to_die() < now) { kill.push_back(ptr); connections_type::iterator tmp = p; ++p; connections_.erase(tmp); } else { ++p; } } } for(std::list::iterator p=kill.begin();p!=kill.end();++p) { (*p)->async_die(); } timer_.expires_from_now(booster::ptime(1)); timer_.async_wait(mfunc_to_event_handler(&http_watchdog::check,shared_from_this())); } http *http_creator::operator()(cppcms::service &srv) const { return new http(srv,ip_,port_,watchdog_,rewrite_); } std::auto_ptr http_api_factory(cppcms::service &srv,std::string ip,int port,int backlog) { typedef socket_acceptor acceptor_type; std::auto_ptr acc(new acceptor_type(srv,ip,port,backlog)); acc->factory(http_creator(srv.get_io_service(),srv.settings(),ip,port)); std::auto_ptr a(acc); return a; } } // cgi } // impl } // cppcms