// Copyright (C) 2006 Davis E. King (davis@dlib.net), Steven Van Ingelgem
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_SERVER_HTTp_1_
#define DLIB_SERVER_HTTp_1_
#include "server_iostream_abstract.h"
#include "server_http_abstract.h"
#include <iostream>
#include <sstream>
#include <string>
#include "../logger.h"
#include "../string.h"
#ifdef __INTEL_COMPILER
// ignore the bogus warning about hiding on_connect()
#pragma warning (disable: 1125)
#endif
#if _MSC_VER
# pragma warning( disable: 4503 )
#endif // _MSC_VER
namespace dlib
{
template <
typename server_base
>
class server_http_1 : public server_base
{
/*!
CONVENTION
this extension doesn't add any new state to this object.
!*/
public:
template <typename Key, typename Value>
class constmap : public std::map<Key, Value>
{
public:
const Value& operator[](const Key& k) const
{
static const Value dummy = Value();
typename std::map<Key, Value>::const_iterator ci = std::map<Key, Value>::find(k);
if ( ci == this->end() )
return dummy;
else
return ci->second;
}
Value& operator[](const Key& k)
{
return std::map<Key, Value>::operator [](k);
}
};
typedef constmap< std::string, std::string > key_value_map;
struct incoming_things
{
incoming_things() : foreign_port(0), local_port(0) {}
std::string path;
std::string request_type;
std::string content_type;
std::string body;
key_value_map queries;
key_value_map cookies;
key_value_map headers;
std::string foreign_ip;
unsigned short foreign_port;
std::string local_ip;
unsigned short local_port;
};
struct outgoing_things
{
outgoing_things() : http_return(200) { }
key_value_map cookies;
key_value_map headers;
unsigned short http_return;
std::string http_return_status;
};
private:
virtual const std::string on_request (
const incoming_things& incoming,
outgoing_things& outgoing
) = 0;
unsigned char to_hex( unsigned char x ) const
{
return x + (x > 9 ? ('A'-10) : '0');
}
const std::string urlencode( const std::string& s ) const
{
std::ostringstream os;
for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci )
{
if ( (*ci >= 'a' && *ci <= 'z') ||
(*ci >= 'A' && *ci <= 'Z') ||
(*ci >= '0' && *ci <= '9') )
{ // allowed
os << *ci;
}
else if ( *ci == ' ')
{
os << '+';
}
else
{
os << '%' << to_hex(*ci >> 4) << to_hex(*ci % 16);
}
}
return os.str();
}
unsigned char from_hex (
unsigned char ch
) const
{
if (ch <= '9' && ch >= '0')
ch -= '0';
else if (ch <= 'f' && ch >= 'a')
ch -= 'a' - 10;
else if (ch <= 'F' && ch >= 'A')
ch -= 'A' - 10;
else
ch = 0;
return ch;
}
const std::string urldecode (
const std::string& str
) const
{
using namespace std;
string result;
string::size_type i;
for (i = 0; i < str.size(); ++i)
{
if (str[i] == '+')
{
result += ' ';
}
else if (str[i] == '%' && str.size() > i+2)
{
const unsigned char ch1 = from_hex(str[i+1]);
const unsigned char ch2 = from_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
}
else
{
result += str[i];
}
}
return result;
}
void parse_url(std::string word, key_value_map& queries)
/*!
Parses the query string of a URL. word should be the stuff that comes
after the ? in the query URL.
!*/
{
std::string::size_type pos;
for (pos = 0; pos < word.size(); ++pos)
{
if (word[pos] == '&')
word[pos] = ' ';
}
std::istringstream sin(word);
sin >> word;
while (sin)
{
pos = word.find_first_of("=");
if (pos != std::string::npos)
{
std::string key = urldecode(word.substr(0,pos));
std::string value = urldecode(word.substr(pos+1));
queries[key] = value;
}
sin >> word;
}
}
void on_connect (
std::istream& in,
std::ostream& out,
const std::string& foreign_ip,
const std::string& local_ip,
unsigned short foreign_port,
unsigned short local_port,
uint64
)
{
bool my_fault = true;
using namespace std;
try
{
incoming_things incoming;
outgoing_things outgoing;
incoming.foreign_ip = foreign_ip;
incoming.foreign_port = foreign_port;
incoming.local_ip = local_ip;
incoming.local_port = local_port;
in >> incoming.request_type;
// get the path
in >> incoming.path;
key_value_map& incoming_headers = incoming.headers;
key_value_map& cookies = incoming.cookies;
std::string& path = incoming.path;
std::string& content_type = incoming.content_type;
unsigned long content_length = 0;
string line;
getline(in,line);
string first_part_of_header;
string::size_type position_of_double_point;
// now loop over all the incoming_headers
while (line.size() > 2)
{
position_of_double_point = line.find_first_of(':');
if ( position_of_double_point != string::npos )
{
first_part_of_header = dlib::trim(line.substr(0, position_of_double_point));
if ( !incoming_headers[first_part_of_header].empty() )
incoming_headers[ first_part_of_header ] += " ";
incoming_headers[first_part_of_header] += dlib::trim(line.substr(position_of_double_point+1));
// look for Content-Type:
if (line.size() > 14 && strings_equal_ignore_case(line, "Content-Type:", 13))
{
content_type = line.substr(14);
if (content_type[content_type.size()-1] == '\r')
content_type.erase(content_type.size()-1);
}
// look for Content-Length:
else if (line.size() > 16 && strings_equal_ignore_case(line, "Content-Length:", 15))
{
istringstream sin(line.substr(16));
sin >> content_length;
if (!sin)
content_length = 0;
}
// look for any cookies
else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7))
{
string::size_type pos = 6;
string key, value;
bool seen_key_start = false;
bool seen_equal_sign = false;
while (pos + 1 < line.size())
{
++pos;
// ignore whitespace between cookies
if (!seen_key_start && line[pos] == ' ')
continue;
seen_key_start = true;
if (!seen_equal_sign)
{
if (line[pos] == '=')
{
seen_equal_sign = true;
}
else
{
key += line[pos];
}
}
else
{
if (line[pos] == ';')
{
cookies[urldecode(key)] = urldecode(value);
seen_equal_sign = false;
seen_key_start = false;
key.clear();
value.clear();
}
else
{
value += line[pos];
}
}
}
if (key.size() > 0)
{
cookies[urldecode(key)] = urldecode(value);
key.clear();
value.clear();
}
}
} // no ':' in it!
getline(in,line);
} // while (line.size() > 2 )
// If there is data being posted back to us then load it into the incoming.body
// string.
if ( content_length > 0 )
{
incoming.body.resize(content_length);
in.read(&incoming.body[0],content_length);
}
// If there is data being posted back to us as a query string then
// pick out the queries using parse_url.
if (strings_equal_ignore_case(incoming.request_type, "POST") &&
strings_equal_ignore_case(left_substr(content_type,";"), "application/x-www-form-urlencoded"))
{
parse_url(incoming.body, incoming.queries);
}
string::size_type pos = path.find_first_of("?");
if (pos != string::npos)
{
parse_url(path.substr(pos+1), incoming.queries);
}
my_fault = false;
key_value_map& new_cookies = outgoing.cookies;
key_value_map& response_headers = outgoing.headers;
// Set some defaults
outgoing.http_return = 200;
outgoing.http_return_status = "OK";
// if there wasn't a problem with the input stream at some point
// then lets trigger this request callback.
std::string result;
if (in)
result = on_request(incoming, outgoing);
my_fault = true;
// only send this header if the user hasn't told us to send another kind
bool has_content_type(false),
has_location(false);
for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
{
if ( !has_content_type && strings_equal_ignore_case(ci->first , "content-type") )
{
has_content_type = true;
}
else if ( !has_location && strings_equal_ignore_case(ci->first , "location") )
{
has_location = true;
}
}
if ( has_location )
{
outgoing.http_return = 302;
}
if ( !has_content_type )
{
response_headers["Content-Type"] = "text/html";
}
{
ostringstream os;
os << result.size();
response_headers["Content-Length"] = os.str();
}
out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n";
// Set any new headers
for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
{
out << ci->first << ": " << ci->second << "\r\n";
}
// set any cookies
for( typename key_value_map::const_iterator ci = new_cookies.begin(); ci != new_cookies.end(); ++ci )
{
out << "Set-Cookie: " << urlencode(ci->first) << '=' << urlencode(ci->second) << "\r\n";
}
out << "\r\n" << result;
}
catch (std::bad_alloc&)
{
dlog << LERROR << "We ran out of memory in server_http::on_connect()";
// If this is an escaped exception from on_request then let it fly!
// Seriously though, this way it is obvious to the user that something bad happened
// since they probably won't have the dlib logger enabled.
if (!my_fault)
throw;
}
}
const static logger dlog;
};
template <
typename server_base
>
const logger server_http_1<server_base>::dlog("dlib.server");
}
#endif // DLIB_SERVER_HTTp_1_