| Index: chrome/browser/chromeos/gdata/test_servers/http_request.cc
|
| diff --git a/chrome/browser/chromeos/gdata/test_servers/http_request.cc b/chrome/browser/chromeos/gdata/test_servers/http_request.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..62f2c857a608f88e335ab82948de1f5af05ad5ae
|
| --- /dev/null
|
| +++ b/chrome/browser/chromeos/gdata/test_servers/http_request.cc
|
| @@ -0,0 +1,274 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/chromeos/gdata/test_servers/http_request.h"
|
| +
|
| +#include <algorithm>
|
| +#include <map>
|
| +#include <string>
|
| +#include "base/basictypes.h"
|
| +#include "base/logging.h"
|
| +#include "googleurl/src/gurl.h"
|
| +
|
| +namespace gdata {
|
| +namespace test_servers {
|
| +
|
| +namespace {
|
| +int kRequestSizeLimit = 64 * 1024 * 1024; // 64 mb.
|
| +} // namespace
|
| +
|
| +HttpRequestParser::HttpRequestParser() : request_builder_(new HttpRequest()),
|
| + state_(REQUEST_LINE),
|
| + buffer_position_(0),
|
| + crlf_position_(-1),
|
| + crlf_checked_position_(0),
|
| + current_content_length_(0) {
|
| +}
|
| +
|
| +HttpRequestParser::~HttpRequestParser() {
|
| +}
|
| +
|
| +void HttpRequestParser::ProcessChunk(const char *data, int length) {
|
| + buffer_.append(data, length);
|
| + if (static_cast<int>(buffer_.length()) + length > kRequestSizeLimit) {
|
| + // Too big request. Treat it as a syntax error.
|
| + LOG(ERROR) << "The HTTP request is too large.";
|
| + state_ = SYNTAX_ERROR;
|
| + return;
|
| + }
|
| + if (crlf_position_ == -1 && state_ != DATA)
|
| + FindNextCrLf();
|
| +}
|
| +
|
| +HttpRequestParser::STATE HttpRequestParser::ParseRequest() {
|
| + while (state_ != READY &&
|
| + state_ != SYNTAX_ERROR &&
|
| + ShouldParseBuffer()) {
|
| + std::string token;
|
| + switch (state_) {
|
| + case REQUEST_LINE:
|
| + // Parse a method, eg. GET.
|
| + token = ShiftToken(IDENTIFIER);
|
| + if (token.empty()) {
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + }
|
| + std::transform(token.begin(), token.end(), token.begin(), ::toupper);
|
| + if (token == "OPTIONS") {
|
| + request_builder_->method = OPTIONS;
|
| + } else if (token == "GET") {
|
| + request_builder_->method = GET;
|
| + } else if (token == "HEAD") {
|
| + request_builder_->method = HEAD;
|
| + } else if (token == "POST") {
|
| + request_builder_->method = POST;
|
| + } else if (token == "PUT") {
|
| + request_builder_->method = PUT;
|
| + } else if (token == "DELETE") {
|
| + request_builder_->method = DELETE;
|
| + } else if (token == "TRACE") {
|
| + request_builder_->method = TRACE;
|
| + } else if (token == "CONNECT") {
|
| + request_builder_->method = CONNECT;
|
| + } else {
|
| + request_builder_->method = CUSTOM;
|
| + }
|
| +
|
| + // Parse an URL, eg. index.cgi.
|
| + token = ShiftToken(IDENTIFIER);
|
| + if (token.empty()) {
|
| + LOG(ERROR) << "The url token must not be empty.";
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + }
|
| + request_builder_->raw_url = token;
|
| +
|
| + // Parse a protocol, eg. HTTP/1.1.
|
| + // Currently we support only HTTP/1.1, since HTTP/1.0 is quite obsolete.
|
| + token = ShiftToken(LINE);
|
| + std::transform(token.begin(), token.end(), token.begin(), ::tolower);
|
| + if (token.empty() || token != "http/1.1") {
|
| + LOG(ERROR) << "This protocol is not supported: [" << token << "]";
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + }
|
| + request_builder_->protocol = token;
|
| + state_ = HEADER_LINE;
|
| + break;
|
| + case HEADER_LINE:
|
| + // If we have an empty line, then finish parsing headers and validate
|
| + // the request.
|
| + if (ShiftCrLf()) {
|
| + // TODO(mtomasz): Check if these are really case-insensitive in the
|
| + // rfc.
|
| + if (request_builder_->headers.find("host") ==
|
| + request_builder_->headers.end()) {
|
| + LOG(ERROR) << "Host header is required for HTTP/1.1";
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + } else {
|
| + // TODO(mtomasz): We add http:// protocol, but how about https://?
|
| + // Do we want to support it?
|
| + GURL host = GURL("http://" + request_builder_->headers["host"]);
|
| + request_builder_->url = host.Resolve(request_builder_->raw_url);
|
| + }
|
| + if (request_builder_->headers.find("content-length") !=
|
| + request_builder_->headers.end()) {
|
| + current_content_length_ =
|
| + atoi(request_builder_->headers["content-length"].c_str());
|
| + }
|
| + if (current_content_length_ > 0) {
|
| + state_ = DATA;
|
| + } else {
|
| + state_ = READY;
|
| + }
|
| + break;
|
| + }
|
| + {
|
| + // Parse a header name, eg. Content-length.
|
| + std::string header_name = ShiftToken(HEADER_KEY);
|
| + std::transform(header_name.begin(),
|
| + header_name.end(),
|
| + header_name.begin(),
|
| + ::tolower);
|
| + if (header_name.empty()) {
|
| + LOG(ERROR) << "Header key must not be empty.";
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + }
|
| +
|
| + // Parse a header value, eg. 100 (for Content-Length).
|
| + token = ShiftToken(LINE);
|
| + request_builder_->headers[header_name] = token;
|
| + break;
|
| + }
|
| + case DATA:
|
| + if (current_content_length_ ==
|
| + static_cast<int>(request_builder_->content.length())) {
|
| + // Entire data received.
|
| + state_ = READY;
|
| + } else {
|
| + int to_read = current_content_length_ -
|
| + request_builder_->content.length();
|
| + if (to_read > static_cast<int>(request_builder_->content.length()) -
|
| + buffer_position_) {
|
| + to_read = request_builder_->content.length() - buffer_position_;
|
| + }
|
| + request_builder_->content.append(ShiftData(to_read), to_read);
|
| + }
|
| + break;
|
| + case SYNTAX_ERROR:
|
| + break;
|
| + default:
|
| + state_ = SYNTAX_ERROR;
|
| + break;
|
| + }
|
| + }
|
| + return state_;
|
| +}
|
| +
|
| +scoped_ptr<HttpRequest> HttpRequestParser::GetRequest() {
|
| + DCHECK(state_ == READY);
|
| + scoped_ptr<HttpRequest> result = request_builder_.Pass();
|
| + request_builder_.reset(new HttpRequest());
|
| +
|
| + // Reset the parser state.
|
| + state_ = REQUEST_LINE;
|
| + buffer_position_ = 0;
|
| + buffer_.clear();
|
| + crlf_position_ = -1;
|
| + crlf_checked_position_ = 0;
|
| + current_content_length_ = 0;
|
| +
|
| + return result.Pass();
|
| +}
|
| +
|
| +std::string HttpRequestParser::ShiftToken(SHIFT_TOKEN_TYPE token_type) {
|
| + SkipSpaces(); // Ignore any spaces from the beginning.
|
| + size_t found_position;
|
| + int token_start_position = buffer_position_;
|
| +
|
| + switch (token_type) {
|
| + case IDENTIFIER:
|
| + found_position = buffer_.find(' ', buffer_position_);
|
| + if (found_position != std::string::npos) {
|
| + buffer_position_ = static_cast<int>(found_position) + 1;
|
| + return buffer_.substr(token_start_position,
|
| + found_position - token_start_position);
|
| + }
|
| + break;
|
| + case HEADER_KEY:
|
| + found_position = buffer_.find(':', buffer_position_);
|
| + if (found_position != std::string::npos) {
|
| + buffer_position_ = static_cast<int>(found_position) + 1;
|
| + return buffer_.substr(token_start_position,
|
| + found_position - token_start_position);
|
| + }
|
| + break;
|
| + case LINE:
|
| + DCHECK(crlf_position_ != -1) << "CRLF not found, but expected.";
|
| + buffer_position_ = crlf_position_ + 2;
|
| + int token_length = crlf_position_ - token_start_position;
|
| + FindNextCrLf();
|
| + return buffer_.substr(token_start_position, token_length);
|
| + break;
|
| + }
|
| +
|
| + return "";
|
| +}
|
| +
|
| +bool HttpRequestParser::ShiftCrLf() {
|
| + if (buffer_position_ == crlf_position_) {
|
| + buffer_position_ += 2;
|
| + FindNextCrLf();
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +const char* HttpRequestParser::ShiftData(int length) {
|
| + DCHECK(length <= static_cast<int>(request_builder_->content.length()) -
|
| + buffer_position_) << "Tried to read more than available.";
|
| + const char *result = buffer_.data() + buffer_position_;
|
| + buffer_position_ += length;
|
| +
|
| + return result;
|
| +}
|
| +
|
| +void HttpRequestParser::SkipSpaces() {
|
| + const char *buffer_data = buffer_.data();
|
| + int buffer_left = static_cast<int>(buffer_.length()) - buffer_position_;
|
| +
|
| + // Ignore any spaces from the beginning.
|
| + while (buffer_left && buffer_data[buffer_position_] == ' ') {
|
| + buffer_position_++;
|
| + buffer_left--;
|
| + }
|
| +}
|
| +
|
| +bool HttpRequestParser::ShouldParseBuffer() {
|
| + return buffer_position_ < static_cast<int>(buffer_.length()) &&
|
| + (crlf_position_ != -1 || state_ == DATA);
|
| +}
|
| +
|
| +bool HttpRequestParser::FindNextCrLf() {
|
| + if (!buffer_.length()) {
|
| + return false;
|
| + }
|
| +
|
| + size_t found_position = buffer_.find("\r\n", crlf_checked_position_);
|
| + if (found_position == std::string::npos) {
|
| + crlf_position_ = -1;
|
| + crlf_checked_position_ = buffer_.length() - 1;
|
| + return false;
|
| + }
|
| +
|
| + crlf_position_ = static_cast<int>(found_position);
|
| + crlf_checked_position_ = crlf_position_ + 2;
|
| + return true;
|
| +}
|
| +
|
| +} // namespace test_servers
|
| +} // namespace gdata
|
|
|