dlib C++ Library - server_http.cpp (original) (raw)

// Copyright (C) 2003 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_SERVER_HTTP_CPp_ #define DLIB_SERVER_HTTP_CPp_

#include "server_http.h"

namespace dlib {

// ----------------------------------------------------------------------------------------

namespace http_impl
**{**
    inline unsigned char **to_hex**( unsigned char x )  
    **{**
        return x + (x > 9 ? ('A'-10) : '0');
    **}**

    const std::string **urlencode**( const std::string& s )  
    **{**
        std::ostringstream os;

        for ( std:🧵: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();
    **}**

    inline unsigned char **from_hex** (
        unsigned char ch
    ) 
    **{**
        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
    ) 
    **{**
        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:🧵: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:🧵:npos)
            **{**
                std::string key = urldecode(word.substr(0,pos));
                std::string value = urldecode(word.substr(pos+1));

                queries[key] = value;
            **}**
            sin >> word;
        **}**
    **}**
  
    void **read_with_limit**(
        std::istream& in, 
        std::string& buffer, 
        int delim = '\n'
    ) 
    **{**
        using namespace std;
        const size_t max = 64*1024;
        buffer.clear();
        buffer.reserve(300);

        while (in.peek() != delim && in.peek() != '\n' && in.peek() != EOF && buffer.size() < max)
        **{**
            buffer += (char)in.get();
        **}**

        // if we quit the loop because the data is longer than expected or we hit EOF
        if (in.peek() == EOF)
            throw http_parse_error("HTTP field from client terminated incorrectly", 414);
        if (buffer.size() == max)
            throw http_parse_error("HTTP field from client is too long", 414);

        in.get();
        // eat any remaining whitespace
        if (delim == ' ')
        **{**
            while (in.peek() == ' ')
                in.get();
        **}**
    **}**
**}**

// ----------------------------------------------------------------------------------------

unsigned long **parse_http_request** ( 
    std::istream& in,
    incoming_things& incoming,
    unsigned long max_content_length
)
**{**
    using namespace std;
    using namespace http_impl;
    read_with_limit(in, incoming.request_type, ' ');

    // get the path
    read_with_limit(in, incoming.path, ' ');

    // Get the HTTP/1.1 - Ignore for now...
    read_with_limit(in, incoming.protocol);

    key_value_map_ci& 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;
    read_with_limit(in, line);
    string first_part_of_header;
    string::size_type position_of_double_point;
    // now loop over all the incoming_headers
    while (line != "\r")
    **{**
        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)
                **{**
                    throw http_parse_error("Invalid Content-Length of '" + line.substr(16) + "'", 411);
                **}**

                if (content_length > max_content_length)
                **{**
                    std::ostringstream sout;
                    sout << "Content-Length of post back is too large.  It must be less than " << max_content_length;
                    throw http_parse_error(sout.str(), 413);
                **}**
            **}**
            // 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!
        read_with_limit(in, line);
    **}** // while (line != "\r")


    // 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(incoming.request_type, "PUT")) && 
        strings_equal_ignore_case(left_substr(content_type,";"), "application/x-www-form-urlencoded"))
    **{**
        if (content_length > 0)
        **{**
            incoming.body.resize(content_length);
            in.read(&incoming.body[0],content_length);
        **}**
        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);
    **}**


    if (!in)
        throw http_parse_error("Error parsing HTTP request", 500);

    return content_length;
**}**

// ----------------------------------------------------------------------------------------

void **read_body** (
    std::istream& in,
    incoming_things& incoming
)
**{**
    // if the body hasn't already been loaded and there is data to load
    if (incoming.body.size() == 0 &&
        incoming.headers.count("Content-Length") != 0)
    **{**
        const unsigned long content_length = string_cast<unsigned long>(incoming.headers["Content-Length"]);

        incoming.body.resize(content_length);
        if (content_length > 0)
        **{**
            in.read(&incoming.body[0],content_length);
        **}**
    **}**
**}**

// ----------------------------------------------------------------------------------------

void **write_http_response** (
    std::ostream& out,
    outgoing_things outgoing,
    const std::string& result
)
**{**
    using namespace http_impl;
    key_value_map& new_cookies      = outgoing.cookies;
    key_value_map_ci& response_headers = outgoing.headers;

    // only send this header if the user hasn't told us to send another kind
    bool has_content_type = false, has_location = false;
    for(key_value_map_ci::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";
    **}**

    response_headers["Content-Length"] = cast_to_string(result.size());

    out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n";

    // Set any new headers
    for(key_value_map_ci::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
    **{**
        out << ci->first << ": " << ci->second << "\r\n";
    **}**

    // set any cookies 
    for(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;
**}**

// ----------------------------------------------------------------------------------------

void **write_http_response** (
    std::ostream& out,
    const http_parse_error& e 
)
**{**
    outgoing_things outgoing;
    outgoing.http_return = e.http_error_code;
    outgoing.http_return_status = e.what();
    write_http_response(out, outgoing, std::string("Error processing request: ") + e.what());
**}**

// ----------------------------------------------------------------------------------------

void **write_http_response** (
    std::ostream& out,
    const std::exception& e 
)
**{**
    outgoing_things outgoing;
    outgoing.http_return = 500;
    outgoing.http_return_status = e.what();
    write_http_response(out, outgoing, std::string("Error processing request: ") + e.what());
**}**

// ----------------------------------------------------------------------------------------

const logger server_http::**dlog**("dlib.server_http");

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_SERVER_HTTP_CPp_