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. | |
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.
| |
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(); | |
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
| |
42 } | |
43 | |
44 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.
| |
45 while (state_ != READY && | |
46 state_ != SYNTAX_ERROR && | |
47 ShouldParseBuffer()) { | |
48 // StringPiece is converted to String, since a copy is needed in most cases | |
49 // to perform upper/lower case conversion. | |
50 std::string token; | |
51 switch (state_) { | |
52 case REQUEST_LINE: | |
53 // Parse a method, eg. GET. | |
54 token = ShiftToken(IDENTIFIER).as_string(); | |
55 if (token.empty()) { | |
56 state_ = SYNTAX_ERROR; | |
57 break; | |
58 } | |
59 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.
| |
60 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 { | |
71 // 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.
| |
72 request_builder_->method = CUSTOM; | |
73 } | |
satorux1
2012/10/16 06:39:48
Please create a function to convert string to meth
mtomasz
2012/11/08 13:29:59
Done.
| |
74 | |
75 // Parse an URL, eg. index.cgi. | |
76 token = ShiftToken(IDENTIFIER).as_string(); | |
77 if (token.empty()) { | |
78 LOG(ERROR) << "The url token must not be empty."; | |
79 state_ = SYNTAX_ERROR; | |
80 break; | |
81 } | |
82 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
| |
83 | |
84 // Parse a protocol, eg. HTTP/1.1. | |
85 // Currently we support only HTTP/1.1, since HTTP/1.0 is quite obsolete. | |
86 token = ShiftToken(LINE).as_string(); | |
87 std::transform(token.begin(), token.end(), token.begin(), ::tolower); | |
88 if (token.empty() || token != "http/1.1") { | |
89 LOG(ERROR) << "This protocol is not supported: [" << token << "]"; | |
90 state_ = SYNTAX_ERROR; | |
91 break; | |
92 } | |
93 request_builder_->protocol = token; | |
94 state_ = HEADER_LINE; | |
95 break; | |
96 case HEADER_LINE: | |
97 // If we have an empty line, then finish parsing headers and validate | |
98 // the request. | |
99 if (ShiftCrLf()) { | |
100 if (request_builder_->headers.find("host") == | |
101 request_builder_->headers.end()) { | |
102 LOG(ERROR) << "Host header is required for HTTP/1.1"; | |
103 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.
| |
104 break; | |
105 } else { | |
106 // TODO(mtomasz): We add http:// protocol, but how about https://? | |
107 // Do we want to support it? | |
108 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.
| |
109 request_builder_->url = host.Resolve(request_builder_->raw_url); | |
110 } | |
111 if (request_builder_->headers.find("content-length") != | |
112 request_builder_->headers.end()) { | |
113 current_content_length_ = | |
114 atoi(request_builder_->headers["content-length"].c_str()); | |
115 } | |
116 if (current_content_length_ > 0) { | |
117 state_ = DATA; | |
118 } else { | |
119 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.
| |
120 } | |
121 break; | |
122 } | |
123 { | |
124 // Parse a header name, eg. Content-length. | |
125 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
| |
126 std::transform(header_name.begin(), | |
127 header_name.end(), | |
128 header_name.begin(), | |
129 ::tolower); | |
satorux1
2012/10/16 06:39:48
StringToLowerASCII
mtomasz
2012/11/08 13:29:59
Done.
| |
130 if (header_name.empty()) { | |
131 LOG(ERROR) << "Header key must not be empty."; | |
132 state_ = SYNTAX_ERROR; | |
133 break; | |
134 } | |
135 | |
136 // Parse a header value, eg. 100 (for Content-Length). | |
137 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.
| |
138 request_builder_->headers[header_name] = header_value.as_string(); | |
139 break; | |
140 } | |
141 case DATA: | |
142 if (current_content_length_ == | |
143 static_cast<int>(request_builder_->content.length())) { | |
144 // Entire data received. | |
145 state_ = READY; | |
146 } else { | |
147 int to_read = current_content_length_ - | |
148 request_builder_->content.length(); | |
149 if (to_read > static_cast<int>(request_builder_->content.length()) - | |
150 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.
| |
151 to_read = request_builder_->content.length() - buffer_position_; | |
152 } | |
153 StringPiece chunk = ShiftData(to_read); | |
154 DCHECK(chunk.length() > 0); | |
155 request_builder_->content.append(chunk.data(), chunk.length()); | |
156 } | |
157 break; | |
158 case SYNTAX_ERROR: | |
159 break; | |
160 default: | |
161 state_ = SYNTAX_ERROR; | |
162 break; | |
163 } | |
164 } | |
165 return state_; | |
166 } | |
167 | |
168 scoped_ptr<HttpRequest> HttpRequestParser::GetRequest() { | |
169 DCHECK(state_ == READY); | |
170 scoped_ptr<HttpRequest> result = request_builder_.Pass(); | |
171 request_builder_.reset(new HttpRequest()); | |
172 | |
173 // Reset the parser state. | |
174 state_ = REQUEST_LINE; | |
175 buffer_position_ = 0; | |
176 buffer_.clear(); | |
177 crlf_position_ = -1; | |
178 crlf_checked_position_ = 0; | |
179 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
| |
180 | |
181 return result.Pass(); | |
182 } | |
183 | |
184 const StringPiece HttpRequestParser::ShiftToken(SHIFT_TOKEN_TYPE token_type) { | |
185 SkipSpaces(); // Ignore any spaces from the beginning. | |
186 size_t found_position; | |
satorux1
2012/10/16 06:39:48
please remove this here.
mtomasz
2012/11/08 13:29:59
Done.
| |
187 int token_start_position = buffer_position_; | |
188 | |
189 switch (token_type) { | |
190 case IDENTIFIER: | |
191 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.
| |
192 if (found_position != std::string::npos) { | |
193 buffer_position_ = static_cast<int>(found_position) + 1; | |
194 return StringPiece(buffer_.data() + token_start_position, | |
195 found_position - token_start_position); | |
196 } | |
197 break; | |
198 case HEADER_KEY: | |
199 found_position = buffer_.find(':', buffer_position_); | |
200 if (found_position != std::string::npos) { | |
201 buffer_position_ = static_cast<int>(found_position) + 1; | |
202 return StringPiece(buffer_.data() + token_start_position, | |
203 found_position - token_start_position); | |
204 } | |
205 break; | |
206 case LINE: | |
207 DCHECK(crlf_position_ != -1) << "CRLF not found, but expected."; | |
208 buffer_position_ = crlf_position_ + 2; | |
209 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
| |
210 FindNextCrLf(); | |
211 return StringPiece(buffer_.data() + token_start_position, token_length); | |
212 break; | |
213 } | |
214 | |
215 return StringPiece(); | |
216 } | |
217 | |
218 bool HttpRequestParser::ShiftCrLf() { | |
219 if (buffer_position_ == crlf_position_) { | |
220 buffer_position_ += 2; | |
221 FindNextCrLf(); | |
222 return true; | |
223 } | |
224 | |
225 return false; | |
226 } | |
227 | |
228 const StringPiece HttpRequestParser::ShiftData(int length) { | |
229 if (length > static_cast<int>(request_builder_->content.length()) - | |
230 buffer_position_) { | |
231 return StringPiece(); | |
232 } | |
233 | |
234 const StringPiece result(buffer_.data() + buffer_position_, length); | |
235 buffer_position_ += length; | |
236 | |
237 return result; | |
238 } | |
239 | |
240 void HttpRequestParser::SkipSpaces() { | |
241 StringPiece buffer_data(buffer_.data() + buffer_position_, | |
242 buffer_.length() - buffer_position_); | |
243 | |
244 // Ignore any spaces from the beginning. | |
245 while (buffer_data.length() && buffer_data[0] == ' ') { | |
246 buffer_data.remove_prefix(1); | |
247 } | |
248 } | |
249 | |
250 bool HttpRequestParser::ShouldParseBuffer() { | |
251 return buffer_position_ < static_cast<int>(buffer_.length()) && | |
252 (crlf_position_ != -1 || state_ == DATA); | |
253 } | |
254 | |
255 bool HttpRequestParser::FindNextCrLf() { | |
256 if (!buffer_.length()) { | |
257 return false; | |
258 } | |
259 | |
260 size_t found_position = buffer_.find("\r\n", crlf_checked_position_); | |
261 if (found_position == std::string::npos) { | |
262 crlf_position_ = -1; | |
263 crlf_checked_position_ = buffer_.length() - 1; | |
264 return false; | |
265 } | |
266 | |
267 crlf_position_ = static_cast<int>(found_position); | |
268 crlf_checked_position_ = crlf_position_ + 2; | |
269 return true; | |
270 } | |
271 | |
272 } // namespace test_servers | |
273 } // namespace gdata | |
OLD | NEW |