/*
This file is part of libhttpserver
Copyright (C) 2011 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef WITH_PYTHON
#include
#endif
#include
#include "gettext.h"
#include "http_utils.hpp"
#include "http_resource.hpp"
#include "http_response.hpp"
#include "http_request.hpp"
#include "http_endpoint.hpp"
#include "string_utilities.hpp"
#include "webserver.hpp"
#include "modded_request.hpp"
#ifdef USE_COMET
#define _REENTRANT 1
#endif //USE_COMET
using namespace std;
namespace httpserver {
using namespace http;
int policy_callback (void *, const struct sockaddr*, socklen_t);
void error_log(void*, const char*, va_list);
void* uri_log(void*, const char*);
void access_log(webserver*, string);
size_t unescaper_func(void*, struct MHD_Connection*, char*);
size_t internal_unescaper(void*, char*);
struct compare_value
{
bool operator() (const std::pair& left, const std::pair& right) const
{
return left.second < right.second;
}
};
static void catcher (int sig)
{
}
static void ignore_sigpipe ()
{
struct sigaction oldsig;
struct sigaction sig;
sig.sa_handler = &catcher;
sigemptyset (&sig.sa_mask);
#ifdef SA_INTERRUPT
sig.sa_flags = SA_INTERRUPT; /* SunOS */
#else //SA_INTERRUPT
sig.sa_flags = SA_RESTART;
#endif //SA_INTERRUPTT
if (0 != sigaction (SIGPIPE, &sig, &oldsig))
fprintf (stderr, gettext("Failed to install SIGPIPE handler: %s\n"), strerror (errno));
}
//LOGGING DELEGATE
logging_delegate::logging_delegate() {}
logging_delegate::~logging_delegate() {}
void logging_delegate::log_access(const string& s) const {}
void logging_delegate::log_error(const string& s) const {}
//REQUEST VALIDATOR
request_validator::request_validator() {}
request_validator::~request_validator() {}
bool request_validator::validate(const string& address) const { return true; }
//UNESCAPER
unescaper::unescaper() {}
unescaper::~unescaper() {}
void unescaper::unescape(char* s) const {}
//WEBSERVER CREATOR
create_webserver& create_webserver::https_mem_key(const std::string& https_mem_key)
{
char* _https_mem_key_pt = http::load_file(https_mem_key.c_str());
_https_mem_key = _https_mem_key_pt;
free(_https_mem_key_pt);
return *this;
}
create_webserver& create_webserver::https_mem_cert(const std::string& https_mem_cert)
{
char* _https_mem_cert_pt = http::load_file(https_mem_cert.c_str());
_https_mem_cert = _https_mem_cert_pt;
free(_https_mem_cert_pt);
return *this;
}
create_webserver& create_webserver::https_mem_trust(const std::string& https_mem_trust)
{
char* _https_mem_trust_pt = http::load_file(https_mem_trust.c_str());
_https_mem_trust = _https_mem_trust_pt;
free(_https_mem_trust_pt);
return *this;
}
//WEBSERVER
webserver::webserver
(
int port,
const http_utils::start_method_T& start_method,
int max_threads,
int max_connections,
int memory_limit,
int connection_timeout,
int per_IP_connection_limit,
logging_delegate* log_delegate,
request_validator* validator,
unescaper* unescaper_pointer,
const struct sockaddr* bind_address,
int bind_socket,
int max_thread_stack_size,
bool use_ssl,
bool use_ipv6,
bool debug,
bool pedantic,
const string& https_mem_key,
const string& https_mem_cert,
const string& https_mem_trust,
const string& https_priorities,
const http_utils::cred_type_T& cred_type,
const string digest_auth_random,
int nonce_nc_size,
const http_utils::policy_T& default_policy,
bool basic_auth_enabled,
bool digest_auth_enabled,
bool regex_checking,
bool ban_system_enabled,
bool post_process_enabled,
http_resource* single_resource,
http_resource* not_found_resource,
http_resource* method_not_allowed_resource,
http_resource* method_not_acceptable_resource,
http_resource* internal_error_resource
) :
port(port),
start_method(start_method),
max_threads(max_threads),
max_connections(max_connections),
memory_limit(memory_limit),
connection_timeout(connection_timeout),
per_IP_connection_limit(per_IP_connection_limit),
log_delegate(log_delegate),
validator(validator),
unescaper_pointer(unescaper_pointer),
bind_address(bind_address),
bind_socket(bind_socket),
max_thread_stack_size(max_thread_stack_size),
use_ssl(use_ssl),
use_ipv6(use_ipv6),
debug(debug),
pedantic(pedantic),
https_mem_key(https_mem_key),
https_mem_cert(https_mem_cert),
https_mem_trust(https_mem_trust),
https_priorities(https_priorities),
cred_type(cred_type),
digest_auth_random(digest_auth_random),
nonce_nc_size(nonce_nc_size),
running(false),
default_policy(default_policy),
basic_auth_enabled(basic_auth_enabled),
digest_auth_enabled(digest_auth_enabled),
regex_checking(regex_checking),
ban_system_enabled(ban_system_enabled),
post_process_enabled(post_process_enabled),
not_found_resource(not_found_resource),
method_not_allowed_resource(method_not_allowed_resource),
method_not_acceptable_resource(method_not_acceptable_resource),
internal_error_resource(internal_error_resource)
{
init(single_resource);
}
webserver::webserver(const create_webserver& params):
port(params._port),
start_method(params._start_method),
max_threads(params._max_threads),
max_connections(params._max_connections),
memory_limit(params._memory_limit),
connection_timeout(params._connection_timeout),
per_IP_connection_limit(params._per_IP_connection_limit),
log_delegate(params._log_delegate),
validator(params._validator),
unescaper_pointer(params._unescaper_pointer),
bind_address(params._bind_address),
bind_socket(params._bind_socket),
max_thread_stack_size(params._max_thread_stack_size),
use_ssl(params._use_ssl),
use_ipv6(params._use_ipv6),
debug(params._debug),
pedantic(params._pedantic),
https_mem_key(params._https_mem_key),
https_mem_cert(params._https_mem_cert),
https_mem_trust(params._https_mem_trust),
https_priorities(params._https_priorities),
cred_type(params._cred_type),
digest_auth_random(params._digest_auth_random),
nonce_nc_size(params._nonce_nc_size),
running(false),
default_policy(params._default_policy),
basic_auth_enabled(params._basic_auth_enabled),
digest_auth_enabled(params._digest_auth_enabled),
regex_checking(params._regex_checking),
ban_system_enabled(params._ban_system_enabled),
post_process_enabled(params._post_process_enabled),
not_found_resource(params._not_found_resource),
method_not_allowed_resource(params._method_not_allowed_resource),
method_not_acceptable_resource(params._method_not_acceptable_resource),
internal_error_resource(params._internal_error_resource)
{
init(params._single_resource);
}
void webserver::init(http_resource* single_resource)
{
if(single_resource != 0x0)
{
this->single_resource = true;
register_resource("", single_resource);
}
else
this->single_resource = false;
ignore_sigpipe();
pthread_mutex_init(&mutexwait, NULL);
pthread_mutex_init(&runguard, NULL);
pthread_cond_init(&mutexcond, NULL);
#ifdef USE_COMET
pthread_rwlock_init(&comet_guard, NULL);
pthread_mutex_init(&cleanmux, NULL);
pthread_cond_init(&cleancond, NULL);
#endif //USE_COMET
}
webserver::~webserver()
{
this->stop();
pthread_mutex_destroy(&mutexwait);
pthread_cond_destroy(&mutexcond);
}
void webserver::sweet_kill()
{
this->stop();
}
void webserver::request_completed (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe)
{
modded_request* mr = (struct modded_request*) *con_cls;
if (NULL == mr)
{
return;
}
if (NULL != mr->pp)
{
MHD_destroy_post_processor (mr->pp);
}
if(mr->second)
delete mr->dhr; //TODO: verify. It could be an error
if(mr->dhrs && mr->dhrs->autodelete)
delete mr->dhrs;
delete mr->complete_uri;
free(mr);
}
void webserver::schedule_fd(int fd, fd_set* schedule_list, int* max)
{
FD_SET(fd, schedule_list);
if(fd > *max)
*max = fd;
}
void* webserver::cleaner(void* self)
{
#ifdef USE_COMET
webserver* _this = static_cast(self);
while(true)
{
pthread_mutex_lock(&_this->cleanmux);
pthread_cond_wait(&_this->cleancond, &_this->cleanmux); //there are no problems with spurious wake-ups
pthread_mutex_unlock(&_this->cleanmux);
_this->clean_connections();
}
#endif //USE_COMET
return 0x0;
}
void webserver::clean_connections()
{
#ifdef USE_COMET
pthread_rwlock_wrlock(&comet_guard);
for(std::map >::iterator it = q_waitings.begin(); it != q_waitings.end(); ++it)
{
std::set::const_iterator itt;
for(itt = (*it).second.begin(); itt != (*it).second.end(); )
{
if(fcntl(*itt, F_GETFL) != -1 || errno != EBADF)
{
++itt;
}
else
{
q_messages.erase(*itt);
q_blocks.erase(*itt);
q_signal.erase(*itt);
q_keepalives.erase(*itt);
(*it).second.erase(itt++);
}
}
}
pthread_rwlock_unlock(&comet_guard);
#endif //USE_COMET
}
void* webserver::select(void* self)
{
#ifdef USE_COMET
fd_set rs;
fd_set ws;
fd_set es;
struct timeval timeout_value;
int max = 0;
webserver* _this = static_cast(self);
while (true)
{
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
if (MHD_YES != MHD_get_fdset (_this->daemon, &rs, &ws, &es, &max))
break; /* fatal internal error */
_this->clean_connections();
//TODO: clean connection structures also when working with threads
unsigned MHD_LONG_LONG mhd_timeout;
if (MHD_get_timeout (_this->daemon, &mhd_timeout) == MHD_YES)
{
timeout_value.tv_sec = mhd_timeout / 1000;
timeout_value.tv_usec = (mhd_timeout - (timeout_value.tv_sec * 1000)) * 1000;
}
int min_wait = timeout_value.tv_sec + (timeout_value.tv_usec / 10E6);
pthread_rwlock_wrlock(&_this->comet_guard);
for(std::map::iterator it = _this->q_keepalives.begin(); it != _this->q_keepalives.end(); ++it)
{
struct timeval curtime;
gettimeofday(&curtime, NULL);
int waited_time = curtime.tv_sec - (*it).second;
if(waited_time >= _this->q_keepalives_mem[(*it).first].first)
_this->send_message_to_consumer((*it).first, _this->q_keepalives_mem[(*it).first].second);
else
{
int to_wait_time = _this->q_keepalives_mem[(*it).first].first - waited_time;
if(to_wait_time < min_wait)
min_wait = to_wait_time;
}
}
pthread_rwlock_unlock(&_this->comet_guard);
pthread_rwlock_rdlock(&_this->comet_guard);
for(std::set::const_iterator it = _this->q_signal.begin(); it != _this->q_signal.end(); ++it)
{
_this->schedule_fd(*it, &ws, &max);
}
timeout_value.tv_sec = min_wait;
timeout_value.tv_usec = 0;
pthread_rwlock_unlock(&_this->comet_guard);
::select (max + 1, &rs, &ws, &es, &timeout_value);
pthread_mutex_lock(&_this->runguard);
MHD_run (_this->daemon);
pthread_mutex_unlock(&_this->runguard);
}
#endif //USE_COMET
return 0x0;
}
bool webserver::start(bool blocking)
{
struct {
MHD_OptionItem operator ()(enum MHD_OPTION opt, intptr_t val, void *ptr = 0) {
MHD_OptionItem x = {opt, val, ptr};
return x;
}
} gen;
vector iov;
iov.push_back(gen(MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) &request_completed, NULL ));
iov.push_back(gen(MHD_OPTION_URI_LOG_CALLBACK, (intptr_t) &uri_log, this));
iov.push_back(gen(MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) &error_log, this));
iov.push_back(gen(MHD_OPTION_UNESCAPE_CALLBACK, (intptr_t) &unescaper_func, this));
iov.push_back(gen(MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout));
if(bind_address != 0x0)
iov.push_back(gen(MHD_OPTION_SOCK_ADDR, (intptr_t) bind_address));
if(bind_socket != 0)
iov.push_back(gen(MHD_OPTION_LISTEN_SOCKET, bind_socket));
#ifndef USE_COMET
if(max_threads != 0)
iov.push_back(gen(MHD_OPTION_THREAD_POOL_SIZE, max_threads));
#endif //USE_COMET
if(max_connections != 0)
iov.push_back(gen(MHD_OPTION_CONNECTION_LIMIT, max_connections));
if(memory_limit != 0)
iov.push_back(gen(MHD_OPTION_CONNECTION_MEMORY_LIMIT, memory_limit));
if(per_IP_connection_limit != 0)
iov.push_back(gen(MHD_OPTION_PER_IP_CONNECTION_LIMIT, per_IP_connection_limit));
if(max_thread_stack_size != 0)
iov.push_back(gen(MHD_OPTION_THREAD_STACK_SIZE, max_thread_stack_size));
if(nonce_nc_size != 0)
iov.push_back(gen(MHD_OPTION_NONCE_NC_SIZE, nonce_nc_size));
if(use_ssl)
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_KEY, 0, (void*)https_mem_key.c_str()));
if(use_ssl)
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_CERT, 0, (void*)https_mem_cert.c_str()));
if(https_mem_trust != "" && use_ssl)
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_TRUST, 0, (void*)https_mem_trust.c_str()));
if(https_priorities != "" && use_ssl)
iov.push_back(gen(MHD_OPTION_HTTPS_PRIORITIES, 0, (void*)https_priorities.c_str()));
if(digest_auth_random != "")
iov.push_back(gen(MHD_OPTION_DIGEST_AUTH_RANDOM, digest_auth_random.size(), (char*)digest_auth_random.c_str()));
#ifdef HAVE_GNUTLS
if(cred_type != http_utils::NONE)
iov.push_back(gen(MHD_OPTION_HTTPS_CRED_TYPE, cred_type));
#endif //HAVE_GNUTLS
iov.push_back(gen(MHD_OPTION_END, 0, NULL ));
struct MHD_OptionItem ops[iov.size()];
for(unsigned int i = 0; i < iov.size(); i++)
{
ops[i] = iov[i];
}
#ifdef USE_COMET
int start_conf;
if(start_method == http_utils::INTERNAL_SELECT)
start_conf = MHD_NO_FLAG;
else
start_conf = start_method;
#else //USE_COMET
int start_conf = start_method;
#endif //USE_COMET
if(use_ssl)
start_conf |= MHD_USE_SSL;
if(use_ipv6)
start_conf |= MHD_USE_IPv6;
if(debug)
start_conf |= MHD_USE_DEBUG;
if(pedantic)
start_conf |= MHD_USE_PEDANTIC_CHECKS;
this->daemon = MHD_start_daemon
(
start_conf, this->port, &policy_callback, this,
&answer_to_connection, this, MHD_OPTION_ARRAY, ops, MHD_OPTION_END
);
if(NULL == daemon)
{
cout << gettext("Unable to connect daemon to port: ") << this->port << endl;
return false;
}
this->running = true;
bool value_onclose = false;
#ifdef USE_COMET
if(start_method == http_utils::INTERNAL_SELECT)
{
int num_threads = 1;
if(max_threads > num_threads)
num_threads = max_threads;
for(int i = 0; i < num_threads; i++)
{
//RUN SELECT THREADS
pthread_t t;
threads.push_back(t);
pthread_create(&threads[i], NULL, &webserver::select, static_cast(this));
//TODO: do something if initialization fails
}
}
else
{
pthread_t c;
threads.push_back(c);
pthread_create(&threads[0], NULL, &webserver::cleaner, static_cast(this));
//TODO: do something if initialization fails
}
#endif //USE_COMET
if(blocking)
{
#ifdef WITH_PYTHON
if(PyEval_ThreadsInitialized())
{
Py_BEGIN_ALLOW_THREADS;
}
#endif //WITH_PYTHON
pthread_mutex_lock(&mutexwait);
while(blocking && running)
pthread_cond_wait(&mutexcond, &mutexwait);
pthread_mutex_unlock(&mutexwait);
#ifdef WITH_PYTHON
if(PyEval_ThreadsInitialized())
{
Py_END_ALLOW_THREADS;
}
#endif //WITH_PYTHON
value_onclose = true;
}
return value_onclose;
}
bool webserver::is_running()
{
return this->running;
}
bool webserver::stop()
{
pthread_mutex_lock(&mutexwait);
if(this->running)
{
MHD_stop_daemon (this->daemon);
this->running = false;
}
pthread_cond_signal(&mutexcond);
pthread_mutex_unlock(&mutexwait);
return true;
}
void webserver::register_resource(const string& resource, http_resource* http_resource, bool family)
{
this->registered_resources[details::http_endpoint(resource, family, true, regex_checking)] = http_resource;
if(method_not_acceptable_resource)
http_resource->method_not_acceptable_resource = method_not_acceptable_resource;
}
void webserver::unregister_resource(const string& resource)
{
this->registered_resources.erase(details::http_endpoint(resource));
}
void webserver::ban_ip(const string& ip)
{
ip_representation t_ip(ip);
set::iterator it = this->bans.find(t_ip);
if(it != this->bans.end() && (t_ip.weight() < (*it).weight()))
{
this->bans.erase(it);
this->bans.insert(t_ip);
}
else
this->bans.insert(t_ip);
}
void webserver::allow_ip(const string& ip)
{
ip_representation t_ip(ip);
set::iterator it = this->allowances.find(t_ip);
if(it != this->allowances.end() && (t_ip.weight() < (*it).weight()))
{
this->allowances.erase(it);
this->allowances.insert(t_ip);
}
else
this->allowances.insert(t_ip);
}
void webserver::unban_ip(const string& ip)
{
this->bans.erase(ip);
}
void webserver::disallow_ip(const string& ip)
{
this->allowances.erase(ip);
}
int webserver::build_request_header (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
http_request* dhr = static_cast(cls);
dhr->set_header(key, value);
return MHD_YES;
}
int webserver::build_request_cookie (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
http_request* dhr = static_cast(cls);
dhr->set_cookie(key, value);
return MHD_YES;
}
int webserver::build_request_footer (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
http_request* dhr = static_cast(cls);
dhr->set_footer(key, value);
return MHD_YES;
}
int webserver::build_request_args (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
{
modded_request* mr = static_cast(cls);
{
char buf[strlen(key) + strlen(value) + 3];
if(mr->dhr->querystring == "")
{
snprintf(buf, sizeof buf, "?%s=%s", key, value);
mr->dhr->querystring = buf;
}
else
{
snprintf(buf, sizeof buf, "&%s=%s", key, value);
mr->dhr->querystring += string(buf);
}
}
int size = internal_unescaper((void*) mr->ws, (char*) value);
mr->dhr->set_arg(key, string(value, size));
return MHD_YES;
}
int policy_callback (void *cls, const struct sockaddr* addr, socklen_t addrlen)
{
if((static_cast(cls))->ban_system_enabled)
{
if((((static_cast(cls))->default_policy == http_utils::ACCEPT) &&
((static_cast(cls))->bans.count(addr)) &&
(!(static_cast(cls))->allowances.count(addr))
) ||
(((static_cast(cls))->default_policy == http_utils::REJECT) &&
((!(static_cast(cls))->allowances.count(addr)) ||
((static_cast(cls))->bans.count(addr)))
))
return MHD_NO;
}
return MHD_YES;
}
void* uri_log(void* cls, const char* uri)
{
struct modded_request* mr = (struct modded_request*) calloc(1,sizeof(struct modded_request));
mr->complete_uri = new string(uri);
mr->second = false;
return ((void*)mr);
}
void error_log(void* cls, const char* fmt, va_list ap)
{
webserver* dws = static_cast(cls);
if(dws->log_delegate != 0x0)
{
dws->log_delegate->log_error(fmt);
}
}
void access_log(webserver* dws, string uri)
{
if(dws->log_delegate != 0x0)
{
dws->log_delegate->log_access(uri);
}
}
size_t unescaper_func(void * cls, struct MHD_Connection *c, char *s)
{
// THIS IS USED TO AVOID AN UNESCAPING OF URL BEFORE THE ANSWER.
// IT IS DUE TO A BOGUS ON libmicrohttpd (V0.99) THAT PRODUCING A
// STRING CONTAINING '\0' AFTER AN UNESCAPING, IS UNABLE TO PARSE
// ARGS WITH get_connection_values FUNC OR lookup FUNC.
return strlen(s);
}
size_t internal_unescaper(void* cls, char* s)
{
webserver* dws = static_cast(cls);
if(dws->unescaper_pointer != 0x0)
{
dws->unescaper_pointer->unescape(s);
return strlen(s);
}
else
{
return http_unescape(s);
}
}
int webserver::post_iterator (void *cls, enum MHD_ValueKind kind,
const char *key,
const char *filename,
const char *content_type,
const char *transfer_encoding,
const char *data, uint64_t off, size_t size
)
{
struct modded_request* mr = (struct modded_request*) cls;
mr->dhr->set_arg(key, data, size);
return MHD_YES;
}
const logging_delegate* webserver::get_logging_delegate() const
{
return this->log_delegate;
}
void webserver::set_logging_delegate(logging_delegate* log_delegate, bool delete_old)
{
if(delete_old && this->log_delegate != 0x0)
delete this->log_delegate;
this->log_delegate = log_delegate;
}
const request_validator* webserver::get_request_validator() const
{
return this->validator;
}
void webserver::set_request_validator(request_validator* validator, bool delete_old)
{
if(delete_old && this->validator != 0x0)
delete this->validator;
this->validator = validator;
}
const unescaper* webserver::get_unescaper() const
{
return this->unescaper_pointer;
}
void webserver::set_unescaper(unescaper* u, bool delete_old)
{
if(delete_old && this->unescaper_pointer != 0x0)
delete this->unescaper_pointer;
this->unescaper_pointer = unescaper_pointer;
}
void webserver::upgrade_handler (void *cls, struct MHD_Connection* connection,
void **con_cls, int upgrade_socket)
{
}
void webserver::not_found_page(http_response** dhrs, modded_request* mr)
{
if(not_found_resource != 0x0)
((not_found_resource)->*(mr->callback))(*mr->dhr, dhrs);
else
*dhrs = new http_string_response(NOT_FOUND_ERROR, http_utils::http_not_found);
}
int webserver::method_not_acceptable_page (const void *cls,
struct MHD_Connection *connection)
{
int ret;
struct MHD_Response *response;
/* unsupported HTTP method */
response = MHD_create_response_from_buffer (strlen (NOT_METHOD_ERROR),
(void *) NOT_METHOD_ERROR,
MHD_RESPMEM_PERSISTENT);
ret = MHD_queue_response (connection,
MHD_HTTP_METHOD_NOT_ACCEPTABLE,
response);
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_ENCODING,
"text/plain");
MHD_destroy_response (response);
return ret;
}
void webserver::method_not_allowed_page(http_response** dhrs, modded_request* mr)
{
if(method_not_allowed_resource != 0x0)
((method_not_allowed_resource)->*(mr->callback))(*mr->dhr, dhrs);
else
*dhrs = new http_string_response(METHOD_ERROR, http_utils::http_method_not_allowed);
}
void webserver::internal_error_page(http_response** dhrs, modded_request* mr)
{
if(internal_error_resource != 0x0)
((internal_error_resource)->*(mr->callback))(*mr->dhr, dhrs);
else
*dhrs = new http_string_response(GENERIC_ERROR, http_utils::http_internal_server_error);
}
int webserver::bodyless_requests_answer(MHD_Connection* connection,
const char* url, const char* method,
const char* version, struct modded_request* mr
)
{
string st_url;
internal_unescaper((void*) this, (char*) url);
http_utils::standardize_url(url, st_url);
http_request req;
mr->dhr = &(req);
return complete_request(connection, mr, version, st_url.c_str(), method);
}
int webserver::bodyfull_requests_answer_first_step(MHD_Connection* connection, struct modded_request* mr)
{
mr->second = true;
mr->dhr = new http_request();
const char *encoding = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, http_utils::http_header_content_type.c_str());
if(encoding != 0x0)
mr->dhr->set_header(http_utils::http_header_content_type, encoding);
if ( post_process_enabled &&
(
0x0 != encoding &&
((0 == strncasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, encoding, strlen (MHD_HTTP_POST_ENCODING_FORM_URLENCODED))))
)
)
{
mr->pp = MHD_create_post_processor (connection, 1024, &post_iterator, mr);
}
else
{
mr->pp = NULL;
}
return MHD_YES;
}
int webserver::bodyfull_requests_answer_second_step(MHD_Connection* connection,
const char* url, const char* method,
const char* version, const char* upload_data,
size_t* upload_data_size, struct modded_request* mr
)
{
string st_url;
internal_unescaper((void*) this, (char*) url);
http_utils::standardize_url(url, st_url);
if ( 0 != *upload_data_size)
{
#ifdef DEBUG
cout << "Writing content: " << upload_data << endl;
#endif //DEBUG
mr->dhr->grow_content(upload_data, *upload_data_size);
if (mr->pp != NULL)
{
MHD_post_process(mr->pp, upload_data, *upload_data_size);
}
*upload_data_size = 0;
return MHD_YES;
}
return complete_request(connection, mr, version, st_url.c_str(), method);
}
void webserver::end_request_construction(MHD_Connection* connection, struct modded_request* mr, const char* version, const char* st_url, const char* method, char* user, char* pass, char* digested_user)
{
mr->ws = this;
MHD_get_connection_values (connection, MHD_GET_ARGUMENT_KIND, &build_request_args, (void*) mr);
MHD_get_connection_values (connection, MHD_HEADER_KIND, &build_request_header, (void*) mr->dhr);
MHD_get_connection_values (connection, MHD_FOOTER_KIND, &build_request_footer, (void*) mr->dhr);
MHD_get_connection_values (connection, MHD_COOKIE_KIND, &build_request_cookie, (void*) mr->dhr);
mr->dhr->set_path(st_url);
mr->dhr->set_method(method);
if(basic_auth_enabled)
{
user = MHD_basic_auth_get_username_password(connection, &pass);
}
if(digest_auth_enabled)
digested_user = MHD_digest_auth_get_username(connection);
mr->dhr->set_version(version);
const MHD_ConnectionInfo * conninfo = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
std::string ip_str;
get_ip_str(conninfo->client_addr, ip_str);
mr->dhr->set_requestor(ip_str);
mr->dhr->set_requestor_port(get_port(conninfo->client_addr));
if(pass != 0x0)
{
mr->dhr->set_pass(pass);
mr->dhr->set_user(user);
}
if(digested_user != 0x0)
{
mr->dhr->set_digested_user(digested_user);
}
}
int webserver::finalize_answer(MHD_Connection* connection, struct modded_request* mr, const char* st_url, const char* method)
{
int to_ret = MHD_NO;
struct MHD_Response *response = 0x0;
http_response* dhrs = 0x0;
map::iterator found_endpoint;
bool found = false;
if(!single_resource)
{
details::http_endpoint endpoint(st_url, false, false, regex_checking);
found_endpoint = registered_resources.find(endpoint);
if(found_endpoint == registered_resources.end())
{
if(regex_checking)
{
map::iterator it;
int len = -1;
int tot_len = -1;
for(it=registered_resources.begin(); it!=registered_resources.end(); ++it)
{
int endpoint_pieces_len = (*it).first.get_url_pieces_num();
int endpoint_tot_len = (*it).first.get_url_complete_size();
if(tot_len == -1 || len == -1 || endpoint_pieces_len > len || (endpoint_pieces_len == len && endpoint_tot_len > tot_len))
{
if((*it).first.match(endpoint))
{
found = true;
len = endpoint_pieces_len;
tot_len = endpoint_tot_len;
found_endpoint = it;
}
}
}
if(found)
{
vector url_pars;
unsigned int pars_size = found_endpoint->first.get_url_pars(url_pars);
vector url_pieces;
endpoint.get_url_pieces(url_pieces);
vector chunkes;
found_endpoint->first.get_chunk_positions(chunkes);
for(unsigned int i = 0; i < pars_size; i++)
{
mr->dhr->set_arg(url_pars[i], url_pieces[chunkes[i]]);
}
}
}
}
else
found = true;
}
else
{
found_endpoint = registered_resources.begin();
found = true;
}
mr->dhr->set_underlying_connection(connection);
#ifdef DEBUG
if(found)
cout << "Using: " << found_endpoint->first.get_url_complete() << endl;
else
cout << "Endpoint not found!" << endl;
#endif //DEBUG
#ifdef WITH_PYTHON
PyGILState_STATE gstate;
if(PyEval_ThreadsInitialized())
{
gstate = PyGILState_Ensure();
}
#endif //WITH_PYTHON
if(found)
{
try
{
if(found_endpoint->second->is_allowed(method))
((found_endpoint->second)->*(mr->callback))(*mr->dhr, &dhrs);
else
{
method_not_allowed_page(&dhrs, mr);
}
}
catch(const std::exception& e)
{
internal_error_page(&dhrs, mr);
}
catch(...)
{
internal_error_page(&dhrs, mr);
}
}
else
{
not_found_page(&dhrs, mr);
}
#ifdef WITH_PYTHON
if(PyEval_ThreadsInitialized())
{
PyGILState_Release(gstate);
}
#endif //WITH_PYTHON
mr->dhrs = dhrs;
mr->dhrs->underlying_connection = connection;
dhrs->get_raw_response(&response, &found, this);
vector > response_headers;
dhrs->get_headers(response_headers);
vector > response_footers;
dhrs->get_footers(response_footers);
vector >::iterator it;
for (it=response_headers.begin() ; it != response_headers.end(); ++it)
MHD_add_response_header(response, (*it).first.c_str(), (*it).second.c_str());
for (it=response_footers.begin() ; it != response_footers.end(); ++it)
MHD_add_response_footer(response, (*it).first.c_str(), (*it).second.c_str());
if(dhrs->response_type == http_response::DIGEST_AUTH_FAIL)
to_ret = MHD_queue_auth_fail_response(connection, dhrs->get_realm().c_str(), dhrs->get_opaque().c_str(), response, dhrs->need_nonce_reload() ? MHD_YES : MHD_NO);
else if(dhrs->response_type == http_response::BASIC_AUTH_FAIL)
to_ret = MHD_queue_basic_auth_fail_response(connection, dhrs->get_realm().c_str(), response);
else
to_ret = MHD_queue_response(connection, dhrs->get_response_code(), response);
MHD_destroy_response (response);
return to_ret;
}
int webserver::complete_request(MHD_Connection* connection, struct modded_request* mr, const char* version, const char* st_url, const char* method)
{
char* pass = 0x0;
char* user = 0x0;
char* digested_user = 0x0;
end_request_construction(connection, mr, version, st_url, method, pass, user, digested_user);
int to_ret = finalize_answer(connection, mr, st_url, method);
if (user != 0x0)
free (user);
if (pass != 0x0)
free (pass);
if (digested_user != 0x0)
free (digested_user);
return to_ret;
}
int webserver::answer_to_connection(void* cls, MHD_Connection* connection,
const char* url, const char* method,
const char* version, const char* upload_data,
size_t* upload_data_size, void** con_cls
)
{
struct modded_request* mr = static_cast(*con_cls);
if(mr->second == false)
{
bool body = false;
access_log(static_cast(cls), *(mr->complete_uri) + " METHOD: " + method);
if( 0 == strcmp(method, http_utils::http_method_get.c_str()))
{
mr->callback = &http_resource::render_GET;
}
else if (0 == strcmp(method, http_utils::http_method_post.c_str()))
{
mr->callback = &http_resource::render_POST;
body = true;
}
else if (0 == strcmp(method, http_utils::http_method_put.c_str()))
{
mr->callback = &http_resource::render_PUT;
body = true;
}
else if (0 == strcmp(method, http_utils::http_method_delete.c_str()))
{
mr->callback = &http_resource::render_DELETE;
}
else if (0 == strcmp(method, http_utils::http_method_head.c_str()))
{
mr->callback = &http_resource::render_HEAD;
}
else if (0 == strcmp(method, http_utils::http_method_connect.c_str()))
{
mr->callback = &http_resource::render_CONNECT;
}
else if (0 == strcmp(method, http_utils::http_method_trace.c_str()))
{
mr->callback = &http_resource::render_TRACE;
}
else
{
if(static_cast(cls)->method_not_acceptable_resource)
mr->callback = &http_resource::render_not_acceptable;
else
return static_cast(cls)->method_not_acceptable_page(cls, connection);
}
if(body)
return static_cast(cls)->bodyfull_requests_answer_first_step(connection, mr);
else
return static_cast(cls)->bodyless_requests_answer(connection, url, method, version, mr);
}
else
{
return static_cast(cls)->bodyfull_requests_answer_second_step(connection, url, method, version, upload_data, upload_data_size, mr);
}
}
void webserver::send_message_to_consumer(int connection_id, const std::string& message, bool to_lock)
{
#ifdef USE_COMET
//This function need to be externally locked on write
q_messages[connection_id].push_back(message);
map::const_iterator it;
if((it = q_keepalives.find(connection_id)) != q_keepalives.end())
{
struct timeval curtime;
gettimeofday(&curtime, NULL);
q_keepalives[connection_id] = curtime.tv_sec;
}
q_signal.insert(connection_id);
if(start_method != http_utils::INTERNAL_SELECT)
{
if(to_lock)
pthread_mutex_lock(&q_blocks[connection_id].first);
pthread_cond_signal(&q_blocks[connection_id].second);
if(to_lock)
pthread_mutex_unlock(&q_blocks[connection_id].first);
}
#endif //USE_COMET
}
void webserver::send_message_to_topic(const std::string& topic, const std::string& message)
{
#ifdef USE_COMET
pthread_rwlock_wrlock(&comet_guard);
for(std::set::const_iterator it = q_waitings[topic].begin(); it != q_waitings[topic].end(); ++it)
{
q_messages[(*it)].push_back(message);
q_signal.insert((*it));
if(start_method != http_utils::INTERNAL_SELECT)
{
pthread_mutex_lock(&q_blocks[(*it)].first);
pthread_cond_signal(&q_blocks[(*it)].second);
pthread_mutex_unlock(&q_blocks[(*it)].first);
}
map::const_iterator itt;
if((itt = q_keepalives.find(*it)) != q_keepalives.end())
{
struct timeval curtime;
gettimeofday(&curtime, NULL);
q_keepalives[*it] = curtime.tv_sec;
}
}
pthread_rwlock_unlock(&comet_guard);
if(start_method != http_utils::INTERNAL_SELECT)
{
pthread_mutex_lock(&cleanmux);
pthread_cond_signal(&cleancond);
pthread_mutex_unlock(&cleanmux);
}
#endif //USE_COMET
}
void webserver::register_to_topics(const std::vector& topics, int connection_id, int keepalive_secs, string keepalive_msg)
{
#ifdef USE_COMET
pthread_rwlock_wrlock(&comet_guard);
for(std::vector::const_iterator it = topics.begin(); it != topics.end(); ++it)
q_waitings[*it].insert(connection_id);
if(keepalive_secs != -1)
{
struct timeval curtime;
gettimeofday(&curtime, NULL);
q_keepalives[connection_id] = curtime.tv_sec;
q_keepalives_mem[connection_id] = make_pair(keepalive_secs, keepalive_msg);
}
if(start_method != http_utils::INTERNAL_SELECT)
{
pthread_mutex_t m;
pthread_cond_t c;
pthread_mutex_init(&m, NULL);
pthread_cond_init(&c, NULL);
q_blocks[connection_id] = std::make_pair(m, c);
}
pthread_rwlock_unlock(&comet_guard);
#endif //USE_COMET
}
size_t webserver::read_message(int connection_id, std::string& message)
{
#ifdef USE_COMET
pthread_rwlock_wrlock(&comet_guard);
std::deque& t_deq = q_messages[connection_id];
message.assign(t_deq.front());
t_deq.pop_front();
pthread_rwlock_unlock(&comet_guard);
return message.size();
#else //USE_COMET
return 0;
#endif //USE_COMET
}
size_t webserver::get_topic_consumers(const std::string& topic, std::set& consumers)
{
#ifdef USE_COMET
pthread_rwlock_rdlock(&comet_guard);
for(std::set::const_iterator it = q_waitings[topic].begin(); it != q_waitings[topic].end(); ++it)
consumers.insert((*it));
int size = consumers.size();
pthread_rwlock_unlock(&comet_guard);
return size;
#else //USE_COMET
return 0;
#endif //USE_COMET
}
bool webserver::pop_signaled(int consumer)
{
#ifdef USE_COMET
if(start_method == http_utils::INTERNAL_SELECT)
{
pthread_rwlock_wrlock(&comet_guard);
std::set::iterator it = q_signal.find(consumer);
if(it != q_signal.end())
{
if(q_messages[consumer].empty())
{
q_signal.erase(it);
pthread_rwlock_unlock(&comet_guard);
return false;
}
pthread_rwlock_unlock(&comet_guard);
return true;
}
else
{
pthread_rwlock_unlock(&comet_guard);
return false;
}
}
else
{
pthread_rwlock_rdlock(&comet_guard);
pthread_mutex_lock(&q_blocks[consumer].first);
struct timespec t;
struct timeval curtime;
{
bool to_unlock = true;
while(q_signal.find(consumer) == q_signal.end())
{
if(to_unlock)
{
pthread_rwlock_unlock(&comet_guard);
to_unlock = false;
}
gettimeofday(&curtime, NULL);
t.tv_sec = curtime.tv_sec + q_keepalives_mem[consumer].first;
t.tv_nsec = 0;
int rslt = pthread_cond_timedwait(&q_blocks[consumer].second, &q_blocks[consumer].first, &t);
if(rslt == ETIMEDOUT)
{
pthread_rwlock_wrlock(&comet_guard);
send_message_to_consumer(consumer, q_keepalives_mem[consumer].second, false);
pthread_rwlock_unlock(&comet_guard);
}
}
if(to_unlock)
pthread_rwlock_unlock(&comet_guard);
}
if(q_messages[consumer].size() == 0)
{
pthread_rwlock_wrlock(&comet_guard);
q_signal.erase(consumer);
pthread_mutex_unlock(&q_blocks[consumer].first);
pthread_rwlock_unlock(&comet_guard);
return false;
}
pthread_rwlock_rdlock(&comet_guard);
pthread_mutex_unlock(&q_blocks[consumer].first);
pthread_rwlock_unlock(&comet_guard);
return true;
}
#else //USE_COMET
return false;
#endif //USE_COMET
}
};