Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: chrome/browser/extensions/api/web_request/post_data_parser.cc

Issue 10694055: Add read-only access to POST data for webRequest's onBeforeRequest (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Rebased + some corrections Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698