// Copyright (c) 2011 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/tools/flip_server/balsa_headers.h" #include <algorithm> #include <ext/hash_set> #include <string> #include <utility> #include <vector> #include "base/logging.h" #include "base/port.h" #include "base/string_piece.h" #include "base/string_util.h" #include "net/tools/flip_server/balsa_enums.h" #include "net/tools/flip_server/buffer_interface.h" #include "net/tools/flip_server/simple_buffer.h" #include "third_party/tcmalloc/chromium/src/base/googleinit.h" // #include "util/gtl/iterator_adaptors-inl.h" // #include "util/gtl/map-util.h" namespace { const char kContentLength[] = "Content-Length"; const char kTransferEncoding[] = "Transfer-Encoding"; const char kSpaceChar = ' '; __gnu_cxx::hash_set<base::StringPiece, net::StringPieceCaseHash, net::StringPieceCaseEqual> g_multivalued_headers; void InitMultivaluedHeaders() { g_multivalued_headers.insert("accept"); g_multivalued_headers.insert("accept-charset"); g_multivalued_headers.insert("accept-encoding"); g_multivalued_headers.insert("accept-language"); g_multivalued_headers.insert("accept-ranges"); g_multivalued_headers.insert("allow"); g_multivalued_headers.insert("cache-control"); g_multivalued_headers.insert("connection"); g_multivalued_headers.insert("content-encoding"); g_multivalued_headers.insert("content-language"); g_multivalued_headers.insert("expect"); g_multivalued_headers.insert("if-match"); g_multivalued_headers.insert("if-none-match"); g_multivalued_headers.insert("pragma"); g_multivalued_headers.insert("proxy-authenticate"); g_multivalued_headers.insert("te"); g_multivalued_headers.insert("trailer"); g_multivalued_headers.insert("transfer-encoding"); g_multivalued_headers.insert("upgrade"); g_multivalued_headers.insert("vary"); g_multivalued_headers.insert("via"); g_multivalued_headers.insert("warning"); g_multivalued_headers.insert("www-authenticate"); // Not mentioned in RFC 2616, but it can have multiple values. g_multivalued_headers.insert("set-cookie"); } REGISTER_MODULE_INITIALIZER(multivalued_headers, InitMultivaluedHeaders()); const int kFastToBufferSize = 32; // I think 22 is adequate, but anyway.. } // namespace namespace net { const size_t BalsaBuffer::kDefaultBlocksize; std::ostream& BalsaHeaders::iterator_base::operator<<(std::ostream& os) const { os << "[" << this->headers_ << ", " << this->idx_ << "]"; return os; } BalsaBuffer::~BalsaBuffer() { CleanupBlocksStartingFrom(0); } // Returns the total amount of memory used by the buffer blocks. size_t BalsaBuffer::GetTotalBufferBlockSize() const { size_t buffer_size = 0; for (Blocks::const_iterator iter = blocks_.begin(); iter != blocks_.end(); ++iter) { buffer_size += iter->buffer_size; } return buffer_size; } void BalsaBuffer::WriteToContiguousBuffer(const base::StringPiece& sp) { if (sp.empty()) { return; } CHECK(can_write_to_contiguous_buffer_); DCHECK_GE(blocks_.size(), 1u); if (blocks_[0].buffer == NULL && sp.size() <= blocksize_) { blocks_[0] = AllocBlock(); memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size()); } else if (blocks_[0].bytes_free < sp.size()) { // the first block isn't big enough, resize it. const size_t old_storage_size_used = blocks_[0].bytes_used(); const size_t new_storage_size = old_storage_size_used + sp.size(); char* new_storage = new char[new_storage_size]; char* old_storage = blocks_[0].buffer; if (old_storage_size_used) { memcpy(new_storage, old_storage, old_storage_size_used); } memcpy(new_storage + old_storage_size_used, sp.data(), sp.size()); blocks_[0].buffer = new_storage; blocks_[0].bytes_free = sp.size(); blocks_[0].buffer_size = new_storage_size; delete[] old_storage; } else { memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size()); } blocks_[0].bytes_free -= sp.size(); } base::StringPiece BalsaBuffer::Write(const base::StringPiece& sp, Blocks::size_type* block_buffer_idx) { if (sp.empty()) { return sp; } char* storage = Reserve(sp.size(), block_buffer_idx); memcpy(storage, sp.data(), sp.size()); return base::StringPiece(storage, sp.size()); } char* BalsaBuffer::Reserve(size_t size, Blocks::size_type* block_buffer_idx) { // There should always be a 'first_block', even if it // contains nothing. DCHECK_GE(blocks_.size(), 1u); BufferBlock* block = NULL; Blocks::size_type block_idx = can_write_to_contiguous_buffer_ ? 1 : 0; for (; block_idx < blocks_.size(); ++block_idx) { if (blocks_[block_idx].bytes_free >= size) { block = &blocks_[block_idx]; break; } } if (block == NULL) { if (blocksize_ < size) { blocks_.push_back(AllocCustomBlock(size)); } else { blocks_.push_back(AllocBlock()); } block = &blocks_.back(); } char* storage = block->start_of_unused_bytes(); block->bytes_free -= size; if (block_buffer_idx) { *block_buffer_idx = block_idx; } return storage; } void BalsaBuffer::Clear() { CHECK(!blocks_.empty()); if (blocksize_ == blocks_[0].buffer_size) { CleanupBlocksStartingFrom(1); blocks_[0].bytes_free = blocks_[0].buffer_size; } else { CleanupBlocksStartingFrom(0); blocks_.push_back(AllocBlock()); } DCHECK_GE(blocks_.size(), 1u); can_write_to_contiguous_buffer_ = true; } void BalsaBuffer::Swap(BalsaBuffer* b) { blocks_.swap(b->blocks_); std::swap(can_write_to_contiguous_buffer_, b->can_write_to_contiguous_buffer_); std::swap(blocksize_, b->blocksize_); } void BalsaBuffer::CopyFrom(const BalsaBuffer& b) { CleanupBlocksStartingFrom(0); blocks_.resize(b.blocks_.size()); for (Blocks::size_type i = 0; i < blocks_.size(); ++i) { blocks_[i] = CopyBlock(b.blocks_[i]); } blocksize_ = b.blocksize_; can_write_to_contiguous_buffer_ = b.can_write_to_contiguous_buffer_; } BalsaBuffer::BalsaBuffer() : blocksize_(kDefaultBlocksize), can_write_to_contiguous_buffer_(true) { blocks_.push_back(AllocBlock()); } BalsaBuffer::BalsaBuffer(size_t blocksize) : blocksize_(blocksize), can_write_to_contiguous_buffer_(true) { blocks_.push_back(AllocBlock()); } BalsaBuffer::BufferBlock BalsaBuffer::AllocBlock() { return AllocCustomBlock(blocksize_); } BalsaBuffer::BufferBlock BalsaBuffer::AllocCustomBlock(size_t blocksize) { return BufferBlock(new char[blocksize], blocksize, blocksize); } BalsaBuffer::BufferBlock BalsaBuffer::CopyBlock(const BufferBlock& b) { BufferBlock block = b; if (b.buffer == NULL) { return block; } block.buffer = new char[b.buffer_size]; memcpy(block.buffer, b.buffer, b.bytes_used()); return block; } void BalsaBuffer::CleanupBlocksStartingFrom(Blocks::size_type start_idx) { for (Blocks::size_type i = start_idx; i < blocks_.size(); ++i) { delete[] blocks_[i].buffer; } blocks_.resize(start_idx); } BalsaHeaders::BalsaHeaders() : balsa_buffer_(4096), content_length_(0), content_length_status_(BalsaHeadersEnums::NO_CONTENT_LENGTH), parsed_response_code_(0), firstline_buffer_base_idx_(0), whitespace_1_idx_(0), non_whitespace_1_idx_(0), whitespace_2_idx_(0), non_whitespace_2_idx_(0), whitespace_3_idx_(0), non_whitespace_3_idx_(0), whitespace_4_idx_(0), end_of_firstline_idx_(0), transfer_encoding_is_chunked_(false) { } BalsaHeaders::~BalsaHeaders() {} void BalsaHeaders::Clear() { balsa_buffer_.Clear(); transfer_encoding_is_chunked_ = false; content_length_ = 0; content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH; parsed_response_code_ = 0; firstline_buffer_base_idx_ = 0; whitespace_1_idx_ = 0; non_whitespace_1_idx_ = 0; whitespace_2_idx_ = 0; non_whitespace_2_idx_ = 0; whitespace_3_idx_ = 0; non_whitespace_3_idx_ = 0; whitespace_4_idx_ = 0; end_of_firstline_idx_ = 0; header_lines_.clear(); } void BalsaHeaders::Swap(BalsaHeaders* other) { // Protect against swapping with self. if (this == other) return; balsa_buffer_.Swap(&other->balsa_buffer_); bool tmp_bool = transfer_encoding_is_chunked_; transfer_encoding_is_chunked_ = other->transfer_encoding_is_chunked_; other->transfer_encoding_is_chunked_ = tmp_bool; size_t tmp_size_t = content_length_; content_length_ = other->content_length_; other->content_length_ = tmp_size_t; BalsaHeadersEnums::ContentLengthStatus tmp_status = content_length_status_; content_length_status_ = other->content_length_status_; other->content_length_status_ = tmp_status; tmp_size_t = parsed_response_code_; parsed_response_code_ = other->parsed_response_code_; other->parsed_response_code_ = tmp_size_t; BalsaBuffer::Blocks::size_type tmp_blk_idx = firstline_buffer_base_idx_; firstline_buffer_base_idx_ = other->firstline_buffer_base_idx_; other->firstline_buffer_base_idx_ = tmp_blk_idx; tmp_size_t = whitespace_1_idx_; whitespace_1_idx_ = other->whitespace_1_idx_; other->whitespace_1_idx_ = tmp_size_t; tmp_size_t = non_whitespace_1_idx_; non_whitespace_1_idx_ = other->non_whitespace_1_idx_; other->non_whitespace_1_idx_ = tmp_size_t; tmp_size_t = whitespace_2_idx_; whitespace_2_idx_ = other->whitespace_2_idx_; other->whitespace_2_idx_ = tmp_size_t; tmp_size_t = non_whitespace_2_idx_; non_whitespace_2_idx_ = other->non_whitespace_2_idx_; other->non_whitespace_2_idx_ = tmp_size_t; tmp_size_t = whitespace_3_idx_; whitespace_3_idx_ = other->whitespace_3_idx_; other->whitespace_3_idx_ = tmp_size_t; tmp_size_t = non_whitespace_3_idx_; non_whitespace_3_idx_ = other->non_whitespace_3_idx_; other->non_whitespace_3_idx_ = tmp_size_t; tmp_size_t = whitespace_4_idx_; whitespace_4_idx_ = other->whitespace_4_idx_; other->whitespace_4_idx_ = tmp_size_t; tmp_size_t = end_of_firstline_idx_; end_of_firstline_idx_ = other->end_of_firstline_idx_; other->end_of_firstline_idx_ = tmp_size_t; swap(header_lines_, other->header_lines_); } void BalsaHeaders::CopyFrom(const BalsaHeaders& other) { // Protect against copying with self. if (this == &other) return; balsa_buffer_.CopyFrom(other.balsa_buffer_); transfer_encoding_is_chunked_ = other.transfer_encoding_is_chunked_; content_length_ = other.content_length_; content_length_status_ = other.content_length_status_; parsed_response_code_ = other.parsed_response_code_; firstline_buffer_base_idx_ = other.firstline_buffer_base_idx_; whitespace_1_idx_ = other.whitespace_1_idx_; non_whitespace_1_idx_ = other.non_whitespace_1_idx_; whitespace_2_idx_ = other.whitespace_2_idx_; non_whitespace_2_idx_ = other.non_whitespace_2_idx_; whitespace_3_idx_ = other.whitespace_3_idx_; non_whitespace_3_idx_ = other.non_whitespace_3_idx_; whitespace_4_idx_ = other.whitespace_4_idx_; end_of_firstline_idx_ = other.end_of_firstline_idx_; header_lines_ = other.header_lines_; } void BalsaHeaders::AddAndMakeDescription(const base::StringPiece& key, const base::StringPiece& value, HeaderLineDescription* d) { CHECK(d != NULL); // + 2 to size for ": " size_t line_size = key.size() + 2 + value.size(); BalsaBuffer::Blocks::size_type block_buffer_idx = 0; char* storage = balsa_buffer_.Reserve(line_size, &block_buffer_idx); size_t base_idx = storage - GetPtr(block_buffer_idx); char* cur_loc = storage; memcpy(cur_loc, key.data(), key.size()); cur_loc += key.size(); *cur_loc = ':'; ++cur_loc; *cur_loc = ' '; ++cur_loc; memcpy(cur_loc, value.data(), value.size()); *d = HeaderLineDescription(base_idx, base_idx + key.size(), base_idx + key.size() + 2, base_idx + key.size() + 2 + value.size(), block_buffer_idx); } void BalsaHeaders::AppendOrPrependAndMakeDescription( const base::StringPiece& key, const base::StringPiece& value, bool append, HeaderLineDescription* d) { // Figure out how much space we need to reserve for the new header size. size_t old_value_size = d->last_char_idx - d->value_begin_idx; if (old_value_size == 0) { AddAndMakeDescription(key, value, d); return; } base::StringPiece old_value(GetPtr(d->buffer_base_idx) + d->value_begin_idx, old_value_size); BalsaBuffer::Blocks::size_type block_buffer_idx = 0; // + 3 because we potentially need to add ": ", and "," to the line. size_t new_size = key.size() + 3 + old_value_size + value.size(); char* storage = balsa_buffer_.Reserve(new_size, &block_buffer_idx); size_t base_idx = storage - GetPtr(block_buffer_idx); base::StringPiece first_value = old_value; base::StringPiece second_value = value; if (!append) { // !append == prepend first_value = value; second_value = old_value; } char* cur_loc = storage; memcpy(cur_loc, key.data(), key.size()); cur_loc += key.size(); *cur_loc = ':'; ++cur_loc; *cur_loc = ' '; ++cur_loc; memcpy(cur_loc, first_value.data(), first_value.size()); cur_loc += first_value.size(); *cur_loc = ','; ++cur_loc; memcpy(cur_loc, second_value.data(), second_value.size()); *d = HeaderLineDescription(base_idx, base_idx + key.size(), base_idx + key.size() + 2, base_idx + new_size, block_buffer_idx); } // Removes all keys value pairs with key 'key' starting at 'start'. void BalsaHeaders::RemoveAllOfHeaderStartingAt(const base::StringPiece& key, HeaderLines::iterator start) { while (start != header_lines_.end()) { start->skip = true; ++start; start = GetHeaderLinesIterator(key, start); } } void BalsaHeaders::HackHeader(const base::StringPiece& key, const base::StringPiece& value) { // See TODO in balsa_headers.h const HeaderLines::iterator end = header_lines_.end(); const HeaderLines::iterator begin = header_lines_.begin(); HeaderLines::iterator i = GetHeaderLinesIteratorNoSkip(key, begin); if (i != end) { // First, remove all of the header lines including this one. We want to // remove before replacing, in case our replacement ends up being appended // at the end (and thus would be removed by this call) RemoveAllOfHeaderStartingAt(key, i); // Now add the replacement, at this location. AddAndMakeDescription(key, value, &(*i)); return; } AppendHeader(key, value); } void BalsaHeaders::HackAppendToHeader(const base::StringPiece& key, const base::StringPiece& append_value) { // See TODO in balsa_headers.h const HeaderLines::iterator end = header_lines_.end(); const HeaderLines::iterator begin = header_lines_.begin(); HeaderLines::iterator i = GetHeaderLinesIterator(key, begin); if (i == end) { HackHeader(key, append_value); return; } AppendOrPrependAndMakeDescription(key, append_value, true, &(*i)); } void BalsaHeaders::ReplaceOrAppendHeader(const base::StringPiece& key, const base::StringPiece& value) { const HeaderLines::iterator end = header_lines_.end(); const HeaderLines::iterator begin = header_lines_.begin(); HeaderLines::iterator i = GetHeaderLinesIterator(key, begin); if (i != end) { // First, remove all of the header lines including this one. We want to // remove before replacing, in case our replacement ends up being appended // at the end (and thus would be removed by this call) RemoveAllOfHeaderStartingAt(key, i); // Now, take the first instance and replace it. This will remove the // 'skipped' tag if the replacement is done in-place. AddAndMakeDescription(key, value, &(*i)); return; } AppendHeader(key, value); } void BalsaHeaders::AppendHeader(const base::StringPiece& key, const base::StringPiece& value) { HeaderLineDescription hld; AddAndMakeDescription(key, value, &hld); header_lines_.push_back(hld); } void BalsaHeaders::AppendToHeader(const base::StringPiece& key, const base::StringPiece& value) { AppendOrPrependToHeader(key, value, true); } void BalsaHeaders::PrependToHeader(const base::StringPiece& key, const base::StringPiece& value) { AppendOrPrependToHeader(key, value, false); } base::StringPiece BalsaHeaders::GetValueFromHeaderLineDescription( const HeaderLineDescription& line) const { DCHECK_GE(line.last_char_idx, line.value_begin_idx); return base::StringPiece(GetPtr(line.buffer_base_idx) + line.value_begin_idx, line.last_char_idx - line.value_begin_idx); } const base::StringPiece BalsaHeaders::GetHeader( const base::StringPiece& key) const { DCHECK(!IsMultivaluedHeader(key)) << "Header '" << key << "' may consist of multiple lines. Do not " << "use BalsaHeaders::GetHeader() or you may be missing some of its " << "values."; const HeaderLines::const_iterator end = header_lines_.end(); const HeaderLines::const_iterator begin = header_lines_.begin(); HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key, begin); if (i == end) { return base::StringPiece(NULL, 0); } return GetValueFromHeaderLineDescription(*i); } BalsaHeaders::const_header_lines_iterator BalsaHeaders::GetHeaderPosition( const base::StringPiece& key) const { const HeaderLines::const_iterator end = header_lines_.end(); const HeaderLines::const_iterator begin = header_lines_.begin(); HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key, begin); if (i == end) { return header_lines_end(); } return const_header_lines_iterator(this, (i - begin)); } BalsaHeaders::const_header_lines_key_iterator BalsaHeaders::GetIteratorForKey( const base::StringPiece& key) const { HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key, header_lines_.begin()); if (i == header_lines_.end()) { return header_lines_key_end(); } const HeaderLines::const_iterator begin = header_lines_.begin(); return const_header_lines_key_iterator(this, (i - begin), key); } void BalsaHeaders::AppendOrPrependToHeader(const base::StringPiece& key, const base::StringPiece& value, bool append) { HeaderLines::iterator i = GetHeaderLinesIterator(key, header_lines_.begin()); if (i == header_lines_.end()) { // The header did not exist already. Instead of appending to an existing // header simply append the key/value pair to the headers. AppendHeader(key, value); return; } HeaderLineDescription hld = *i; AppendOrPrependAndMakeDescription(key, value, append, &hld); // Invalidate the old header line and add the new one. i->skip = true; header_lines_.push_back(hld); } BalsaHeaders::HeaderLines::const_iterator BalsaHeaders::GetConstHeaderLinesIterator( const base::StringPiece& key, BalsaHeaders::HeaderLines::const_iterator start) const { const HeaderLines::const_iterator end = header_lines_.end(); for (HeaderLines::const_iterator i = start; i != end; ++i) { const HeaderLineDescription& line = *i; if (line.skip) { continue; } const size_t key_len = line.key_end_idx - line.first_char_idx; if (key_len != key.size()) { continue; } if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx, key.data(), key_len) == 0) { DCHECK_GE(line.last_char_idx, line.value_begin_idx); return i; } } return end; } BalsaHeaders::HeaderLines::iterator BalsaHeaders::GetHeaderLinesIteratorNoSkip( const base::StringPiece& key, BalsaHeaders::HeaderLines::iterator start) { const HeaderLines::iterator end = header_lines_.end(); for (HeaderLines::iterator i = start; i != end; ++i) { const HeaderLineDescription& line = *i; const size_t key_len = line.key_end_idx - line.first_char_idx; if (key_len != key.size()) { continue; } if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx, key.data(), key_len) == 0) { DCHECK_GE(line.last_char_idx, line.value_begin_idx); return i; } } return end; } BalsaHeaders::HeaderLines::iterator BalsaHeaders::GetHeaderLinesIterator( const base::StringPiece& key, BalsaHeaders::HeaderLines::iterator start) { const HeaderLines::iterator end = header_lines_.end(); for (HeaderLines::iterator i = start; i != end; ++i) { const HeaderLineDescription& line = *i; if (line.skip) { continue; } const size_t key_len = line.key_end_idx - line.first_char_idx; if (key_len != key.size()) { continue; } if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx, key.data(), key_len) == 0) { DCHECK_GE(line.last_char_idx, line.value_begin_idx); return i; } } return end; } void BalsaHeaders::GetAllOfHeader( const base::StringPiece& key, std::vector<base::StringPiece>* out) const { for (const_header_lines_key_iterator it = GetIteratorForKey(key); it != header_lines_end(); ++it) { out->push_back(it->second); } } bool BalsaHeaders::HasNonEmptyHeader(const base::StringPiece& key) const { for (const_header_lines_key_iterator it = GetIteratorForKey(key); it != header_lines_key_end(); ++it) { if (!it->second.empty()) return true; } return false; } void BalsaHeaders::GetAllOfHeaderAsString(const base::StringPiece& key, std::string* out) const { const_header_lines_iterator it = header_lines_begin(); const_header_lines_iterator end = header_lines_end(); for (; it != end; ++it) { if (key == it->first) { if (!out->empty()) { out->append(","); } out->append(std::string(it->second.data(), it->second.size())); } } } // static bool BalsaHeaders::IsMultivaluedHeader(const base::StringPiece& header) { return g_multivalued_headers.find(header) != g_multivalued_headers.end(); } void BalsaHeaders::RemoveAllOfHeader(const base::StringPiece& key) { HeaderLines::iterator it = GetHeaderLinesIterator(key, header_lines_.begin()); RemoveAllOfHeaderStartingAt(key, it); } void BalsaHeaders::RemoveAllHeadersWithPrefix(const base::StringPiece& key) { for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) { if (header_lines_[i].skip) { continue; } HeaderLineDescription& line = header_lines_[i]; const size_t key_len = line.key_end_idx - line.first_char_idx; if (key_len < key.size()) { // If the key given to us is longer than this header, don't consider it. continue; } if (!strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx, key.data(), key.size())) { line.skip = true; } } } size_t BalsaHeaders::GetMemoryUsedLowerBound() const { return (sizeof(*this) + balsa_buffer_.GetTotalBufferBlockSize() + header_lines_.capacity() * sizeof(HeaderLineDescription)); } size_t BalsaHeaders::GetSizeForWriteBuffer() const { // First add the space required for the first line + CRLF size_t write_buf_size = whitespace_4_idx_ - non_whitespace_1_idx_ + 2; // Then add the space needed for each header line to write out + CRLF. const HeaderLines::size_type end = header_lines_.size(); for (HeaderLines::size_type i = 0; i < end; ++i) { const HeaderLineDescription& line = header_lines_[i]; if (!line.skip) { // Add the key size and ": ". write_buf_size += line.key_end_idx - line.first_char_idx + 2; // Add the value size and the CRLF write_buf_size += line.last_char_idx - line.value_begin_idx + 2; } } // Finally tag on the terminal CRLF. return write_buf_size + 2; } void BalsaHeaders::DumpToString(std::string* str) const { const base::StringPiece firstline = first_line(); const int buffer_length = OriginalHeaderStreamEnd() - OriginalHeaderStreamBegin(); // First check whether the header object is empty. if (firstline.empty() && buffer_length == 0) { str->append("\n<empty header>\n"); return; } // Then check whether the header is in a partially parsed state. If so, just // dump the raw data. if (balsa_buffer_.can_write_to_contiguous_buffer()) { base::StringAppendF(str, "\n<incomplete header len: %d>\n%.*s\n", buffer_length, buffer_length, OriginalHeaderStreamBegin()); return; } // If the header is complete, then just dump them with the logical key value // pair. str->reserve(str->size() + GetSizeForWriteBuffer()); base::StringAppendF(str, "\n %.*s\n", static_cast<int>(firstline.size()), firstline.data()); BalsaHeaders::const_header_lines_iterator i = header_lines_begin(); for (; i != header_lines_end(); ++i) { base::StringAppendF(str, " %.*s: %.*s\n", static_cast<int>(i->first.size()), i->first.data(), static_cast<int>(i->second.size()), i->second.data()); } } void BalsaHeaders::SetFirstLine(const base::StringPiece& line) { base::StringPiece new_line = balsa_buffer_.Write(line, &firstline_buffer_base_idx_); whitespace_1_idx_ = new_line.data() - GetPtr(firstline_buffer_base_idx_); non_whitespace_1_idx_ = whitespace_1_idx_; whitespace_4_idx_ = whitespace_1_idx_ + line.size(); whitespace_2_idx_ = whitespace_4_idx_; non_whitespace_2_idx_ = whitespace_4_idx_; whitespace_3_idx_ = whitespace_4_idx_; non_whitespace_3_idx_ = whitespace_4_idx_; end_of_firstline_idx_ = whitespace_4_idx_; } void BalsaHeaders::SetContentLength(size_t length) { // If the content-length is already the one we want, don't do anything. if (content_length_status_ == BalsaHeadersEnums::VALID_CONTENT_LENGTH && content_length_ == length) { return; } const base::StringPiece content_length(kContentLength, sizeof(kContentLength) - 1); // If header state indicates that there is either a content length or // transfer encoding header, remove them before adding the new content // length. There is always the possibility that client can manually add // either header directly and cause content_length_status_ or // transfer_encoding_is_chunked_ to be inconsistent with the actual header. // In the interest of efficiency, however, we will assume that clients will // use the header object correctly and thus we will not scan the all headers // each time this function is called. if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH) { RemoveAllOfHeader(content_length); } else if (transfer_encoding_is_chunked_) { const base::StringPiece transfer_encoding(kTransferEncoding, sizeof(kTransferEncoding) - 1); RemoveAllOfHeader(transfer_encoding); transfer_encoding_is_chunked_ = false; } content_length_status_ = BalsaHeadersEnums::VALID_CONTENT_LENGTH; content_length_ = length; // FastUInt64ToBuffer is supposed to use a maximum of kFastToBufferSize bytes. char buffer[kFastToBufferSize]; int len_converted = snprintf(buffer, sizeof(buffer), "%zd", length); CHECK_GT(len_converted, 0); const base::StringPiece length_str(buffer, len_converted); AppendHeader(content_length, length_str); } void BalsaHeaders::SetChunkEncoding(bool chunk_encode) { if (transfer_encoding_is_chunked_ == chunk_encode) { return; } if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH && chunk_encode) { // Want to change to chunk encoding, but have content length. Arguably we // can leave this step out, since transfer-encoding overrides // content-length. const base::StringPiece content_length(kContentLength, sizeof(kContentLength) - 1); RemoveAllOfHeader(content_length); content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH; content_length_ = 0; } const base::StringPiece transfer_encoding(kTransferEncoding, sizeof(kTransferEncoding) - 1); if (chunk_encode) { const char kChunked[] = "chunked"; const base::StringPiece chunked(kChunked, sizeof(kChunked) - 1); AppendHeader(transfer_encoding, chunked); } else { RemoveAllOfHeader(transfer_encoding); } transfer_encoding_is_chunked_ = chunk_encode; } // See the comment about this function in the header file for a // warning about its usage. void BalsaHeaders::SetFirstlineFromStringPieces( const base::StringPiece& firstline_a, const base::StringPiece& firstline_b, const base::StringPiece& firstline_c) { size_t line_size = (firstline_a.size() + firstline_b.size() + firstline_c.size() + 2); char* storage = balsa_buffer_.Reserve(line_size, &firstline_buffer_base_idx_); char* cur_loc = storage; memcpy(cur_loc, firstline_a.data(), firstline_a.size()); cur_loc += firstline_a.size(); *cur_loc = ' '; ++cur_loc; memcpy(cur_loc, firstline_b.data(), firstline_b.size()); cur_loc += firstline_b.size(); *cur_loc = ' '; ++cur_loc; memcpy(cur_loc, firstline_c.data(), firstline_c.size()); whitespace_1_idx_ = storage - GetPtr(firstline_buffer_base_idx_); non_whitespace_1_idx_ = whitespace_1_idx_; whitespace_2_idx_ = non_whitespace_1_idx_ + firstline_a.size(); non_whitespace_2_idx_ = whitespace_2_idx_ + 1; whitespace_3_idx_ = non_whitespace_2_idx_ + firstline_b.size(); non_whitespace_3_idx_ = whitespace_3_idx_ + 1; whitespace_4_idx_ = non_whitespace_3_idx_ + firstline_c.size(); end_of_firstline_idx_ = whitespace_4_idx_; } void BalsaHeaders::SetRequestMethod(const base::StringPiece& method) { // This is the first of the three parts of the firstline. if (method.size() <= (whitespace_2_idx_ - non_whitespace_1_idx_)) { non_whitespace_1_idx_ = whitespace_2_idx_ - method.size(); char* stream_begin = GetPtr(firstline_buffer_base_idx_); memcpy(stream_begin + non_whitespace_1_idx_, method.data(), method.size()); } else { // The new method is too large to fit in the space available for the old // one, so we have to reformat the firstline. SetFirstlineFromStringPieces(method, request_uri(), request_version()); } } void BalsaHeaders::SetResponseVersion(const base::StringPiece& version) { // Note: There is no difference between request_method() and // response_Version(). Thus, a function to set one is equivalent to a // function to set the other. We maintain two functions for this as it is // much more descriptive, and makes code more understandable. SetRequestMethod(version); } void BalsaHeaders::SetRequestUri(const base::StringPiece& uri) { SetFirstlineFromStringPieces(request_method(), uri, request_version()); } void BalsaHeaders::SetResponseCode(const base::StringPiece& code) { // Note: There is no difference between request_uri() and response_code(). // Thus, a function to set one is equivalent to a function to set the other. // We maintain two functions for this as it is much more descriptive, and // makes code more understandable. SetRequestUri(code); } void BalsaHeaders::SetParsedResponseCodeAndUpdateFirstline( size_t parsed_response_code) { char buffer[kFastToBufferSize]; int len_converted = snprintf(buffer, sizeof(buffer), "%zd", parsed_response_code); CHECK_GT(len_converted, 0); SetResponseCode(base::StringPiece(buffer, len_converted)); } void BalsaHeaders::SetRequestVersion(const base::StringPiece& version) { // This is the last of the three parts of the firstline. // Since whitespace_3_idx and non_whitespace_3_idx may point to the same // place, we ensure below that any available space includes space for a // litteral space (' ') character between the second component and the third // component. If the space between whitespace_3_idx_ and // end_of_firstline_idx_ is >= to version.size() + 1 (for the space), then we // can update the firstline in-place. char* stream_begin = GetPtr(firstline_buffer_base_idx_); if (version.size() + 1 <= end_of_firstline_idx_ - whitespace_3_idx_) { *(stream_begin + whitespace_3_idx_) = kSpaceChar; non_whitespace_3_idx_ = whitespace_3_idx_ + 1; whitespace_4_idx_ = non_whitespace_3_idx_ + version.size(); memcpy(stream_begin + non_whitespace_3_idx_, version.data(), version.size()); } else { // The new version is to large to fit in the space available for the old // one, so we have to reformat the firstline. SetFirstlineFromStringPieces(request_method(), request_uri(), version); } } void BalsaHeaders::SetResponseReasonPhrase(const base::StringPiece& reason) { // Note: There is no difference between request_version() and // response_reason_phrase(). Thus, a function to set one is equivalent to a // function to set the other. We maintain two functions for this as it is // much more descriptive, and makes code more understandable. SetRequestVersion(reason); } } // namespace net