Browse Source

Started content upload filter implementation

master
Artyom Beilis 8 years ago
parent
commit
f8ce4db302
11 changed files with 363 additions and 33 deletions
  1. +2
    -0
      CMakeLists.txt
  2. +118
    -0
      cppcms/http_content_filter.h
  3. +10
    -0
      cppcms/http_content_type.h
  4. +26
    -0
      cppcms/http_context.h
  5. +5
    -0
      cppcms/http_request.h
  6. +1
    -1
      private/cgi_api.h
  7. +7
    -6
      src/cgi_api.cpp
  8. +95
    -0
      src/http_content_filter.cpp
  9. +12
    -0
      src/http_content_type.cpp
  10. +75
    -24
      src/http_context.cpp
  11. +12
    -2
      src/http_request.cpp

+ 2
- 0
CMakeLists.txt View File

@@ -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)


+ 118
- 0
cppcms/http_content_filter.h View File

@@ -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

+ 10
- 0
cppcms/http_content_type.h View File

@@ -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
///


+ 26
- 0
cppcms/http_context.h View File

@@ -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);


+ 5
- 0
cppcms/http_request.h View File

@@ -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 &);


+ 1
- 1
private/cgi_api.h View File

@@ -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;

/****************************************************************************/



+ 7
- 6
src/cgi_api.cpp View File

@@ -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) {


+ 95
- 0
src/http_content_filter.cpp View File

@@ -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

+ 12
- 0
src/http_content_type.cpp View File

@@ -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");


+ 75
- 24
src/http_context.cpp View File

@@ -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);


+ 12
- 2
src/http_request.cpp View File

@@ -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()
{
}


Loading…
Cancel
Save