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/extensions/api/web_request/post_data_parser.h" | |
6 | |
7 #include "base/base64.h" | |
8 #include "base/file_path.h" | |
9 #include "base/string_piece.h" | |
10 #include "base/string_util.h" | |
11 #include "base/values.h" | |
12 #include "net/base/escape.h" | |
13 #include "net/url_request/url_request.h" | |
14 | |
15 namespace { | |
16 | |
17 const char kContentDisposition[] = "Content-Disposition:"; | |
18 | |
19 // Takes |dictionary| of <string, list of strings> pairs, and gets the list | |
20 // for |key|, creating it if necessary. | |
21 ListValue* GetOrCreateList(DictionaryValue* dictionary, | |
22 const std::string& key) { | |
23 ListValue* list = NULL; | |
24 if (!dictionary->GetList(key, &list)) { | |
25 list = new ListValue(); | |
26 dictionary->Set(key, list); | |
27 } | |
28 return list; | |
29 } | |
30 | |
31 } // namespace | |
32 | |
33 namespace extensions { | |
34 | |
35 // Implementation of FormDataParser and FormDataParser::Result . | |
36 | |
37 FormDataParser::Result::Result() {} | |
38 FormDataParser::Result::~Result() {} | |
39 | |
40 void FormDataParser::Result::Reset() { | |
41 name_.erase(); | |
42 value_.erase(); | |
43 } | |
44 | |
45 void FormDataParser::Result::set_name(const base::StringPiece& str) { | |
46 str.CopyToString(&name_); | |
47 } | |
48 | |
49 void FormDataParser::Result::set_value(const base::StringPiece& str) { | |
50 str.CopyToString(&value_); | |
51 } | |
52 | |
53 void FormDataParser::Result::set_name(const std::string& str) { | |
54 name_ = str; | |
55 } | |
56 | |
57 void FormDataParser::Result::set_value(const std::string& str) { | |
58 value_ = str; | |
59 } | |
60 | |
61 FormDataParser::~FormDataParser() {} | |
62 | |
63 // static | |
64 scoped_ptr<FormDataParser> FormDataParser::Create( | |
65 const net::URLRequest* request) { | |
66 std::string value; | |
67 const bool found = request->extra_request_headers().GetHeader( | |
68 net::HttpRequestHeaders::kContentType, &value); | |
69 return Create(found ? &value : NULL); | |
70 } | |
71 | |
72 // static | |
73 scoped_ptr<FormDataParser> FormDataParser::Create( | |
74 const std::string* content_type_header) { | |
75 enum ParserChoice {kUrlEncoded, kMultipart, kError}; | |
76 ParserChoice choice = kError; | |
77 std::string boundary; | |
78 | |
79 if (content_type_header == NULL) { | |
80 choice = kUrlEncoded; | |
81 } else { | |
82 const std::string content_type( | |
83 content_type_header->substr(0, content_type_header->find(';'))); | |
84 | |
85 if (base::strcasecmp( | |
86 content_type.c_str(), "application/x-www-form-urlencoded") == 0) { | |
87 choice = kUrlEncoded; | |
88 } else if (base::strcasecmp( | |
89 content_type.c_str(), "multipart/form-data") == 0) { | |
90 static const char kBoundaryString[] = "boundary="; | |
91 size_t offset = content_type_header->find(kBoundaryString); | |
92 if (offset == std::string::npos) { | |
93 // Malformed header. | |
94 return scoped_ptr<FormDataParser>(); | |
95 } | |
96 offset += strlen(kBoundaryString); | |
97 boundary = content_type_header->substr( | |
98 offset, content_type_header->find(';', offset)); | |
99 if (!boundary.empty()) | |
100 choice = kMultipart; | |
101 } | |
102 } | |
103 // Other cases are unparseable, including when |content_type| is "text/plain". | |
104 | |
105 switch (choice) { | |
106 case kUrlEncoded: | |
107 return scoped_ptr<FormDataParser>(new FormDataParserUrlEncoded()); | |
108 case kMultipart: | |
109 return scoped_ptr<FormDataParser>(new FormDataParserMultipart(boundary)); | |
110 default: // In other words, case kError: | |
111 return scoped_ptr<FormDataParser>(); | |
112 } | |
113 } | |
114 | |
115 FormDataParser::FormDataParser() {} | |
116 | |
117 // Implementation of FormDataParserUrlEncoded. | |
118 | |
119 FormDataParserUrlEncoded::FormDataParserUrlEncoded() | |
120 : source_(NULL), | |
121 aborted_(false), | |
122 equality_signs_(0), | |
123 amp_signs_(0), | |
124 expect_equality_(true) { | |
125 } | |
126 | |
127 FormDataParserUrlEncoded::~FormDataParserUrlEncoded() {} | |
128 | |
129 bool FormDataParserUrlEncoded::AllDataReadOK() { | |
130 return source_ != NULL && | |
131 !aborted_ && | |
132 offset_ == source_->end() && | |
133 equality_signs_ == amp_signs_ + 1; | |
134 } | |
135 | |
136 bool FormDataParserUrlEncoded::GetNextNameValue(Result* result) { | |
137 result->Reset(); | |
138 if (source_ == NULL || aborted_) | |
139 return false; | |
140 if (offset_ == source_->end()) | |
141 return false; | |
142 const char* const name_start = &(*offset_); | |
143 char c; | |
144 bool last_read_success = GetNextChar(&c); | |
145 while (last_read_success && c != '=') | |
146 last_read_success = GetNextChar(&c); | |
wtc
2012/08/09 23:39:40
Nit: this can be a do-while loop:
bool last_rea
vabr (Chromium)
2012/08/10 17:12:55
I prefer the while-loop because:
* it is shorter,
| |
147 if (!last_read_success) { // This means the data is malformed. | |
148 Abort(); | |
149 return false; | |
150 } | |
151 const char* const name_end = &(*(offset_ - 1)); | |
152 const std::string encoded_name(name_start, name_end - name_start); | |
153 const net::UnescapeRule::Type unescape_rules = | |
154 net::UnescapeRule::URL_SPECIAL_CHARS | net::UnescapeRule::CONTROL_CHARS | | |
155 net::UnescapeRule::SPACES | net::UnescapeRule::REPLACE_PLUS_WITH_SPACE; | |
156 result->set_name(net::UnescapeURLComponent(encoded_name, unescape_rules)); | |
157 | |
158 const char* const value_start = &(*offset_); | |
159 last_read_success = GetNextChar(&c); | |
160 while (last_read_success && c != '&') | |
161 last_read_success = GetNextChar(&c); | |
162 const char* const value_end = | |
163 last_read_success ? &(*(offset_ - 1)) : &(*offset_); | |
164 const std::string encoded_value(value_start, value_end - value_start); | |
165 result->set_value(net::UnescapeURLComponent(encoded_value, unescape_rules)); | |
166 return true; | |
167 } | |
168 | |
169 bool FormDataParserUrlEncoded::SetSource(const std::vector<char>* source) { | |
170 if (source_ != NULL || aborted_) | |
171 return false; | |
172 source_ = source; | |
173 offset_ = source_->begin(); | |
174 return true; | |
175 } | |
176 | |
177 bool FormDataParserUrlEncoded::GetNextChar(char* c) { | |
178 if (offset_ == source_->end() || aborted_) | |
179 return false; | |
180 *c = *offset_; | |
181 ++offset_; | |
182 | |
183 if (*c == '=') { | |
184 if (expect_equality_) { | |
185 ++equality_signs_; | |
186 expect_equality_ = false; | |
187 } else { | |
188 Abort(); | |
189 return false; | |
190 } | |
191 } | |
192 if (*c == '&' && offset_ != source_->end()) { | |
193 if (!expect_equality_) { | |
194 ++amp_signs_; | |
195 expect_equality_ = true; | |
196 } else { | |
197 Abort(); | |
198 return false; | |
199 } | |
200 } | |
201 | |
202 return true; | |
203 } | |
204 | |
205 void FormDataParserUrlEncoded::Abort() { | |
206 aborted_ = true; | |
207 } | |
208 | |
209 // Implementation of FormDataParserMultipart. | |
210 | |
211 FormDataParserMultipart::FormDataParserMultipart( | |
212 const std::string& boundary_separator) | |
213 : source_(NULL), | |
214 length_(0), // Dummy value. | |
215 line_start_(0), // Dummy value. | |
216 line_end_(0), // Dummy value. | |
217 next_line_(0), // Dummy value. | |
218 boundary_("--" + boundary_separator), | |
219 end_boundary_(boundary_ + "--"), | |
220 state_(kInit), | |
221 line_type_(kEmpty) { // Dummy value. | |
222 } | |
223 | |
224 FormDataParserMultipart::~FormDataParserMultipart() {} | |
225 | |
226 bool FormDataParserMultipart::AllDataReadOK() { | |
227 return source_ != NULL && next_line_ >= length_ && state_ == kFinal; | |
228 } | |
229 | |
230 // This function reads one block of the data, between two boundaries. | |
231 // First it reads the header to learn the name, and possibly also the | |
232 // value, if this block is for a file input element. | |
233 // Otherwise it then reads the value from the body. | |
234 bool FormDataParserMultipart::GetNextNameValue(Result* result) { | |
235 result->Reset(); | |
236 if (state_ == kError) | |
237 return false; | |
238 while (state_ != kHeaderRead) { | |
239 if (!DoStep()) | |
240 return false; | |
241 } | |
242 bool value_extracted = false; | |
243 bool name_parsed = ParseHeader(result, &value_extracted); | |
244 while (state_ != kBody) { | |
245 if (!DoStep()) | |
246 return false; | |
247 } | |
248 size_t value_start; | |
249 size_t value_end = 0; // Dummy value, replaced below, see (*). | |
250 // There may not be more to read from |source_| if the current result comes | |
251 // from a "file" input element. But then |result| is complete already. | |
252 if (!DoStep()) | |
253 return value_extracted; | |
254 value_start = line_start_; | |
255 // (*) Now state_ == kBody, so value_end gets updated below. | |
wtc
2012/08/09 23:39:40
Are you sure state_ == kBody here?
At line 249, s
vabr (Chromium)
2012/08/10 17:12:55
Thanks for catching this. I was missing a corner c
| |
256 while (state_ != kHeaderStart && state_ != kFinal) { | |
257 value_end = line_end_; | |
258 if (!DoStep()) break; | |
259 } | |
260 if (name_parsed && !value_extracted) { | |
261 result->set_value( | |
262 base::StringPiece(source_ + value_start, value_end - value_start)); | |
263 } | |
264 return name_parsed; | |
265 } | |
266 | |
267 bool FormDataParserMultipart::SetSource(const std::vector<char>* source) { | |
268 if (state_ == kError) | |
269 return false; | |
270 if (source_ != NULL && next_line_ < length_) | |
271 return false; | |
272 source_ = &(source->front()); | |
273 length_ = source->size(); | |
274 next_line_ = 0; | |
275 return true; | |
276 } | |
277 | |
278 bool FormDataParserMultipart::DoStep() { | |
279 if (!SeekNextLine()) | |
280 return false; | |
281 switch (state_) { | |
282 case kInit: | |
283 if (line_type_ == kBoundary) | |
284 state_ = kHeaderStart; | |
285 else | |
286 state_ = kError; | |
287 break; | |
288 case kHeaderStart: | |
289 if (line_type_ == kDisposition) | |
290 state_ = kHeaderRead; | |
291 else | |
292 state_ = kHeader; | |
293 break; | |
294 case kHeader: | |
295 if (line_type_ == kDisposition) | |
296 state_ = kHeaderRead; | |
297 break; | |
298 case kHeaderRead: | |
299 if (line_type_ == kEmpty) | |
300 state_ = kBody; | |
301 break; | |
302 case kBody: | |
303 if (line_type_ == kBoundary) | |
304 state_ = kHeaderStart; | |
305 else if (line_type_ == kEndBoundary) | |
306 state_ = kFinal; | |
307 break; | |
308 case kFinal: | |
309 if (line_type_ != kEmpty) | |
310 state_ = kError; | |
311 break; | |
312 case kError: | |
313 break; | |
314 } | |
315 return true; | |
316 } | |
317 | |
318 FormDataParserMultipart::LineType FormDataParserMultipart::GetLineType() { | |
319 const size_t line_length = line_end_ - line_start_; | |
wtc
2012/08/09 23:39:40
If empty lines are common, you can do this optimiz
vabr (Chromium)
2012/08/10 17:12:55
Good point, done.
| |
320 const base::StringPiece line(source_ + line_start_, line_length); | |
321 if (line == boundary_) | |
322 return kBoundary; | |
323 else if (line == end_boundary_) | |
324 return kEndBoundary; | |
325 else if (line.starts_with(kContentDisposition)) | |
326 return kDisposition; | |
327 else if (line_start_ == line_end_) | |
328 return kEmpty; | |
329 else | |
330 return kOther; | |
331 } | |
332 | |
333 // Contract: only to be called from DoStep(). | |
334 bool FormDataParserMultipart::SeekNextLine() { | |
335 if (source_ == NULL || state_ == kError) | |
336 return false; | |
337 if (next_line_ >= length_) | |
338 return false; | |
339 line_start_ = next_line_; | |
340 size_t seek = line_start_; | |
341 while (seek < length_ && *(source_ + seek) != '\r') | |
342 ++seek; | |
343 line_end_ = seek; | |
344 line_type_ = GetLineType(); | |
345 next_line_ = seek; | |
346 if (seek + 1 < length_ && strncmp(source_ + seek, "\r\n", 2) == 0) { | |
wtc
2012/08/09 23:39:40
Nit: since you have already checked the length, yo
vabr (Chromium)
2012/08/10 17:12:55
Done.
| |
347 next_line_ += 2; | |
348 return true; | |
349 } else if (seek == length_) { | |
wtc
2012/08/09 23:39:40
Nit: don't use "else" after a return statement. S
vabr (Chromium)
2012/08/10 17:12:55
Done. (Not quite sure, though: what is wrong with
| |
350 return true; | |
351 } else { | |
352 // Neither at the end, nor ending with a CRLF -- abort. | |
wtc
2012/08/09 23:39:40
Can you find out if we need to support a line endi
vabr (Chromium)
2012/08/10 17:12:55
RFC 2388 (Returning Values from Forms: multipart/
| |
353 state_ = kError; | |
354 return false; | |
355 } | |
356 } | |
357 | |
358 // Contract: line_type_ == kDisposition. | |
359 bool FormDataParserMultipart::ParseHeader( | |
360 Result* result, bool* value_extracted) { | |
361 DCHECK_EQ(kDisposition, line_type_); | |
362 base::StringPiece line(source_ + line_start_, line_end_ - line_start_); | |
363 const char kNameEquals[] = " name=\""; | |
364 const char kFilenameEquals[] = " filename=\""; | |
wtc
2012/08/09 23:39:40
Nit: these two const char arrays can be 'static'.
vabr (Chromium)
2012/08/10 17:12:55
Done.
| |
365 size_t name_offset = line.find(kNameEquals); | |
366 if (name_offset == base::StringPiece::npos) | |
367 return false; | |
368 name_offset += strlen(kNameEquals); | |
wtc
2012/08/09 23:39:40
Nit: strlen(kNameEquals) can be replaced by
sizeof
vabr (Chromium)
2012/08/10 17:12:55
Actually, at least in GCC, strlen called on a comp
| |
369 result->set_name(base::StringPiece( | |
370 source_ + line_start_ + name_offset, | |
371 line.find('"', name_offset) - name_offset)); | |
wtc
2012/08/09 23:39:40
If line.find('"', name_offset) does not find the '
vabr (Chromium)
2012/08/10 17:12:55
Check added. Thanks for catching this.
| |
372 size_t value_offset = line.find(kFilenameEquals); | |
373 if (value_offset == std::string::npos) { | |
374 *value_extracted = false; | |
375 } else { | |
376 *value_extracted = true; | |
377 value_offset += strlen(kFilenameEquals); | |
378 result->set_value(base::StringPiece( | |
379 source_ + line_start_ + value_offset, | |
380 line.find('"', value_offset) - value_offset)); | |
381 } | |
382 return true; | |
383 } | |
384 | |
385 // Implementation of RequestDataRepresentationProducer. | |
386 | |
387 RequestDataRepresentationProducer::~RequestDataRepresentationProducer() {} | |
388 | |
389 // Implementation of ChunkedErrorProducer. | |
390 | |
391 ChunkedErrorProducer::ChunkedErrorProducer(const net::URLRequest* request) | |
392 : chunks_found_(TransferEncodingChunked(request)) { | |
393 } | |
394 | |
395 ChunkedErrorProducer::~ChunkedErrorProducer() {} | |
396 | |
397 // static | |
398 bool ChunkedErrorProducer::TransferEncodingChunked( | |
399 const net::URLRequest* request){ | |
400 std::string transfer_encoding; | |
401 if (!request->extra_request_headers().GetHeader( | |
402 net::HttpRequestHeaders::kTransferEncoding, &transfer_encoding)) | |
403 return false; | |
404 return base::strcasecmp(transfer_encoding.c_str(), "chunked") == 0; | |
405 } | |
406 | |
407 void ChunkedErrorProducer::FeedNext(const net::UploadData::Element& element) { | |
408 if (chunks_found_) | |
409 return; // We already found a reason to report an error. | |
410 | |
411 if (element.type() == net::UploadData::TYPE_CHUNK) | |
412 chunks_found_ = true; | |
413 } | |
414 | |
415 bool ChunkedErrorProducer::Succeeded() { | |
416 return chunks_found_; | |
417 } | |
418 | |
419 scoped_ptr<Value> ChunkedErrorProducer::Result() { | |
420 if (!chunks_found_) | |
421 return scoped_ptr<Value>(); | |
422 | |
423 scoped_ptr<StringValue> error_string(new StringValue( | |
424 "Not supported: data is uploaded chunked.")); | |
425 return error_string.PassAs<Value>(); | |
426 } | |
427 | |
428 // Implementation of RawDataProducer. | |
429 | |
430 RawDataProducer::RawDataProducer() : success_(true) {} | |
431 RawDataProducer::~RawDataProducer() {} | |
432 | |
433 void RawDataProducer::FeedNext(const net::UploadData::Element& element) { | |
434 if (!success_) | |
435 return; | |
436 | |
437 if (element.type() == net::UploadData::TYPE_BYTES) { | |
438 data_.insert(data_.end(), element.bytes().begin(), element.bytes().end()); | |
439 } else if (element.type() == net::UploadData::TYPE_CHUNK) { | |
440 Abort(); // Chunks are not supported (yet). | |
441 } else if (element.type() == net::UploadData::TYPE_FILE) { | |
442 // Insert the file path instead of the contents, which may be too large. | |
443 const char kFileEntryPrefix[] = "FILE_PATH=["; | |
vabr (Chromium)
2012/08/05 18:54:47
This might not be optimal to distinguish the file
Matt Perry
2012/08/06 21:06:45
So, our raw data is still not quite "raw". Maybe w
vabr (Chromium)
2012/08/10 17:12:55
Great idea, done.
Now I'm returning an array (List
| |
444 const char kFileEntrySuffix[] = "]"; | |
445 data_.insert(data_.end(), | |
446 kFileEntryPrefix, | |
447 kFileEntryPrefix + arraysize(kFileEntryPrefix)); | |
wtc
2012/08/09 23:39:40
NOTE: there may be an off-by-one error here.
arra
vabr (Chromium)
2012/08/10 17:12:55
Indeed, this was an error. However, the code was r
| |
448 const std::string& path = element.file_path().AsUTF8Unsafe(); | |
449 data_.insert(data_.end(), path.begin(), path.end()); | |
450 data_.insert(data_.end(), | |
451 kFileEntrySuffix, | |
452 kFileEntrySuffix + arraysize(kFileEntrySuffix)); | |
453 } // TYPE_BLOB is silently ignored. | |
454 } | |
455 | |
456 bool RawDataProducer::Succeeded() { | |
457 if (!success_) | |
458 return false; | |
459 | |
460 // Ideally, we would pass data_ as a BinaryValue. But those cannot be | |
461 // serialized via JSON, so we pass it as a Byte64 encoded string. | |
462 base::StringPiece data_raw(&(data_[0]), data_.size()); | |
463 return base::Base64Encode(data_raw, &data_string_); | |
464 } | |
465 | |
466 scoped_ptr<Value> RawDataProducer::Result() { | |
467 if (!success_) | |
468 return scoped_ptr<Value>(); | |
469 | |
470 return scoped_ptr<Value>(new StringValue(data_string_)); | |
471 } | |
472 | |
473 void RawDataProducer::Abort() { | |
474 success_ = false; | |
475 data_.clear(); | |
476 } | |
477 | |
478 // Implementation of ParsedDataProducer. | |
479 | |
480 ParsedDataProducer::ParsedDataProducer(const net::URLRequest* request) | |
481 : parser_(FormDataParser::Create(request).release()), | |
482 success_(parser_.get() != NULL), | |
483 dictionary_(success_ ? new DictionaryValue() : NULL) { | |
484 } | |
485 | |
486 ParsedDataProducer::~ParsedDataProducer() {} | |
487 | |
488 void ParsedDataProducer::FeedNext(const net::UploadData::Element& element) { | |
489 if (!success_) | |
490 return; | |
491 | |
492 if (element.type() != net::UploadData::TYPE_BYTES) { | |
493 if (element.type() != net::UploadData::TYPE_FILE) { | |
494 Abort(); // We do not handle blobs nor chunks. | |
495 } | |
496 return; // But we just ignore files. | |
497 } | |
498 if (!parser_->SetSource(&(element.bytes()))) { | |
499 Abort(); | |
500 return; | |
501 } | |
502 | |
503 FormDataParser::Result result; | |
504 while (parser_->GetNextNameValue(&result)) { | |
505 GetOrCreateList(dictionary_.get(), result.name())->Append( | |
506 new StringValue(result.value())); | |
507 } | |
508 } | |
509 | |
510 bool ParsedDataProducer::Succeeded() { | |
511 if (success_ && !parser_->AllDataReadOK()) | |
512 Abort(); | |
513 return success_; | |
514 } | |
515 | |
516 scoped_ptr<Value> ParsedDataProducer::Result() { | |
517 if (success_) | |
518 return dictionary_.PassAs<Value>(); | |
519 else | |
520 return scoped_ptr<Value>(); | |
521 } | |
522 | |
523 void ParsedDataProducer::Abort() { | |
524 success_ = false; | |
525 dictionary_.reset(); | |
526 parser_.reset(); | |
527 } | |
528 | |
529 } // namespace extensions | |
OLD | NEW |