dlib C++ Library - server_http_1.h

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

    #ifdef  __INTEL_COMPILER
    // ignore the bogus warning about hiding on_connect()
    #pragma warning (disable: 1125)
    #endif

    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_