OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/chromeos/gdata/test_servers/http_request.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <map> |
| 9 #include <string> |
| 10 #include "base/basictypes.h" |
| 11 #include "base/logging.h" |
| 12 #include "googleurl/src/gurl.h" |
| 13 |
| 14 namespace gdata { |
| 15 namespace test_servers { |
| 16 |
| 17 namespace { |
| 18 int kRequestSizeLimit = 64 * 1024 * 1024; // 64 mb. |
| 19 } // namespace |
| 20 |
| 21 HttpRequestParser::HttpRequestParser() : request_builder_(new HttpRequest()), |
| 22 state_(REQUEST_LINE), |
| 23 buffer_position_(0), |
| 24 crlf_position_(-1), |
| 25 crlf_checked_position_(0), |
| 26 current_content_length_(0) { |
| 27 } |
| 28 |
| 29 HttpRequestParser::~HttpRequestParser() { |
| 30 } |
| 31 |
| 32 void HttpRequestParser::ProcessChunk(const char *data, int length) { |
| 33 buffer_.append(data, length); |
| 34 if (static_cast<int>(buffer_.length()) + length > kRequestSizeLimit) { |
| 35 // Too big request. Treat it as a syntax error. |
| 36 LOG(ERROR) << "The HTTP request is too large."; |
| 37 state_ = SYNTAX_ERROR; |
| 38 return; |
| 39 } |
| 40 if (crlf_position_ == -1 && state_ != DATA) |
| 41 FindNextCrLf(); |
| 42 } |
| 43 |
| 44 HttpRequestParser::STATE HttpRequestParser::ParseRequest() { |
| 45 while (state_ != READY && |
| 46 state_ != SYNTAX_ERROR && |
| 47 ShouldParseBuffer()) { |
| 48 std::string token; |
| 49 switch (state_) { |
| 50 case REQUEST_LINE: |
| 51 // Parse a method, eg. GET. |
| 52 token = ShiftToken(IDENTIFIER); |
| 53 if (token.empty()) { |
| 54 state_ = SYNTAX_ERROR; |
| 55 break; |
| 56 } |
| 57 std::transform(token.begin(), token.end(), token.begin(), ::toupper); |
| 58 if (token == "OPTIONS") { |
| 59 request_builder_->method = OPTIONS; |
| 60 } else if (token == "GET") { |
| 61 request_builder_->method = GET; |
| 62 } else if (token == "HEAD") { |
| 63 request_builder_->method = HEAD; |
| 64 } else if (token == "POST") { |
| 65 request_builder_->method = POST; |
| 66 } else if (token == "PUT") { |
| 67 request_builder_->method = PUT; |
| 68 } else if (token == "DELETE") { |
| 69 request_builder_->method = DELETE; |
| 70 } else if (token == "TRACE") { |
| 71 request_builder_->method = TRACE; |
| 72 } else if (token == "CONNECT") { |
| 73 request_builder_->method = CONNECT; |
| 74 } else { |
| 75 request_builder_->method = CUSTOM; |
| 76 } |
| 77 |
| 78 // Parse an URL, eg. index.cgi. |
| 79 token = ShiftToken(IDENTIFIER); |
| 80 if (token.empty()) { |
| 81 LOG(ERROR) << "The url token must not be empty."; |
| 82 state_ = SYNTAX_ERROR; |
| 83 break; |
| 84 } |
| 85 request_builder_->raw_url = token; |
| 86 |
| 87 // Parse a protocol, eg. HTTP/1.1. |
| 88 // Currently we support only HTTP/1.1, since HTTP/1.0 is quite obsolete. |
| 89 token = ShiftToken(LINE); |
| 90 std::transform(token.begin(), token.end(), token.begin(), ::tolower); |
| 91 if (token.empty() || token != "http/1.1") { |
| 92 LOG(ERROR) << "This protocol is not supported: [" << token << "]"; |
| 93 state_ = SYNTAX_ERROR; |
| 94 break; |
| 95 } |
| 96 request_builder_->protocol = token; |
| 97 state_ = HEADER_LINE; |
| 98 break; |
| 99 case HEADER_LINE: |
| 100 // If we have an empty line, then finish parsing headers and validate |
| 101 // the request. |
| 102 if (ShiftCrLf()) { |
| 103 // TODO(mtomasz): Check if these are really case-insensitive in the |
| 104 // rfc. |
| 105 if (request_builder_->headers.find("host") == |
| 106 request_builder_->headers.end()) { |
| 107 LOG(ERROR) << "Host header is required for HTTP/1.1"; |
| 108 state_ = SYNTAX_ERROR; |
| 109 break; |
| 110 } else { |
| 111 // TODO(mtomasz): We add http:// protocol, but how about https://? |
| 112 // Do we want to support it? |
| 113 GURL host = GURL("http://" + request_builder_->headers["host"]); |
| 114 request_builder_->url = host.Resolve(request_builder_->raw_url); |
| 115 } |
| 116 if (request_builder_->headers.find("content-length") != |
| 117 request_builder_->headers.end()) { |
| 118 current_content_length_ = |
| 119 atoi(request_builder_->headers["content-length"].c_str()); |
| 120 } |
| 121 if (current_content_length_ > 0) { |
| 122 state_ = DATA; |
| 123 } else { |
| 124 state_ = READY; |
| 125 } |
| 126 break; |
| 127 } |
| 128 { |
| 129 // Parse a header name, eg. Content-length. |
| 130 std::string header_name = ShiftToken(HEADER_KEY); |
| 131 std::transform(header_name.begin(), |
| 132 header_name.end(), |
| 133 header_name.begin(), |
| 134 ::tolower); |
| 135 if (header_name.empty()) { |
| 136 LOG(ERROR) << "Header key must not be empty."; |
| 137 state_ = SYNTAX_ERROR; |
| 138 break; |
| 139 } |
| 140 |
| 141 // Parse a header value, eg. 100 (for Content-Length). |
| 142 token = ShiftToken(LINE); |
| 143 request_builder_->headers[header_name] = token; |
| 144 break; |
| 145 } |
| 146 case DATA: |
| 147 if (current_content_length_ == |
| 148 static_cast<int>(request_builder_->content.length())) { |
| 149 // Entire data received. |
| 150 state_ = READY; |
| 151 } else { |
| 152 int to_read = current_content_length_ - |
| 153 request_builder_->content.length(); |
| 154 if (to_read > static_cast<int>(request_builder_->content.length()) - |
| 155 buffer_position_) { |
| 156 to_read = request_builder_->content.length() - buffer_position_; |
| 157 } |
| 158 request_builder_->content.append(ShiftData(to_read), to_read); |
| 159 } |
| 160 break; |
| 161 case SYNTAX_ERROR: |
| 162 break; |
| 163 default: |
| 164 state_ = SYNTAX_ERROR; |
| 165 break; |
| 166 } |
| 167 } |
| 168 return state_; |
| 169 } |
| 170 |
| 171 scoped_ptr<HttpRequest> HttpRequestParser::GetRequest() { |
| 172 DCHECK(state_ == READY); |
| 173 scoped_ptr<HttpRequest> result = request_builder_.Pass(); |
| 174 request_builder_.reset(new HttpRequest()); |
| 175 |
| 176 // Reset the parser state. |
| 177 state_ = REQUEST_LINE; |
| 178 buffer_position_ = 0; |
| 179 buffer_.clear(); |
| 180 crlf_position_ = -1; |
| 181 crlf_checked_position_ = 0; |
| 182 current_content_length_ = 0; |
| 183 |
| 184 return result.Pass(); |
| 185 } |
| 186 |
| 187 std::string HttpRequestParser::ShiftToken(SHIFT_TOKEN_TYPE token_type) { |
| 188 SkipSpaces(); // Ignore any spaces from the beginning. |
| 189 size_t found_position; |
| 190 int token_start_position = buffer_position_; |
| 191 |
| 192 switch (token_type) { |
| 193 case IDENTIFIER: |
| 194 found_position = buffer_.find(' ', buffer_position_); |
| 195 if (found_position != std::string::npos) { |
| 196 buffer_position_ = static_cast<int>(found_position) + 1; |
| 197 return buffer_.substr(token_start_position, |
| 198 found_position - token_start_position); |
| 199 } |
| 200 break; |
| 201 case HEADER_KEY: |
| 202 found_position = buffer_.find(':', buffer_position_); |
| 203 if (found_position != std::string::npos) { |
| 204 buffer_position_ = static_cast<int>(found_position) + 1; |
| 205 return buffer_.substr(token_start_position, |
| 206 found_position - token_start_position); |
| 207 } |
| 208 break; |
| 209 case LINE: |
| 210 DCHECK(crlf_position_ != -1) << "CRLF not found, but expected."; |
| 211 buffer_position_ = crlf_position_ + 2; |
| 212 int token_length = crlf_position_ - token_start_position; |
| 213 FindNextCrLf(); |
| 214 return buffer_.substr(token_start_position, token_length); |
| 215 break; |
| 216 } |
| 217 |
| 218 return ""; |
| 219 } |
| 220 |
| 221 bool HttpRequestParser::ShiftCrLf() { |
| 222 if (buffer_position_ == crlf_position_) { |
| 223 buffer_position_ += 2; |
| 224 FindNextCrLf(); |
| 225 return true; |
| 226 } |
| 227 |
| 228 return false; |
| 229 } |
| 230 |
| 231 const char* HttpRequestParser::ShiftData(int length) { |
| 232 DCHECK(length <= static_cast<int>(request_builder_->content.length()) - |
| 233 buffer_position_) << "Tried to read more than available."; |
| 234 const char *result = buffer_.data() + buffer_position_; |
| 235 buffer_position_ += length; |
| 236 |
| 237 return result; |
| 238 } |
| 239 |
| 240 void HttpRequestParser::SkipSpaces() { |
| 241 const char *buffer_data = buffer_.data(); |
| 242 int buffer_left = static_cast<int>(buffer_.length()) - buffer_position_; |
| 243 |
| 244 // Ignore any spaces from the beginning. |
| 245 while (buffer_left && buffer_data[buffer_position_] == ' ') { |
| 246 buffer_position_++; |
| 247 buffer_left--; |
| 248 } |
| 249 } |
| 250 |
| 251 bool HttpRequestParser::ShouldParseBuffer() { |
| 252 return buffer_position_ < static_cast<int>(buffer_.length()) && |
| 253 (crlf_position_ != -1 || state_ == DATA); |
| 254 } |
| 255 |
| 256 bool HttpRequestParser::FindNextCrLf() { |
| 257 if (!buffer_.length()) { |
| 258 return false; |
| 259 } |
| 260 |
| 261 size_t found_position = buffer_.find("\r\n", crlf_checked_position_); |
| 262 if (found_position == std::string::npos) { |
| 263 crlf_position_ = -1; |
| 264 crlf_checked_position_ = buffer_.length() - 1; |
| 265 return false; |
| 266 } |
| 267 |
| 268 crlf_position_ = static_cast<int>(found_position); |
| 269 crlf_checked_position_ = crlf_position_ + 2; |
| 270 return true; |
| 271 } |
| 272 |
| 273 } // namespace test_servers |
| 274 } // namespace gdata |
OLD | NEW |