| 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 =
|
|
|