// This file is part of the Boost Network library
// Based on the Pion Network Library (r421)
// Copyright Atomic Labs, Inc. 2007-2008
// See http://cpp-netlib.sourceforge.net for library home page.
//
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_NETWORK_PROTOCOL_HTTP_PARSER_HPP
#define BOOST_NETWORK_PROTOCOL_HTTP_PARSER_HPP

#include <boost/network/protocol/http/traits.hpp>
#include <boost/network/traits/string.hpp>
#include <boost/network/message.hpp>
#include <boost/logic/tribool.hpp>
#include <boost/cstdint.hpp>
#include <boost/noncopyable.hpp>
#include <string>

namespace boost { namespace network { namespace http {
    
    // forward declarations used to finish HTTP requests
    template <typename Tag>
    class basic_request;

    // forward declarations used to finish HTTP requests
    template <typename Tag>
    class basic_response;
    
    /// an incremental HTTP 1.0/1.1 protocol parser
    template <typename Tag, typename ParserTraits = parser_traits<Tag> >
    class basic_parser :
        private boost::noncopyable
    {
    public:
        
		// import types from ParserTraits template
		typedef ParserTraits    traits_type;
        typedef typename string<Tag>::type string_type;

        // default destructor
        virtual ~basic_parser() {}
    
        /**
         * creates a new HTTP protocol parser
         *
         * @param is_request if true, the message is parsed as an HTTP request;
         *                   if false, the message is parsed as an HTTP response
         */
        basic_parser(const bool is_request) :
            m_is_request(is_request), m_read_ptr(NULL), m_read_end_ptr(NULL),
            m_headers_parse_state(is_request ? PARSE_METHOD_START : PARSE_HTTP_VERSION_H),
            m_chunked_content_parse_state(PARSE_CHUNK_SIZE_START),
            m_status_code(0), m_bytes_last_read(0), m_bytes_total_read(0)
        {}
        
        /**
         * parses an HTTP message up to the end of the headers using bytes 
         * available in the read buffer
         *
         * @param http_msg the HTTP message object to populate from parsing
         *
         * @return boost::tribool result of parsing:
         *                        false = message has an error,
         *                        true = finished parsing HTTP headers,
         *                        indeterminate = not yet finished parsing HTTP headers
         */
        boost::tribool parse_http_headers(basic_message<Tag>& http_msg);
        
        /**
         * parses a chunked HTTP message-body using bytes available in the read buffer
         *
         * @param chunk_buffers buffers to be populated from parsing chunked content
         *
         * @return boost::tribool result of parsing:
         *                        false = message has an error,
         *                        true = finished parsing message,
         *                        indeterminate = message is not yet finished
         */
        boost::tribool parse_chunks(types::chunk_cache_t& chunk_buffers);
    
        /**
         * prepares the payload content buffer and consumes any content remaining
         * in the parser's read buffer
         *
         * @param http_msg the HTTP message object to consume content for
         * @return unsigned long number of content bytes consumed, if any
         */
        std::size_t consume_content(basic_message<Tag>& http_msg);
    
        /**
         * consume the bytes available in the read buffer, converting them into
         * the next chunk for the HTTP message
         *
         * @param chunk_buffers buffers to be populated from parsing chunked content
         * @return unsigned long number of content bytes consumed, if any
         */
        std::size_t consume_content_as_next_chunk(types::chunk_cache_t& chunk_buffers);
        
        /**
         * finishes parsing an HTTP request message (copies over request-only data)
         *
         * @param http_request the HTTP request object to finish
         */
        void finish(basic_request<Tag>& http_request);
        
        /**
         * finishes an HTTP response message (copies over response-only data)
         *
         * @param http_request the HTTP response object to finish
         */
        void finish(basic_response<Tag>& http_response);
    
        /**
         * resets the location and size of the read buffer
         *
         * @param ptr pointer to the first bytes available to be read
         * @param len number of bytes available to be read
         */
        inline void set_read_buffer(const char *ptr, std::size_t len) {
            m_read_ptr = ptr;
            m_read_end_ptr = ptr + len;
        }
        
        /**
         * saves the current read position bookmark
         *
         * @param read_ptr points to the next character to be consumed in the read_buffer
         * @param read_end_ptr points to the end of the read_buffer (last byte + 1)
         */
        inline void save_read_position(const char *&read_ptr, const char *&read_end_ptr) const {
            read_ptr = m_read_ptr;
            read_end_ptr = m_read_end_ptr;
        }
        
        /// resets the parser to its initial state
        inline void reset(void);
    
        /// returns true if there are no more bytes available in the read buffer
        inline bool eof(void) const {
            return m_read_ptr == NULL || m_read_ptr >= m_read_end_ptr;
        }

        /// returns the number of bytes read during the last parse operation
        inline std::size_t gcount(void) const { return m_bytes_last_read; }
        
        /// returns the total number of bytes read while parsing the HTTP message
        inline std::size_t bytes_read(void) const { return m_bytes_total_read; }

        /// returns the number of bytes available in the read buffer
        inline std::size_t bytes_available(void) const {
            return (eof() ? 0 : (m_read_end_ptr - m_read_ptr));
        }
        
        
    protected:
                    
        /**
         * parse key-value pairs out of a url-encoded string
         * (i.e. this=that&a=value)
         * 
         * @param params container for key-values string pairs
         * @param ptr points to the start of the encoded string
         * @param len length of the encoded string, in bytes
         * 
         * @return bool true if successful
         */
        static bool parse_url_encoded(types::query_params& params,
                                      const char *ptr, const std::size_t len);

        /**
         * parse key-value pairs out of a "Cookie" request header
         * (i.e. this=that; a=value)
         * 
         * @param params container for key-values string pairs
         * @param cookie_header header string to be parsed
         * 
         * @return bool true if successful
         */
        static bool parse_cookie_header(types::cookie_params& params,
                                        const string_type& cookie_header);


        /// true if the message is an HTTP request; false if it is an HTTP response
        const bool							m_is_request;
        
        /// points to the next character to be consumed in the read_buffer
        const char *						m_read_ptr;
        
        /// points to the end of the read_buffer (last byte + 1)
        const char *						m_read_end_ptr;


    private:

        // returns true if the argument is a special character
        inline static bool is_special(int c) {
            switch (c) {
            case '(': case ')': case '<': case '>': case '@':
            case ',': case ';': case ':': case '\\': case '"':
            case '/': case '[': case ']': case '?': case '=':
            case '{': case '}': case ' ': case '\t':
                return true;
            default:
                return false;
            }
        }
        
        // returns true if the argument is a character
        inline static bool is_char(int c) {
            return(c >= 0 && c <= 127);
        }
    
        // returns true if the argument is a control character
        inline static bool is_control(int c) {
            return( (c >= 0 && c <= 31) || c == 127);
        }
    
        // returns true if the argument is a digit
        inline static bool is_digit(int c) {
            return(c >= '0' && c <= '9');
        }
    
        // returns true if the argument is a hexadecimal digit
        inline static bool is_hex_digit(int c) {
            return((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
        }


        /// state used to keep track of where we are in parsing the HTTP headers
        enum headers_parse_state_t {
            PARSE_METHOD_START, PARSE_METHOD, PARSE_URI_STEM, PARSE_URI_QUERY,
            PARSE_HTTP_VERSION_H, PARSE_HTTP_VERSION_T_1, PARSE_HTTP_VERSION_T_2,
            PARSE_HTTP_VERSION_P, PARSE_HTTP_VERSION_SLASH,
            PARSE_HTTP_VERSION_MAJOR_START, PARSE_HTTP_VERSION_MAJOR,
            PARSE_HTTP_VERSION_MINOR_START, PARSE_HTTP_VERSION_MINOR,
            PARSE_STATUS_CODE_START, PARSE_STATUS_CODE, PARSE_STATUS_MESSAGE,
            PARSE_EXPECTING_NEWLINE, PARSE_EXPECTING_CR,
            PARSE_HEADER_WHITESPACE, PARSE_HEADER_START, PARSE_HEADER_NAME,
            PARSE_SPACE_BEFORE_HEADER_VALUE, PARSE_HEADER_VALUE,
            PARSE_EXPECTING_FINAL_NEWLINE, PARSE_EXPECTING_FINAL_CR
        };
    
        /// state used to keep track of where we are in parsing chunked content
        enum chunked_content_parse_state_t {
            PARSE_CHUNK_SIZE_START, PARSE_CHUNK_SIZE, 
            PARSE_EXPECTING_CR_AFTER_CHUNK_SIZE,
            PARSE_EXPECTING_LF_AFTER_CHUNK_SIZE, PARSE_CHUNK, 
            PARSE_EXPECTING_CR_AFTER_CHUNK, PARSE_EXPECTING_LF_AFTER_CHUNK,
            PARSE_EXPECTING_FINAL_CR_AFTER_LAST_CHUNK, 
            PARSE_EXPECTING_FINAL_LF_AFTER_LAST_CHUNK
        };


        /// the current state of parsing HTTP headers
        headers_parse_state_t				m_headers_parse_state;
    
        /// the current state of parsing chunked content
        chunked_content_parse_state_t		m_chunked_content_parse_state;
    
        /// Used for parsing the HTTP response status code
        boost::uint16_t						m_status_code;
        
        /// Used for parsing the HTTP response status message
        string_type							m_status_message;
        
        /// Used for parsing the request method
        string_type							m_method;
        
        /// Used for parsing the name of resource requested
        string_type							m_resource;
        
        /// Used for parsing the query string portion of a URI
        string_type							m_query_string;
        
        /// Used for parsing the name of HTTP headers
        string_type							m_header_name;
    
        /// Used for parsing the value of HTTP headers
        string_type							m_header_value;
    
        /// Used for parsing the chunk size
        string_type							m_chunk_size_str;
    
        /// Used for parsing the current chunk
        std::vector<char>					m_current_chunk;
    
        /// number of bytes in the chunk currently being parsed
        std::size_t 						m_size_of_current_chunk;
    
        /// number of bytes read so far in the chunk currently being parsed
        std::size_t 						m_bytes_read_in_current_chunk;
    
        /// number of bytes read during last parse operation
        std::size_t 						m_bytes_last_read;
        
        /// total number of bytes read while parsing the HTTP message
        std::size_t 						m_bytes_total_read;
    };

    /// typedef for the default HTTP protocol parser implementation
    typedef basic_parser<tags::default_>    parser;

}; // namespace http

}; // namespace network

}; // namespace boost

// import implementation file
#include <boost/network/protocol/http/impl/parser.ipp>

#endif // BOOST_NETWORK_PROTOCOL_HTTP_PARSER_HPP
