Index: chrome/browser/extensions/api/web_request/web_request_api.cc |
diff --git a/chrome/browser/extensions/api/web_request/web_request_api.cc b/chrome/browser/extensions/api/web_request/web_request_api.cc |
index bff5aea226f38494b67568fe844e87eda55c0c8f..3165bd01b3526aa2279823de3b6331570e086c16 100644 |
--- a/chrome/browser/extensions/api/web_request/web_request_api.cc |
+++ b/chrome/browser/extensions/api/web_request/web_request_api.cc |
@@ -44,6 +44,7 @@ |
#include "grit/generated_resources.h" |
#include "net/base/auth.h" |
#include "net/base/net_errors.h" |
+#include "net/base/upload_data.h" |
#include "net/http/http_response_headers.h" |
#include "net/url_request/url_request.h" |
#include "ui/base/l10n/l10n_util.h" |
@@ -169,6 +170,341 @@ void ExtractRequestInfo(net::URLRequest* request, DictionaryValue* out) { |
out->SetDouble(keys::kTimeStampKey, base::Time::Now().ToDoubleT() * 1000); |
} |
+// Interface for parsers for the POST data. |
+class PostDataParser { |
+ public: |
+ struct Result { |
+ const char* key; |
+ size_t key_length; |
battre
2012/07/06 07:36:24
This looks like a usecase of StringPiece
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ const char* val; |
+ size_t val_length; |
+ }; |
+ // Attempts to set the |length| bytes at |source| as the data to be parsed. |
battre
2012/07/06 07:36:24
s/Attempts to set/Sets/
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ // Returns true on success. |
+ virtual bool SetSource(const char* source, size_t length) = 0; |
+ // Returns the next key-value pair as |result|. After SetSource has succeeded, |
+ // this allows to iterate over all pairs in the source. |
+ // Returns true as long as a new pair was successfully found. |
+ virtual bool GetNextPair(Result* result) = 0; |
+ // Returns true if there was some data, it was well formed and all was read. |
+ virtual bool AllDataReadOK() = 0; |
battre
2012/07/06 07:36:24
new line before protected: and private:
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ protected: |
+ PostDataParser() {} |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(PostDataParser); |
+}; |
+ |
+class PostDataParserUrlEncoded : public PostDataParser { |
+ public: |
+ PostDataParserUrlEncoded() : source_(NULL) {} |
battre
2012/07/06 07:36:24
length_ and offset_ should be initialized
vabr (Chromium)
2012/07/06 14:09:01
I did not initialize them on purpose. Their value
|
+ ~PostDataParserUrlEncoded() {} |
+ virtual bool SetSource(const char* source, size_t length) OVERRIDE; |
battre
2012/07/06 07:36:24
Please add a new line
// Implementation of PostDa
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ virtual bool GetNextPair(Result* result) OVERRIDE; |
+ virtual bool AllDataReadOK() OVERRIDE; |
battre
2012/07/06 07:36:24
new line before private:
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ private: |
+ // We parse the first |length_| bytes from |source_|. |
+ const char* source_; |
+ size_t length_; |
+ size_t offset_; |
+ DISALLOW_COPY_AND_ASSIGN(PostDataParserUrlEncoded); |
+}; |
+ |
+bool PostDataParserUrlEncoded::AllDataReadOK() { |
+ return source_ != NULL && offset_ >= length_; |
+} |
+ |
+bool PostDataParserUrlEncoded::SetSource(const char* source, size_t length) { |
+ if (source_ != NULL) return false; |
+ source_ = source; |
+ length_ = length; |
+ offset_ = 0; |
+ return true; |
+} |
+ |
+bool PostDataParserUrlEncoded::GetNextPair(Result* result) { |
+ if (source_ == NULL) return false; |
+ if (offset_ >= length_) return false; |
+ size_t seek = offset_; |
+ // (*) Now we have |seek| >= |offset_| until the end of this function: |
+ while (seek < length_ && *(source_ + seek) != '=') ++seek; |
battre
2012/07/06 07:36:24
I think this is legal but unconventional style. Ty
vabr (Chromium)
2012/07/06 14:09:01
Done, also at the similar while-cycle below.
|
+ if (seek >= length_) { |
+ // This means the data is malformed. |
+ offset_ = seek; |
+ return false; |
+ } |
+ result->key = source_ + offset_; |
+ result->key_length = seek - offset_; // Safe, see (*). |
+ offset_ = ++seek; |
+ while (seek < length_ && *(source_ + seek) != '&') ++seek; |
+ result->val = source_ + offset_; |
+ result->val_length = seek - offset_; // Safe, see (*). |
+ offset_ = ++seek; |
+ return true; |
+} |
+ |
+class PostDataParserMultipart : public PostDataParser { |
+ public: |
+ explicit PostDataParserMultipart(const std::string& boundary) |
+ : source_(NULL), |
+ boundary_(boundary), |
+ state_(kInit) {} |
battre
2012/07/06 07:36:24
initialize all atomic variables
vabr (Chromium)
2012/07/06 14:09:01
Similarly to PostDataParserUrlEncoded, the variabl
|
+ ~PostDataParserMultipart() {} |
+ virtual bool SetSource(const char* source, size_t length) OVERRIDE; |
+ virtual bool GetNextPair(Result* result) OVERRIDE; |
+ virtual bool AllDataReadOK() OVERRIDE; |
+ private: |
battre
2012/07/06 07:36:24
newline before private:
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ // Note on implementation: |
+ // This parser reads the source line by line. There are four types of lines: |
+ // (BOUND) "Boundary" line, separating entries. |
+ // (FBOUND) Final "boundary" line, ends the data. |
+ // (DISP) "Content-Disposition" line, containing the "key" for |result|. |
+ // (EMPTY) empty lines |
+ // (OTHER) other non-empty lines |
+ enum Lines {kBound, kFBound, kDisp, kEmpty, kOther}; |
+ // The parser uses the following 7-state automaton to check for structure: |
+ // kInit --BOUND--> kFirst, kInit --not(BOUND)--> kError |
+ // kFirst --DISP--> kSkip, kFirst --not(DISP)--> kHead |
+ // kHead --DISP--> kSkip, kHead --not(DISP)-->kHead |
+ // kSkip --EMPTY-->kBody, kSkip --not(EMPTY)--> kSkip |
+ // kBody --BOUND--> kFirst, kBody --FBOUND--> kFinal, |
+ // kBody --not(BOUND)--> kBody |
+ enum States {kInit, kFirst, kHead, kSkip, kBody, kFinal, kError}; |
+ // Read one more line from |source_|, update line pointers. |
+ bool GetNextLine(); |
+ // Determine the |line_type_| of the current line_. |
+ void GetLineType(); |
+ // One-step of the automaton, based on |state_| and |line_type_|. |
+ // This calls GetNextLine() and returns it return value. |
+ bool DoStep(); |
+ // Extracts "key" and possibly "val" from a DISP line. Returns success. |
+ bool ParseHead(Result* result); |
+ // We parse the first |length_| bytes from |source_|. |
+ const char* source_; |
+ size_t length_; |
+ // Offset of the current and next line from |source_|: |
+ // [line_]...line... [line_end_]EOL [next_line_]...line... |
+ size_t line_; |
+ size_t line_end_; |
+ size_t next_line_; |
+ const std::string boundary_; |
+ States state_; |
+ Lines line_type_; |
+ DISALLOW_COPY_AND_ASSIGN(PostDataParserMultipart); |
+}; |
+ |
+bool PostDataParserMultipart::AllDataReadOK() { |
+ return source_ != NULL && next_line_ >= length_ && state_ == kFinal; |
+} |
+ |
+bool PostDataParserMultipart::SetSource(const char* source, size_t length) { |
+ if (state_ == kError) return false; |
+ if (source_ != NULL && next_line_ < length_) return false; |
+ source_ = source; |
+ length_ = length; |
+ next_line_ = 0; |
+ return true; |
+} |
+ |
+bool PostDataParserMultipart::GetNextPair(Result* result) { |
+ if (state_ == kError) return false; |
+ while (state_ != kSkip) if (!DoStep()) return false; |
battre
2012/07/06 07:36:24
This should go into multiple lines
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ bool name_parsed = ParseHead(result); |
+ while (state_ != kBody) if (!DoStep()) return false; |
+ size_t val_start; |
+ size_t val_end; |
+ // There may not be more to read from |source_| if the current result comes |
+ // from a "file" input element. But then |result->val| != NULL already. |
+ if (!DoStep()) return result->val != NULL; |
+ val_start = line_; |
+ while (state_ != kFirst && state_ != kFinal) { |
+ val_end = line_end_; |
+ if (!DoStep()) break; |
+ } |
+ if (name_parsed && result->val == NULL) { |
+ // This pair is from non-file form item, |val| is on subsequent lines. |
+ result->val = source_ + val_start; |
+ result->val_length = val_end - val_start; |
+ } |
+ return name_parsed; |
+} |
+ |
+// Contract: only to be called from GetNextLine(). |
+void PostDataParserMultipart::GetLineType() { |
+ const char* line = source_ + line_; |
+ const size_t line_length = line_end_ - line_; |
+ const bool boundary_test = line[0] == '-' && line[1] == '-' && |
+ strncmp(boundary_.c_str(), line+2, boundary_.size()) == 0; |
+ if (boundary_test && line_length == boundary_.size() + 2) |
+ line_type_ = kBound; |
+ else if (boundary_test && line_length == boundary_.size() + 4 && |
+ line[line_length-2] == '-' && line[line_length-1] == '-') |
+ line_type_ = kFBound; |
+ else if (strncmp(line, "Content-Disposition:", 20) == 0) |
battre
2012/07/06 07:36:24
static const char kContentDisposition[] = "Content
vabr (Chromium)
2012/07/06 14:09:01
Done via strlen(), as it appears to be evaluated a
|
+ line_type_ = kDisp; |
+ else if (line_ == line_end_) |
+ line_type_ = kEmpty; |
+ else |
+ line_type_ = kOther; |
+} |
+ |
+// Contract: only to be called from DoStep(). |
+bool PostDataParserMultipart::GetNextLine() { |
+ if (source_ == NULL || state_ == kError) return false; |
+ if (next_line_ >= length_) return false; |
+ size_t seek = line_ = next_line_; |
+ while (seek < length_ && *(source_ + seek) != '\r') ++seek; |
+ line_end_ = seek; |
+ GetLineType(); |
+ if (seek < length_ && *(source_ + seek + 1) != '\n') return false; |
+ next_line_ = seek + 2; |
+ return true; |
+} |
+ |
+bool PostDataParserMultipart::DoStep() { |
+ if (!GetNextLine()) return false; |
+ switch (state_) { |
+ case kInit: |
+ if (line_type_ == kBound) state_ = kFirst; |
battre
2012/07/06 07:36:24
\n after )
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ else |
+ state_ = kError; |
+ break; |
+ case kFirst: |
+ if (line_type_ == kDisp) state_ = kSkip; |
+ else |
+ state_ = kHead; |
+ break; |
+ case kHead: |
+ if (line_type_ == kDisp) state_ = kSkip; |
+ break; |
+ case kSkip: |
+ if (line_type_ == kEmpty) state_ = kBody; |
+ break; |
+ case kBody: |
+ if (line_type_ == kBound) state_ = kFirst; |
+ else if (line_type_ == kFBound) state_ = kFinal; |
+ break; |
+ case kFinal: |
+ if (line_type_ != kEmpty) state_ = kError; |
+ case kError: |
+ break; |
+ } |
+ return true; |
+} |
+ |
+// Contract: line_type_ == kDisp. |
+bool PostDataParserMultipart::ParseHead(Result* result) { |
+ std::string line(source_ + line_, line_end_-line_); |
+ size_t key_offset = line.find(" name=\"") + 7; |
+ if (key_offset == std::string::npos) return false; |
+ result->key = source_ + line_ + key_offset; |
+ result->key_length = line.find('"', key_offset) - key_offset; |
+ size_t val_offset = line.find(" filename=\""); |
+ if (val_offset == std::string::npos) { |
+ result->val = NULL; |
+ } else { |
+ val_offset += 11; |
+ result->val = source_ + line_ + val_offset; |
+ result->val_length = line.find('"', val_offset) - val_offset; |
+ } |
+ return true; |
+} |
+ |
+// Helps to choose the right POST data parser and takes care of its lifetime. |
+class PostDataParserProxy : public PostDataParser { |
+ public: |
+ PostDataParserProxy() : parser_(NULL) {} |
+ ~PostDataParserProxy() { |
+ if (parser_ != NULL) delete parser_; |
+ } |
+ // Chooses the parser based on content-type of the request. True == success. |
+ bool Init(net::URLRequest* request); |
+ virtual bool SetSource(const char* source, size_t length) OVERRIDE; |
+ virtual bool GetNextPair(Result* result) OVERRIDE; |
+ virtual bool AllDataReadOK() OVERRIDE; |
+ private: |
+ PostDataParser* parser_; |
+ DISALLOW_COPY_AND_ASSIGN(PostDataParserProxy); |
+}; |
+ |
+bool PostDataParserProxy::AllDataReadOK() { |
+ return parser_ != NULL ? parser_->AllDataReadOK() : false; |
+} |
+ |
+bool PostDataParserProxy::Init(net::URLRequest* request) { |
+ std::string value; |
+ const bool found = request->extra_request_headers().GetHeader( |
+ "Content-Type", &value); |
+ std::string content_type = value.substr(0, value.find(';')); |
+ if (!found || content_type == "application/x-www-form-urlencoded") { |
+ parser_ = new PostDataParserUrlEncoded(); |
+ } else if (content_type == "text/plain") { |
+ // Unable to parse, may be ambiguous. |
+ } else if (content_type == "multipart/form-data") { |
+ size_t offset = value.find("boundary="); |
+ offset += 9; // 9 == length of "boundary=" |
+ std::string boundary = value.substr(offset, value.find(';', offset)); |
+ parser_ = new PostDataParserMultipart(boundary); |
+ } else { |
+ return false; |
+ } |
+ return parser_ != NULL; |
+} |
+ |
+bool PostDataParserProxy::SetSource(const char* source, size_t length) { |
+ return parser_ != NULL ? parser_->SetSource(source, length) : false; |
+} |
+ |
+bool PostDataParserProxy::GetNextPair(Result* result) { |
+ if (parser_ != NULL) return parser_->GetNextPair(result); |
battre
2012/07/06 07:36:24
newline before return. http://google-styleguide.go
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ else |
+ return false; |
+} |
+ |
+// Takes |dictionary| of <string, list of strings> pairs, and appends |
+// |value| to the list for |key|, creating it if necessary. |
+// Contract: |dictionary| only contains ListValue objects. |
+void UpdateDictionaryWithString(DictionaryValue* dictionary, |
+ const std::string& key, |
+ const std::string& value) { |
+ ListValue* list = NULL; |
+ Value* receive = NULL; |
+ if (!dictionary->Get(key, &receive)) { |
+ list = new ListValue(); |
+ dictionary->Set(key, list); |
+ } else { |
+ list = reinterpret_cast<ListValue*>(receive); // Safe due to contract. |
battre
2012/07/06 07:36:24
you can get rid of the else clause if you use
if (
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ } |
+ StringValue* string_value = new StringValue(value); |
+ list->Append(string_value); |
battre
2012/07/06 07:36:24
I would inline string_value.
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+} |
+ |
+// Extracts the POST data from |request| and writes the data into |out|. |
+// This can be expensive, so it's separated from ExtractRequestInfo(). |
+// Contract: request->method() == "POST" |
+void ExtractRequestInfoPost(net::URLRequest* request, DictionaryValue* out) { |
+ const std::vector<net::UploadData::Element>* elements = |
+ request->get_upload()->elements(); |
+ PostDataParserProxy parser; |
+ parser.Init(request); |
+ DictionaryValue* post_data = new DictionaryValue(); |
battre
2012/07/06 07:36:24
please use a scoped_ptr<DictionaryValue>, this way
vabr (Chromium)
2012/07/06 14:09:01
Done, including using get()/release() where approp
|
+ std::vector<net::UploadData::Element>::const_iterator element; |
+ for (element = elements->begin(); element != elements->end(); ++element) { |
+ if (element->type() != net::UploadData::TYPE_BYTES) continue; |
+ const char* bytes = &(element->bytes()[0]); |
+ const size_t size = element->bytes().size(); |
+ if (!parser.SetSource(bytes, size)) continue; |
battre
2012/07/06 07:36:24
Any reason why you don't pass a const std::vector<
vabr (Chromium)
2012/07/06 14:09:01
No, good point. Done.
|
+ PostDataParser::Result result; |
+ while (parser.GetNextPair(&result)) { |
+ std::string key(result.key, result.key_length); |
+ std::string val(result.val, result.val_length); |
+ UpdateDictionaryWithString(post_data, key, val); |
battre
2012/07/06 07:36:24
How about
GetOrCreateList(post_data, key)->Append(
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ } |
+ } |
+ if (parser.AllDataReadOK()) out->Set(keys::kPostDataKey, post_data); |
battre
2012/07/06 07:36:24
newline before out->Set http://google-styleguide.g
vabr (Chromium)
2012/07/06 14:09:01
Done.
|
+ else |
+ delete post_data; |
+} |
+ |
// Converts a HttpHeaders dictionary to a |name|, |value| pair. Returns |
// true if successful. |
bool FromHeaderDictionary(const DictionaryValue* header_value, |
@@ -416,6 +752,8 @@ bool ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue( |
*extra_info_spec |= BLOCKING; |
else if (str == "asyncBlocking") |
*extra_info_spec |= ASYNC_BLOCKING; |
+ else if (str == "requestPostData") |
+ *extra_info_spec |= REQUEST_POST_DATA; |
else |
return false; |
@@ -500,6 +838,9 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest( |
ListValue args; |
DictionaryValue* dict = new DictionaryValue(); |
ExtractRequestInfo(request, dict); |
+ if (request->method() == "POST" && |
+ extra_info_spec | ExtraInfoSpec::REQUEST_POST_DATA) |
battre
2012/07/06 07:36:24
this should probably be extra_info_spec & ...
I w
vabr (Chromium)
2012/07/06 14:09:01
Done. I agree that reversing the order could be a
|
+ ExtractRequestInfoPost(request, dict); |
args.Append(dict); |
initialize_blocked_requests |= |