| // |
| // request_parser.cpp |
| // ~~~~~~~~~~~~~~~~~~ |
| // |
| // Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
| // |
| // 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) |
| // |
| |
| #include "request_parser.hpp" |
| #include <algorithm> |
| #include <cctype> |
| #include <boost/lexical_cast.hpp> |
| #include "request.hpp" |
| |
| namespace http { |
| namespace server4 { |
| |
| #include "yield.hpp" // Enable the pseudo-keywords reenter, yield and fork. |
| |
| std::string request_parser::content_length_name_ = "Content-Length"; |
| |
| boost::tribool request_parser::consume(request& req, char c) |
| { |
| reenter (this) |
| { |
| req.method.clear(); |
| req.uri.clear(); |
| req.http_version_major = 0; |
| req.http_version_minor = 0; |
| req.headers.clear(); |
| req.content.clear(); |
| content_length_ = 0; |
| |
| // Request method. |
| while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ' ') |
| { |
| req.method.push_back(c); |
| yield return boost::indeterminate; |
| } |
| if (req.method.empty()) |
| return false; |
| |
| // Space. |
| if (c != ' ') return false; |
| yield return boost::indeterminate; |
| |
| // URI. |
| while (!is_ctl(c) && c != ' ') |
| { |
| req.uri.push_back(c); |
| yield return boost::indeterminate; |
| } |
| if (req.uri.empty()) return false; |
| |
| // Space. |
| if (c != ' ') return false; |
| yield return boost::indeterminate; |
| |
| // HTTP protocol identifier. |
| if (c != 'H') return false; |
| yield return boost::indeterminate; |
| if (c != 'T') return false; |
| yield return boost::indeterminate; |
| if (c != 'T') return false; |
| yield return boost::indeterminate; |
| if (c != 'P') return false; |
| yield return boost::indeterminate; |
| |
| // Slash. |
| if (c != '/') return false; |
| yield return boost::indeterminate; |
| |
| // Major version number. |
| if (!is_digit(c)) return false; |
| while (is_digit(c)) |
| { |
| req.http_version_major = req.http_version_major * 10 + c - '0'; |
| yield return boost::indeterminate; |
| } |
| |
| // Dot. |
| if (c != '.') return false; |
| yield return boost::indeterminate; |
| |
| // Minor version number. |
| if (!is_digit(c)) return false; |
| while (is_digit(c)) |
| { |
| req.http_version_minor = req.http_version_minor * 10 + c - '0'; |
| yield return boost::indeterminate; |
| } |
| |
| // CRLF. |
| if (c != '\r') return false; |
| yield return boost::indeterminate; |
| if (c != '\n') return false; |
| yield return boost::indeterminate; |
| |
| // Headers. |
| while ((is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != '\r') |
| || (c == ' ' || c == '\t')) |
| { |
| if (c == ' ' || c == '\t') |
| { |
| // Leading whitespace. Must be continuation of previous header's value. |
| if (req.headers.empty()) return false; |
| while (c == ' ' || c == '\t') |
| yield return boost::indeterminate; |
| } |
| else |
| { |
| // Start the next header. |
| req.headers.push_back(header()); |
| |
| // Header name. |
| while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ':') |
| { |
| req.headers.back().name.push_back(c); |
| yield return boost::indeterminate; |
| } |
| |
| // Colon and space separates the header name from the header value. |
| if (c != ':') return false; |
| yield return boost::indeterminate; |
| if (c != ' ') return false; |
| yield return boost::indeterminate; |
| } |
| |
| // Header value. |
| while (is_char(c) && !is_ctl(c) && c != '\r') |
| { |
| req.headers.back().value.push_back(c); |
| yield return boost::indeterminate; |
| } |
| |
| // CRLF. |
| if (c != '\r') return false; |
| yield return boost::indeterminate; |
| if (c != '\n') return false; |
| yield return boost::indeterminate; |
| } |
| |
| // CRLF. |
| if (c != '\r') return false; |
| yield return boost::indeterminate; |
| if (c != '\n') return false; |
| |
| // Check for optional Content-Length header. |
| for (std::size_t i = 0; i < req.headers.size(); ++i) |
| { |
| if (headers_equal(req.headers[i].name, content_length_name_)) |
| { |
| try |
| { |
| content_length_ = |
| boost::lexical_cast<std::size_t>(req.headers[i].value); |
| } |
| catch (boost::bad_lexical_cast&) |
| { |
| return false; |
| } |
| } |
| } |
| |
| // Content. |
| while (req.content.size() < content_length_) |
| { |
| yield return boost::indeterminate; |
| req.content.push_back(c); |
| } |
| } |
| |
| return true; |
| } |
| |
| #include "unyield.hpp" // Disable the pseudo-keywords reenter, yield and fork. |
| |
| bool request_parser::is_char(int c) |
| { |
| return c >= 0 && c <= 127; |
| } |
| |
| bool request_parser::is_ctl(int c) |
| { |
| return (c >= 0 && c <= 31) || (c == 127); |
| } |
| |
| bool request_parser::is_tspecial(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; |
| } |
| } |
| |
| bool request_parser::is_digit(int c) |
| { |
| return c >= '0' && c <= '9'; |
| } |
| |
| bool request_parser::tolower_compare(char a, char b) |
| { |
| return std::tolower(a) == std::tolower(b); |
| } |
| |
| bool request_parser::headers_equal(const std::string& a, const std::string& b) |
| { |
| if (a.length() != b.length()) |
| return false; |
| |
| return std::equal(a.begin(), a.end(), b.begin(), |
| &request_parser::tolower_compare); |
| } |
| |
| } // namespace server4 |
| } // namespace http |