blob: 6bd325746bf1ae8f1438694615a6aeab69e670e6 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/spdy/spdy_header_block.h"
#include <string.h>
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "base/values.h"
#include "net/base/arena.h"
#include "net/http/http_log_util.h"
using base::StringPiece;
using std::dec;
using std::hex;
using std::max;
using std::min;
using std::string;
namespace net {
namespace {
// SpdyHeaderBlock::Storage allocates blocks of this size by default.
const size_t kDefaultStorageBlockSize = 2048;
const char kCookieKey[] = "cookie";
} // namespace
// This class provides a backing store for StringPieces. It previously used
// custom allocation logic, but now uses an UnsafeArena instead. It has the
// property that StringPieces that refer to data in Storage are never
// invalidated until the Storage is deleted or Clear() is called.
//
// Write operations always append to the last block. If there is not enough
// space to perform the write, a new block is allocated, and any unused space
// is wasted.
class SpdyHeaderBlock::Storage {
public:
Storage() : arena_(kDefaultStorageBlockSize) {}
~Storage() { Clear(); }
StringPiece Write(const StringPiece s) {
return StringPiece(arena_.Memdup(s.data(), s.size()), s.size());
}
// Given value, a string already in the arena, perform a realloc and append
// separator and more to the end of the value's new location. If value is the
// most recently added string (via Write), then UnsafeArena will not copy the
// existing value but instead will increase the space reserved for value.
StringPiece Realloc(StringPiece value,
StringPiece separator,
StringPiece more) {
size_t total_length = value.size() + separator.size() + more.size();
char* ptr = const_cast<char*>(value.data());
ptr = arena_.Realloc(ptr, value.size(), total_length);
StringPiece result(ptr, total_length);
ptr += value.size();
memcpy(ptr, separator.data(), separator.size());
ptr += separator.size();
memcpy(ptr, more.data(), more.size());
return result;
}
// If |s| points to the most recent allocation from arena_, the arena will
// reclaim the memory. Otherwise, this method is a no-op.
void Rewind(const StringPiece s) {
arena_.Free(const_cast<char*>(s.data()), s.size());
}
void Clear() { arena_.Reset(); }
private:
UnsafeArena arena_;
};
SpdyHeaderBlock::StringPieceProxy::StringPieceProxy(
SpdyHeaderBlock::MapType* block,
SpdyHeaderBlock::Storage* storage,
SpdyHeaderBlock::MapType::iterator lookup_result,
const StringPiece key)
: block_(block),
storage_(storage),
lookup_result_(lookup_result),
key_(key),
valid_(true) {}
SpdyHeaderBlock::StringPieceProxy::StringPieceProxy(StringPieceProxy&& other)
: block_(other.block_),
storage_(other.storage_),
lookup_result_(other.lookup_result_),
key_(other.key_),
valid_(true) {
other.valid_ = false;
}
SpdyHeaderBlock::StringPieceProxy& SpdyHeaderBlock::StringPieceProxy::operator=(
SpdyHeaderBlock::StringPieceProxy&& other) {
block_ = other.block_;
storage_ = other.storage_;
lookup_result_ = other.lookup_result_;
key_ = other.key_;
valid_ = true;
other.valid_ = false;
return *this;
}
SpdyHeaderBlock::StringPieceProxy::~StringPieceProxy() {
// If the StringPieceProxy is destroyed while lookup_result_ == block_->end(),
// the assignment operator was never used, and the block's Storage can
// reclaim the memory used by the key. This makes lookup-only access to
// SpdyHeaderBlock through operator[] memory-neutral.
if (valid_ && lookup_result_ == block_->end()) {
storage_->Rewind(key_);
}
}
SpdyHeaderBlock::StringPieceProxy& SpdyHeaderBlock::StringPieceProxy::operator=(
const StringPiece value) {
if (lookup_result_ == block_->end()) {
DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")";
lookup_result_ =
block_->insert(std::make_pair(key_, storage_->Write(value))).first;
} else {
DVLOG(1) << "Updating key: " << key_ << " with value: " << value;
lookup_result_->second = storage_->Write(value);
}
return *this;
}
SpdyHeaderBlock::StringPieceProxy::operator StringPiece() const {
return (lookup_result_ == block_->end()) ? StringPiece()
: lookup_result_->second;
}
SpdyHeaderBlock::SpdyHeaderBlock() {}
SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other) {
block_.swap(other.block_);
storage_.swap(other.storage_);
}
SpdyHeaderBlock::~SpdyHeaderBlock() {}
SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) {
block_.swap(other.block_);
storage_.swap(other.storage_);
return *this;
}
SpdyHeaderBlock SpdyHeaderBlock::Clone() const {
SpdyHeaderBlock copy;
for (auto iter : *this) {
copy.AppendHeader(iter.first, iter.second);
}
return copy;
}
bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const {
return size() == other.size() && std::equal(begin(), end(), other.begin());
}
bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const {
return !(operator==(other));
}
string SpdyHeaderBlock::DebugString() const {
if (empty()) {
return "{}";
}
string output = "\n{\n";
for (auto it = begin(); it != end(); ++it) {
output +=
" " + it->first.as_string() + ":" + it->second.as_string() + "\n";
}
output.append("}\n");
return output;
}
void SpdyHeaderBlock::clear() {
block_.clear();
storage_.reset();
}
void SpdyHeaderBlock::insert(
const SpdyHeaderBlock::MapType::value_type& value) {
ReplaceOrAppendHeader(value.first, value.second);
}
SpdyHeaderBlock::StringPieceProxy SpdyHeaderBlock::operator[](
const StringPiece key) {
DVLOG(2) << "Operator[] saw key: " << key;
StringPiece out_key;
auto iter = block_.find(key);
if (iter == block_.end()) {
// We write the key first, to assure that the StringPieceProxy has a
// reference to a valid StringPiece in its operator=.
out_key = GetStorage()->Write(key);
DVLOG(2) << "Key written as: " << std::hex
<< static_cast<const void*>(key.data()) << ", " << std::dec
<< key.size();
} else {
out_key = iter->first;
}
return StringPieceProxy(&block_, GetStorage(), iter, out_key);
}
StringPiece SpdyHeaderBlock::GetHeader(const StringPiece key) const {
auto iter = block_.find(key);
return iter == block_.end() ? StringPiece() : iter->second;
}
void SpdyHeaderBlock::ReplaceOrAppendHeader(const StringPiece key,
const StringPiece value) {
// TODO(birenroy): Write new value in place of old value, if it fits.
auto iter = block_.find(key);
if (iter == block_.end()) {
DVLOG(1) << "Inserting: (" << key << ", " << value << ")";
AppendHeader(key, value);
} else {
DVLOG(1) << "Updating key: " << iter->first << " with value: " << value;
iter->second = GetStorage()->Write(value);
}
}
void SpdyHeaderBlock::AppendValueOrAddHeader(const StringPiece key,
const StringPiece value) {
auto iter = block_.find(key);
if (iter == block_.end()) {
DVLOG(1) << "Inserting: (" << key << ", " << value << ")";
AppendHeader(key, value);
return;
}
DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value;
StringPiece separator("", 1);
if (key == kCookieKey) {
separator = "; ";
}
iter->second = GetStorage()->Realloc(iter->second, separator, value);
}
void SpdyHeaderBlock::AppendHeader(const StringPiece key,
const StringPiece value) {
block_.emplace(GetStorage()->Write(key), GetStorage()->Write(value));
}
SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() {
if (!storage_) {
storage_.reset(new Storage);
}
return storage_.get();
}
std::unique_ptr<base::Value> SpdyHeaderBlockNetLogCallback(
const SpdyHeaderBlock* headers,
NetLogCaptureMode capture_mode) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
base::DictionaryValue* headers_dict = new base::DictionaryValue();
for (SpdyHeaderBlock::const_iterator it = headers->begin();
it != headers->end(); ++it) {
headers_dict->SetWithoutPathExpansion(
it->first.as_string(),
new base::StringValue(ElideHeaderValueForNetLog(
capture_mode, it->first.as_string(), it->second.as_string())));
}
dict->Set("headers", headers_dict);
return std::move(dict);
}
bool SpdyHeaderBlockFromNetLogParam(
const base::Value* event_param,
SpdyHeaderBlock* headers) {
headers->clear();
const base::DictionaryValue* dict = NULL;
const base::DictionaryValue* header_dict = NULL;
if (!event_param ||
!event_param->GetAsDictionary(&dict) ||
!dict->GetDictionary("headers", &header_dict)) {
return false;
}
for (base::DictionaryValue::Iterator it(*header_dict); !it.IsAtEnd();
it.Advance()) {
string value;
if (!it.value().GetAsString(&value)) {
headers->clear();
return false;
}
(*headers)[it.key()] = value;
}
return true;
}
} // namespace net