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

Unified Diff: chrome/browser/extensions/api/web_request/web_request_api.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: Created 8 years, 6 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 side-by-side diff with in-line comments
Download patch
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 |=

Powered by Google App Engine
This is Rietveld 408576698