root/net/tools/balsa/balsa_headers.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. InitMultivaluedHeaders
  2. idx_
  3. idx_
  4. idx_
  5. GetTotalBufferBlockSize
  6. WriteToContiguousBuffer
  7. Write
  8. Reserve
  9. Clear
  10. Swap
  11. CopyFrom
  12. can_write_to_contiguous_buffer_
  13. can_write_to_contiguous_buffer_
  14. AllocBlock
  15. AllocCustomBlock
  16. CopyBlock
  17. CleanupBlocksStartingFrom
  18. key_
  19. key_
  20. transfer_encoding_is_chunked_
  21. Clear
  22. Swap
  23. CopyFrom
  24. AddAndMakeDescription
  25. AppendOrPrependAndMakeDescription
  26. RemoveAllOfHeaderStartingAt
  27. HackHeader
  28. HackAppendToHeader
  29. ReplaceOrAppendHeader
  30. AppendHeader
  31. AppendToHeader
  32. PrependToHeader
  33. GetValueFromHeaderLineDescription
  34. GetHeader
  35. GetHeaderPosition
  36. GetIteratorForKey
  37. AppendOrPrependToHeader
  38. GetConstHeaderLinesIterator
  39. GetHeaderLinesIteratorNoSkip
  40. GetHeaderLinesIterator
  41. GetAllOfHeader
  42. HasNonEmptyHeader
  43. GetAllOfHeaderAsString
  44. IsMultivaluedHeader
  45. RemoveAllOfHeader
  46. RemoveAllHeadersWithPrefix
  47. GetMemoryUsedLowerBound
  48. GetSizeForWriteBuffer
  49. DumpToString
  50. SetFirstLine
  51. SetContentLength
  52. SetChunkEncoding
  53. SetFirstlineFromStringPieces
  54. SetRequestMethod
  55. SetResponseVersion
  56. SetRequestUri
  57. SetResponseCode
  58. SetParsedResponseCodeAndUpdateFirstline
  59. SetRequestVersion
  60. SetResponseReasonPhrase

// Copyright 2013 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/balsa/balsa_headers.h"

#include <stdio.h>
#include <algorithm>
#include <ext/hash_set>
#include <string>
#include <utility>
#include <vector>

#include "base/logging.h"
#include "base/port.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "net/tools/balsa/balsa_enums.h"
#include "net/tools/balsa/buffer_interface.h"
#include "net/tools/balsa/simple_buffer.h"
#include "third_party/tcmalloc/chromium/src/base/googleinit.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;

BalsaHeaders::iterator_base::iterator_base() : headers_(NULL), idx_(0) { }

BalsaHeaders::iterator_base::iterator_base(const iterator_base& it)
    : headers_(it.headers_),
      idx_(it.idx_) {
}

std::ostream& BalsaHeaders::iterator_base::operator<<(std::ostream& os) const {
  os << "[" << this->headers_ << ", " << this->idx_ << "]";
  return os;
}

BalsaHeaders::iterator_base::iterator_base(const BalsaHeaders* headers,
                                           HeaderLines::size_type index)
    : headers_(headers),
      idx_(index) {
}

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::const_header_lines_key_iterator::const_header_lines_key_iterator(
    const const_header_lines_key_iterator& other)
    : iterator_base(other),
      key_(other.key_) {
}

BalsaHeaders::const_header_lines_key_iterator::const_header_lines_key_iterator(
    const BalsaHeaders* headers,
    HeaderLines::size_type index,
    const base::StringPiece& key)
    : iterator_base(headers, index),
      key_(key) {
}

BalsaHeaders::const_header_lines_key_iterator::const_header_lines_key_iterator(
    const BalsaHeaders* headers,
    HeaderLines::size_type index)
    : iterator_base(headers, index) {
}

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

/* [<][>][^][v][top][bottom][index][help] */