Chromium Code Reviews| 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..c6e9fe1ce63e5341173120582e2c2991df339e6b |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/gdata/test_servers/http_request.cc |
| @@ -0,0 +1,273 @@ |
| +// 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. |
|
satorux1
2012/10/16 03:12:18
This server is used only for testing, so I think w
mtomasz
2012/11/08 13:29:59
Discussed offline, leaving as it is.
|
| +} // 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(); |
|
satorux1
2012/10/16 06:39:48
I was confused about this. Why do we want to find
mtomasz
2012/11/08 13:29:59
You were right. Parser rewritten and simplified. D
|
| +} |
| + |
| +HttpRequestParser::STATE HttpRequestParser::ParseRequest() { |
|
satorux1
2012/10/16 03:12:18
The state machine code looks efficient, but for th
mtomasz
2012/11/08 13:29:59
Done. Please check the new parser.
|
| + while (state_ != READY && |
| + state_ != SYNTAX_ERROR && |
| + ShouldParseBuffer()) { |
| + // StringPiece is converted to String, since a copy is needed in most cases |
| + // to perform upper/lower case conversion. |
| + std::string token; |
| + switch (state_) { |
| + case REQUEST_LINE: |
| + // Parse a method, eg. GET. |
| + token = ShiftToken(IDENTIFIER).as_string(); |
| + if (token.empty()) { |
| + state_ = SYNTAX_ERROR; |
| + break; |
| + } |
| + std::transform(token.begin(), token.end(), token.begin(), ::toupper); |
|
satorux1
2012/10/16 06:39:48
StringToUpperASCII()
mtomasz
2012/11/08 13:29:59
Done.
|
| + 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 { |
| + // TODO(mtomasz): Implement other methods. |
|
satorux1
2012/10/16 06:39:48
You can do
NOTREACHED() << "Unsupported method: "
mtomasz
2012/11/08 13:29:59
Done.
|
| + request_builder_->method = CUSTOM; |
| + } |
|
satorux1
2012/10/16 06:39:48
Please create a function to convert string to meth
mtomasz
2012/11/08 13:29:59
Done.
|
| + |
| + // Parse an URL, eg. index.cgi. |
| + token = ShiftToken(IDENTIFIER).as_string(); |
| + if (token.empty()) { |
| + LOG(ERROR) << "The url token must not be empty."; |
| + state_ = SYNTAX_ERROR; |
| + break; |
| + } |
| + request_builder_->raw_url = token; |
|
satorux1
2012/10/16 06:39:48
raw_url seems to be a misnomer. This is path + que
mtomasz
2012/11/08 13:29:59
Path_and_query looks to be quite long. How about j
|
| + |
| + // Parse a protocol, eg. HTTP/1.1. |
| + // Currently we support only HTTP/1.1, since HTTP/1.0 is quite obsolete. |
| + token = ShiftToken(LINE).as_string(); |
| + 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()) { |
| + if (request_builder_->headers.find("host") == |
| + request_builder_->headers.end()) { |
| + LOG(ERROR) << "Host header is required for HTTP/1.1"; |
| + state_ = SYNTAX_ERROR; |
|
satorux1
2012/10/16 06:39:48
I think you don't have to check this. We can assum
mtomasz
2012/11/08 13:29:59
Done.
|
| + 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"]); |
|
satorux1
2012/10/16 06:39:48
Let's just use http://localhost for now
mtomasz
2012/11/08 13:29:59
Done.
|
| + 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; |
|
satorux1
2012/10/16 06:39:48
There is something called "chunked encoding" but w
mtomasz
2012/11/08 13:29:59
Done in the header file.
|
| + } |
| + break; |
| + } |
| + { |
| + // Parse a header name, eg. Content-length. |
| + std::string header_name = ShiftToken(HEADER_KEY).as_string(); |
|
satorux1
2012/10/16 06:39:48
This assumes that |buffer_| contains the key name
mtomasz
2012/11/08 13:29:59
I don't think so. We were parsing headers line by
|
| + std::transform(header_name.begin(), |
| + header_name.end(), |
| + header_name.begin(), |
| + ::tolower); |
|
satorux1
2012/10/16 06:39:48
StringToLowerASCII
mtomasz
2012/11/08 13:29:59
Done.
|
| + 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). |
| + StringPiece header_value = ShiftToken(LINE); |
|
satorux1
2012/10/16 06:39:48
I think the multi-line values are not supported ye
mtomasz
2012/11/08 13:29:59
Rewritten parser. Not it is supported. Done.
|
| + request_builder_->headers[header_name] = header_value.as_string(); |
| + 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_) { |
|
satorux1
2012/10/16 06:39:48
I'm confused. What does the following value mean?
mtomasz
2012/11/08 13:29:59
You were right. Removed. Done.
|
| + to_read = request_builder_->content.length() - buffer_position_; |
| + } |
| + StringPiece chunk = ShiftData(to_read); |
| + DCHECK(chunk.length() > 0); |
| + request_builder_->content.append(chunk.data(), chunk.length()); |
| + } |
| + 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; |
|
satorux1
2012/10/16 06:39:48
Please add Reset() function that resets everything
mtomasz
2012/11/08 13:29:59
Do we need it? Now there is less stuff there. Also
|
| + |
| + return result.Pass(); |
| +} |
| + |
| +const StringPiece HttpRequestParser::ShiftToken(SHIFT_TOKEN_TYPE token_type) { |
| + SkipSpaces(); // Ignore any spaces from the beginning. |
| + size_t found_position; |
|
satorux1
2012/10/16 06:39:48
please remove this here.
mtomasz
2012/11/08 13:29:59
Done.
|
| + int token_start_position = buffer_position_; |
| + |
| + switch (token_type) { |
| + case IDENTIFIER: |
| + found_position = buffer_.find(' ', buffer_position_); |
|
satorux1
2012/10/16 06:39:48
const size_t found_position = ..
mtomasz
2012/11/08 13:29:59
Done.
|
| + if (found_position != std::string::npos) { |
| + buffer_position_ = static_cast<int>(found_position) + 1; |
| + return StringPiece(buffer_.data() + 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 StringPiece(buffer_.data() + 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; |
|
satorux1
2012/10/16 06:39:48
I'm confused about this. Why don't we find the CRL
mtomasz
2012/11/08 13:29:59
This was to keep the parser's complexity O(n). Now
|
| + FindNextCrLf(); |
| + return StringPiece(buffer_.data() + token_start_position, token_length); |
| + break; |
| + } |
| + |
| + return StringPiece(); |
| +} |
| + |
| +bool HttpRequestParser::ShiftCrLf() { |
| + if (buffer_position_ == crlf_position_) { |
| + buffer_position_ += 2; |
| + FindNextCrLf(); |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +const StringPiece HttpRequestParser::ShiftData(int length) { |
| + if (length > static_cast<int>(request_builder_->content.length()) - |
| + buffer_position_) { |
| + return StringPiece(); |
| + } |
| + |
| + const StringPiece result(buffer_.data() + buffer_position_, length); |
| + buffer_position_ += length; |
| + |
| + return result; |
| +} |
| + |
| +void HttpRequestParser::SkipSpaces() { |
| + StringPiece buffer_data(buffer_.data() + buffer_position_, |
| + buffer_.length() - buffer_position_); |
| + |
| + // Ignore any spaces from the beginning. |
| + while (buffer_data.length() && buffer_data[0] == ' ') { |
| + buffer_data.remove_prefix(1); |
| + } |
| +} |
| + |
| +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 |