@@ -380,7 +380,9 @@ set(CPPCMS_SOURCES | |||
src/xss.cpp | |||
src/copy_filter.cpp | |||
src/send_timeout.cpp | |||
src/http_content_filter.cpp | |||
src/capi/session.cpp | |||
) | |||
if(NOT DISABLE_TCPCACHE) | |||
@@ -0,0 +1,118 @@ | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// | |||
// Copyright (C) 2008-2015 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com> | |||
// | |||
// See accompanying file COPYING.TXT file for licensing details. | |||
// | |||
/////////////////////////////////////////////////////////////////////////////// | |||
#ifndef CPPCMS_HTTP_CONTENT_FILTER_H | |||
#define CPPCMS_HTTP_CONTENT_FILTER_H | |||
#include <cppcms/defs.h> | |||
#include <cppcms/cppcms_error.h> | |||
#include <booster/hold_ptr.h> | |||
#include <booster/noncopyable.h> | |||
#include <string> | |||
namespace cppcms { | |||
namespace impl { | |||
class cached_settings; | |||
} | |||
namespace http { | |||
class file; | |||
class context; | |||
/// | |||
/// Exceptions that is thrown to abort content upload progress indicating an error | |||
/// | |||
class CPPCMS_API abort_upload : public cppcms_error { | |||
public: | |||
/// | |||
/// Abort | |||
/// | |||
abort_upload(int status_code); | |||
abort_upload(int status_code,std::string const &message); | |||
virtual ~abort_upload() throw(); | |||
int code() const; | |||
std::string message() const; | |||
private: | |||
int code_; | |||
std::string message_; | |||
}; | |||
class CPPCMS_API content_limits : public booster::noncopyable { | |||
friend class request; | |||
content_limits(impl::cached_settings const &); | |||
public: | |||
content_limits(); | |||
~content_limits(); | |||
size_t content_length_limit() const; | |||
void content_length_limit(size_t size); | |||
long long multipart_form_data_limit() const; | |||
void multipart_form_data_limit(long long size); | |||
size_t file_in_memory_limit() const; | |||
void file_in_memory_limit(size_t size); | |||
std::string uploads_path() const; | |||
void uploads_path(std::string const &path); | |||
private: | |||
size_t content_length_limit_; | |||
size_t file_in_memory_limit_; | |||
long long multipart_form_data_limit_; | |||
std::string uploads_path_; | |||
struct _data; | |||
booster::hold_ptr<_data> d; | |||
}; | |||
class CPPCMS_API basic_content_filter { | |||
basic_content_filter(basic_content_filter const &); | |||
void operator=(basic_content_filter const &); | |||
public: | |||
basic_content_filter(); | |||
virtual ~basic_content_filter(); | |||
virtual void on_end_of_content(); | |||
virtual void on_error(); | |||
private: | |||
struct _data; | |||
booster::hold_ptr<_data> d; | |||
}; | |||
class CPPCMS_API raw_content_filter : basic_content_filter { | |||
public: | |||
virtual void on_data_chunk(void const *data,size_t data_size) = 0; | |||
virtual ~raw_content_filter(); | |||
private: | |||
struct _raw_data; | |||
booster::hold_ptr<_raw_data> d; | |||
}; | |||
class CPPCMS_API multipart_filter : public basic_content_filter { | |||
public: | |||
multipart_filter(); | |||
virtual ~multipart_filter(); | |||
virtual void on_new_file(http::file &input_file); | |||
virtual void on_upload_progress(http::file &input_file); | |||
virtual void on_data_ready(http::file &input_file); | |||
private: | |||
struct _mp_data; | |||
booster::hold_ptr<_mp_data> d; | |||
}; | |||
} // http | |||
} // cppcms | |||
#endif |
@@ -52,6 +52,16 @@ public: | |||
/// | |||
bool parameter_is_set(std::string const &key) const; | |||
/// | |||
/// Check if media type application/x-www-form-urlencoded content_type | |||
/// | |||
bool is_form_urlencoded() const; | |||
/// | |||
/// Check if media type is multipart/form-data content_type | |||
/// | |||
bool is_multipart_form_data() const; | |||
/// | |||
/// Parse content type \a ct and create the class | |||
/// | |||
@@ -164,8 +164,34 @@ namespace cppcms { | |||
/// this handler would be called as well, so you need to check both | |||
/// | |||
void async_on_peer_reset(booster::callback<void()> const &h); | |||
/// | |||
/// Submit the context to alternative pool - allows to transfer context from application to application, \a matched_url | |||
/// would be given to application::main for processing | |||
/// | |||
/// Note: | |||
/// | |||
/// - no output should be itransfered on this context | |||
/// - the context MUST be detached for existing application | |||
/// | |||
/// This function can be called from any thread | |||
/// | |||
void submit_to_pool(booster::shared_ptr<application_specific_pool> pool,std::string const &matched_url); | |||
/// | |||
/// Submit the context to alternative application - allows to transfer context from application to application, \a matched_url | |||
/// would be given to application::main for processing | |||
/// | |||
/// Note: | |||
/// | |||
/// - the app should be asynchronous | |||
/// - no output should be itransfered on this context | |||
/// - the context MUST be detached for existing application | |||
/// | |||
/// This function can be called from any thread | |||
/// | |||
void submit_to_asynchronous_application(booster::intrusive_ptr<application> app,std::string const &matched_url); | |||
private: | |||
void on_request_ready(bool error); | |||
void submit_to_pool_internal(booster::shared_ptr<application_specific_pool> pool,std::string const &matched,bool now); | |||
static void dispatch(booster::shared_ptr<application_specific_pool> const &pool,booster::shared_ptr<context> const &self,std::string const &url); | |||
static void dispatch(booster::intrusive_ptr<application> const &app,std::string const &url,bool syncronous); | |||
void try_restart(bool e); | |||
@@ -25,6 +25,7 @@ namespace http { | |||
class cookie; | |||
class file; | |||
class content_limits; | |||
/// | |||
/// \brief This class represents all information related to the HTTP/CGI request. | |||
@@ -290,6 +291,10 @@ namespace http { | |||
/// | |||
std::pair<void *,size_t> raw_post_data(); | |||
/// | |||
/// Get content limits for incoming data processing | |||
/// | |||
content_limits &limits(); | |||
public: | |||
/// \cond INTERNAL | |||
request(impl::cgi::connection &); | |||
@@ -121,6 +121,7 @@ namespace cgi { | |||
virtual void do_eof() = 0; | |||
virtual booster::aio::const_buffer format_output(booster::aio::const_buffer const &in,bool completed,booster::system::error_code &e) = 0; | |||
virtual bool write_to_socket(booster::aio::const_buffer const &in,booster::system::error_code &e); | |||
virtual booster::aio::io_service &get_io_service() = 0; | |||
protected: | |||
void append_pending(booster::aio::const_buffer const &new_data); | |||
@@ -135,7 +136,6 @@ namespace cgi { | |||
virtual void async_read_some(void *,size_t,io_handler const &h) = 0; | |||
virtual void on_async_read_complete() {} | |||
virtual void async_read_eof(callback const &h) = 0; | |||
virtual booster::aio::io_service &get_io_service() = 0; | |||
/****************************************************************************/ | |||
@@ -11,6 +11,7 @@ | |||
#include <cppcms/http_request.h> | |||
#include <cppcms/http_response.h> | |||
#include <cppcms/forwarder.h> | |||
#include <cppcms/http_content_filter.h> | |||
#include "http_protocol.h" | |||
#include <cppcms/service.h> | |||
#include "service_impl.h" | |||
@@ -307,9 +308,9 @@ void connection::load_content(booster::system::error_code const &e,http::context | |||
} | |||
if(content_length > 0) { | |||
if(content_type.media_type()=="multipart/form-data") { | |||
if(content_type.is_multipart_form_data()) { | |||
// 64 MB | |||
long long allowed=service().cached_settings().security.multipart_form_data_limit*1024; | |||
long long allowed=context->request().limits().multipart_form_data_limit(); | |||
if(content_length > allowed) { | |||
BOOSTER_NOTICE("cppcms") << "multipart/form-data size too big " << content_length << | |||
" REMOTE_ADDR = `" << getenv("REMOTE_ADDR") << "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'"; | |||
@@ -317,8 +318,8 @@ void connection::load_content(booster::system::error_code const &e,http::context | |||
return; | |||
} | |||
multipart_parser_.reset(new multipart_parser( | |||
service().cached_settings().security.uploads_path, | |||
service().cached_settings().security.file_in_memory_limit)); | |||
context->request().limits().uploads_path(), | |||
context->request().limits().file_in_memory_limit())); | |||
read_size_ = content_length; | |||
if(!multipart_parser_->set_content_type(content_type)) { | |||
BOOSTER_NOTICE("cppcms") << "Invalid multipart/form-data request" << content_length << | |||
@@ -335,7 +336,7 @@ void connection::load_content(booster::system::error_code const &e,http::context | |||
h)); | |||
} | |||
else { | |||
long long allowed=service().cached_settings().security.content_length_limit*1024; | |||
long long allowed=context->request().limits().content_length_limit(); | |||
if(content_length > allowed) { | |||
BOOSTER_NOTICE("cppcms") << "POST data size too big " << content_length << | |||
" REMOTE_ADDR = `" << getenv("REMOTE_ADDR") << "' REMOTE_HOST=`" << getenv("REMOTE_HOST") << "'"; | |||
@@ -362,7 +363,7 @@ void connection::on_some_multipart_read(booster::system::error_code const &e,siz | |||
char const *begin = &content_.front(); | |||
char const *end = begin + n; | |||
multipart_parser::parsing_result_type r = multipart_parser::continue_input; | |||
long long allowed=service().cached_settings().security.content_length_limit*1024; | |||
long long allowed=context->request().limits().content_length_limit(); | |||
while(begin!=end) { | |||
r = multipart_parser_->consume(begin,end); | |||
if(r==multipart_parser::content_ready || r==multipart_parser::content_partial) { | |||
@@ -0,0 +1,95 @@ | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// | |||
// Copyright (C) 2008-2015 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com> | |||
// | |||
// See accompanying file COPYING.TXT file for licensing details. | |||
// | |||
/////////////////////////////////////////////////////////////////////////////// | |||
#define CPPCMS_SOURCE | |||
#include <cppcms/http_content_filter.h> | |||
#include <cppcms/http_response.h> | |||
#include "cached_settings.h" | |||
namespace cppcms { | |||
namespace http { | |||
abort_upload::abort_upload(int status_code) : | |||
cppcms_error(http::response::status_to_string(status_code)), | |||
code_(status_code), | |||
message_(http::response::status_to_string(status_code)) | |||
{ | |||
} | |||
abort_upload::abort_upload(int status_code,std::string const &message): | |||
cppcms_error(message), | |||
code_(status_code), | |||
message_(message) | |||
{ | |||
} | |||
int abort_upload::code() const | |||
{ | |||
return code_; | |||
} | |||
abort_upload::~abort_upload() throw() | |||
{ | |||
} | |||
std::string abort_upload::message() const | |||
{ | |||
return message_; | |||
} | |||
struct content_limits::_data {}; | |||
content_limits::content_limits(impl::cached_settings const &s) : | |||
content_length_limit_(s.security.content_length_limit * 1024), | |||
file_in_memory_limit_(s.security.file_in_memory_limit), | |||
multipart_form_data_limit_(s.security.multipart_form_data_limit * 1024LL), | |||
uploads_path_(s.security.uploads_path) | |||
{ | |||
} | |||
content_limits::~content_limits() | |||
{ | |||
} | |||
content_limits::content_limits() : | |||
content_length_limit_(0), | |||
file_in_memory_limit_(0), | |||
multipart_form_data_limit_(0) | |||
{ | |||
} | |||
size_t content_limits::content_length_limit() const { return content_length_limit_; } | |||
void content_limits::content_length_limit(size_t size) { content_length_limit_=size; } | |||
long long content_limits::multipart_form_data_limit() const { return multipart_form_data_limit_; } | |||
void content_limits::multipart_form_data_limit(long long size) { multipart_form_data_limit_=size; } | |||
size_t content_limits::file_in_memory_limit() const { return file_in_memory_limit_; } | |||
void content_limits::file_in_memory_limit(size_t size) { file_in_memory_limit_=size; } | |||
std::string content_limits::uploads_path() const { return uploads_path_; } | |||
void content_limits::uploads_path(std::string const &path) { uploads_path_=path; } | |||
struct basic_content_filter::_data {}; | |||
basic_content_filter::basic_content_filter() {} | |||
basic_content_filter::~basic_content_filter() {} | |||
void basic_content_filter::on_end_of_content() {} | |||
void basic_content_filter::on_error() {} | |||
struct multipart_filter::_mp_data{}; | |||
multipart_filter::multipart_filter() {} | |||
multipart_filter::~multipart_filter() {} | |||
void multipart_filter::on_new_file(http::file &) {} | |||
void multipart_filter::on_upload_progress(http::file &) {} | |||
void multipart_filter::on_data_ready(http::file &) {} | |||
} // http | |||
}// cppcms |
@@ -51,6 +51,18 @@ namespace cppcms { namespace http { | |||
return d->media_type; | |||
return std::string(); | |||
} | |||
bool content_type::is_form_urlencoded() const | |||
{ | |||
if(d) | |||
return d->media_type == "application/x-www-form-urlencoded"; | |||
return false; | |||
} | |||
bool content_type::is_multipart_form_data() const | |||
{ | |||
if(d) | |||
return d->media_type == "multipart/form-data"; | |||
return false; | |||
} | |||
std::string content_type::charset() const | |||
{ | |||
return parameter_by_key("charset"); | |||
@@ -20,6 +20,7 @@ | |||
#include <cppcms/cppcms_error.h> | |||
#include <booster/log.h> | |||
#include <booster/backtrace.h> | |||
#include <booster/aio/io_service.h> | |||
#include "cached_settings.h" | |||
@@ -43,7 +44,6 @@ namespace http { | |||
{ | |||
} | |||
}; | |||
context::context(booster::shared_ptr<impl::cgi::connection> conn) : | |||
conn_(conn) | |||
{ | |||
@@ -100,30 +100,40 @@ namespace { | |||
}; | |||
} | |||
void context::on_request_ready(bool error) | |||
void context::submit_to_pool(booster::shared_ptr<application_specific_pool> pool,std::string const &matched) | |||
{ | |||
if(error) return; | |||
submit_to_pool_internal(pool,matched,false); | |||
} | |||
char const *host = conn_->cgetenv("HTTP_HOST"); | |||
char const *path_info = conn_->cgetenv("PATH_INFO"); | |||
char const *script_name = conn_->cgetenv("SCRIPT_NAME"); | |||
std::string matched; | |||
booster::shared_ptr<application_specific_pool> pool = | |||
service().applications_pool().get_application_specific_pool( | |||
host, | |||
script_name, | |||
path_info, | |||
matched | |||
); | |||
namespace { | |||
struct dispatch_binder { | |||
void (*dispatch)(booster::intrusive_ptr<application> const &,std::string const &,bool); | |||
booster::intrusive_ptr<application> app; | |||
std::string matched; | |||
bool flag; | |||
if(!pool) { | |||
response().io_mode(http::response::asynchronous); | |||
response().make_error_response(http::response::not_found); | |||
async_complete_response(); | |||
return; | |||
} | |||
void operator()() | |||
{ | |||
dispatch(app,matched,flag); | |||
} | |||
}; | |||
} | |||
void context::submit_to_asynchronous_application(booster::intrusive_ptr<application> app,std::string const &matched) | |||
{ | |||
app->assign_context(self()); | |||
response().io_mode(http::response::asynchronous); | |||
dispatch_binder bd = { &context::dispatch, app,matched,false }; | |||
conn_->get_io_service().post(bd); | |||
} | |||
void context::submit_to_pool_internal(booster::shared_ptr<application_specific_pool> pool,std::string const &matched,bool now) | |||
{ | |||
if((pool->flags() & app::op_mode_mask)!=app::synchronous) { | |||
// asynchronous | |||
booster::intrusive_ptr<application> app = pool->get(service()); | |||
@@ -135,10 +145,15 @@ void context::on_request_ready(bool error) | |||
async_complete_response(); | |||
return; | |||
} | |||
if(now) { | |||
app->assign_context(self()); | |||
response().io_mode(http::response::asynchronous); | |||
dispatch(app,matched,false); | |||
} | |||
else { | |||
submit_to_asynchronous_application(app,matched); | |||
} | |||
app->assign_context(self()); | |||
response().io_mode(http::response::asynchronous); | |||
dispatch(app,matched,false); | |||
return; | |||
} | |||
else { | |||
@@ -146,12 +161,39 @@ void context::on_request_ready(bool error) | |||
dt.func = &context::dispatch; | |||
dt.pool = pool; | |||
dt.ctx = self(); | |||
dt.url.swap(matched); | |||
dt.url=matched; | |||
service().thread_pool().post(dt); | |||
return; | |||
} | |||
} | |||
void context::on_request_ready(bool error) | |||
{ | |||
if(error) return; | |||
char const *host = conn_->cgetenv("HTTP_HOST"); | |||
char const *path_info = conn_->cgetenv("PATH_INFO"); | |||
char const *script_name = conn_->cgetenv("SCRIPT_NAME"); | |||
std::string matched; | |||
booster::shared_ptr<application_specific_pool> pool = | |||
service().applications_pool().get_application_specific_pool( | |||
host, | |||
script_name, | |||
path_info, | |||
matched | |||
); | |||
if(!pool) { | |||
response().io_mode(http::response::asynchronous); | |||
response().make_error_response(http::response::not_found); | |||
async_complete_response(); | |||
return; | |||
} | |||
submit_to_pool_internal(pool,matched,true); | |||
} | |||
namespace { | |||
struct run_ctx { | |||
booster::shared_ptr<context> ctx; | |||
@@ -188,6 +230,15 @@ void context::dispatch(booster::shared_ptr<application_specific_pool> const &poo | |||
void context::dispatch(booster::intrusive_ptr<application> const &app,std::string const &url,bool syncronous) | |||
{ | |||
try { | |||
if(syncronous) { | |||
app->response().io_mode(http::response::normal); | |||
if(!app->context().service().cached_settings().session.disable_automatic_load) | |||
app->context().session().load(); | |||
} | |||
else { | |||
app->response().io_mode(http::response::asynchronous); | |||
} | |||
if(syncronous && !app->context().service().cached_settings().session.disable_automatic_load) | |||
app->context().session().load(); | |||
app->main(url); | |||
@@ -10,8 +10,11 @@ | |||
#include <cppcms/http_request.h> | |||
#include <cppcms/http_cookie.h> | |||
#include <cppcms/http_file.h> | |||
#include <cppcms/http_content_filter.h> | |||
#include "http_protocol.h" | |||
#include <cppcms/util.h> | |||
#include "cached_settings.h" | |||
#include <cppcms/service.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
@@ -125,6 +128,8 @@ bool request::parse_cookies() | |||
struct request::_data { | |||
std::vector<char> post_data; | |||
content_limits limits; | |||
_data(cppcms::service &srv) : limits(srv.cached_settings()) {} | |||
}; | |||
void request::set_post_data(std::vector<char> &post_data) | |||
@@ -132,7 +137,7 @@ void request::set_post_data(std::vector<char> &post_data) | |||
d->post_data.clear(); | |||
d->post_data.swap(post_data); | |||
if(content_type_.media_type() == "application/x-www-form-urlencoded") { | |||
if(content_type_.is_form_urlencoded()) { | |||
if(!d->post_data.empty()) { | |||
char const *pdata=&d->post_data.front(); | |||
parse_form_urlencoded(pdata,pdata+d->post_data.size(),post_); | |||
@@ -209,11 +214,16 @@ std::pair<void *,size_t> request::raw_post_data() | |||
} | |||
request::request(impl::cgi::connection &conn) : | |||
d(new _data), | |||
d(new _data(conn.service())), | |||
conn_(&conn) | |||
{ | |||
} | |||
content_limits &request::limits() | |||
{ | |||
return d->limits; | |||
} | |||
request::~request() | |||
{ | |||
} | |||