| // 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/third_party/quiche/src/spdy/core/spdy_header_block.h" |
| |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h" |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" |
| |
| namespace spdy { |
| namespace { |
| |
| // By default, linked_hash_map's internal map allocates space for 100 map |
| // buckets on construction, which is larger than necessary. Standard library |
| // unordered map implementations use a list of prime numbers to set the bucket |
| // count for a particular capacity. |kInitialMapBuckets| is chosen to reduce |
| // memory usage for small header blocks, at the cost of having to rehash for |
| // large header blocks. |
| const size_t kInitialMapBuckets = 11; |
| |
| const char kCookieKey[] = "cookie"; |
| const char kNullSeparator = 0; |
| |
| absl::string_view SeparatorForKey(absl::string_view key) { |
| if (key == kCookieKey) { |
| static absl::string_view cookie_separator = "; "; |
| return cookie_separator; |
| } else { |
| return absl::string_view(&kNullSeparator, 1); |
| } |
| } |
| |
| } // namespace |
| |
| Http2HeaderBlock::HeaderValue::HeaderValue(SpdyHeaderStorage* storage, |
| absl::string_view key, |
| absl::string_view initial_value) |
| : storage_(storage), |
| fragments_({initial_value}), |
| pair_({key, {}}), |
| size_(initial_value.size()), |
| separator_size_(SeparatorForKey(key).size()) {} |
| |
| Http2HeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other) |
| : storage_(other.storage_), |
| fragments_(std::move(other.fragments_)), |
| pair_(std::move(other.pair_)), |
| size_(other.size_), |
| separator_size_(other.separator_size_) {} |
| |
| Http2HeaderBlock::HeaderValue& Http2HeaderBlock::HeaderValue::operator=( |
| HeaderValue&& other) { |
| storage_ = other.storage_; |
| fragments_ = std::move(other.fragments_); |
| pair_ = std::move(other.pair_); |
| size_ = other.size_; |
| separator_size_ = other.separator_size_; |
| return *this; |
| } |
| |
| void Http2HeaderBlock::HeaderValue::set_storage(SpdyHeaderStorage* storage) { |
| storage_ = storage; |
| } |
| |
| Http2HeaderBlock::HeaderValue::~HeaderValue() = default; |
| |
| absl::string_view Http2HeaderBlock::HeaderValue::ConsolidatedValue() const { |
| if (fragments_.empty()) { |
| return absl::string_view(); |
| } |
| if (fragments_.size() > 1) { |
| fragments_ = { |
| storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))}; |
| } |
| return fragments_[0]; |
| } |
| |
| void Http2HeaderBlock::HeaderValue::Append(absl::string_view fragment) { |
| size_ += (fragment.size() + separator_size_); |
| fragments_.push_back(fragment); |
| } |
| |
| const std::pair<absl::string_view, absl::string_view>& |
| Http2HeaderBlock::HeaderValue::as_pair() const { |
| pair_.second = ConsolidatedValue(); |
| return pair_; |
| } |
| |
| Http2HeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {} |
| |
| Http2HeaderBlock::iterator::iterator(const iterator& other) = default; |
| |
| Http2HeaderBlock::iterator::~iterator() = default; |
| |
| Http2HeaderBlock::ValueProxy::ValueProxy( |
| Http2HeaderBlock* block, |
| Http2HeaderBlock::MapType::iterator lookup_result, |
| const absl::string_view key, |
| size_t* spdy_header_block_value_size) |
| : block_(block), |
| lookup_result_(lookup_result), |
| key_(key), |
| spdy_header_block_value_size_(spdy_header_block_value_size), |
| valid_(true) {} |
| |
| Http2HeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other) |
| : block_(other.block_), |
| lookup_result_(other.lookup_result_), |
| key_(other.key_), |
| spdy_header_block_value_size_(other.spdy_header_block_value_size_), |
| valid_(true) { |
| other.valid_ = false; |
| } |
| |
| Http2HeaderBlock::ValueProxy& Http2HeaderBlock::ValueProxy::operator=( |
| Http2HeaderBlock::ValueProxy&& other) { |
| block_ = other.block_; |
| lookup_result_ = other.lookup_result_; |
| key_ = other.key_; |
| valid_ = true; |
| other.valid_ = false; |
| spdy_header_block_value_size_ = other.spdy_header_block_value_size_; |
| return *this; |
| } |
| |
| Http2HeaderBlock::ValueProxy::~ValueProxy() { |
| // If the ValueProxy is destroyed while lookup_result_ == block_->end(), |
| // the assignment operator was never used, and the block's SpdyHeaderStorage |
| // can reclaim the memory used by the key. This makes lookup-only access to |
| // Http2HeaderBlock through operator[] memory-neutral. |
| if (valid_ && lookup_result_ == block_->map_.end()) { |
| block_->storage_.Rewind(key_); |
| } |
| } |
| |
| Http2HeaderBlock::ValueProxy& Http2HeaderBlock::ValueProxy::operator=( |
| absl::string_view value) { |
| *spdy_header_block_value_size_ += value.size(); |
| SpdyHeaderStorage* storage = &block_->storage_; |
| if (lookup_result_ == block_->map_.end()) { |
| SPDY_DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; |
| lookup_result_ = |
| block_->map_ |
| .emplace(std::make_pair( |
| key_, HeaderValue(storage, key_, storage->Write(value)))) |
| .first; |
| } else { |
| SPDY_DVLOG(1) << "Updating key: " << key_ << " with value: " << value; |
| *spdy_header_block_value_size_ -= lookup_result_->second.SizeEstimate(); |
| lookup_result_->second = HeaderValue(storage, key_, storage->Write(value)); |
| } |
| return *this; |
| } |
| |
| bool Http2HeaderBlock::ValueProxy::operator==(absl::string_view value) const { |
| if (lookup_result_ == block_->map_.end()) { |
| return false; |
| } else { |
| return value == lookup_result_->second.value(); |
| } |
| } |
| |
| std::string Http2HeaderBlock::ValueProxy::as_string() const { |
| if (lookup_result_ == block_->map_.end()) { |
| return ""; |
| } else { |
| return std::string(lookup_result_->second.value()); |
| } |
| } |
| |
| Http2HeaderBlock::Http2HeaderBlock() : map_(kInitialMapBuckets) {} |
| |
| Http2HeaderBlock::Http2HeaderBlock(Http2HeaderBlock&& other) |
| : map_(kInitialMapBuckets) { |
| map_.swap(other.map_); |
| storage_ = std::move(other.storage_); |
| for (auto& p : map_) { |
| p.second.set_storage(&storage_); |
| } |
| key_size_ = other.key_size_; |
| value_size_ = other.value_size_; |
| } |
| |
| Http2HeaderBlock::~Http2HeaderBlock() = default; |
| |
| Http2HeaderBlock& Http2HeaderBlock::operator=(Http2HeaderBlock&& other) { |
| map_.swap(other.map_); |
| storage_ = std::move(other.storage_); |
| for (auto& p : map_) { |
| p.second.set_storage(&storage_); |
| } |
| key_size_ = other.key_size_; |
| value_size_ = other.value_size_; |
| return *this; |
| } |
| |
| Http2HeaderBlock Http2HeaderBlock::Clone() const { |
| Http2HeaderBlock copy; |
| for (const auto& p : *this) { |
| copy.AppendHeader(p.first, p.second); |
| } |
| return copy; |
| } |
| |
| bool Http2HeaderBlock::operator==(const Http2HeaderBlock& other) const { |
| return size() == other.size() && std::equal(begin(), end(), other.begin()); |
| } |
| |
| bool Http2HeaderBlock::operator!=(const Http2HeaderBlock& other) const { |
| return !(operator==(other)); |
| } |
| |
| std::string Http2HeaderBlock::DebugString() const { |
| if (empty()) { |
| return "{}"; |
| } |
| |
| std::string output = "\n{\n"; |
| for (auto it = begin(); it != end(); ++it) { |
| SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n"); |
| } |
| SpdyStrAppend(&output, "}\n"); |
| return output; |
| } |
| |
| void Http2HeaderBlock::erase(absl::string_view key) { |
| auto iter = map_.find(key); |
| if (iter != map_.end()) { |
| SPDY_DVLOG(1) << "Erasing header with name: " << key; |
| key_size_ -= key.size(); |
| value_size_ -= iter->second.SizeEstimate(); |
| map_.erase(iter); |
| } |
| } |
| |
| void Http2HeaderBlock::clear() { |
| key_size_ = 0; |
| value_size_ = 0; |
| map_.clear(); |
| storage_.Clear(); |
| } |
| |
| void Http2HeaderBlock::insert(const Http2HeaderBlock::value_type& value) { |
| // TODO(birenroy): Write new value in place of old value, if it fits. |
| value_size_ += value.second.size(); |
| |
| auto iter = map_.find(value.first); |
| if (iter == map_.end()) { |
| SPDY_DVLOG(1) << "Inserting: (" << value.first << ", " << value.second |
| << ")"; |
| AppendHeader(value.first, value.second); |
| } else { |
| SPDY_DVLOG(1) << "Updating key: " << iter->first |
| << " with value: " << value.second; |
| value_size_ -= iter->second.SizeEstimate(); |
| iter->second = |
| HeaderValue(&storage_, iter->first, storage_.Write(value.second)); |
| } |
| } |
| |
| Http2HeaderBlock::ValueProxy Http2HeaderBlock::operator[]( |
| const absl::string_view key) { |
| SPDY_DVLOG(2) << "Operator[] saw key: " << key; |
| absl::string_view out_key; |
| auto iter = map_.find(key); |
| if (iter == map_.end()) { |
| // We write the key first, to assure that the ValueProxy has a |
| // reference to a valid absl::string_view in its operator=. |
| out_key = WriteKey(key); |
| SPDY_DVLOG(2) << "Key written as: " << std::hex |
| << static_cast<const void*>(key.data()) << ", " << std::dec |
| << key.size(); |
| } else { |
| out_key = iter->first; |
| } |
| return ValueProxy(this, iter, out_key, &value_size_); |
| } |
| |
| void Http2HeaderBlock::AppendValueOrAddHeader(const absl::string_view key, |
| const absl::string_view value) { |
| value_size_ += value.size(); |
| |
| auto iter = map_.find(key); |
| if (iter == map_.end()) { |
| SPDY_DVLOG(1) << "Inserting: (" << key << ", " << value << ")"; |
| |
| AppendHeader(key, value); |
| return; |
| } |
| SPDY_DVLOG(1) << "Updating key: " << iter->first |
| << "; appending value: " << value; |
| value_size_ += SeparatorForKey(key).size(); |
| iter->second.Append(storage_.Write(value)); |
| } |
| |
| size_t Http2HeaderBlock::EstimateMemoryUsage() const { |
| // TODO(xunjieli): https://crbug.com/669108. Also include |map_| when EMU() |
| // supports linked_hash_map. |
| return SpdyEstimateMemoryUsage(storage_); |
| } |
| |
| void Http2HeaderBlock::AppendHeader(const absl::string_view key, |
| const absl::string_view value) { |
| auto backed_key = WriteKey(key); |
| map_.emplace(std::make_pair( |
| backed_key, HeaderValue(&storage_, backed_key, storage_.Write(value)))); |
| } |
| |
| absl::string_view Http2HeaderBlock::WriteKey(const absl::string_view key) { |
| key_size_ += key.size(); |
| return storage_.Write(key); |
| } |
| |
| size_t Http2HeaderBlock::bytes_allocated() const { |
| return storage_.bytes_allocated(); |
| } |
| |
| } // namespace spdy |