//
/**
* @package de.atwillys.cc.swl
* @license BSD (simplified)
* @author Stefan Wilhelm (stfwi)
*
* @file http.hh
* @ccflags
* @ldflags
* @platform linux, bsd, windows
* @standard >= c++11
*
* -----------------------------------------------------------------------------
*
* Simple HTTP protocol handling class for use in modules that shall be able
* to read HTTP requests and generate HTTP responses.
*
* This is NOT a web server, more a capability base to implement request handlers
* or CLI programs that can act optionally as CGI.
*
* Annotations:
*
* - Headers are no string-vector-maps, but string-maps, means you always replace
* headers. Use comma separation (RFC2616) like "Cache-Control: no-cache, no-store"
* to concatenate header fields.
*
* - Data are read and written binary. Possible encodings in header and body are
* therefore forwarded to the API / the output stream.
*
* -----------------------------------------------------------------------------
* +++ BSD license header +++
* Copyright (c) 2010-2014, Stefan Wilhelm (stfwi, )
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* -----------------------------------------------------------------------------
*/
//
#ifndef SW_HTTP_HH
#define SW_HTTP_HH
//
#include
#include
#include
#include
#include
namespace sw { namespace detail {
//
/**
* Storage class for POST/PUT input exceeding the RAM storage limit.
* THIS IS THE TEMPLATE FOR EXTERNALLY IMPLEMENTED STORAGE CLASSES.
* The functions are similar to standard C basic I/O functions, except that the
* basic_http<> class is not interested in internal variables like file pointers,
* stream refs or handles. It's basically an interface not requiring RTTI.
*
* The main reason requiring this provider template is the platform dependent disk i/o
* handling, more specifically locking the temporary files.
*
* Note: The basic_http<> class does not catch exceptions of this provider. The methods must not
* throw. Instead return negative.
*
* Note: The template return_t must be signed.
*/
template
class http_tmpstorage_provider
{
public:
/**
* Constructors / Destructor
*/
inline http_tmpstorage_provider()
{ ; }
/**
* Will not be used by basic_http<>
*/
inline http_tmpstorage_provider(const http_tmpstorage_provider&)
{ ; }
/**
* Will not be used by basic_http<>
*/
inline http_tmpstorage_provider & operator = (http_tmpstorage_provider&)
{ return *this; }
/**
* Note: close and delete the tmp file.
*/
virtual ~http_tmpstorage_provider()
{ close(); }
/**
* Open a storage, return a negative value on error, 0 on success.
* The argument sz is informational and tells how many bytes will be required - good to
* e.g. deny (return e.g -1) or reserve space.
*
* Note: You should always lock the file.
* Note: I/O MUST NOT be non-blocking. EAGAIN/EWOULDBLOCK is not differed from other errors.
* @return return_t
*/
virtual return_t open(unsigned long) throw()
{ return -1; }
/**
* Write to the storage, return negative on error, positive or 0 for number of bytes written.
* This function must write all bytes or return an error (not non-blocking).
* @param const void* d
* @param unsigned long sz
* @return return_t
*/
virtual return_t write(const void*, unsigned long) throw()
{ return -1; }
/**
* Read from storage, return negative on error, positive or 0 for number of bytes read.
* @param void* d
* @param unsigned long sz
* @return return_t
*/
virtual return_t read(void*, unsigned long) throw()
{ return -1; }
/**
* Set the current position the byte specified with pos from the beginning of the file.
* Return negative on error, 0 on success.
* @return return_t
*/
virtual return_t seek(unsigned long) throw()
{ return -1; }
/**
* Return the size in bytes of the storage, 0 on error.
* @return unsigned long
*/
virtual unsigned long size() throw()
{ return 0; }
/**
* Has to return true if the end of storage is reached or the storage is not open / invalid state.
* @return bool
*/
virtual bool eof() const throw()
{ return true; }
/**
* Close and cleanup (e.g. delete temporary file). Must not throw
* @return void
*/
virtual void close() throw()
{ }
};
//
//
template
class http_ram_storage_provider : public http_tmpstorage_provider
{
public:
/**
* Constructor
*/
inline http_ram_storage_provider() : buf_(), pos_(0)
{ ; }
/**
* Copy constuctor
*/
inline http_ram_storage_provider(const http_ram_storage_provider & o) : buf_(o.buf_), pos_(o.pos_)
{ ; }
/**
* Copy assignment
*/
inline http_ram_storage_provider & operator = (http_ram_storage_provider & o)
{ buf_=o.buf_; pos_=o.pos_; return *this; }
/**
* Destructor
*/
virtual ~http_ram_storage_provider()
{ http_ram_storage_provider::close(); }
/**
* Open the storage
*/
return_t open(unsigned long sz) throw()
{ try { buf_.clear(); buf_.reserve(sz); return 0; } catch(...) { return -1; } }
/**
* Write to the storage
*/
return_t write(const void* data, unsigned long sz) throw()
{
if(!data) return -1;
if(sz == 0) return 0;
try { buf_.reserve(buf_.length()+sz); } catch(...) { return -1; }
const char* end = (const char*) data+sz;
const char* p = (const char*) data;
for(; p= buf_.length()) return 0;
if(pos_+sz > buf_.length()) sz = buf_.length() - pos_;
memcpy(data, buf_.data()+pos_, sz);
pos_ += sz;
return (return_t) sz;
}
/**
* Seek to a position for reading / writing
*/
return_t seek(unsigned long pos) throw()
{ pos_ = pos; if(pos_ >= buf_.length()) { pos_ =buf_.length(); return -1; } else return 0; }
/**
* Check if EOF
*/
bool eof() const throw()
{ return pos_ >= buf_.length(); }
/**
* Get the total size of the data in the storage
*/
unsigned long size() throw()
{ return buf_.length(); }
/**
* Close it
*/
void close() throw()
{ std::string s; buf_.swap(s); pos_=0; }
public:
// This method is not part of the storage_provider interface, but
// a special case used in basic_http POST parsing.
void swap_string_buffer_with(std::string & s)
{ buf_.swap(s); pos_=0; }
private:
/**
* The buffer used
*/
std::string buf_;
/**
* Current position
*/
unsigned long pos_;
};
//
/**
* Http main class template, uses detail for a connection provider class (e.g. for
* stdio/socket/unix socket) and storage provider class (normally for disk i/o).
*/
template >
class basic_http
{
public:
//
/**
* Reading method, overload e.g. for socket use. Return the number of bytes read
* or negative on error.
* @param void* data
* @param unsigned long sz
* @return long
*/
virtual long read(void* data, unsigned long sz)
{
#ifdef __MSC_VER
return _read(_fileno(stdin), data, sz);
#else
return ::read(::fileno(stdin), data, sz);
#endif
}
/**
* Writing method, overload e.g. for socket use. Return the number of bytes written
* or negative on error.
* @param const void* data
* @param unsigned long sz
* @return long
*/
virtual long write(const void* data, unsigned long sz)
{
#ifdef __MSC_VER
return _write(_fileno(stdout), data, sz);
#else
return ::write(::fileno(stdout), data, sz);
#endif
}
//
public:
//
//////////////////////////////////////////////////////////////////////////////
// Types
typedef std::string str_t;
typedef char char_t;
typedef std::map str_map_t;
typedef typename str_t::size_type sz_t;
typedef std::basic_ostream os_t;
typedef std::basic_istream is_t;
typedef std::basic_stringstream ss_t;
typedef std::vector > range_t; // range elements: from -> to
typedef Storage_Type storage_t;
struct upload_file_t;
typedef std::map upload_files_t;
/**
* HTTP request methods
*/
typedef enum {
rq_get=0, rq_head, rq_post, rq_put, rq_delete, rq_options, rq_move, rq_copy, rq_mkcol,
rq_lock, rq_unlock, rq_trace, rq_invalid
} request_method_t;
/**
* Processor state
*/
typedef enum { st_r_header=0, st_r_body, st_r_done, st_r_error, st_s_header,
st_s_body, st_done, st_error } state_t;
/**
* Logging level note: you need to set a callback in the config, too in order to log.
*/
typedef enum {
log_crit=0, log_error, log_warn, log_notice, log_debug
} log_level_t;
/**
* Content type identifier. Used to choose how parse POST.
*/
typedef enum {
cont_tp_any=0, cont_tp_text_plain, cont_tp_urlenc, cont_tp_multipart
} content_type_id_t;
/**
* Response cache control
*/
typedef enum {
cache_ctrl_none=0, // No info
cache_ctrl_public=0x1, // Cache-Control: public
cache_ctrl_private=0x2, // Cache-Control: private
cache_ctrl_no_cache=0x4, // Cache-Control: no-cache --> max_age=0
cache_ctrl_no_store=0x8 // Cache-Control: no-store --> no-cache max_age=0
} cache_ctrl_t;
/**
* Response transfer-encoding setting. Note: Chunked is done in this class, deflate and
* gzip are only set in the response header, the input data has to be zipped/deflated already.
*/
typedef enum {
tfe_omit=0, tfe_chunked, tfe_deflate, tfe_gzip
} transfer_enc_t;
//
//
/**
* Configuration
*/
struct config_t
{
/**
* Log level
*/
log_level_t log_level;
/**
* Log callback
*/
void (*log_callback)(const str_t &, int);
/**
* Maximum total size of the header. Note that the header is always copied into the
* RAM before processing it.
*/
unsigned max_request_header_size;
/**
* Maximum length of the URI including query string and hash string
*/
unsigned max_request_path_length;
/**
* The maximum string size of GET/POST variable names
*/
unsigned max_arg_key_size;
/**
* The maximum string size of GET/POST variable values
*/
uint32_t max_arg_val_size;
/**
* Defines the maximum specified request content length
*/
uint64_t max_content_length;
/**
* Defines the maximum specified request content length that the parser will implicitly
* store in RAM. If exceeded, the body content will be redirected into a temporary file.
* Note: max_content_length is still respected, so max_ram_content_length must be smaller than
* max_content_length, otherwise temporary files are never used.
*/
uint32_t max_ram_content_length;
/**
* Default max-age for Cache-Control (-1 == omit, 0 --> "no-cache" if cache_ctrl not set)
*/
int32_t max_age;
/**
* Default response cache control setting
*/
cache_ctrl_t cache_ctrl;
/**
* Host that will be shown in the header field. Note, if you like to check the host matching
* the requested host, you have to do this yourself.
*/
str_t server;
/**
* Default setting for Connection: keep-alive/close header field.
*/
bool keep_alive;
/**
* Specifies if the request and response structures shall be freed (as much as possible)to
* save memory when sending the header information to the client.
*/
bool implicit_clear;
/**
* Specifies weather error messages - if they are set before - shall be added to
* error pages sent. That applies only if no custom error pages are sent (response data
* empty).
*/
bool append_error_messages;
/**
* Initialisation
*/
inline config_t() : log_level(log_crit), log_callback(0), max_request_header_size(1u<<14),
max_request_path_length(1u<<13), max_arg_key_size(64), max_arg_val_size(1ul<<14),
max_content_length(1ul<<30), max_ram_content_length(1ul<<15), max_age(-1),
cache_ctrl(cache_ctrl_none), server(), keep_alive(0), implicit_clear(0),
append_error_messages(false)
{ ; }
/**
* Checks and sanatises config entries. Returns 0 if all was ok, >0 if entries were modified,
* and <0 on error.
* @return int
*/
int check_sanatize()
{
/// stfwi: add logging for individual errors
size_t sz;
int nerrs = 0, nchanged=0;
if(log_level < log_crit) { ++nerrs; log_level = log_crit; }
if(log_level > log_debug) { ++nerrs; log_level = log_debug; }
if(max_request_header_size < 256) { ++nerrs; max_request_header_size = 256; }
if(max_request_path_length < 32) { ++nerrs; max_request_path_length = 32; }
if(max_arg_key_size < 8) { ++nchanged; max_arg_key_size = 32; }
if(max_arg_val_size < 8) { ++nchanged; max_arg_val_size = 32; }
if(max_age <-1) { ++nerrs; max_age=-1; }
if(cache_ctrl < 0) { ++nerrs; cache_ctrl = cache_ctrl_none; }
// max_ram_content_length can be 0 or > max_content_length
// max_content_length can be 0 if e.g. only get/head shall be accepted
sz = server.length(); server = tr(server); if(sz != server.length()) nchanged++;
return nerrs > 0 ? -nerrs : nchanged;
}
};
//
//
/**
* Small buffer class allowing to quickly push data and implicitly freeing allocated memory.
*/
class buffer_t
{
private:
typedef std::deque > buf_t;
buf_t buf_;
long sz_;
long r_; // read position
char no_; // used to always provide a valid pointer, not NULL
public:
buffer_t() : buf_(), sz_(0), r_(0), no_(0)
{ ; }
virtual ~buffer_t()
{ ; }
inline bool empty() const
{ return buf_.empty(); }
inline void clear()
{ buf_.clear(); sz_=r_=0; }
inline sz_t size() const
{ return (sz_t) sz_; }
inline const char* chunk() const
{ return (!sz_) ? (&no_) : buf_.front().data()+r_; }
inline sz_t chunk_size() const
{ return (sz_<=0) ? 0 : buf_.front().size() - r_; }
void advance(sz_t n)
{
if(!sz_) return;
if((long)n >= sz_) { clear(); return; }
long bs = (long) buf_.front().size(); // already caught by sz_ type limits above
sz_ -= n;
if((r_ += n) < bs) return;
buf_.pop_front();
r_ -= bs; // 0 or positive, read position in next chunk
if(buf_.empty()) clear(); // double check
}
void chunk(const char *c, sz_t sz)
{
if(!c || !sz) return; // no error, we just don't do it
buf_.push_back(std::vector(sz));
memcpy(buf_.back().data(), c, sz);
sz_ += sz;
}
void chunk(const str_t & s)
{ chunk(s.data(), s.length()); }
void prepend(const str_t & s)
{
if(!s.length()) return;
buf_.push_front(std::vector(s.length()));
memcpy(buf_.front().data(), s.c_str(), s.length());
sz_ += s.length();
}
};
//
//
struct upload_file_t
{
inline upload_file_t() : start(-1), size(0), name(), filename(), content_type(),
content_encoding(), data()
{;}
virtual ~upload_file_t()
{;}
inline upload_file_t(const upload_file_t& f) : start(f.start), size(f.size), name(f.name),
filename(f.filename), content_type(f.content_type), content_encoding(), data(f.data)
{;}
upload_file_t& operator=(const upload_file_t& f)
{ start=f.start; size=f.size; name=f.name; filename=f.filename; content_type=f.content_type;
content_encoding=f.content_encoding; data=f.data; return *this; }
int32_t start;
unsigned long size;
str_t name, filename, content_type, content_encoding, data;
};
//
//
/**
* Request data type
*/
struct request_t
{
request_method_t method; // Enum parsed request method ("GET" = rq_get etc)
str_t service; // If given, e.g. "http", "https", "ws" ...
str_t host; // Host part, from URL or header "Host:"
str_t path; // Path of URI
str_t query_string; // URI after "?" and before "#"
str_t query_hash; // URI after "#"
int http_version; // Int of METHOD URI/PATH HTTP1.
str_t accept; // Header "Accept:"
long content_length; // Header "Content-Length:"
str_t content_type; // Header "Content-Type:"
content_type_id_t content_type_id; // Content type identifier for parsing POST
str_map_t headers; // Additional headers not listed above
str_map_t get; // GET query variables
str_map_t post; // POST variables
str_map_t cookie; // Cookie from header, to response
upload_files_t files; // Information about uploaded files
time_t if_modified_since; // For sending a 304 header (Not modified)
time_t if_unmodified_since; // For sending a 412 header (Precondition failed) (force abort request)
range_t range; // Range: bytes ...
str_t referer; // Referer/Referrer:
str_t user_agent; // User-agent:
std::string data; // POST/PUT, use str_t::data() function for binary access
bool keep_alive:1; // Connection: keep-alive (or close), what the client prefers
bool no_cache:1; // Cache-Control: no-cache set by client
/**
* Default constructor
*/
inline request_t() : method(rq_invalid), service(), host(), path(), query_string(), query_hash(),
http_version(), accept(), content_length(0), content_type(), content_type_id(cont_tp_any),
headers(), if_modified_since(0), if_unmodified_since(0), range(), data(), keep_alive(false),
no_cache(false)
{ ; }
/**
* Frees container memory where possible.
*/
void clear()
{
headers.clear(); get.clear(); post.clear(); cookie.clear(); files.clear(); sc(content_type);
sc(accept); sc(query_string); sc(path); sc(service); sc(host); sc(referer); sc(user_agent);
sc(query_hash);
}
};
//
//
/**
* Response data type
*/
struct response_t
{
uint16_t status; // Response status (default=0 ---> will send 500 )
time_t date; // Date (default -1 === auto)
time_t last_modified; // Local last modified timestamp of the resource (-1===omit)
int32_t content_length; // Content length in bytes of the resource
str_t content_type; // Mime content type
str_t etag; // Etag of the resource
str_map_t cookie; // Cookies to set keys are variable names
str_map_t headers; // Additional headers not listed above
buffer_t data; // Buffer for header and text data
int32_t max_age; // Cache-Control: max-age=
cache_ctrl_t cache_ctrl; // Cache-Control: ... settings
transfer_enc_t transf_encoding; // Compression used in Content-Encoding header (read compression_t !)
bool keep_alive:1; // Connection: keep-alive (or close)
bool accept_ranges:1; // Add "Accept-Ranges: bytes" to header if true
inline response_t() : status(0), date(0), last_modified(0), content_length(-1), content_type(),
etag(), headers(), data(), max_age(config.max_age), cache_ctrl(config.cache_ctrl),
transf_encoding(tfe_omit), keep_alive(config.keep_alive), accept_ranges(0)
{ ; }
void clear(bool with_data=false)
{ headers.clear(); sc(content_type); cookie.clear(); if(with_data) data.clear(); }
};
//
public:
//
//////////////////////////////////////////////////////////////////////////////
// Instance
/**
* Constructors
*/
basic_http() : state_(st_r_header), rlen_(0), lch_(0), n_sent_(0), rx_(), rq_(), rs_(),
storage_(), error_(0), error_text_()
{ ; }
explicit basic_http(const storage_t & st) :
state_(st_r_header), rlen_(0), lch_(0), n_sent_(0), rq_(), rs_(), rx_(), storage_(st),
error_(0), error_text_()
{ ; }
/**
* Destructor
*/
virtual ~basic_http()
{ ; }
// No copy
basic_http(const basic_http & ht) = delete;
basic_http& operator = (const basic_http & ht) = delete;
//
public:
//
/**
* Returns the process state
* @return state_t
*/
inline state_t state() const throw()
{ return state_; }
/**
* Returns the currently set error code, 0 if no error is set.
* @return int
*/
inline int error() const throw()
{ return error_; }
/**
* Returns the currently set error message or empty if not set yet.
* @return const str_t &
*/
inline const str_t & error_msg() const throw()
{ return error_text_; }
/**
* Returns the request
* @return const request_t &
*/
inline const request_t & request() const throw()
{ return rq_; }
/**
* Returns the request
* @return request_t &
*/
inline request_t & request() throw()
{ return rq_; }
/**
* Returns the response
* @return const response_t &
*/
inline const response_t & response() const throw()
{ return rs_; }
/**
* Returns the response
* @return response_t &
*/
inline response_t & response() throw()
{ return rs_; }
/**
* Returns a reference to the storage provider object
* @return storage_t &
*/
inline storage_t & storage() throw()
{ return storage_; }
/**
* Returns a reference to the storage provider object
* @return const storage_t &
*/
inline const storage_t & storage() const throw()
{ return storage_; }
/**
* Returns the list of url-decoded GET variables
* @return const str_map_t &
*/
inline const str_map_t & get() const throw()
{ return rq_.get; }
/**
* Returns the value of a GET variable or an empty string if not found
* @param const str_t & var
* @return const str_t &
*/
inline const str_t & get(const str_t & var) const throw()
{
try {
str_map_t::const_iterator it = rq_.get.find(var);
return it==rq_.get.end() ? nstr : it->second;
} catch(...) {
return nstr;
}
}
/**
* Convenience numeric return of get(const str_t & var). Returns NAN if key not found or
* conversion failed.
* @param const str_t & var
* @return double
*/
inline double get_dbl(const str_t & var) const throw()
{ const str_t & s = get(var); return s.empty() ? nan : s2d(s); }
/**
* Returns the list of url-decoded POST variables
* @return const str_map_t &
*/
inline const str_map_t & post() const throw()
{ return rq_.post; }
/**
* Returns the value of a POST variable or an empty string if not found
* @param const str_t & var
* @return const str_t &
*/
inline const str_t & post(const str_t & var) const throw()
{
try {
str_map_t::const_iterator it = rq_.post.find(var);
return it==rq_.post.end() ? nstr : it->second;
} catch(...) {
return nstr;
}
}
/**
* Convenience numeric return of post(const str_t & var). Returns NAN if key not found or
* conversion failed.
* @param const str_t & var
* @return double
*/
inline double post_dbl(const str_t & var) const throw()
{ const str_t & s = post(var); return s.empty() ? nan : s2d(s); }
/**
* Returns the list of cookies sent with the request
* @return const str_map_t &
*/
inline const str_map_t & cookie() const throw()
{ return rq_.cookie; }
/**
* Returns the value of a COOKIE variable or an empty string if not found
* @param const str_t & var
* @return const str_t &
*/
inline const str_t & cookie(const str_t & var) const throw()
{
str_map_t::const_iterator it = rq_.cookie.find(var);
return (it==rq_.cookie.end()) ? nstr : it->second;
}
/**
* Convenience numeric return of cookie(const str_t & var). Returns NAN if key not found or
* conversion failed.
* @param const str_t & var
* @return double
*/
inline double cookie_dbl(const str_t & var) const throw()
{ const str_t & s = cookie(var); return s.empty() ? nan : s2d(s); }
/**
* Set a cookie in the response header
*/
basic_http & cookie(const str_t & name, const str_t & value, time_t expires=0, const str_t & domain="",
const str_t & path="", bool secure=false, bool httponly=false)
{
if(!name.length()) return *this;
str_t ck;
ck.reserve(name.length()+value.length()+32);
ck += urlencode(name); ck += "="; ck += urlencode(value);
if(expires > 0) { ck += "; expires="; ck += timestamp2string(expires); }
if(domain.length() > 0) { ck += "; domain="; ck += domain; }
if(path.length() > 0) { ck += "; path="; ck += path; }
if(secure) { ck += "; Secure"; }
if(httponly) { ck += "; HttpOnly"; }
rs_.cookie[name] = ck;
return *this;
}
/**
* Set a cookie in the response header
*/
basic_http & cookie(const str_t & name, double d, time_t expires=0, const str_t & domain="",
const str_t & path="", bool secure=false, bool httponly=false)
{ return std::isnan(d) ? cookie(name,"") : cookie(name,d2s(d),expires,domain,path,secure,httponly); }
/**
* Returns the map of uploaded files
* @return const upload_files_t &
*/
const upload_files_t & files() const
{ return rq_.files; }
/**
* Returns the number of bytes that have to be still pushed into the response().data buffer
* to reach the amount of bytes specified in the Content-Length response header field.
* If no Content-Length is specified (-1) returns 0.
* @return sz_t
*/
inline sz_t num_send_bytes_expected_left() const throw()
{ return (rs_.content_length<0) ? 0 : (n_sent_<=0 ? rs_.content_length :
(rs_.content_length-(n_sent_+rs_.data.size()))); }
/**
* Returns the number of bytes the parser expects until the request data is completely submitted.
* @return sz_t
*/
inline sz_t num_read_bytes_expected_left() const throw()
{ return rlen_ < 0 ? 0 : (sz_t)rlen_; }
/**
* Returns if the request content body did not have to be stored with the storage provider
* object.
* @return bool
*/
inline bool ram_handled() const throw()
{ return (unsigned long)rq_.content_length <= config.max_ram_content_length; }
/**
* Returns false if the `response().last_modified` is set and matches the conditions of
* if-modified-since/if-unmodified-since OR `response().etag` is set and matches the conditions
* of If-None-Match.
*/
bool modified() const throw()
{
if(rq_.no_cache) {
return true; // Client wants a fresh version
}
if(rs_.last_modified && rq_.if_modified_since) {
return rs_.last_modified > rq_.if_modified_since;
}
if(!rs_.etag.empty()) {
try {
typename str_map_t::const_iterator it;
if((it = rq_.headers.find("if-none-match")) != rq_.headers.end()) {
return tr(it->second, "\"\t ") != rs_.etag;
}
} catch(...) {
return true;
}
}
return true;
}
//
//
/**
* Set or remove a response header, so that it will appear in the header as
* "key: value\n". Empty keys are ignored, empty values cause to delete a header.
*
* @param const str_t& key
* @param const str_t& value
* @return basic_http &
*/
inline basic_http & header(const str_t& key, const str_t& value)
{
if(key.empty()) return *this;
if(!value.empty()) { rs_.headers[lc(key)] = value; } else { rs_.headers.erase(lc(key)); }
return *this;
}
/**
* Returns a header setting or "" if not found.
* @param const str_t& key
* @return const str_t &
*/
inline const str_t & header(const str_t& key) const throw()
{
try {
str_map_t::const_iterator it = rs_.headers.find(lc(key));
return it == rs_.headers.end() ? nstr : it->second;
} catch(...) {
return nstr;
}
}
/**
* Compose the response header text
* @return str_t
*/
str_t header() const
{
const char* nl = "\r\n";
str_t hd; hd.reserve(256+128);
hd += "HTTP/1."; hd += ('0' + rq_.http_version); hd += " ";
hd += reponse_status_text(rs_.status); hd += nl;
hd += str_t("Date: ") + timestamp2string((!rs_.date) ? time(0) : rs_.date) + nl;
if(config.server.length()) hd += str_t("Server: ") + config.server + nl;
if(rs_.status < 200 || rs_.status == 304 || rs_.status == 204) {
hd += str_t("Connection: close") + nl;
} else {
if(rs_.content_type.length()) hd += str_t("Content-Type: ") + rs_.content_type + nl;
if(rs_.content_length >= 0) hd += str_t("Content-Length: ") + l2s(rs_.content_length) + nl;
if(rs_.last_modified > 0) hd += str_t("Last-Modified: ") + timestamp2string(rs_.last_modified) + nl;
if(!rs_.etag.empty()) hd += str_t("Etag: \"") + rs_.etag + '\"' + nl;
if(rs_.max_age >= 0 || rs_.cache_ctrl) {
str_t max_age = rs_.max_age >= 0 ? (str_t(", max-age=") + l2s(rs_.max_age)) : str_t();
cache_ctrl_t cc = rs_.cache_ctrl;
if(!cc) cc = (rs_.max_age==0) ? cache_ctrl_no_cache : cache_ctrl_public;
switch(cc) {
case cache_ctrl_no_store:
hd += str_t("Cache-Control: no-cache, no-store, max-age=0, must-revalidate") + nl;
if(rq_.http_version == 0) hd += str_t("pragma: no-cache") + nl;
break;
case cache_ctrl_no_cache:
hd += str_t("Cache-Control: no-cache, max-age=0") + nl;
if(rq_.http_version == 0) hd += str_t("pragma: no-cache") + nl;
break;
case cache_ctrl_private:
hd += str_t("Cache-Control: private") + max_age + nl;
break;
case cache_ctrl_public:
hd += str_t("Cache-Control: public") + max_age + nl;
break;
default:
// Default public with max-age
if(rs_.max_age > 0) hd += str_t("Cache-control: public") + max_age + nl;
}
}
if(rq_.http_version > 0 && rs_.accept_ranges) {
hd += str_t("Accept-Ranges: Bytes") + nl;
}
if(rs_.transf_encoding) {
hd += str_t("Transfer-Encoding: ");
switch(rs_.transf_encoding) {
case tfe_chunked: hd += str_t("chunked")+nl; break;
case tfe_gzip: hd += str_t("gzip")+nl; break;
case tfe_deflate: hd += str_t("deflate")+nl; break;
default: hd += nl;
}
}
if(rq_.http_version < 1 || !rs_.keep_alive) { // HTTP/1.1 is default keep-alive
hd += str_t("Connection: ") + (rs_.keep_alive ? "keep-alive" : "close") + nl;
}
for(str_map_t::const_iterator it=rs_.cookie.begin(); it != rs_.cookie.end(); ++it) {
if(it->second.length()) { hd += "Set-Cookie: "; hd += it->second + nl; }
}
for(str_map_t::const_iterator it=rs_.headers.begin(); it != rs_.headers.end(); ++it) {
if(it->first.length() > 3 && !it->second.empty()) {
str_t s(it->first);
s[0] = ::toupper(s[0]);
str_t::iterator c = s.begin()+1, e = s.end()-1;
while(c != e) if(*c == '-') *(++c) = ::toupper(*(c)); else ++c;
hd += s + ": " + it->second + nl;
}
}
}
hd += nl;
return hd;
}
//
//
/**
* Process by given file descriptor of an opened file/socket/pipe. If that is blocking or not etc
* needs to be set before calling this function. Returns ...
*
* 0 when all data are processed (EOF),
* 1 if more data are waited for (read said EAGAIN/EWOULDBLOCK), so you have to ::select()
* -1 on a connection or fatal read error - means you have to close the connection.
* -> Other negative values are inverted HTTP error status codes.
*
* @return int
*/
int recv()
{
if(error_ != 0) return -error_; // Don't continue doing anything if an error has occurred.
char s[128];
ssize_t n=0;
for(n = this->read(s, sizeof(s)); n > 0; n = this->read(s, sizeof(s))) {
int r = recv(s,n);
if(r <= 0) return r;
}
if(n > 0 || n == EWOULDBLOCK) {
return 1;
} else if(n < 0) {
str_t s = str_t("recv(): read() failed, error code is ") + l2s(n);
return -err(500, s.c_str()); // From EINTR over EBADF,EINVAL,..., they seem to be all fatal.
} else if(state_ < st_r_body) {
return -err(400, "recv(): read eof before header parsed");
} else {
return 0;
}
}
/**
* Process binary/text input data read from the socket or CGI pipe. Returns 1 if it needs more
* data, 0 if the input is complete, or negative on error. The input length sz==0 will be
* interpreted as end of input. Negative numbers are error codes corresponding to (negated)
* HTTP errors.
* @param const char* rq
* @param sz_t sz
* @return int
*/
int recv(const char* rq, sz_t sz)
{
if(error_ != 0) return -error_; // Don't continue doing anything if an error has occurred.
sz_t i = 0;
switch(state_) {
case st_r_header:
if(!rq || !sz) return -err(500);
rx_.reserve(rx_.length()+sz);
while(i < sz) {
char c = rq[i++];
rx_ += c;
if((!(i & 0xff)) && rx_.length() > config.max_request_header_size) {
return -err(413); // Entity Too Large, we check all 256 adding steps
} else if(c != '\n' && c != '\r') {
lch_ = 0;
} else {
// Check double newline --> eoh
const uint32_t cr='\r', lf='\n', crlf = (cr<<8)|lf;
const uint32_t crcr=(cr<<8)|cr;
const uint32_t lflf=(lf<<8)|lf;
const uint32_t clcl=((crlf<<16)|crlf);
lch_ = ((lch_<<8)|(c&0xff)) & 0xffffffff;
if((lch_ == crcr)
|| (lch_ == lflf)
|| (lch_ == clcl)
) {
// (PHASE 3) Header transferred
switch(rq_.method) {
case rq_get:
case rq_head:
case rq_delete:
case rq_trace:
state_ = st_r_done;
rlen_ = 0;
sc(rx_);
return 0;
case rq_post: // Body required
case rq_put:
if(rq_.content_length < 0) {
return -err(411); // Length required
}
case rq_mkcol: // Body optional
case rq_unlock:
case rq_lock:
case rq_copy:
case rq_move:
case rq_options:
if(rq_.content_length <= 0) {
// Post, Put, ...: no content body.
state_ = st_r_done; sc(rx_);
return 0;
}
if(rq_.content_type.length() == 0) rq_.content_type = "text/plain; charset=us-ascii"; // RFC822
rlen_ = rq_.content_length;
if(rlen_ < 0) { sz -= (-rlen_); rlen_ = 0; } // Not more input bytes than needed
if((unsigned long)rq_.content_length <= config.max_ram_content_length) {
// Input is copied into the RAM until the request is completely received, then
// parsed if applicable or left in the binary buffer.
rq_.data.reserve(rq_.content_length);
for(; i0; ++i,--rlen_) rq_.data += rq[i]; // Copy rest of the buffer
} else {
// Input is redirected into a storage defined externally (normally a file).
rq_.data.reserve(sz-i+2);
for(; i0; ++i,--rlen_) rq_.data += rq[i];
if(storage_.open(rq_.content_length) < 0) { // rq_.content_length>0 here
sc(rq_.data);
return -err(500, "Failed to open storage");
} else if(storage_.write(rq_.data.data(), rq_.data.length()) < 0) {
sc(rq_.data);
return -err(500, "Failed to write storage");
}
sc(rq_.data);
}
state_ = st_r_body;
break;
default:
log(str_t("Invalid request method enum ")+l2s(rq_.method));
}
} else if(rq_.method == rq_invalid) {
// (PHASE 1) That would be a head line ...
rx_ = rtr(rx_, "\r\n\t ");
std::string s; s.reserve(32);
std::string::size_type k,l, len=rx_.length();
for(k=0; k config.max_request_path_length) return -err(414); // URI Too Long
if((l=rq_.path.find_first_of('#')) != str_t::npos) { // Also RFC3986 delim
rq_.query_hash = (l rq_.path.length()-4) return -err(400, "URI: Invalid use of '://)'");
rq_.service = lc(rq_.path.substr(0, l));
if(rq_.service == "http") sc(rq_.service); // That is the default anyway.
rq_.path = rq_.path.substr(l+3);
if((l=rq_.path.find('/')) == str_t::npos || l == 0 || l >= rq_.path.length()-1) {
return -err(400, "URI: Missing path or host after service");
}
rq_.host = rq_.path.substr(0, l);
rq_.path = rq_.path.substr(l); // Path not empty because "rq_.path.length()-4"
}
if(k == len || rq_.path.length() == 0) return -err(400, "Path length zero");
rq_.path = urldecode(rq_.path);
for(; k 9) {
return -err(400, "http version invalid"); // in case someone sends HTTP/1.Z or the like
}
rq_.keep_alive = rq_.http_version > 0; // HTTP/1.1 default preferred is keep-alive
rx_.clear();
} else {
// (PHASE 2) Header field line
rx_ = tr(rx_, "\r\n\t ");
std::string::size_type k=0, len=rx_.length();
str_t key; key.reserve(32);
str_t val; val.reserve(64);
for(; k (long)config.max_content_length) return -err(413); // Entity too long
} else if(key == "content-type") {
val = lc(val);
rq_.content_type = val;
if(rq_.method == rq_post) {
if(val.find("text") == 0) {
rq_.content_type_id = cont_tp_text_plain;
} else if(val.find("application/x-www-form-urlencoded") != str_t::npos) {
rq_.content_type_id = cont_tp_urlenc;
} else if(val.find("multipart/form-data") != str_t::npos) {
rq_.content_type_id = cont_tp_multipart;
}
}
} else if(key == "cookie") {
k = 0;
len = val.length();
str_t ck, cv; ck.reserve(len/2); cv.reserve(len/2);
while(k=len) break;
if((ck = lc(tr(urldecode(ck)))).length() > 0) {
if(val[k++] != ';') {
if(val[k] == '"') {
// version 1 cookie, quoted string
for(++k; k q;
do {
str_t s; s.reserve(val.length());
bool hasdash = false;
for(str_t::const_iterator it=val.begin()+6; it!=val.end(); ++it) {
if(::isspace(*it)) continue;
if(*it == ',') {
if(!hasdash || s.length()<2) { bad=true; break; }
q.push_back(s);
s.clear();
hasdash = false;
} else if(*it == '-') {
if(hasdash) { bad=true; break; }
hasdash = true;
s += *it;
} else if(!::isdigit(*it)) {
bad = true; break;
} else {
s += (*it == ',') ? ' ' : *it;
}
}
if(!s.empty()) q.push_back(s);
} while(0);
rq_.range.reserve(q.size());
while(!q.empty()) {
str_t s; s.swap(q.front()); q.pop_front();
if(s[0] == '-') {
std::basic_stringstream ss(s.substr(1));
int64_t l; ss >> l; // That should be ok, pre-checked
rq_.range.push_back(typename range_t::value_type(-1, l));
} else if(s[s.length()-1] == '-') {
s.resize(s.length()-1);
std::basic_stringstream ss(s);
int64_t l; ss >> l;
rq_.range.push_back(typename range_t::value_type(l, -1));
} else {
sz_t i = s.find('-');
int64_t l0, l1;
{ std::basic_stringstream ss(s.substr(0,i)); ss >> l0; }
{ std::basic_stringstream ss(s.substr(i+1)); ss >> l1; }
if(l0 > l1) { bad=true; break; }
rq_.range.push_back(typename range_t::value_type(l0, l1));
}
}
if(bad) return -err(400, "range header field invalid");
}
} else {
rq_.headers[key] = val;
}
break;
case 'u':
if(key == "user-agent") {
rq_.user_agent = lc(val);
} else {
rq_.headers[key] = val;
}
break;
default:
rq_.headers[key] = val;
}
}
rx_.clear();
}
}
}
if(state_ > st_r_body) return 0; // all current sz processed, and we don't need more
if(state_ < st_r_body) return 1;
case st_r_body:
if(this->ram_handled()) {
for(; i0; ++i,--rlen_) rq_.data += rq[i]; // Copy rest of the buffer
if(sz == 0 && rlen_ > 0) return -err(400, "Client did not send all announced body bytes.");
if(rlen_ > 0) return 1;
state_ = st_r_done;
// Parse input in case of known text data
if(rq_.method == rq_post) {
switch(rq_.content_type_id) {
case cont_tp_text_plain:
rq_.post["text"] = rq_.data;
sc(rq_.data); // free memory of request data
break;
case cont_tp_urlenc:
do { // Block for local int l.
int l = parse_url_encoded(rq_.data, rq_.post, true, config.max_arg_key_size,
config.max_arg_val_size); // Parse all in one go
if(l < 0) return -err(-l, "Parsing POST urlencoded failed");
sc(rq_.data);
} while(0);
break;
case cont_tp_multipart:
do {
http_ram_storage_provider<> ram;
ram.swap_string_buffer_with(rq_.data);
int l = parse_body_post_multipart_formdata(ram);
ram.close();
if(l < 0) return l; // error already set
} while(0);
break;
default:
break; // no parsing
}
}
} else {
if((long)sz > rlen_) sz = rlen_;
if(storage_.write(rq, sz) < 0) return -err(500, "Failed to write storage");
if(sz == 0 && rlen_ > 0) return -err(400, "Client did not send all announced body bytes.");
if((rlen_ -= sz) > 0) return 1;
// All data received, parse what can be parsed. That is only
// POST multipart. Other cases are either short enough to be RAM handled (above),
// or the method is not POST, or the content length is too large to parse url-encoded
// data to RAM (menas the rq_.post map).
if(rq_.method == rq_post && rq_.content_type_id == cont_tp_multipart) {
do {
int l = parse_body_post_multipart_formdata(storage_);
if(l < 0) return l; // error already set
} while(0);
break;
}
}
// return 0 branch
state_ = st_r_done;
case st_r_done:
return 0;
case st_r_error:
case st_error:
return -error_;
default:
return -err(500, "recv: invalid state");
}
return -err(500, "recv: state block did not return");
}
/**
* Parses form url encoded data and adds the results to the POST map of this object.
* @param const str_t &
* @return int
*/
int parse_body_post_form_url_encoded(const str_t & s)
{
int r = parse_url_encoded(s, rq_.post, true, config.max_arg_key_size, config.max_arg_val_size);
if(r < 0) return -err(-r, "Failed to parse form-url-encoded data"); else return r; // num added
}
/**
* Parses POST multipart formdata, adds
* @param const str_t &
* @return int
*/
int parse_body_post_multipart_formdata(http_tmpstorage_provider<> & st)
{
std::deque files; // List if found boundaries
bool with_cr = false; // correction indicator for sizes
// (1) Find boundaries
do {
str_t boundary = rq_.content_type;
do {
str_t::iterator it = std::remove_if(boundary.begin(), boundary.end(), ::isspace);
boundary.erase(it, boundary.end());
} while(0);
sz_t boundlen = boundary.find("boundary="); // e.g. boundary=aBcDe
if(boundlen == str_t::npos || boundlen+10 >= boundary.length()) {
return -err(400, "Form multipart data: missing 'boundary='");
}
boundary = str_t("--") + boundary.substr(boundlen+9); // e.g. --> --aBcDe
do {
rq_.content_type.resize(boundlen);
boundlen = rq_.content_type.find(';');
if(boundlen != str_t::npos && boundlen > 0) rq_.content_type.resize(boundlen);
rq_.content_type = tr(rq_.content_type); // you never know
} while(0);
boundlen = boundary.length();
if(boundlen > 255) {
return -err(400, "Form multipart data: boundary string too long");
}
st.seek(0);
if(!st.size() || st.eof()) {
return -err(500, "Form multipart data parser: no content in tmp storage");
} else if(st.size() < ((2*boundlen)+3)) { // start '--aBcDe', end '\n--aBcDe--'
return -err(400, "Form multipart data parser: content too short to hold boundaries");
}
// Maybe not the most efficient way, but reading goes byte-per-byte
char c; // read character
long r; // read result
str_t buf; // compare buffer for matching boundaries
unsigned long end=(st.size()-boundlen-2); // not including '\n--aBcDe--'
unsigned long p; // current position
buf.reserve(boundlen+3);
// Double boundary at check end of stream
st.seek(end); // position after last \n --> must be the boundary string + '--'
while((r=st.read(&c, sizeof(char))) > 0) {
if(!::isspace(c)) buf += c;
}
if(buf.find(boundary) != 0) return -err(400, "multipart parser: missing closing boundary");
// Check start of stream
st.seek(0);
buf.clear();
for(p=0; (r=st.read(&c, sizeof(char)))>0 && p0 && ++p::iterator it= files.begin(); it != files.end(); ++it) {
header.clear();
line.clear();
st.seek(it->start);
char c;
bool was_nl = false;
unsigned long i;
for(i=0; i < it->size; ++i) {
st.read(&c, sizeof(char));
if(c=='\r') continue;
if(c=='\n' && was_nl) break;
was_nl = c=='\n';
if(was_nl || !::isspace(c)) header += c;
}
it->start += i+1;
if(i+1+(with_cr ? 1:0) >= it->size) it->size = 0; else it->size -= i+1+(with_cr ? 1:0);
sz_t p = header.find_last_not_of('\n');
if(p != str_t::npos) header.resize(p+1);
while(!header.empty()) {
if((p = header.rfind('\n')) == str_t::npos) {
line = header;
header.clear();
} else {
line = header.substr(p+1); // \n is trimmed already, so can't be at the end
header.resize(p);
}
str_t s = lc(line);
if(s.find("content-disposition:") == 0) {
// Before modifying s check for filename=, so that it is case correct.
if((p = s.find(";filename=")) != str_t::npos) { // quote checked separately
p += 10;
if(p >= s.length()-2 || s[p] != '"') { // two quotes, at least one char for the name
return -err(400, "Multipart header: file name not quoted.");
}
sz_t p1 = s.find('"', p+1);
if(p1 == str_t::npos || p1 < p+3 ) {
return -err(400, "Multipart header: file name not quoted.");
}
it->filename = line.substr(p+1, p1-p-1);
}
// The rest is lowercase, name inclusively
line = s.substr(20);
if(line.find("form-data;") != 0 ) {
return -err(400, "Multipart: Disposition not form-data.");
}
line = line.substr(10);
// Normally there are no leading / tailing ";" but you never know:
while(!line.empty() && line[line.length()-1] == ';') line.resize(line.length()-1);
while(!line.empty() && line[0] == ';') line = line.substr(1);
// Line chunks can be in random order
while(!line.empty()) {
if((p = line.rfind(';')) != str_t::npos) {
s = line.substr(p+1); line.resize(p);
} else {
s = line; line.clear();
}
if(s.find("name=\"") == 0) {
if(s.length() < 8) return -err(400, "Multipart header: Bad entity name");
s.resize(s.length()-1);
s = s.substr(6);
for(sz_t i=0; iname = s;
}
// Others may follow
}
} else if(s.find("content-type:") == 0) {
it->content_type = s.substr(13);
} else if(s.find("content-transfer-encoding:") == 0) {
it->content_encoding = s.substr(26);
} else {
log(str_t("Multipart header line ignored: ")+line, log_debug);
}
}
if(it->name.empty()) return -err(400, "Multipart header: No entity name for boundary");
}
} while(0);
// (3) Transfer data to rq_.post and rq_.files
for(typename std::deque::iterator it= files.begin(); it != files.end(); ++it) {
if(it->filename.empty()) {
if(it->size > config.max_arg_val_size && !ram_handled()) {
return -err(400, "Multipart header: Missing file name for large data");
} else if(!it->content_type.empty() && (it->content_type.find("text")!=0)) {
return -err(400, "Multipart header: Missing file name for non-text data");
}
// Stored in POST
if(it->size == 0) {
rq_.post[it->name] = str_t();
} else {
str_t s;
s.reserve(it->size);
st.seek(it->start);
char c;
for(unsigned long i=0; isize; ++i) {
st.read(&c, 1); // maybe check that, too
s += c;
}
rq_.post[it->name] = s;
}
} else if(it->size < config.max_arg_val_size || ram_handled()) {
// File data is saved in file.data
if(it->size > 0) {
str_t s;
s.reserve(it->size);
st.seek(it->start);
char c;
for(unsigned long i=0; isize; ++i) {
st.read(&c, sizeof(char)); s += c;
}
it->data.swap(s);
}
it->start = -1; // indicator that the data is in ram, not as storage position
rq_.files[it->name] = *it;
} else {
// Data can be found in the storage, file.start and file.size say where to find it.
rq_.files[it->name] = *it;
}
}
return 0;
}
//
//
/**
* Sends the header data and data set in the `response().data`. If a content_length is specified,
* it will be used, weather it exceeds the data length or not.
*
* - The content length will be modified if it is not -1 and it is smaller than the
* `response().data` length.
*
* - If the request method is HEAD, all data will be omitted, but the content-length added to
* the header (if not -1). As well for 204 and 304, where no body is allowed.
*
* - If the status represents a status code >= 300 and no data nor content-length is given, a
* short message is generated if the accepted type is *%*, text/plain or text/html. Otherwise
* the message is sent without content.
*
* - Transforms invalid status codes (such as 0) into a 500 status.
*
* This function is generally used to send the header before sending other data, such as file
* contents or any other information.
*
* @return int
*/
int send()
{
if(state_ >= st_done) {
return 0;
} else if(state_ <= st_s_header) {
if(rs_.status < 100 || rs_.status >= 600) {
str_t s = str_t("Invalid response status code ") + l2s(rs_.status);
err(500, s.c_str());
}
if(rq_.http_version < 1 && rs_.status < 200) {
log(str_t("HTTP/1.0 does not know 1xx status"), log_warn);
}
if(rq_.method == rq_head) {
// Do nothing, header will be added below
} else if(rs_.status < 200 || rs_.status == 304 || rs_.status == 204) {
// Codes where no body is allowed
rs_.content_length = 0;
rs_.content_type = "";
rs_.keep_alive = false;
rs_.transf_encoding = tfe_omit;
} else if(rs_.status >= 300) {
// Codes where content is allowed and may have to be automatically generated.
// The content is not variable. Redirections and errors should close the connection.
rs_.keep_alive = false;
if(rs_.data.size() == 0) {
// Messages are only generated if no response data is set and no
// content length defined.
rs_.transf_encoding = tfe_omit;
if(rq_.accept.empty() || rq_.accept.find("html") != str_t::npos
|| rq_.accept.find("*/*") != str_t::npos) {
rs_.content_type = "text/html";
rs_.data.chunk(str_t("") + reponse_status_text(rs_.status) + "
\n");
if(config.append_error_messages && !error_text_.empty()) {
rs_.data.chunk(str_t("
") + htmlspecialchars(error_text_) + "\n");
}
rs_.content_length = rs_.data.size();
} else if(rq_.accept.find("text/plain") != str_t::npos) {
rs_.content_type = "text/plain";
rs_.data.chunk(str_t(reponse_status_text(rs_.status)) + "\n");
if(config.append_error_messages && !error_text_.empty()) {
rs_.data.chunk(str_t("\n") + error_text_ + "\n");
}
rs_.content_length = rs_.data.size();
}
} else {
// Data set, we leave the content-length as it is because we might want to send more than
// only these data. -1 is ignored anyway in header(). Only exception: incorrect length:
rs_.content_length = rs_.data.size();
}
} else {
// 2xx codes, normally with content. Content length==-1 means we don't know the content length,
// hence omitted.
if(rs_.content_length >= 0 && (sz_t)rs_.content_length < rs_.data.size()) {
rs_.content_length = rs_.data.size();
}
}
rs_.data.prepend(header());
// 1st chunk size == header size. n_sent_ passes 0 at the first body byte.
n_sent_ = -((long)rs_.data.chunk_size());
if(config.implicit_clear) { rq_.clear(); rs_.clear(false); }
state_ = st_s_body;
}
// Send data contents. Return 1 until rs_.data is empty, then 0 (-1 = error).
if(rs_.data.empty()) {
if(n_sent_ >= rs_.content_length) {
if(rs_.transf_encoding == tfe_chunked) this->write("0\r\n\r\n", 5); // Chunked: finish
state_ = st_done;
}
return 0; // This function has no more data to send
}
ssize_t r = 1; // send return variable
while(r > 0 && !rs_.data.empty()) {
if(rs_.transf_encoding != tfe_chunked || n_sent_ < 0) { // not chunked or still sending header
r = this->write(rs_.data.chunk(), rs_.data.chunk_size());
} else {
str_t s = ul2hex(rs_.data.chunk_size()) + str_t("\r\n");
ssize_t r2;
r = this->write(s.data(), s.length());
if(r > 0) r = this->write(rs_.data.chunk(), rs_.data.chunk_size());
if(r > 0 && (r2 = this->write("\r\n", 2)) < 0) r = r2;
}
if(r > 0) {
n_sent_ += r;
rs_.data.advance((sz_t)r);
} else if(r != EWOULDBLOCK) {
state_ = st_error;
rs_.data.clear();
str_t s = str_t("send(): Failed to write data: ") + l2s(r);
return -err(1, s.c_str()); // == EBADF EFBIG EINTR EIO ENOSPC EPIPE EINVAL
}
}
return 1; // This function did send data
}
/**
* Send a status, dependent on the status code, a small notice body is added.
* @param int status
* @return int
*/
int send(int status)
{
if(state_ <= st_s_header) {
rs_.status = status;
rs_.data.clear();
} else {
log("Headers already sent", log_warn);
}
return send();
}
/**
* Send a string. Default content-type=text/plain.
* @param const str_t & s
* @return int
*/
int send(const str_t & s, int status=200, bool implicit_content_length=true)
{ return send_binary(s.data(), s.length(), status, implicit_content_length); }
/**
* Send binary.
* @param const void* data
* @param sz_t sz
* @param int status=200
* @param bool implicit_content_length=true
* @return int
*/
int send_binary(const void *data, sz_t sz, int status=200, bool implicit_content_length=true)
{
if(state_ <= st_s_header) {
rs_.status = status;
rs_.data.chunk((const char*) data, sz);
if(implicit_content_length) rs_.content_length = rs_.data.size();
if(rs_.content_type.length() == 0) rs_.content_type = "text/plain"; // charset=client guess
} else if(sz > 0) {
if(rs_.content_length >= 0 && n_sent_+(long)sz > rs_.content_length) {
log("send_binary(): Trimmed data size to fit content-length", log_warn);
sz = rs_.content_length - n_sent_;
}
rs_.data.chunk((const char*) data, sz);
}
return send();
}
/**
* Send a file.
*
* @param const str_t & filename
* @param int status=200
* @return int
*/
int send_file(const str_t & filename, int status=200)
{
struct stat st;
status = status <= 0 ? 500 : status;
switch(stat(filename.c_str(), &st)) {
case 0:
if(!S_ISREG(st.st_mode)) { // no links
return send(404);
} else {
{
str_t::size_type n = filename.find_last_of('.');
if(n != str_t::npos && n < filename.length()-1) {
str_t s = mime_content_type(lc(tr(filename.substr(n+1))));
rs_.content_type = (s.length() > 0) ? s : str_t("application/octet-stream");
}
rs_.last_modified = st.st_mtime;
rs_.content_length = st.st_size;
rs_.etag = mketag(filename, st);
}
if(!modified()) {
send(304);
} else {
std::ifstream fs;
fs.open(filename.c_str(), std::ifstream::binary);
if(!fs.good()) {
if(fs.eof()) { send(204); } else { send(403); } // That needs to be double checked ...
} else {
rs_.status = status; // check if 200 is always correct
send();
char s[256];
sz_t n;
while(fs.good()) {
fs.read(s, sizeof(s));
n = (sz_t)fs.gcount();
if(n == 0) { while(send() > 0) {;} break; } // flush
if(send_binary(s, n) < 0) break;
while((!rs_.data.empty()) && (send() > 0)); // in case of EAGAIN
if(state_ == st_done) break;
}
if(num_send_bytes_expected_left() > 0) {
log(str_t("Sending '") + filename + "' did not send the last " +
l2s((long)num_send_bytes_expected_left()) + " bytes.");
}
}
fs.close();
}
}
break;
case EACCES:
return send(403);
case ENOENT:
case ENAMETOOLONG:
case ENOTDIR:
return send(404);
case EOVERFLOW:
default:
return send(500);
}
return -err(1, "send_file: not all cases covered.");
}
//
public:
//
//////////////////////////////////////////////////////////////////////////////
// Public static functions
/**
* Log something
* @param str_t txt
* @return void
*/
static void log(str_t txt, int level = log_notice)
{
static const char* lvls[] = { "CRT", "ERR ", "WRN", "INF", "DBG", "!!!" };
if(!config.log_callback || level < 0 || level > log_debug || level > config.log_level) return;
txt = str_t() + ul2s(::time(NULL)) + " [" + lvls[level] + "] " + txt + "\n";
config.log_callback(txt, level);
}
/**
* Decodes url encoded data (RFC 3986), normally for url/GET or optionally
* for POST form data accepting "+" as well to mark spaces.
* @param const str_t &in
* @param bool form=false
* @return str_t
*/
static str_t urldecode(const str_t &in, bool form=false)
{
str_t out; out.reserve(in.length());
int n = in.length();
typename str_t::const_iterator p = in.begin();
for(; --n >= 2; ++p) {
if(*p == '+' && form) {
out += ' ';
} else if(*p == '%' && std::isxdigit(*(p+1)) && std::isxdigit(*(p+2))) {
char_t c1=*(++p), c0=*(++p);
out += hex2char(c1, c0); n-=2;
} else {
out += *p;
}
}
for(; n-- >= 0; ++p) {
out += (*p == '+' && form) ? '+' : *p;
}
return out;
}
/**
* Encodes a string RFC 3986 compliant.
* @param const str_t &in
* @return str_t
*/
static str_t urlencode(const str_t &in)
{
str_t out; out.reserve(in.length()*2);
typename str_t::const_iterator p = in.begin();
typename str_t::const_iterator e = in.end();
for(; p!=e; ++p) {
if(std::isalnum(*p) || *p=='-' || *p=='_' || *p=='.') {
out += *p;
} else {
out += "%"; out += char2hex(*p);
}
}
return out;
}
/**
* Escapes HTML entities < > and &
* @param const str_t & s
* @param bool with_quotes=true
* @return str_t
*/
static str_t htmlspecialchars(const str_t & s, bool with_quotes=true)
{
str_t ss;
if(with_quotes) {
do {
sz_t n = s.length();
for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
switch(*it) {
case '>': // >
case '<': n+= 3; break; // <
case '&': n+= 4; break; // &
case '"': n+= 5; break; // "
case '\'':n+= 5; break; // '
}
}
ss.reserve(n);
} while(0);
for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
switch(*it) {
case '>' : ss += str_t(">"); break;
case '<' : ss += str_t("<"); break;
case '&' : ss += str_t("&"); break;
case '"' : ss += str_t("""); break;
case '\'': ss += str_t("'"); break;
default : ss += *it;
}
}
} else {
do {
sz_t n = s.length();
for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
switch(*it) {
case '>': // >
case '<': n+= 3; break; // <
case '&': n+= 4; break; // &
}
}
ss.reserve(n);
} while(0);
for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
switch(*it) {
case '>' : ss += str_t(">"); break;
case '<' : ss += str_t("<"); break;
case '&' : ss += str_t("&"); break;
default : ss += *it;
}
}
}
return ss;
}
/**
* Returns the mime type for a given extension (without additional tags
* like ";charset=...").
* @param const str_t &ext
* @return str_t
*/
static str_t mime_content_type(const str_t &ext)
{
static const char_t *text_plain = "text/plain";
static const char_t *lut[] = { // sorted lookup table
"aac","audio/aac", "asf","video/x-ms-asf", "avi","video/x-msvideo",
"bmp","image/bmp", "css","text/css", "csv","text/csv",
"doc","application/msword", "eps","application/postscript",
"exe","application/octet-stream", "gif","image/gif",
"gz","application/x-gunzip", "htm","text/html", "html","text/html",
"ico","image/x-icon", "jpeg","image/jpeg", "jpg","image/jpeg",
"js","application/javascript", "json","application/json",
"m4v","video/x-m4v", "mid","audio/x-midi", "mov","video/quicktime",
"mp3","audio/mpeg", "mp4","video/mp4", "mpeg","video/mpeg",
"mpg","video/mpeg", "oga","audio/ogg", "ogg","audio/ogg",
"ogv","video/ogg", "pdf","application/pdf", "png","image/png",
"ps","application/postscript", "rar","application/x-arj-compressed",
"rtf","application/rtf", "sgm","text/sgml",
"shtm","text/html", "shtml","text/html", "svg","image/svg+xml",
"swf","application/x-shockwave-flash", "tar","application/x-tar",
"tgz","application/x-tar-gz", "tif","image/tiff", "tiff","image/tiff",
"txt","text/plain", "wav","audio/x-wav", "webm","video/webm",
"wrl","model/vrml", "xhtml","application/xhtml+xml",
"xls","application/x-msexcel", "xml","text/xml", "xsl","application/xml",
"xslt","application/xml", "zip","application/x-zip-compressed",
};
if(ext.empty()) return text_plain;
{ str_t s = mime_content_type_add(ext.c_str(), 0); if(!s.empty()) return s; }
int n = (sizeof(lut)/sizeof(const char_t*)), p;
#ifdef assert
assert(!(sizeof(lut)/sizeof(const char_t*)&0x1));
#endif
while(n > 0) {
n = (n)>>1u;
p = n & 0xfffe;
const char* lv = lut[p];
if(ext.compare(lv)) { p+=n; continue; }
if(ext == lv) return lut[p+1];
p -= n;
}
for (p = (p-=2)<0 ? 0:p, n=sizeof(lut)/sizeof(const char_t*); p lut;
if(!ext || !ext[0]) return "";
if(!mime) {
typename std::map::const_iterator it = lut.find(ext);
return (it==lut.end()) ? "" : it->second;
} else {
str_t e = lc(ext);
str_t m = lc(mime);
if(!m.empty()) { lut[e] = m; }
return m;
}
}
/**
* Returns the text version of a response code for use in the response header.
* Contains two versions of the status text table. The compiler selects/drops one
* dependent on the template parameter With_All_Status_Code_Strings.
* @param unsigned code
* @return const char *
*/
static const char * reponse_status_text(unsigned code)
{
typedef struct { unsigned c; const char* h; } rc_t;
static const rc_t rc[] = {
{100,"100 Continue"},{101,"101 Switching Protocols"},{102,"102 Processing"},
{200,"200 OK"},{201,"201 Created"},{202,"202 Accepted"},{203,"203 Non-Authoritative Information"},
{204,"204 No Content"},{205,"205 Reset Content"},{206,"206 Partial Content"},
{207,"207 Multi-Status"},{208,"208 Already Reported"},{226,"226 IM Used"},
{300,"300 Multiple Choices"},{301,"301 Moved Permanently"},{302,"302 Found"},
{303,"303 See Other"},{304,"304 Not Modified"},{305,"305 Use Proxy"},{306,"306 Switch Proxy"},
{307,"307 Temporary Redirect"},{308,"308 Permanent Redirect"},{400,"400 Bad Request"},
{401,"401 Unauthorized"},{402,"402 Payment Required"},{403,"403 Forbidden"},{404,"404 Not Found"},
{405,"405 Method Not Allowed"},{406,"406 Not Acceptable"},{407,"407 Proxy Authentication Required"},
{408,"408 Request Timeout"},{409,"409 Conflict"},{410,"410 Gone"},{411,"411 Length Required"},
{412,"412 Precondition Failed"},{413,"413 Request Entity Too Large"},{414,"414 Request-URI Too Long"},
{415,"415 Unsupported Media Type"},{416,"416 Requested Range Not Satisfiable"},
{417,"417 Expectation Failed"},{419,"419 Authentication Timeout"},
{420,"420 Enhance Your Calm"},{422,"422 Unprocessable Entity"},{423,"423 Locked"},
{424,"424 Failed Dependency"},{425,"425 Unordered Collection"},{426,"426 Upgrade Required"},
{428,"428 Precondition Required"},{429,"429 Too Many Requests"},
{431,"431 Request Header Fields Too Large"},{440,"440 Login Timeout"},{444,"444 No Response"},
{449,"449 Retry With"},{450,"450 Blocked by Windows Parental Controls"},
{451,"451 Unavailable For Legal Reasons"},/*{451,"451 Redirect"},*/{494,"494 Request Header Too Large"},
{495,"495 Cert Error"},{496,"496 No Cert"},{497,"497 HTTP to HTTPS"},{499,"499 Client Closed Request"},
{500,"500 Internal Server Error"},{501,"501 Not Implemented"},{502,"502 Bad Gateway"},
{503,"503 Service Unavailable"},{504,"504 Gateway Timeout"},{505,"505 HTTP Version Not Supported"},
{506,"506 Variant Also Negotiates"},{507,"507 Insufficient Storage"},
{508,"508 Loop Detected"},{509,"509 Bandwidth Limit Exceeded"},{510,"510 Not Extended"},
{511,"511 Network Authentication Required"},{520,"520 Origin Error"},{521,"521 Web server is down"},
{522,"522 Connection timed out"},{523,"523 Proxy Declined Request"},{524,"524 A timeout occurred"},
{598,"598 Network read timeout error"},{599,"599 Network connect timeout error"},
{0,"500 Internal Server Error"}
};
const unsigned rcsz = (sizeof(rc)/sizeof(rc_t))-1; // 81; {0,"500 error"}=82
switch(code) {
case 200: return "200 OK"; // that will be the same pointer as in the list.
case 404: return "404 Not Found";
case 500: return "500 Internal Server Error";
case 501: return "501 Not Implemented";
default:
if(code >= 600 || code < 100) return rc[rcsz].h;
unsigned i=rcsz>>1, k=rcsz>>1;
while(rc[i].c != code && k>0) i = (code > rc[i].c) ? i+(k>>=1) : i-(k>>=1);
while(code > rc[i].c) ++i;
while(code < rc[i].c) --i;
return code == rc[i].c ? rc[i].h : rc[rcsz].h;
}
}
/**
* Parses URL encoded data into a string map. Returns the number of added key-value pairs or
* negative on error.
* @param const str_t & inp
* @param str_map_t & out
* @param bool form_encoded=false
* @param unsigned max_key_sz=64
* @param unsigned max_val_sz=4096
* @return int
*/
static int parse_url_encoded(const str_t & inp, str_map_t & out, bool form_encoded=false,
unsigned max_key_sz=64, unsigned max_val_sz=4096)
{
sz_t qlen = inp.length();
int n=0;
for(sz_t l=0; l max_key_sz) return -431;
if(val.length() > max_val_sz) return -431;
if(!key.empty()) { out[key] = val; n++; }
}
return n;
}
/**
* Returns a GMT time based string of a time stamp suitable to be used in header date information.
* @param time_t t
* @return str_t
*/
static str_t timestamp2string(time_t t)
{
char s[128];
struct ::tm tm;
memset(s, 0, sizeof(s));
memset(&tm, 0, sizeof(tm));
#if !defined(_WINDOWS_) && !defined(_WIN32) && !defined(__MSC_VER)
if(!gmtime_r(&t, &tm)) return "Thu, 01 Jan 1970 00:00:00 GMT";
#else
errno_t e = gmtime_s(&tm, &t);
if(e != 0) {
using namespace std;
cerr << ::strerror(e) << endl;
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
#endif
::strftime(s, sizeof(s), "%a, %d %b %Y %H:%M:%S GMT", &tm);
return s;
}
/**
* Returns a GMT time based on a formatted string or 0 if the string could not be parsed.
* @param const str_t & stime
* @return time_t
*/
static time_t string2timestamp(const str_t & stime)
{ // no c++11 crono requirement, no plain strptime (unsafe) --> detect pattern, parse then
// bit of a pain, but well ...
if(stime.empty()) return 0;
str_t s, pattern;
do {
// Preconditioning the time string (lower, no double spaces, strip unknown characters)
s.reserve(48);
bool wassp=false;
for(str_t::const_iterator it=stime.begin(); it!=stime.end(); ++it) {
char_t c = *it;
if(::isalnum(c)) { wassp=false; s += ::tolower(c); continue; }
if(c == ',') c = ' '; // save some trouble later on
if(::isspace(c)) { if(!wassp) { wassp = true; s += ' '; } continue; }
if(str_t(",:+-./").find(c) != str_t::npos) wassp = true; else continue;
s += c;
}
s = tr(s);
} while(0);
do {
// Pattern
pattern.reserve(32);
char_t tl = 0;
for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
char_t tc = ::isdigit(*it) ? 'd' : (::isalpha(*it) ? 'a' : *it);
if(tc != tl) { tl = tc; pattern += tc; }
}
} while(0);
do {
int y=-1,m=-1,d=-1,h=0,min=0,sec=0;
str_t wds, tzs, ms;
char_t c;
if(pattern == "a d a d d:d:d a") { // Thu, 01 Jan 1970 00:00:00 GMT : RFC 822, RFC 1123
std::stringstream ss(s);
ss >> wds >> std::dec >> d >> ms >> y >> h >> c >> min >> c >> sec >> tzs;
} else if(pattern == "a d-a-d d:d:d a") { // Sunday, 06-Nov-94 08:49:37 GMT, RFC 850
for(str_t::iterator it=s.begin(); it != s.end(); ++it) if(*it == '-') *it = ' ';
std::stringstream ss(s);
ss >> wds >> d >> ms >> y >> h >> c >> min >> c >> sec >> tzs;
} else if(pattern == "a a d d:d:d d") { // Sun Nov 6 08:49:37 1994, asctime
std::stringstream ss(s);
ss >> wds >> ms >> d >> h >> c >> min >> c >> sec >> y;
}
if(y<0 || d<0 || h<0 || min<0 || sec<0 || ((m<0) && (ms.length()<3))) return 0;
if((!tzs.empty()) && (tzs != "gmt")) return 0; // for now only specified timezone accepted.
if(m<0) {
ms.resize(3);
const char* months[12] = {
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
};
for(int i=0; i<12; ++i) if(ms == months[i]) { m=i+1; break; }
}
//if(y < 100) { if(y >=70) y += 1900; else if(y<8) y += 2000; else return 0; }
struct ::tm tm;
memset(&tm, 0, sizeof(struct ::tm));
tm.tm_year = y-1900;
tm.tm_mon = m-1;
tm.tm_mday = d;
tm.tm_hour = h;
tm.tm_min = min;
tm.tm_sec = sec;
#if !defined(_WINDOWS_) && !defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__MSC_VER)
tm.tm_gmtoff = 0; // stfwi: double check
return ::timegm(&tm);
#else
return time_t(_mkgmtime(&tm));
#endif
} while(0);
return 0;
}
//
protected:
//
//////////////////////////////////////////////////////////////////////////////
// Internal functions / methods
/**
* Switch to error state, set response code. Returns int cast for return of negative numbers.
* @param unsigned code
* @return int
*/
inline int err(unsigned code)
{
error_ = code;
if(state_ < st_s_header) {
state_ = st_r_error;
rs_.status = code;
} else {
state_ = st_error;
}
rlen_ = 0; // ensure no additional read actions
return (int) code;
}
/**
* Switch to error state, set response code. Returns int cast for return of negative numbers.
* @param unsigned code
* @param const char *msg
* @return int
*/
inline int err(unsigned code, const char *msg)
{ if(msg) { error_text_ = msg; log(msg, log_error); } return err(code); }
/**
* Returns a character value from two hex digits. Does not check if the
* digits are really hex.
* @param char_t c1 (higher nibble)
* @param char_t c0 (lower nibble)
* @return char_t
*/
static inline char_t hex2char(register char_t c1, register char_t c0)
{
c1 = std::tolower(c1); c1 -= (c1>'9' ? ('a'-10) : '0');
c0 = std::tolower(c0); c0 -= (c0>'9' ? ('a'-10) : '0');
return (char_t) ((c1)<<4) | ((c0)<<0);
}
/**
* Transforms a character value to hex (upper case)
* @param char_t c
* @return str_t
*/
static inline str_t char2hex(register char_t c)
{
static const char_t lut[17] = "0123456789abcdef";
return str_t(1, lut[(c>>4)&0xf]) + lut[(c>>0)&0xf];
}
/**
* Transforms positive number into a hex string
* @param uint64_t n
* @return str_t
*/
static inline str_t ul2hex(unsigned long n)
{
static const char_t lut[17] = "0123456789abcdef"; // The same pointer as above after compiling
static const sz_t l = 2*sizeof(unsigned long)+1; // note: one additional character in the buf
if(!n) return str_t("0");
str_t s(l, ' ');
sz_t i = l;
while(n && i>0) { s[--i] = lut[n & 0x0ful]; n >>= 4u; }
return s.substr(i);
}
/**
* Transform lower case
* @param str_t s
*/
static inline str_t lc(str_t s)
{ std::transform(s.begin(), s.end(), s.begin(), ::tolower); return s; }
/**
* Transform upper case
* @param str_t s
*/
static inline str_t uc(str_t s)
{ std::transform(s.begin(), s.end(), s.begin(), ::toupper); return s; }
/**
* Trim white spaces right
* @param str_t s
* @param const char *spacechars=" "
*/
static str_t rtr(const str_t & s, const char* spacechars=" ")
{
if(s.empty()) return s;
sz_t n = s.find_last_not_of(spacechars);
return (n == str_t::npos) ? "" : (n >= s.length()-1 ? s : s.substr(0, n+1));
}
/**
* Trim white spaces left
* @param str_t s
* @param const char *spacechars=" "
*/
static str_t ltr(const str_t & s, const char* spacechars=" ")
{
if(s.empty()) return s;
sz_t n = s.find_first_not_of(spacechars);
return (n == str_t::npos) ? "" : (n >= s.length() ? s : s.substr(n, str_t::npos));
}
/**
* Trim white spaces
* @param str_t s
* @param const char *spacechars=" "
*/
static inline str_t tr(const str_t & s, const char* spacechars=" ")
{ return rtr(ltr(s, spacechars), spacechars); }
/**
* Returns true if a given string represents an unsigned integer
* @param const str_t & s
* @return bool
*/
static inline bool isuint(const str_t & s)
{ return s.length()>0 && s.length()<11 && (unsigned) std::count_if(s.begin(), s.end(), ::isdigit)
+ (s[0]=='+' ? 1:0) == s.length(); }
/**
* Returns true if a given string represents an unsigned integer
* @param const str_t & s
* @return bool
*/
static inline bool isint(const str_t & s)
{ return s.length()>0 && s.length()<11 && (unsigned) std::count_if(s.begin(), s.end(), ::isdigit)
+ ((s[0]=='-' || s[0]=='+') ? 1:0) == s.length(); }
/**
* Alpha to long wrapper
* @param const str_t & s
* @return long
*/
static inline long s2l(const str_t & s)
{ return ::atol(s.c_str()); }
/**
* Long to string wrapper
* @return str_t
*/
static inline str_t l2s(long l)
{ std::stringstream ss; ss << l; return ss.str(); }
/**
* Unsigned long to string wrapper
* @return str_t
*/
static inline str_t ul2s(unsigned long ul)
{ std::stringstream ss; ss << ul; return ss.str(); }
/**
* String to double
* @param const str_t & s
* @return double
*/
static inline double s2d(const str_t & s)
{ std::stringstream ss(s); double d; return (!(ss>>d) || !ss.eof()) ?
std::numeric_limits::quiet_NaN() : d; }
/**
* Double to string
* @return str_t
*/
static inline str_t d2s(double d)
{ std::stringstream ss; ss << d; return ss.str(); }
/**
* String clear with memory shrinking deallocation. c++11: shrinktofit, more general: swap.
* @param str_t s
*/
static inline void sc(str_t & s)
{ s.clear(); str_t().swap(s); }
/**
* Generate a simple etag from file path and data
* @param filename
* @param const struct stat & st
* @return str_t
*/
static str_t mketag(const str_t & filename, const struct stat & st)
{
static const char* enc = "0123456789abcdefghijklmnopqrstuv----";
static const str_t::size_type l = 32;
str_t s; s.reserve(l);
uint64_t v;
for(v=(uint64_t) st.st_mtime; v>0 && s.length()>=5u) s += enc[v&((1u<<5)-1)];
for(v=(uint64_t) st.st_size; v>0 && s.length()>=5u) s += enc[v&((1u<<5)-1)];
for(v=(uint64_t) st.st_ino; v>0 && s.length()>=5u) s += enc[v&((1u<<5)-1)];
for(v=(uint64_t) filename.length(); v>0 && s.length()>=5u) s += enc[v&((1u<<5)-1)];
v = 0;
for(str_t::const_iterator it=filename.begin(); it!=filename.end(); ++it) v += (uint64_t) *it;
for(; v>0 && s.length()>=5u) s += enc[v&((1u<<5)-1)];
return s;
}
//
protected:
//
//////////////////////////////////////////////////////////////////////////////
// Instance variables / internal types
state_t state_; // Current parser state
long rlen_; // Temporary, remaining length, used to match request content length.
uint32_t lch_; // Last characters used for input parsing, temporary
long n_sent_; // Number of sent content bytes (without header)
str_t rx_; // "request header text", temporary
request_t rq_; // Request
response_t rs_; // Response
storage_t storage_; // The storage provider
int error_; // Error number
str_t error_text_; // String of last error, if set
//
public:
//
str_t dump()
{
std::stringstream ss;
const char endl = '\n';
ss << "basic_http<...> = {" << endl
<< " state : ";
switch(state()) {
case st_r_header: ss << "rx header"; break;
case st_r_body: ss << "rx body"; break;
case st_r_done: ss << "rx done"; break;
case st_r_error: ss << "rx error"; break;
case st_s_header: ss << "tx header"; break;
case st_s_body: ss << "tx body"; break;
case st_done: ss << "done"; break;
case st_error: ss << "error"; break;
}
ss << endl
<< " modified() : " << modified() << endl
<< " num_read_bts_exp: " << num_read_bytes_expected_left() << endl
<< " num_send_bts_exp: " << num_send_bytes_expected_left() << endl
<< " request() {" << endl
<< " method : ";
switch(rq_.method) {
case rq_get: ss << "GET"; break;
case rq_head: ss << "HEAD"; break;
case rq_post: ss << "POST"; break;
case rq_put: ss << "PUT"; break;
case rq_delete: ss << "DELETE"; break;
case rq_options: ss << "OPTIONS"; break;
case rq_trace: ss << "TRACE"; break;
case rq_mkcol: ss << "MKCOL"; break;
case rq_move: ss << "MOVE"; break;
case rq_copy: ss << "COPY"; break;
case rq_lock: ss << "LOCK"; break;
case rq_unlock: ss << "UNLOCK"; break;
case rq_invalid: ss << "INVALID"; break;
}
ss << endl;
if(!rq_.service.empty()) ss << " service : " << rq_.service << endl;
if(!rq_.host.empty()) ss << " host : " << rq_.host << endl;
if(!rq_.path.empty()) ss << " path : " << rq_.path << endl;
if(!rq_.query_string.empty()) ss << " query_string : " << rq_.query_string << endl;
if(!rq_.query_hash.empty()) ss << " query_hash : " << rq_.query_hash << endl;
ss << " http_ver : 1." << rq_.http_version << endl;
if(!rq_.referer.empty()) ss << " referer : " << rq_.referer << endl;
if(!rq_.accept.empty()) ss << " accept: : " << rq_.accept << endl;
if(!rq_.user_agent.empty()) ss << " user_agent : " << rq_.user_agent << endl;
if(!rq_.content_type.empty()) ss << " content_type : " << rq_.content_type << endl;
if(rq_.content_length>-1) ss << " content_length: " << rq_.content_length << endl;
if(!rq_.if_modified_since) ss << " if_mod_since : " << rq_.if_modified_since << endl;
if(!rq_.if_unmodified_since) ss << " if_unmod_since: " << rq_.if_unmodified_since << endl;
ss << " keep_alive : " << rq_.keep_alive << endl;
ss << " no_cache : " << rq_.no_cache << endl;
if(!rq_.range.empty()) {
ss << " range : ";
for(range_t::const_iterator it=rq_.range.begin(); it!=rq_.range.end(); ++it) {
ss << "[" << it->first << "->" << it->second << "] ";
}
ss << endl;
}
ss << " data (ram) : " << rq_.data.length() << " bytes" << endl
<< " storage() : " << storage().size() << " bytes" << endl;
if(!rq_.headers.empty()) {
ss << " headers() {" << endl;
for(str_map_t::const_iterator it=rq_.headers.begin(); it != rq_.headers.end(); ++it) {
ss << " \"" << it->first << "\": \"" << it->second << "\"" << endl;
}
ss << " }" << endl;
}
if(!get().empty()) {
ss << " get() {" << endl;
for(str_map_t::const_iterator it = get().begin(); it != get().end(); ++it) {
ss << " \"" << it->first << "\": \"" << it->second << "\"" << endl;
}
ss << " }" << endl;
}
if(!post().empty()) {
ss << " post() {" << endl;
for(str_map_t::const_iterator it = post().begin(); it != post().end(); ++it) {
ss << " \"" << it->first << "\": \"" << it->second << "\"" << endl;
}
ss << " }" << endl;
}
if(!rq_.cookie.empty()) {
ss << " cookie() {" << endl;
for(str_map_t::const_iterator it = rq_.cookie.begin(); it != rq_.cookie.end(); ++it) {
ss << " \"" << it->first << "\": \"" << it->second << "\"" << endl;
}
ss << " }" << endl;
}
if(!rq_.files.empty()) {
ss << " files() {" << endl;
for(typename upload_files_t::const_iterator it= rq_.files.begin(); it!=rq_.files.end(); ++it){
ss << " \"" << it->first << "\": {" << endl
<< " filename : \"" << it->second.filename << "\"" << endl
<< " content-type: \"" << it->second.content_type << "\"" << endl
<< " encoding : \"" << it->second.content_encoding << "\"" << endl
<< " start : " << it->second.start << endl
<< " size : " << it->second.size << endl
<< " data : " << it->second.data.length() << " bytes" << endl
<< " }" << endl ;
}
}
ss << " }" << endl
<< " response() {" << endl
<< " status : " << response().status << endl
<< " date : " << response().date << endl
<< " content_type :" << response().content_type << endl
<< " content_length :" << response().content_length << endl
<< " etag :" << response().etag << endl
<< " data : " << response().data.size() << " bytes" << endl
<< " last_modified : " << response().last_modified << endl
<< " accept_ranges : " << response().accept_ranges << endl
<< " cache_ctrl : ";
switch(response().cache_ctrl) {
case cache_ctrl_none: ss << "cache_ctrl_none"; break;
case cache_ctrl_public: ss << "cache_ctrl_public"; break;
case cache_ctrl_private: ss << "cache_ctrl_private"; break;
case cache_ctrl_no_cache: ss << "cache_ctrl_no_cache"; break;
case cache_ctrl_no_store: ss << "cache_ctrl_no_store"; break;
}
ss << endl
<< " transf_encoding: ";
switch(response().transf_encoding) {
case tfe_omit: ss << "tfe_omit"; break;
case tfe_chunked: ss << "tfe_chunked"; break;
case tfe_deflate: ss << "tfe_deflate"; break;
case tfe_gzip: ss << "tfe_gzip"; break;
}
ss << endl
<< " keep_alive : " <first << "\": \"" << it->second << "\"" << endl;
}
ss << " }" << endl
<< " }" << endl
<< "}" << endl;
return ss.str();
}
//
public:
//
static config_t config; // On config to rule them all
static const double nan; // Convenience NaN
static const str_t nstr; // For referencing "not found" string in case of const str_t & returns.
//
};
//
////////////////////////////////////////////////////////////////////////////////
// Static variables
template
typename basic_http::config_t basic_http::config;
template
const typename basic_http::str_t basic_http::nstr;
template
const double basic_http::nan = std::numeric_limits::quiet_NaN();
//
}}
//
////////////////////////////////////////////////////////////////////////////////
// Default specialisation
namespace sw {
typedef detail::basic_http<> http;
}
//
#endif