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

Unified Diff: net/http/http_stream_parser.cc

Issue 403393003: HTTP retry support. Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 5 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: net/http/http_stream_parser.cc
diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc
index 86b188b6a1f9c09fb62bb3f20f2e40461ac2e7db..ad3b05e2cecde4c00a3e932c0fcabc679d769f9e 100644
--- a/net/http/http_stream_parser.cc
+++ b/net/http/http_stream_parser.cc
@@ -7,8 +7,12 @@
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
#include "base/strings/string_util.h"
#include "base/values.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/upload_data_stream.h"
@@ -78,6 +82,59 @@ bool ShouldTryReadingOnUploadError(int error_code) {
} // namespace
+// To verify that retry attempts will not cause errors we hash all received
+// content. When retrying we hash the content again and verify that the
+// previous hash matches once we have received the same amount of data.
+class HttpStreamParser::HttpStreamHash {
+public:
+ HttpStreamHash()
+ :hash_(crypto::SecureHash::Create(crypto::SecureHash::SHA256)) {
+ }
+
+ // Add to hash.
+ void Update(const void* input, size_t len) {
+ hash_->Update(input, len);
+ }
+
+ // Finish hash once all content has been received.
+ void Finish(void* output, size_t len) {
+ hash_->Finish(output, len);
+ }
+
+ // Verify hash after serializing the state. Then deserialize so that we can
+ // keep hashing if we decide to continue fetching the content.
+ int VerifyHash() {
+ int result = OK;
+ uint8 hash[8];
+ Pickle pickle;
+ hash_->Serialize(&pickle);
+ hash_->Finish(hash, sizeof(hash));
+ DCHECK(sizeof(hash) == sizeof(previous_hash_));
+ if (memcmp(hash, previous_hash_, sizeof(previous_hash_)) != 0) {
+ result = ERR_RETRY_HASH_MISMATCH;
+ UMA_HISTOGRAM_COUNTS("Net.HttpRetry.VerifyHashFailure", 1);
+ } else {
+ PickleIterator data_iterator(pickle);
+ hash_->Deserialize(&data_iterator);
+ UMA_HISTOGRAM_COUNTS("Net.HttpRetry.VerifyHashSuccess", 1);
+ }
+
+ return result;
+ }
+
+ void SetPreviousHash(const void* hash, size_t len) {
+ DCHECK(len == sizeof(previous_hash_));
+ memcpy(previous_hash_, hash, len);
+ }
+
+private:
+ // Hash of current attempt to retrieve the resource.
+ scoped_ptr<crypto::SecureHash> hash_;
+
+ // Hash of previous attempt to retrieve the resource.
+ uint8 previous_hash_[8];
+};
+
// Similar to DrainableIOBuffer(), but this version comes with its own
// storage. The motivation is to avoid repeated allocations of
// DrainableIOBuffer.
@@ -188,6 +245,8 @@ HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection,
response_header_start_offset_(-1),
received_bytes_(0),
response_body_length_(-1),
+ read_offset_(0),
+ stream_hash_(new HttpStreamHash()),
response_body_read_(0),
user_read_buf_(NULL),
user_read_buf_len_(0),
@@ -612,6 +671,22 @@ int HttpStreamParser::DoReadBody() {
// There may be some data left over from reading the response headers.
if (read_buf_->offset()) {
int available = read_buf_->offset() - read_buf_unused_offset_;
+ if (available > 0 && read_offset_) {
+ int64 bytes_from_buffer =
+ available < read_offset_ ? available : read_offset_;
+ stream_hash_->Update((uint8*)read_buf_->StartOfBuffer() +
+ read_buf_unused_offset_, bytes_from_buffer);
+ read_buf_unused_offset_ += bytes_from_buffer;
+ read_offset_ -= bytes_from_buffer;
+ response_body_read_ += bytes_from_buffer;
+ available -= bytes_from_buffer;
+
+ if (read_offset_ == 0 && stream_hash_->VerifyHash() != OK) {
+ io_state_ = STATE_DONE;
+ return ERR_RETRY_HASH_MISMATCH;
+ }
+ }
+
if (available) {
CHECK_GT(available, 0);
int bytes_from_buffer = std::min(available, user_read_buf_len_);
@@ -692,6 +767,32 @@ int HttpStreamParser::DoReadBodyComplete(int result) {
if (result > 0)
response_body_read_ += result;
+ if (result > 0 && read_offset_) {
+ if (result < read_offset_) {
+ read_offset_ -= result;
+ stream_hash_->Update((uint8*)user_read_buf_->data(), result);
+ result = 0;
+ } else {
+ stream_hash_->Update((uint8*)user_read_buf_->data(), read_offset_);
+ memmove(user_read_buf_->data(),
+ user_read_buf_->data() + read_offset_,
+ result - read_offset_);
+ result -= read_offset_;
+ read_offset_ = 0;
+
+ if (stream_hash_->VerifyHash() != OK){
+ io_state_ = STATE_DONE;
+ return ERR_RETRY_HASH_MISMATCH;
+ }
+ }
+
+ if (result == 0) {
+ io_state_ = STATE_READ_BODY;
+ return OK;
+ }
+ } else if (result > 0)
+ stream_hash_->Update((uint8*)user_read_buf_->data(), result);
+
if (result <= 0 || IsResponseBodyComplete()) {
io_state_ = STATE_DONE;
@@ -908,6 +1009,8 @@ int HttpStreamParser::DoParseResponseHeaders(int end_offset) {
response_->headers = headers;
response_->connection_info = HttpResponseInfo::CONNECTION_INFO_HTTP1;
response_->vary_data.Init(*request_, *response_->headers.get());
+ if (response_->headers->response_code() != 200)
+ read_offset_ = 0;
DVLOG(1) << __FUNCTION__ << "()"
<< " content_length = \"" << response_->headers->GetContentLength()
<< "\n\""
@@ -995,6 +1098,16 @@ bool HttpStreamParser::IsConnectionReusable() const {
return connection_->socket() && connection_->socket()->IsConnectedAndIdle();
}
+void HttpStreamParser::SetRestartInfo(
+ int64 read_offset, const void* hash, size_t hash_length) {
+ read_offset_ = read_offset;
+ stream_hash_->SetPreviousHash(hash, hash_length);
+}
+
+void HttpStreamParser::GetHash(void* output, size_t hash_length) {
+ stream_hash_->Finish(output, hash_length);
+}
+
void HttpStreamParser::GetSSLInfo(SSLInfo* ssl_info) {
if (request_->url.SchemeIsSecure() && connection_->socket()) {
SSLClientSocket* ssl_socket =

Powered by Google App Engine
This is Rietveld 408576698