OSDN Git Service

Refactoring HTTP parser.
[ultramonkey-l7/sslproxy.git] / src / http_message.cpp
diff --git a/src/http_message.cpp b/src/http_message.cpp
new file mode 100644 (file)
index 0000000..9af3ec3
--- /dev/null
@@ -0,0 +1,538 @@
+/*
+ * @file  http_message.cpp
+ * @brief module of HTTP Message
+ * @brief HTTP Message parser
+ *
+ * Copyright (C) 2009  NTT COMWARE Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ **********************************************************************/
+
+#include "http_message.h"
+
+/*!
+ * HTTP Message constructor.
+ */
+http_message::http_message()
+    :
+    modified(false)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 1, // XXX log
+        "in/out_function : Constructor http_message::http_message(void)");
+    }
+    /*------ DEBUG LOG END ------*/
+}
+
+/*!
+ * HTTP Message constructor.
+ * Parse HTTP message header.
+ *
+ * @param[in]   header  full http message header string
+ */
+http_message::http_message(std::string header)
+    :
+    modified(false)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 1, // XXX log
+        "in/out_function : Constructor http_message::http_message(std::string header) : "
+        "header(%s)", header.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+    this->parse(header);
+}
+
+/*!
+ * HTTP Message destructor.
+ */
+http_message::~http_message()
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 2, // XXX log
+        "in/out_function : Destructor http_message::~http_message(void)");
+    }
+    /*------ DEBUG LOG END ------*/
+}
+
+/*!
+ * Get HTTP header field function.
+ *
+ * @param[in]   field_name  lookup field name
+ * @return      header field value
+ */
+field_range http_message::header(std::string field_name)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : field_range http_message::header(std::string field_name) : "
+        "field_name(%s)", field_name.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    std::string name = convert_upper_camel_case(field_name);
+    field_range ret = this->_header.get<field_map>().equal_range(name);
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : field_range http_message::header(std::string field_name)");
+    }
+    /*------ DEBUG LOG END ------*/
+
+    return ret;
+}
+
+/*!
+ * Set HTTP header field function.
+ * Set new HTTP header field and return old HTTP header field.
+ *
+ * @param[in]   field_name  lookup field name
+ * @param[in]   field_value field value
+ */
+void http_message::header(std::string field_name, std::string field_value)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : field_range http_message::header(std::string field_name, std::string field_value) : "
+        "field_name(%s), field_value(%s)", field_name.c_str(), field_value.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    bool changed = false;
+    std::string name = convert_upper_camel_case(field_name);
+    field_range ret = this->_header.get<field_map>().equal_range(name);
+    field_map_iterator it = ret.first;
+    field_map_iterator it_end = ret.second;
+    try {
+        for (;it != it_end; ++it) {
+            if (field_value != "") {
+                if ( _header.get<field_map>().replace(it, field(name, field_value)) ) {
+                    changed = true;
+                    this->modified = true;
+                }
+            }
+            else {
+                _header.get<field_map>().erase(it);
+                changed = true;
+                this->modified = true;
+            }
+        }
+        if (!changed && field_value != "") {
+            _header.get<field_map>().insert( field(name, field_value) );
+            this->modified = true;
+        }
+    }
+    catch (...) {
+        // XXX log: insert, replace (no throw by erase)
+    }
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : field_range http_message::header(std::string field_name, std::string field_value)");
+    }
+    /*------ DEBUG LOG END ------*/
+}
+
+/*!
+ * Get message body function.
+ *
+ * @return    message body
+ */
+std::string http_message::body()
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : std::string http_message::body(void)");
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : std::string http_message::body(void) : "
+        "return(%s)", this->_body.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    return this->_body;
+}
+
+/*!
+ * Set message body function.
+ * Set new message body and return old message body.
+ *
+ * @param[in]   _body   new message body
+ * @return  old message body
+ */
+std::string http_message::body(std::string _body)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : std::string http_message::http_version(std::string _message) : "
+        "_body(%s)", _body.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    std::string ret = this->_body;
+    this->_body = _body;
+    this->modified = true;
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : std::string http_message::body(std::string _body) : "
+        "return(%s)", ret.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    return ret;
+}
+
+/*!
+ * Get full HTTP message function.
+ *
+ * @return    HTTP message
+ */
+std::string http_message::as_string()
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : std::string http_message::as_string(void)");
+    }
+    /*------ DEBUG LOG END ------*/
+
+    if (this->modified)
+        this->rebuild();
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : std::string http_message::as_string(void) : "
+        "return(%s)", this->raw_message.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    return this->raw_message;
+}
+
+/*!
+ * Parse HTTP header function.
+ *
+ * @param[in]   message     full HTTP message header
+ */
+void http_message::parse(std::string message)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : void http_message::parse(std::string message) : "
+        "message(%s)", message.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    // save raw message
+    if (this->raw_message.length() == 0)
+        this->raw_message = message;
+
+    // parse message
+    HTTP_MESSAGE_POSITION pos = MESSAGE_TOP;
+
+    /*
+     * RFC2616
+     *  OCTET       : 8bit data
+     *  CHAR        : US-ASCII(0-127)
+     *  UPALPHA     : A-Z
+     *  LOALPHA     : a-z
+     *  ALPHA       : UPALPHA | LOALPHA
+     *  DIGIT       : 0-9
+     *  HEXDIG      : A-F | a-f | DIGIT
+     *  SP          : SPace(32)
+     *  HT          : Horizontal Tab(9)
+     *  CR          : Carriage Return(13)
+     *  LF          : Line Feed(10)
+     *  CTL         : ConTLol char(0-31,127)
+     *  LWS         : [CRLF] 1*(SP|HT)
+     *  separators  : ()<>@,;:\"/[]?={} and SP, HT
+     *  token       : 1*(CHAR not CTL, separators)
+     */
+    std::string::iterator ptr = message.begin();
+    std::string::iterator end = message.end();
+    std::string::iterator start = ptr;
+    std::pair<std::string, std::string> field_pair;
+    while (ptr != end) {
+        switch(pos) {
+        /*
+         * MESSAGE-HEADER   : field-name ":" [ field-value ]
+         * field-name       : token
+         * field-value      : *( field-content | LWS )
+         * field-content    : <the OCTETs making up the field-value and
+         *                    consisting of either *TEXT or combinations
+         *                    of token, separators, and quoted-string>
+         * TEXT             : <any OCTET except CTLs, but including LWS>
+         * quoted-string    : ( <"> *(qdtext | quoted-pair ) <"> )
+         * qdtext           : <any TEXT except <">>
+         * quoted-pair      : "\" CHAR
+         */
+        case MESSAGE_TOP:
+            if (isalpha(*ptr) || *ptr == '-' || isdigit(*ptr) || 
+                *ptr == '.' || *ptr == '_' || *ptr == '~' || *ptr == '!' ||
+                *ptr == '$' || *ptr == '&' || *ptr == '*' || *ptr == '+' ||
+                *ptr == '%') {
+                start = ptr;
+                pos = MESSAGE_FIELD_NAME;
+            } else if (*ptr == '\r') { // CRLF + CRLF
+                pos = MESSAGE_LAST_CR;
+            } else {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : not CRLF");
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        case MESSAGE_CR:
+            // LF only
+            if (*ptr == '\n') {
+                pos = MESSAGE_LF;
+            } else {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_request::parse(std::string request) : not CRLF");
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        case MESSAGE_LF:
+            if (isalpha(*ptr) || *ptr == '-' || isdigit(*ptr) || 
+                *ptr == '.' || *ptr == '_' || *ptr == '~' || *ptr == '!' ||
+                *ptr == '$' || *ptr == '&' || *ptr == '*' || *ptr == '+' ||
+                *ptr == '%') {
+                if (field_pair.first.length()) {
+                    field_pair.first = convert_upper_camel_case(field_pair.first);
+                    boost::trim(field_pair.second);
+                    _header.get<field_map>().insert(field_pair);
+                    field_pair.first.clear();
+                }
+                start = ptr;
+                pos = MESSAGE_FIELD_NAME;
+            } else if (*ptr == ' ' || *ptr == '\t') {
+                pos = MESSAGE_FIELD_VALUE;
+            } else if (*ptr == '\r') { // CRLF + CRLF
+                if (field_pair.first.length()) {
+                    field_pair.first = convert_upper_camel_case(field_pair.first);
+                    boost::trim(field_pair.second);
+                    _header.get<field_map>().insert(field_pair);
+                    field_pair.first.clear();
+                }
+                pos = MESSAGE_LAST_CR;
+            } else {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : not CRLF");
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        case MESSAGE_FIELD_NAME:
+            // field-name end with ':'
+            if (*ptr == ':') {
+                pos = MESSAGE_FIELD_NAME_COLON;
+                field_pair.first.assign(start, ptr);
+            } else if (!isalpha(*ptr) && *ptr != '-' && !isdigit(*ptr) && 
+                *ptr != '.' && *ptr != '_' && *ptr != '~' && *ptr != '!' &&
+                *ptr != '$' && *ptr != '&' && *ptr != '*' && *ptr != '+' &&
+                *ptr != '%') {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : Invalid header field name.");
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        case MESSAGE_FIELD_NAME_COLON:
+            if (*ptr == ' ' || isalpha(*ptr) || isdigit(*ptr) || *ptr == '-' ||
+                *ptr == '.' || *ptr == '_' || *ptr == '~' || *ptr == ':' || 
+                *ptr == '@' || *ptr == '!' || *ptr == '$' || *ptr == '&' ||
+                *ptr == '(' || *ptr == ')' || *ptr == '*' || *ptr == '+' ||
+                *ptr == ',' || *ptr == ';' || *ptr == '=' || *ptr == '%' ||
+                *ptr == '<' || *ptr == '>' || *ptr == '[' || *ptr == ']' ||
+                *ptr == '{' || *ptr == '}' || *ptr == '?' || *ptr == '"' ||
+                *ptr == '|' || *ptr == '/' || *ptr == '\\' || *ptr == '\t') {
+                start = ptr;
+                pos = MESSAGE_FIELD_VALUE;
+            } else if (*ptr == '\r') { // omit field value
+                field_pair.second.clear();
+            } else {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : Invalid header field value.('%c')", *ptr);
+                }
+                /*------ DEBUG LOG END ------*/
+            }
+            break;
+
+        case MESSAGE_FIELD_VALUE:
+            // field-value end with CR
+            if (*ptr == '\r') {
+                pos = MESSAGE_CR;
+                field_pair.second.assign(start, ptr);
+            } else if (*ptr != ' ' && !isalpha(*ptr) && !isdigit(*ptr) && *ptr != '-' &&
+                *ptr != '.' && *ptr != '_' && *ptr != '~' && *ptr != ':' && 
+                *ptr != '@' && *ptr != '!' && *ptr != '$' && *ptr != '&' &&
+                *ptr != '(' && *ptr != ')' && *ptr != '*' && *ptr != '+' &&
+                *ptr != ',' && *ptr != ';' && *ptr != '=' && *ptr != '%' &&
+                *ptr != '<' && *ptr != '>' && *ptr != '[' && *ptr != ']' &&
+                *ptr != '{' && *ptr != '}' && *ptr != '?' && *ptr != '"' &&
+                *ptr != '|' && *ptr != '/' && *ptr != '\\'&& *ptr != '\t' ) {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : Invalid header field value.('%c')", *ptr);
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        case MESSAGE_LAST_CR:
+            // LF only
+            if (*ptr == '\n') {
+                pos = MESSAGE_LAST_LF;
+            } else {
+                /*-------- DEBUG LOG --------*/
+                if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+                    LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+                    "out_function : void http_message::parse(std::string message) : not CRLF");
+                }
+                /*------ DEBUG LOG END ------*/
+                throw -1;
+            }
+            break;
+
+        /*
+         * MESSAGE-BODY     : *OCTET
+         */
+        case MESSAGE_LAST_LF:
+            pos = MESSAGE_BODY;
+            start = ptr;
+            break;
+
+        case MESSAGE_BODY:
+            break;
+        }
+        ptr++;
+    }
+
+    switch (pos) {
+    case MESSAGE_BODY:
+        this->_body.assign(start, ptr);
+    }
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : void http_message::parse(std::string message)");
+    }
+    /*------ DEBUG LOG END ------*/
+}
+
+/*!
+ * Rebuild HTTP header function.
+ */
+void http_message::rebuild()
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : void http_message::rebuild()");
+    }
+    /*------ DEBUG LOG END ------*/
+
+    // insertion order
+    header_container::iterator it = this->_header.begin();
+    header_container::iterator it_end = this->_header.end();
+
+    while (it != it_end) {
+        this->raw_message += it->first + ": " + it->second + "\r\n";
+        it++;
+    }
+
+    this->raw_message += "\r\n" + this->body();
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : void http_message::rebuild()");
+    }
+    /*------ DEBUG LOG END ------*/
+}
+
+/*!
+ * Field name convert function.
+ *
+ * @param[in]   field_name  field name
+ * @return  converted to camel case
+ */
+std::string http_message::convert_upper_camel_case(std::string field_name)
+{
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "in_function : std::string http_message::upper_camel_case(std::string field_name) : "
+        "field_name(%s)", field_name.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+
+    std::string ret;
+    boost::char_separator<char> sep("-_0123456789", "-_0123456789", boost::keep_empty_tokens);
+    boost::tokenizer<boost::char_separator<char> > tokens(field_name, sep);
+    boost::tokenizer<boost::char_separator<char> >::iterator tok_it  = tokens.begin();
+    boost::tokenizer<boost::char_separator<char> >::iterator tok_end = tokens.end();
+    for (; tok_it != tok_end; ++tok_it) {
+        std::string token(*tok_it);
+        boost::to_lower(token);
+        token.at(0) = std::toupper(token.at(0));
+        ret += token;
+    }
+
+    /*-------- DEBUG LOG --------*/
+    if (LOG_LV_DEBUG == logger_get_log_level(LOG_CAT_PACKET_EDIT_HTTP)) {
+        LOGGER_PUT_LOG_DEBUG(LOG_CAT_PACKET_EDIT_HTTP, 3, // XXX log
+        "out_function : std::string http_message::upper_camel_case(std::string field_name) : "
+        "return(%s)", ret.c_str());
+    }
+    /*------ DEBUG LOG END ------*/
+    return ret;
+}