// Copyright (C) 2006 Davis E. King (davisking@users.sourceforge.net)
// 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"
namespace dlib
{
template <
typename server_base,
typename map_ss_type,
typename queue_string_type
>
class server_http_1 : public server_base
{
/*!
CONVENTION
this extension doesn't add any new state to this object.
!*/
public:
typedef map_ss_type map_type;
typedef queue_string_type queue_type;
private:
virtual void on_request (
const std::string& path,
std::string& result,
const map_type& queries,
const map_type& cookies,
queue_type& new_cookies,
const map_type& incoming_headers,
map_type& response_headers,
const std::string& foreign_ip,
const std::string& local_ip,
unsigned short foreign_port,
unsigned short local_port
) = 0;
unsigned char to_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 decode_query_string (
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 = to_hex(str[i+1]);
const unsigned char ch2 = to_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
}
else
{
result += str[i];
}
}
return result;
}
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;
try
{
enum req_type {get, post} rtype;
using namespace std;
map_type cookies;
string word;
string path;
in >> word;
if (word == "GET" || word == "get")
{
rtype = get;
}
else if ( word == "POST" || word == "post")
{
rtype = post;
}
else
{
// this isn't a GET or POST request so just drop the connection
return;
}
// get the path
in >> path;
// now loop over all the incoming_headers
string line;
getline(in,line);
unsigned long content_length = 0;
string content_type;
map_type incoming_headers;
string first_part_of_header;
string::size_type position_of_double_point;
while (line.size() > 2)
{
position_of_double_point = line.find_first_of(':');
if ( position_of_double_point != string::npos )
{
first_part_of_header = line.substr(0, position_of_double_point);
if ( incoming_headers.is_in_domain(first_part_of_header) )
{
incoming_headers[ first_part_of_header ] += " " + line.substr(position_of_double_point+1);
}
else
{
string second_part_of_header(line.substr(position_of_double_point+1));
incoming_headers.add( first_part_of_header, second_part_of_header );
}
// look for Content-Type:
if (line.size() > 14 &&
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'n' &&
line[3] == 't' &&
line[4] == 'e' &&
line[5] == 'n' &&
line[6] == 't' &&
line[7] == '-' &&
(line[8] == 'T' || line[8] == 't') &&
line[9] == 'y' &&
line[10] == 'p' &&
line[11] == 'e' &&
line[12] == ':'
)
{
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 &&
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'n' &&
line[3] == 't' &&
line[4] == 'e' &&
line[5] == 'n' &&
line[6] == 't' &&
line[7] == '-' &&
(line[8] == 'L' || line[8] == 'l') &&
line[9] == 'e' &&
line[10] == 'n' &&
line[11] == 'g' &&
line[12] == 't' &&
line[13] == 'h' &&
line[14] == ':'
)
{
istringstream sin(line.substr(16));
sin >> content_length;
if (!sin)
content_length = 0;
}
// look for any cookies
else if (line.size() > 6 &&
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'o' &&
line[3] == 'k' &&
line[4] == 'i' &&
line[5] == 'e' &&
line[6] == ':'
)
{
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] == ';')
{
if (cookies.is_in_domain(key) == false)
cookies.add(key,value);
seen_equal_sign = false;
seen_key_start = false;
}
else
{
value += line[pos];
}
}
}
if (key.size() > 0 && cookies.is_in_domain(key) == false)
cookies.add(key,value);
}
} // no ':' in it!
getline(in,line);
} // while (line.size() > 2 )
// If there is data being posted back to us as a query string then
// just stick it onto the end of the path so the following code can
// then just pick it out like we do for GET requests.
if (rtype == post && content_type == "application/x-www-form-urlencoded"
&& content_length > 0)
{
line.resize(content_length);
in.read(&line[0],content_length);
path += "?" + line;
}
string result;
map_type queries;
string::size_type pos = path.find_first_of("?");
if (pos != string::npos)
{
word = path.substr(pos+1);
path = path.substr(0,pos);
for (pos = 0; pos < word.size(); ++pos)
{
if (word[pos] == '&')
word[pos] = ' ';
}
istringstream sin(word);
sin >> word;
while (sin)
{
pos = word.find_first_of("=");
if (pos != string::npos)
{
string key = decode_query_string(word.substr(0,pos));
string value = decode_query_string(word.substr(pos+1));
if (queries.is_in_domain(key) == false)
queries.add(key,value);
}
sin >> word;
}
}
my_fault = false;
queue_type new_cookies;
map_type response_headers;
// if there wasn't a problem with the input stream at some point
// then lets trigger this request callback.
if (in)
on_request(path,result,queries,cookies,new_cookies,incoming_headers, response_headers, foreign_ip,local_ip,foreign_port,local_port);
my_fault = true;
out << "HTTP/1.0 200 OK\r\n";
// only send this header if the user hasn't told us to send another kind
if (response_headers.is_in_domain("Content-Type") == false &&
response_headers.is_in_domain("content-type") == false)
{
out << "Content-Type: text/html\r\n";
}
out << "Content-Length: " << result.size() << "\r\n";
// Set any new headers
response_headers.reset();
while (response_headers.move_next())
out << response_headers.element().key() << ':' << response_headers.element().value() << "\r\n";
// set any cookies
new_cookies.reset();
while (new_cookies.move_next())
{
out << "Set-Cookie: " << new_cookies.element() << "\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,
typename map_ss_type,
typename queue_string_type
>
const logger server_http_1<server_base,map_ss_type,queue_string_type>::dlog("dlib.server");
}
#endif // DLIB_SERVER_HTTp_1_