///////////////////////////////////////////////////////////////////////////////
//
// 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"
#include "response_headers.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_),
sync_option_is_set_(false),
in_watchdog_(false),
eof_callback_(false),
watchdog_(wd),
rewrite_(rw),
ip_(ip),
port_(port)
{
timeout_ = srv.cached_settings().http.timeout;
reset_all();
}
void reset_all()
{
request_method_ = non_const_empty_string;
env_script_name_ = non_const_empty_string;
env_path_info_ = non_const_empty_string;
env_remote_addr_ = non_const_empty_string;
env_query_string_ = non_const_empty_string;
env_content_type_ = non_const_empty_string;
env_content_length_ = 0;
request_uri_ = non_const_empty_string;
headers_done_ = false;
first_header_observerd_ = false;
total_read_ = 0;
is_http_11_ = false;
client_accepts_keep_alive_ = false;
keep_alive_ = false;
chunked_te_ = false;
output_content_length_ = -1;
output_written_ = 0;
eof_callback_ = false;
input_parser_.reset();
env_.clear();
pool_.clear();
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");
connection::reset_all();
}
~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_;
}
bool input_buffer_empty()
{
return input_body_.empty() || input_body_ptr_ == input_body_.size();
}
void async_read_some_headers(handler const &h)
{
if(!input_buffer_empty()) {
auto ptr = self();
socket_.get_io_service().post([=] {
ptr->some_headers_data_read(booster::system::error_code(),h);
});
}
else {
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();
total_read_ = 0;
async_read_some_headers(h);
}
virtual void some_headers_data_read(booster::system::error_code const &er,handler const &h)
{
if(er) { h(er); return; }
if(input_buffer_empty()) {
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);
}
else {
total_read_+=input_body_.size() - input_body_ptr_;
}
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);
char const *http_protocol = query+1;
env_.add("SERVER_PROTOCOL",pool_.add(http_protocol));
is_http_11_ = strcmp(http_protocol,"HTTP/1.1") == 0;
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) {
env_.add(name,value);
if(*value!=0)
env_content_length_ = atoll(value);
else
env_content_length_ = 0;
}
else if(strcmp(name,"CONTENT_TYPE")==0) {
env_content_type_ = value;
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;
if(!keep_alive_) {
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()
{
bool ka_value = keep_alive_;
if(ka_value) {
reset_all();
update_time();
}
return ka_value;
}
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 void set_response_headers(cppcms::impl::response_headers &hdr)
{
char const *conn = cgetenv("HTTP_CONNECTION");
client_accepts_keep_alive_ = conn && cppcms::http::protocol::compare(conn,"keep-alive") == 0;
std::string const &cl = hdr.get_header("Content-Length");
if(!cl.empty()) {
output_content_length_ = atoll(cl.c_str());
}
else {
output_content_length_ = -1;
}
output_written_ = 0;
// 128 for extra headers like Connection/TransferEncoding etc
// make sure we reuse memore for K/A connection
cppcms::impl::response_headers::string_buffer_wrapper buf;
response_headers_.clear();
response_headers_.reserve(hdr.estimate_size() + 128);
buf.data().swap(response_headers_);
hdr.format_http_headers(buf,(is_http_11_ ? "1.1" : "1.0"),false);
response_headers_.swap(buf.data());
}
booster::aio::const_buffer make_chunked_wrapper(booster::aio::const_buffer const &in,bool completed)
{
if(in.bytes_count() == 0) {
if(!completed)
return in;
else
return booster::aio::buffer("0\r\n\r\n",5);
}
std::ostringstream ss;
ss << std::hex << in.bytes_count() << "\r\n";
chunked_header_ = std::move(ss.str());
char const *trailer = "\r\n";
int trailer_len = 2;
if(completed) {
trailer = "\r\n0\r\n\r\n";
trailer_len = 7;
}
return booster::aio::buffer(chunked_header_) + in + booster::aio::buffer(trailer,trailer_len);
}
virtual booster::aio::const_buffer format_output(booster::aio::const_buffer const &in,bool completed,booster::system::error_code &e)
{
if(headers_done_) {
if(chunked_te_)
return make_chunked_wrapper(in,completed);
else {
output_written_ += in.bytes_count();
if(output_content_length_ != -1 && output_written_ > output_content_length_) {
e = booster::system::error_code(errc::protocol_violation,cppcms_category);
}
return in;
}
}
if(response_headers_.empty()) {
cppcms::impl::response_headers dummy;
set_response_headers(dummy);
}
chunked_te_=false;
response_headers_ += "Server: CppCMS-Embedded/" CPPCMS_PACKAGE_VERSION "\r\n";
/// add content lengths if does not exist and it can be calculated
if(output_content_length_ == -1 && completed) {
output_content_length_ = in.bytes_count();
char buf[std::numeric_limits::digits10 + 4];
format_number(in.bytes_count(),buf,sizeof(buf));
response_headers_ += "Content-Length: ";
response_headers_ += buf;
response_headers_ += "\r\n";
}
if(client_accepts_keep_alive_ && !error_state_ && (output_content_length_ != -1 || is_http_11_)) {
response_headers_ += "Connection: keep-alive\r\n";
keep_alive_ = true;
if(output_content_length_ == -1) {
response_headers_ += "Transfer-Encoding: chunked\r\n";
chunked_te_=true;
}
}
else {
response_headers_ += "Connection: close\r\n";
keep_alive_ = false;
}
response_headers_ += "\r\n";
booster::aio::const_buffer packet = booster::aio::buffer(response_headers_);
if(chunked_te_)
packet+= make_chunked_wrapper(in,completed);
else {
output_written_ += in.bytes_count();
if(output_content_length_ != -1 && output_written_ > output_content_length_) {
e = booster::system::error_code(errc::protocol_violation,cppcms_category);
}
packet+=in;
}
headers_done_ = true;
return packet;
}
public:
virtual char const *env_request_method() { return request_method_; }
virtual char const *env_script_name() { return env_script_name_; }
virtual char const *env_path_info() { return env_path_info_; }
virtual char const *env_remote_addr() { return env_remote_addr_; }
virtual char const *env_query_string() { return env_query_string_; }
virtual char const *env_content_type() { return env_content_type_; }
virtual long long env_content_length() { return env_content_length_; }
private:
virtual 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<:string> 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_script_name_ = pool_.add(name);
env_.add("SCRIPT_NAME",env_script_name_);
path = path + name_size;
break;
}
}
env_path_info_ = pool_.add(util::urldecode(path,path+strlen(path)));
env_.add("PATH_INFO",env_path_info_);
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));
}
virtual 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;
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;
}
}
/////
///// MEMBERS
/////
booster::aio::stream_socket socket_;
std::vector input_body_;
unsigned input_body_ptr_;
::cppcms::http::impl::parser input_parser_;
std::string response_headers_;
char *request_method_;
char const *env_script_name_;
char const *env_path_info_;
char const *env_remote_addr_;
char const *env_query_string_;
char const *env_content_type_;
long long env_content_length_;
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_;
bool eof_callback_;
bool is_http_11_;
bool client_accepts_keep_alive_;
bool keep_alive_;
bool chunked_te_;
long long output_content_length_;
long long output_written_;
std::string chunked_header_;
booster::shared_ptr watchdog_;
booster::shared_ptr rewrite_;
std::string ip_;
std::string remote_ip_;
int port_;
};
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::unique_ptr http_api_factory(cppcms::service &srv,std::string ip,int port,int backlog)
{
typedef socket_acceptor acceptor_type;
std::unique_ptr acc(new acceptor_type(srv,ip,port,backlog));
acc->factory(http_creator(srv.get_io_service(),srv.settings(),ip,port));
std::unique_ptr a(std::move(acc));
return a;
}
} // cgi
} // impl
} // cppcms