Chromium Code Reviews| 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 |