summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjgraettinger@chromium.org <jgraettinger@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-11 16:49:35 +0000
committerjgraettinger@chromium.org <jgraettinger@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-11 16:49:35 +0000
commit90c190720e6520910ae6c0e36307cd151a160747 (patch)
tree15fe32da34ee409e32b95d54e0165a8f3da1136f
parenta40deb8b73004102fee62c8348eae562524253a4 (diff)
downloadchromium_src-90c190720e6520910ae6c0e36307cd151a160747.zip
chromium_src-90c190720e6520910ae6c0e36307cd151a160747.tar.gz
chromium_src-90c190720e6520910ae6c0e36307cd151a160747.tar.bz2
Refactor HpackDecoder's public API to ease integration into SpdyFramer.
HpackDecoder now mimics SpdyHeadersBlockParser's API, one suited to incremental parsing and the planned SpdyHeadersHandlerInterface switch. However the current implementation buffers the headers block until fully recieved, and further buffers the decoded block on SpdyFramer's behalf. This is a temporary measure suited for testing only. HpackDecoder & HpackEncoder now handle details of Cookie crumbling and restruction, and a canonical read-only Huffman table for use by SpdyFramer has been added. This lands server change 62275041 by jgraettinger. BUG=339578 Review URL: https://codereview.chromium.org/178603003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256257 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/spdy/hpack_constants.cc28
-rw-r--r--net/spdy/hpack_constants.h16
-rw-r--r--net/spdy/hpack_decoder.cc95
-rw-r--r--net/spdy/hpack_decoder.h79
-rw-r--r--net/spdy/hpack_decoder_test.cc187
-rw-r--r--net/spdy/hpack_encoder.cc46
-rw-r--r--net/spdy/hpack_encoder.h18
-rw-r--r--net/spdy/hpack_encoder_test.cc64
-rw-r--r--net/spdy/hpack_huffman_table_test.cc28
-rw-r--r--net/spdy/hpack_input_stream.h6
10 files changed, 423 insertions, 144 deletions
diff --git a/net/spdy/hpack_constants.cc b/net/spdy/hpack_constants.cc
index 5e93292..71b85e5 100644
--- a/net/spdy/hpack_constants.cc
+++ b/net/spdy/hpack_constants.cc
@@ -6,6 +6,11 @@
#include <bitset>
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "net/spdy/hpack_huffman_table.h"
+
namespace net {
namespace {
@@ -14,6 +19,25 @@ uint32 bits32(const std::string& bitstring) {
return std::bitset<32>(bitstring).to_ulong();
}
+// SharedHpackHuffmanTable is a Singleton wrapping a HpackHuffmanTable
+// instance initialized with |kHpackHuffmanCode|.
+struct SharedHpackHuffmanTable {
+ public:
+ SharedHpackHuffmanTable() {
+ std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
+ scoped_ptr<HpackHuffmanTable> mutable_table(new HpackHuffmanTable());
+ CHECK(mutable_table->Initialize(&code[0], code.size()));
+ CHECK(mutable_table->IsInitialized());
+ table.reset(mutable_table.release());
+ }
+
+ static SharedHpackHuffmanTable* GetInstance() {
+ return Singleton<SharedHpackHuffmanTable>::get();
+ }
+
+ scoped_ptr<const HpackHuffmanTable> table;
+};
+
} // namespace
// Produced by applying the python program [1] to tables provided by [2].
@@ -299,4 +323,8 @@ std::vector<HpackHuffmanSymbol> HpackHuffmanCode() {
kHpackHuffmanCode + arraysize(kHpackHuffmanCode));
}
+const HpackHuffmanTable& ObtainHpackHuffmanTable() {
+ return *SharedHpackHuffmanTable::GetInstance()->table;
+}
+
} // namespace net
diff --git a/net/spdy/hpack_constants.h b/net/spdy/hpack_constants.h
index a1f2d34..577837e 100644
--- a/net/spdy/hpack_constants.h
+++ b/net/spdy/hpack_constants.h
@@ -29,8 +29,19 @@ struct HpackHuffmanSymbol {
uint16 id;
};
+class HpackHuffmanTable;
+
const uint32 kDefaultHeaderTableSizeSetting = 4096;
+// Largest string literal an HpackDecoder/HpackEncoder will attempt to process
+// before returning an error.
+const uint32 kDefaultMaxStringLiteralSize = 16 * 1024;
+
+// Maximum amount of encoded header buffer HpackDecoder will retain before
+// returning an error.
+// TODO(jgraettinger): Remove with SpdyHeadersHandlerInterface switch.
+const uint32 kMaxDecodeBufferSize = 32 * 1024;
+
// The marker for a string literal that is stored unmodified (i.e.,
// without Huffman encoding) (from 4.1.2).
const HpackPrefix kStringLiteralIdentityEncoded = { 0x0, 1 };
@@ -66,6 +77,11 @@ const HpackPrefix kLiteralIncrementalIndexOpcode = { 0x00, 2 };
// Returns symbol code table from "Appendix C. Huffman Codes".
NET_EXPORT_PRIVATE std::vector<HpackHuffmanSymbol> HpackHuffmanCode();
+// Returns a HpackHuffmanTable instance initialized with |kHpackHuffmanCode|.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+NET_EXPORT_PRIVATE const HpackHuffmanTable& ObtainHpackHuffmanTable();
+
} // namespace net
#endif // NET_SPDY_HPACK_CONSTANTS_H_
diff --git a/net/spdy/hpack_decoder.cc b/net/spdy/hpack_decoder.cc
index 72d8be6..2e1d500 100644
--- a/net/spdy/hpack_decoder.cc
+++ b/net/spdy/hpack_decoder.cc
@@ -6,6 +6,7 @@
#include "base/basictypes.h"
#include "base/logging.h"
+#include "base/strings/string_util.h"
#include "net/spdy/hpack_constants.h"
#include "net/spdy/hpack_output_stream.h"
@@ -14,9 +15,8 @@ namespace net {
using base::StringPiece;
using std::string;
-HpackDecoder::HpackDecoder(const HpackHuffmanTable& table,
- uint32 max_string_literal_size)
- : max_string_literal_size_(max_string_literal_size),
+HpackDecoder::HpackDecoder(const HpackHuffmanTable& table)
+ : max_string_literal_size_(kDefaultMaxStringLiteralSize),
huffman_table_(table) {}
HpackDecoder::~HpackDecoder() {}
@@ -25,32 +25,75 @@ void HpackDecoder::ApplyHeaderTableSizeSetting(uint32 max_size) {
context_.ApplyHeaderTableSizeSetting(max_size);
}
-bool HpackDecoder::DecodeHeaderSet(StringPiece input,
- HpackHeaderPairVector* header_list) {
- HpackInputStream input_stream(max_string_literal_size_, input);
+bool HpackDecoder::HandleControlFrameHeadersData(SpdyStreamId id,
+ const char* headers_data,
+ size_t headers_data_length) {
+ decoded_block_.clear();
+
+ size_t new_size = headers_block_buffer_.size() + headers_data_length;
+ if (new_size > kMaxDecodeBufferSize) {
+ return false;
+ }
+ headers_block_buffer_.insert(headers_block_buffer_.end(),
+ headers_data,
+ headers_data + headers_data_length);
+ return true;
+}
+
+bool HpackDecoder::HandleControlFrameHeadersComplete(SpdyStreamId id) {
+ HpackInputStream input_stream(max_string_literal_size_,
+ headers_block_buffer_);
while (input_stream.HasMoreData()) {
- // May add to |header_list|.
- if (!DecodeNextOpcode(&input_stream, header_list))
+ if (!DecodeNextOpcode(&input_stream))
return false;
}
+ headers_block_buffer_.clear();
- // After processing opcodes, emit everything in the reference set
- // that hasn't already been emitted.
+ // Emit everything in the reference set that hasn't already been emitted.
for (size_t i = 1; i <= context_.GetMutableEntryCount(); ++i) {
if (context_.IsReferencedAt(i) &&
(context_.GetTouchCountAt(i) == HpackEncodingContext::kUntouched)) {
- header_list->push_back(
- HpackHeaderPair(context_.GetNameAt(i).as_string(),
- context_.GetValueAt(i).as_string()));
+ HandleHeaderRepresentation(context_.GetNameAt(i).as_string(),
+ context_.GetValueAt(i).as_string());
}
context_.ClearTouchesAt(i);
}
-
+ // Emit the Cookie header, if any crumbles were encountered.
+ if (!cookie_name_.empty()) {
+ decoded_block_[cookie_name_] = cookie_value_;
+ cookie_name_.clear();
+ cookie_value_.clear();
+ }
return true;
}
-bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream,
- HpackHeaderPairVector* header_list) {
+void HpackDecoder::HandleHeaderRepresentation(StringPiece name,
+ StringPiece value) {
+ typedef std::pair<std::map<string, string>::iterator, bool> InsertResult;
+
+ // TODO(jgraettinger): HTTP/2 requires strict lowercasing of headers,
+ // and the permissiveness here isn't wanted. Back this out in upstream.
+ if (LowerCaseEqualsASCII(name.begin(), name.end(), "cookie")) {
+ if (cookie_name_.empty()) {
+ cookie_name_.assign(name.data(), name.size());
+ cookie_value_.assign(value.data(), value.size());
+ } else {
+ cookie_value_ += "; ";
+ cookie_value_.insert(cookie_value_.end(), value.begin(), value.end());
+ }
+ } else {
+ InsertResult result = decoded_block_.insert(
+ std::make_pair(name.as_string(), value.as_string()));
+ if (!result.second) {
+ result.first->second.push_back('\0');
+ result.first->second.insert(result.first->second.end(),
+ value.begin(),
+ value.end());
+ }
+ }
+}
+
+bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream) {
// Implements 4.4: Encoding context update. Context updates are a special-case
// of indexed header, and must be tested prior to |kIndexedOpcode| below.
if (input_stream->MatchPrefixAndConsume(kEncodingContextOpcode)) {
@@ -58,15 +101,15 @@ bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream,
}
// Implements 4.2: Indexed Header Field Representation.
if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) {
- return DecodeNextIndexedHeader(input_stream, header_list);
+ return DecodeNextIndexedHeader(input_stream);
}
// Implements 4.3.1: Literal Header Field without Indexing.
if (input_stream->MatchPrefixAndConsume(kLiteralNoIndexOpcode)) {
- return DecodeNextLiteralHeader(input_stream, false, header_list);
+ return DecodeNextLiteralHeader(input_stream, false);
}
// Implements 4.3.2: Literal Header Field with Incremental Indexing.
if (input_stream->MatchPrefixAndConsume(kLiteralIncrementalIndexOpcode)) {
- return DecodeNextLiteralHeader(input_stream, true, header_list);
+ return DecodeNextLiteralHeader(input_stream, true);
}
// Unrecognized opcode.
return false;
@@ -87,8 +130,7 @@ bool HpackDecoder::DecodeNextContextUpdate(HpackInputStream* input_stream) {
return false;
}
-bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream,
- HpackHeaderPairVector* header_list) {
+bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream) {
uint32 index = 0;
if (!input_stream->DecodeNextUint32(&index))
return false;
@@ -102,9 +144,8 @@ bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream,
bool emitted = false;
// The index will be put into the reference set.
if (!context_.IsReferencedAt(index)) {
- header_list->push_back(
- HpackHeaderPair(context_.GetNameAt(index).as_string(),
- context_.GetValueAt(index).as_string()));
+ HandleHeaderRepresentation(context_.GetNameAt(index).as_string(),
+ context_.GetValueAt(index).as_string());
emitted = true;
}
@@ -121,8 +162,7 @@ bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream,
}
bool HpackDecoder::DecodeNextLiteralHeader(HpackInputStream* input_stream,
- bool should_index,
- HpackHeaderPairVector* header_list) {
+ bool should_index) {
StringPiece name;
if (!DecodeNextName(input_stream, &name))
return false;
@@ -131,8 +171,7 @@ bool HpackDecoder::DecodeNextLiteralHeader(HpackInputStream* input_stream,
if (!DecodeNextStringLiteral(input_stream, false, &value))
return false;
- header_list->push_back(
- HpackHeaderPair(name.as_string(), value.as_string()));
+ HandleHeaderRepresentation(name, value);
if (!should_index)
return true;
diff --git a/net/spdy/hpack_decoder.h b/net/spdy/hpack_decoder.h
index dbb4728..4e3dbea 100644
--- a/net/spdy/hpack_decoder.h
+++ b/net/spdy/hpack_decoder.h
@@ -5,6 +5,7 @@
#ifndef NET_SPDY_HPACK_DECODER_H_
#define NET_SPDY_HPACK_DECODER_H_
+#include <map>
#include <string>
#include <vector>
@@ -14,6 +15,7 @@
#include "net/base/net_export.h"
#include "net/spdy/hpack_encoding_context.h"
#include "net/spdy/hpack_input_stream.h"
+#include "net/spdy/spdy_protocol.h"
// An HpackDecoder decodes header sets as outlined in
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06
@@ -32,50 +34,79 @@ class NET_EXPORT_PRIVATE HpackDecoder {
// |table| is an initialized HPACK Huffman table, having an
// externally-managed lifetime which spans beyond HpackDecoder.
- explicit HpackDecoder(const HpackHuffmanTable& table,
- uint32 max_string_literal_size);
+ explicit HpackDecoder(const HpackHuffmanTable& table);
~HpackDecoder();
// Called upon acknowledgement of SETTINGS_HEADER_TABLE_SIZE.
// See HpackEncodingContext::ApplyHeaderTableSizeSetting().
void ApplyHeaderTableSizeSetting(uint32 max_size);
- // Decodes the given string into the given header set. Returns
- // whether or not the decoding was successful.
- //
- // TODO(jgraettinger): Parse headers using incrementally-receieved data,
- // and emit headers via a delegate. (See SpdyHeadersBlockParser and
- // SpdyHeadersHandlerInterface).
- bool DecodeHeaderSet(base::StringPiece input,
- HpackHeaderPairVector* header_list);
-
- // Accessors for testing.
-
- bool DecodeNextNameForTest(HpackInputStream* input_stream,
- base::StringPiece* next_name) {
- return DecodeNextName(input_stream, next_name);
+ // Called as headers data arrives. Returns false if an error occurred.
+ // TODO(jgraettinger): A future version of this method will incrementally
+ // parse and deliver headers via SpdyHeadersHandlerInterface. For now,
+ // header data is buffered until HandleControlFrameHeadersComplete().
+ bool HandleControlFrameHeadersData(SpdyStreamId stream_id,
+ const char* headers_data,
+ size_t headers_data_length);
+
+ // Called after a headers block has been completely delivered via
+ // HandleControlFrameHeadersData(). Returns false if an error occurred.
+ // TODO(jgraettinger): A future version of this method will simply deliver
+ // the Cookie header (which has been incrementally reconstructed) and notify
+ // the visitor that the block is finished. For now, this method decodes the
+ // complete buffered block, and stores results to |decoded_block_|.
+ bool HandleControlFrameHeadersComplete(SpdyStreamId stream_id);
+
+ // Accessor for the most recently decoded headers block. Valid until the next
+ // call to HandleControlFrameHeadersData().
+ // TODO(jgraettinger): This was added to facilitate re-encoding the block in
+ // SPDY3 format for delivery to the SpdyFramer visitor, and will be removed
+ // with the migration to SpdyHeadersHandlerInterface.
+ const std::map<std::string, std::string>& decoded_block() {
+ return decoded_block_;
}
private:
+ // Adds the header representation to |decoded_block_|, applying the
+ // following rules, as per sections 8.1.3.3 & 8.1.3.4 of the HTTP2 draft
+ // specification:
+ // - Multiple values of the Cookie header are joined, delmited by '; '.
+ // This reconstruction is required to properly handle Cookie crumbling.
+ // - Multiple values of other headers are joined and delimited by '\0'.
+ // Note that this may be too accomodating, as the sender's HTTP2 layer
+ // should have already joined and delimited these values.
+ //
+ // TODO(jgraettinger): This method will eventually emit to the
+ // SpdyHeadersHandlerInterface visitor.
+ void HandleHeaderRepresentation(base::StringPiece name,
+ base::StringPiece value);
+
const uint32 max_string_literal_size_;
HpackEncodingContext context_;
+ // Incrementally reconstructed cookie value. Name is also kept to preserve
+ // input casing.
+ std::string cookie_value_, cookie_name_;
+
+ // TODO(jgraettinger): Buffer for headers data, and storage for the last-
+ // processed headers block. Both will be removed with the switch to
+ // SpdyHeadersHandlerInterface.
+ std::string headers_block_buffer_;
+ std::map<std::string, std::string> decoded_block_;
+
// Huffman table to be applied to decoded Huffman literals,
// and scratch space for storing those decoded literals.
const HpackHuffmanTable& huffman_table_;
std::string huffman_key_buffer_, huffman_value_buffer_;
// Handlers for decoding HPACK opcodes and header representations
- // (or parts thereof). These methods emit headers into
- // |header_list|, and return true on success and false on error.
- bool DecodeNextOpcode(HpackInputStream* input_stream,
- HpackHeaderPairVector* header_list);
+ // (or parts thereof). These methods return true on success and
+ // false on error.
+ bool DecodeNextOpcode(HpackInputStream* input_stream);
bool DecodeNextContextUpdate(HpackInputStream* input_stream);
- bool DecodeNextIndexedHeader(HpackInputStream* input_stream,
- HpackHeaderPairVector* header_list);
+ bool DecodeNextIndexedHeader(HpackInputStream* input_stream);
bool DecodeNextLiteralHeader(HpackInputStream* input_stream,
- bool should_index,
- HpackHeaderPairVector* header_list);
+ bool should_index);
bool DecodeNextName(HpackInputStream* input_stream,
base::StringPiece* next_name);
bool DecodeNextStringLiteral(HpackInputStream* input_stream,
diff --git a/net/spdy/hpack_decoder_test.cc b/net/spdy/hpack_decoder_test.cc
index 4663c62..9631cd1 100644
--- a/net/spdy/hpack_decoder_test.cc
+++ b/net/spdy/hpack_decoder_test.cc
@@ -20,6 +20,9 @@ namespace net {
namespace test {
+using base::StringPiece;
+using std::string;
+
class HpackEncodingContextPeer {
public:
explicit HpackEncodingContextPeer(const HpackEncodingContext& context)
@@ -34,14 +37,39 @@ class HpackEncodingContextPeer {
class HpackDecoderPeer {
public:
- explicit HpackDecoderPeer(const HpackDecoder& decoder)
+ explicit HpackDecoderPeer(HpackDecoder* decoder)
: decoder_(decoder) {}
+
+ void HandleHeaderRepresentation(StringPiece name, StringPiece value) {
+ decoder_->HandleHeaderRepresentation(name, value);
+ }
+ bool DecodeNextName(HpackInputStream* in, StringPiece* out) {
+ return decoder_->DecodeNextName(in, out);
+ }
HpackEncodingContextPeer context_peer() {
- return HpackEncodingContextPeer(decoder_.context_);
+ return HpackEncodingContextPeer(decoder_->context_);
+ }
+ void set_cookie_name(string name) {
+ decoder_->cookie_name_ = name;
+ }
+ string cookie_name() {
+ return decoder_->cookie_name_;
+ }
+ void set_cookie_value(string value) {
+ decoder_->cookie_value_ = value;
+ }
+ string cookie_value() {
+ return decoder_->cookie_value_;
+ }
+ const std::map<string, string>& decoded_block() const {
+ return decoder_->decoded_block_;
+ }
+ const string& headers_block_buffer() const {
+ return decoder_->headers_block_buffer_;
}
private:
- const HpackDecoder& decoder_;
+ HpackDecoder* decoder_;
};
} // namespace test
@@ -59,49 +87,122 @@ const size_t kLiteralBound = 1024;
class HpackDecoderTest : public ::testing::Test {
protected:
HpackDecoderTest()
- : decoder_(huffman_table_, kLiteralBound),
- decoder_peer_(decoder_),
+ : decoder_(ObtainHpackHuffmanTable()),
+ decoder_peer_(&decoder_),
context_peer_(decoder_peer_.context_peer()) {}
- // Utility function to decode a string into a header set, assuming
- // that the emitted headers have unique names.
- std::map<string, string> DecodeUniqueHeaderSet(StringPiece str) {
- HpackHeaderPairVector header_list;
- EXPECT_TRUE(decoder_.DecodeHeaderSet(str, &header_list));
- std::map<string, string> header_set(
- header_list.begin(), header_list.end());
- // Make sure |header_list| has no duplicates.
- EXPECT_EQ(header_set.size(), header_list.size());
- return header_set;
+ bool DecodeHeaderBlock(StringPiece str) {
+ return decoder_.HandleControlFrameHeadersData(0, str.data(), str.size()) &&
+ decoder_.HandleControlFrameHeadersComplete(0);
+ }
+ const std::map<string, string>& decoded_block() const {
+ // TODO(jgraettinger): HpackDecoderTest should implement
+ // SpdyHeadersHandlerInterface, and collect headers for examination.
+ return decoder_peer_.decoded_block();
+ }
+ // TODO(jgraettinger): Eliminate uses of this in tests below. Prefer
+ // DecodeHeaderBlock().
+ const std::map<string, string>& DecodeUniqueHeaderSet(StringPiece str) {
+ EXPECT_TRUE(DecodeHeaderBlock(str));
+ return decoded_block();
}
- HpackHuffmanTable huffman_table_;
HpackDecoder decoder_;
test::HpackDecoderPeer decoder_peer_;
test::HpackEncodingContextPeer context_peer_;
};
+TEST_F(HpackDecoderTest, HandleControlFrameHeadersData) {
+ // Strings under threshold are concatenated in the buffer.
+ EXPECT_TRUE(decoder_.HandleControlFrameHeadersData(
+ 0, "small string one", 16));
+ EXPECT_TRUE(decoder_.HandleControlFrameHeadersData(
+ 0, "small string two", 16));
+ // A string which would push the buffer over the threshold is refused.
+ EXPECT_FALSE(decoder_.HandleControlFrameHeadersData(
+ 0, "fails", kMaxDecodeBufferSize - 32 + 1));
+
+ EXPECT_EQ(decoder_peer_.headers_block_buffer(),
+ "small string onesmall string two");
+}
+
+TEST_F(HpackDecoderTest, HandleControlFrameHeadersComplete) {
+ // Decode a block which toggles two static headers into the reference set.
+ EXPECT_TRUE(DecodeHeaderBlock("\x82\x86"));
+
+ decoder_peer_.set_cookie_name("CooKie");
+ decoder_peer_.set_cookie_value("foobar=baz");
+
+ // Headers in the reference set should be emitted.
+ // Incremental cookie buffer should be emitted and cleared.
+ decoder_.HandleControlFrameHeadersData(0, NULL, 0);
+ decoder_.HandleControlFrameHeadersComplete(0);
+
+ EXPECT_THAT(decoded_block(), ElementsAre(
+ Pair(":method", "GET"),
+ Pair(":path", "/index.html"),
+ Pair("CooKie", "foobar=baz")));
+
+ EXPECT_EQ(decoder_peer_.cookie_name(), "");
+ EXPECT_EQ(decoder_peer_.cookie_value(), "");
+}
+
+TEST_F(HpackDecoderTest, HandleHeaderRepresentation) {
+ // Casing of first Cookie is retained, but all instances are joined.
+ decoder_peer_.HandleHeaderRepresentation("cOOkie", " part 1");
+ decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 ");
+ decoder_peer_.HandleHeaderRepresentation("cookie", "part3");
+
+ // Already-delimited headers are passed through.
+ decoder_peer_.HandleHeaderRepresentation("passed-through",
+ string("foo\0baz", 7));
+
+ // Other headers are joined on \0. Case matters.
+ decoder_peer_.HandleHeaderRepresentation("joined", "not joined");
+ decoder_peer_.HandleHeaderRepresentation("joineD", "value 1");
+ decoder_peer_.HandleHeaderRepresentation("joineD", "value 2");
+
+ // Empty headers remain empty.
+ decoder_peer_.HandleHeaderRepresentation("empty", "");
+
+ // Joined empty headers work as expected.
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "foo");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+
+ // Non-contiguous cookie crumb.
+ decoder_peer_.HandleHeaderRepresentation("Cookie", " fin!");
+
+ // Finish and emit all headers.
+ decoder_.HandleControlFrameHeadersComplete(0);
+
+ EXPECT_THAT(decoded_block(), ElementsAre(
+ Pair("cOOkie", " part 1; part 2 ; part3; fin!"),
+ Pair("empty", ""),
+ Pair("empty-joined", string("\0foo\0\0", 6)),
+ Pair("joineD", string("value 1\0value 2", 15)),
+ Pair("joined", "not joined"),
+ Pair("passed-through", string("foo\0baz", 7))));
+}
+
// Decoding an encoded name with a valid string literal should work.
TEST_F(HpackDecoderTest, DecodeNextNameLiteral) {
HpackInputStream input_stream(kLiteralBound, StringPiece("\x00\x04name", 6));
StringPiece string_piece;
- EXPECT_TRUE(decoder_.DecodeNextNameForTest(&input_stream, &string_piece));
+ EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ("name", string_piece);
EXPECT_FALSE(input_stream.HasMoreData());
}
TEST_F(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) {
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(huffman_table_.Initialize(&code[0], code.size()));
- }
char input[] = "\x00\x88\x4e\xb0\x8b\x74\x97\x90\xfa\x7f";
StringPiece foo(input, arraysize(input) - 1);
HpackInputStream input_stream(kLiteralBound, foo);
StringPiece string_piece;
- EXPECT_TRUE(decoder_.DecodeNextNameForTest(&input_stream, &string_piece));
+ EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ("custom-key", string_piece);
EXPECT_FALSE(input_stream.HasMoreData());
}
@@ -111,7 +212,7 @@ TEST_F(HpackDecoderTest, DecodeNextNameIndexed) {
HpackInputStream input_stream(kLiteralBound, "\x01");
StringPiece string_piece;
- EXPECT_TRUE(decoder_.DecodeNextNameForTest(&input_stream, &string_piece));
+ EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ(":authority", string_piece);
EXPECT_FALSE(input_stream.HasMoreData());
}
@@ -122,7 +223,7 @@ TEST_F(HpackDecoderTest, DecodeNextNameInvalidIndex) {
HpackInputStream input_stream(kLiteralBound, "\x3d");
StringPiece string_piece;
- EXPECT_FALSE(decoder_.DecodeNextNameForTest(&input_stream, &string_piece));
+ EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
}
// Decoding an indexed header should toggle the index's presence in
@@ -150,19 +251,16 @@ TEST_F(HpackDecoderTest, IndexedHeaderBasic) {
// Test a too-large indexed header.
TEST_F(HpackDecoderTest, InvalidIndexedHeader) {
- HpackHeaderPairVector header_list;
// High-bit set, and a prefix of one more than the number of static entries.
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece("\xbd", 1), &header_list));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\xbd", 1)));
}
TEST_F(HpackDecoderTest, ContextUpdateMaximumSize) {
- HpackHeaderPairVector header_list;
EXPECT_EQ(kDefaultHeaderTableSizeSetting,
context_peer_.header_table().max_size());
{
// Maximum-size update with size 126. Succeeds.
- EXPECT_TRUE(decoder_.DecodeHeaderSet(StringPiece("\x80\x7e", 2),
- &header_list));
+ EXPECT_TRUE(DecodeHeaderBlock(StringPiece("\x80\x7e", 2)));
EXPECT_EQ(126u, context_peer_.header_table().max_size());
}
string input;
@@ -174,7 +272,7 @@ TEST_F(HpackDecoderTest, ContextUpdateMaximumSize) {
output_stream.AppendUint32ForTest(kDefaultHeaderTableSizeSetting);
output_stream.TakeString(&input);
- EXPECT_TRUE(decoder_.DecodeHeaderSet(StringPiece(input), &header_list));
+ EXPECT_TRUE(DecodeHeaderBlock(StringPiece(input)));
EXPECT_EQ(kDefaultHeaderTableSizeSetting,
context_peer_.header_table().max_size());
}
@@ -186,7 +284,7 @@ TEST_F(HpackDecoderTest, ContextUpdateMaximumSize) {
output_stream.AppendUint32ForTest(kDefaultHeaderTableSizeSetting + 1);
output_stream.TakeString(&input);
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece(input), &header_list));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece(input)));
EXPECT_EQ(kDefaultHeaderTableSizeSetting,
context_peer_.header_table().max_size());
}
@@ -211,7 +309,6 @@ TEST_F(HpackDecoderTest, ContextUpdateClearReferenceSet) {
// Decoding two valid encoded literal headers with no indexing should
// work.
TEST_F(HpackDecoderTest, LiteralHeaderNoIndexing) {
- HpackHeaderPairVector header_list;
// First header with indexed name, second header with string literal
// name.
std::map<string, string> header_set =
@@ -244,24 +341,22 @@ TEST_F(HpackDecoderTest, LiteralHeaderIncrementalIndexing) {
// Decoding literal headers with invalid indices should fail
// gracefully.
TEST_F(HpackDecoderTest, LiteralHeaderInvalidIndices) {
- HpackHeaderPairVector header_list;
-
// No indexing.
// One more than the number of static table entries.
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece("\x7d", 1), &header_list));
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece("\x40", 1), &header_list));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x7d", 1)));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x40", 1)));
// Incremental indexing.
// One more than the number of static table entries.
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece("\x3d", 1), &header_list));
- EXPECT_FALSE(decoder_.DecodeHeaderSet(StringPiece("\x00", 1), &header_list));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x3d", 1)));
+ EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x00", 1)));
}
// Round-tripping the header set from E.2.1 should work.
TEST_F(HpackDecoderTest, BasicE21) {
- HpackEncoder encoder(kLiteralBound);
+ HpackEncoder encoder;
std::map<string, string> expected_header_set;
expected_header_set[":method"] = "GET";
@@ -273,17 +368,11 @@ TEST_F(HpackDecoderTest, BasicE21) {
EXPECT_TRUE(encoder.EncodeHeaderSet(
expected_header_set, &encoded_header_set));
- HpackHeaderPairVector header_list;
- EXPECT_TRUE(decoder_.DecodeHeaderSet(encoded_header_set, &header_list));
- std::map<string, string> header_set(header_list.begin(), header_list.end());
- EXPECT_EQ(expected_header_set, header_set);
+ EXPECT_TRUE(DecodeHeaderBlock(encoded_header_set));
+ EXPECT_EQ(expected_header_set, decoded_block());
}
TEST_F(HpackDecoderTest, SectionD3RequestHuffmanExamples) {
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(huffman_table_.Initialize(&code[0], code.size()));
- }
std::map<string, string> header_set;
// 82 | == Indexed - Add ==
@@ -376,10 +465,6 @@ TEST_F(HpackDecoderTest, SectionD3RequestHuffmanExamples) {
}
TEST_F(HpackDecoderTest, SectionD5ResponseHuffmanExamples) {
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(huffman_table_.Initialize(&code[0], code.size()));
- }
std::map<string, string> header_set;
decoder_.ApplyHeaderTableSizeSetting(256);
diff --git a/net/spdy/hpack_encoder.cc b/net/spdy/hpack_encoder.cc
index 3755cc1..4690426 100644
--- a/net/spdy/hpack_encoder.cc
+++ b/net/spdy/hpack_encoder.cc
@@ -4,28 +4,38 @@
#include "net/spdy/hpack_encoder.h"
+#include "base/strings/string_util.h"
#include "net/spdy/hpack_output_stream.h"
namespace net {
+using base::StringPiece;
using std::string;
-HpackEncoder::HpackEncoder(uint32 max_string_literal_size)
- : max_string_literal_size_(max_string_literal_size) {}
+HpackEncoder::HpackEncoder()
+ : max_string_literal_size_(kDefaultMaxStringLiteralSize) {}
HpackEncoder::~HpackEncoder() {}
bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set,
string* output) {
- // TOOD(akalin): Do more sophisticated encoding.
+ // TOOD(jgraettinger): Do more sophisticated encoding.
HpackOutputStream output_stream(max_string_literal_size_);
for (std::map<string, string>::const_iterator it = header_set.begin();
it != header_set.end(); ++it) {
- // TODO(akalin): Clarify in the spec that encoding with the name
- // as a literal is OK even if the name already exists in the
- // header table.
- if (!output_stream.AppendLiteralHeaderNoIndexingWithName(
- it->first, it->second)) {
+ // TODO(jgraettinger): HTTP/2 requires strict lowercasing of headers,
+ // and the permissiveness here isn't wanted. Back this out in upstream.
+ if (LowerCaseEqualsASCII(it->first, "cookie")) {
+ std::vector<StringPiece> crumbs;
+ CookieToCrumbs(it->second, &crumbs);
+ for (size_t i = 0; i != crumbs.size(); i++) {
+ if (!output_stream.AppendLiteralHeaderNoIndexingWithName(
+ it->first, crumbs[i])) {
+ return false;
+ }
+ }
+ } else if (!output_stream.AppendLiteralHeaderNoIndexingWithName(
+ it->first, it->second)) {
return false;
}
}
@@ -33,4 +43,24 @@ bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set,
return true;
}
+void HpackEncoder::CookieToCrumbs(StringPiece cookie,
+ std::vector<StringPiece>* out) {
+ out->clear();
+ for (size_t pos = 0;;) {
+ size_t end = cookie.find(';', pos);
+
+ if (end == StringPiece::npos) {
+ out->push_back(cookie.substr(pos));
+ return;
+ }
+ out->push_back(cookie.substr(pos, end - pos));
+
+ // Consume next space if present.
+ pos = end + 1;
+ if (pos != cookie.size() && cookie[pos] == ' ') {
+ pos++;
+ }
+ }
+}
+
} // namespace net
diff --git a/net/spdy/hpack_encoder.h b/net/spdy/hpack_encoder.h
index 92bdf6b3..12fa8b4 100644
--- a/net/spdy/hpack_encoder.h
+++ b/net/spdy/hpack_encoder.h
@@ -10,17 +10,24 @@
#include "base/basictypes.h"
#include "base/macros.h"
+#include "base/strings/string_piece.h"
#include "net/base/net_export.h"
#include "net/spdy/hpack_encoding_context.h"
-namespace net {
-
// An HpackEncoder encodes header sets as outlined in
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-06
+namespace net {
+
+namespace test {
+class HpackEncoderPeer;
+} // namespace test
+
class NET_EXPORT_PRIVATE HpackEncoder {
public:
- explicit HpackEncoder(uint32 max_string_literal_size);
+ friend class test::HpackEncoderPeer;
+
+ explicit HpackEncoder();
~HpackEncoder();
// Encodes the given header set into the given string. Returns
@@ -29,7 +36,10 @@ class NET_EXPORT_PRIVATE HpackEncoder {
std::string* output);
private:
- const uint32 max_string_literal_size_;
+ static void CookieToCrumbs(base::StringPiece cookie,
+ std::vector<base::StringPiece>* out);
+
+ uint32 max_string_literal_size_;
HpackEncodingContext context_;
DISALLOW_COPY_AND_ASSIGN(HpackEncoder);
diff --git a/net/spdy/hpack_encoder_test.cc b/net/spdy/hpack_encoder_test.cc
index d02e276..316952e 100644
--- a/net/spdy/hpack_encoder_test.cc
+++ b/net/spdy/hpack_encoder_test.cc
@@ -7,18 +7,63 @@
#include <map>
#include <string>
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
+using base::StringPiece;
+using std::string;
+using testing::ElementsAre;
+
+namespace test {
+
+class HpackEncoderPeer {
+ public:
+ explicit HpackEncoderPeer(HpackEncoder* encoder)
+ : encoder_(encoder) {}
+
+ void set_max_string_literal_size(uint32 size) {
+ encoder_->max_string_literal_size_ = size;
+ }
+ static void CookieToCrumbs(StringPiece cookie,
+ std::vector<StringPiece>* out) {
+ HpackEncoder::CookieToCrumbs(cookie, out);
+ }
+ private:
+ HpackEncoder* encoder_;
+};
+
+} // namespace test
+
namespace {
-using std::string;
+TEST(HpackEncoderTest, CookieToCrumbs) {
+ test::HpackEncoderPeer peer(NULL);
+ std::vector<StringPiece> out;
+
+ // A space after ';' is consumed. All other spaces remain.
+ // ';' at begin and end of string produce empty crumbs.
+ peer.CookieToCrumbs(" foo=1;bar=2 ; baz=3; bing=4;", &out);
+ EXPECT_THAT(out, ElementsAre(" foo=1", "bar=2 ", "baz=3", " bing=4", ""));
+
+ peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
+ EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));
+
+ peer.CookieToCrumbs("foo=bar; baz=bing", &out);
+ EXPECT_THAT(out, ElementsAre("foo=bar", "baz=bing"));
+
+ peer.CookieToCrumbs("baz=bing", &out);
+ EXPECT_THAT(out, ElementsAre("baz=bing"));
+
+ peer.CookieToCrumbs("", &out);
+ EXPECT_THAT(out, ElementsAre(""));
+}
// Test that EncoderHeaderSet() simply encodes everything as literals
// without indexing.
TEST(HpackEncoderTest, Basic) {
- HpackEncoder encoder(kuint32max);
+ HpackEncoder encoder;
std::map<string, string> header_set1;
header_set1["name1"] = "value1";
@@ -39,10 +84,23 @@ TEST(HpackEncoderTest, Basic) {
"\x40\x05name3\x06value3", encoded_header_set2);
}
+TEST(HpackEncoderTest, CookieCrumbling) {
+ HpackEncoder encoder;
+
+ std::map<string, string> header_set;
+ header_set["Cookie"] = "key1=value1; key2=value2";
+
+ string encoded_header_set;
+ EXPECT_TRUE(encoder.EncodeHeaderSet(header_set, &encoded_header_set));
+ EXPECT_EQ("\x40\x06""Cookie\x0bkey1=value1"
+ "\x40\x06""Cookie\x0bkey2=value2", encoded_header_set);
+}
+
// Test that trying to encode a header set with a too-long header
// field will fail.
TEST(HpackEncoderTest, HeaderTooLarge) {
- HpackEncoder encoder(10);
+ HpackEncoder encoder;
+ test::HpackEncoderPeer(&encoder).set_max_string_literal_size(10);
std::map<string, string> header_set;
header_set["name1"] = "too-long value";
diff --git a/net/spdy/hpack_huffman_table_test.cc b/net/spdy/hpack_huffman_table_test.cc
index 3e669e6..0c6417b 100644
--- a/net/spdy/hpack_huffman_table_test.cc
+++ b/net/spdy/hpack_huffman_table_test.cc
@@ -372,11 +372,8 @@ TEST(HpackHuffmanTableTest, DecodeWithBadInput) {
}
TEST(HpackHuffmanTableTest, SpecRequestExamples) {
- HpackHuffmanTable table;
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(table.Initialize(&code[0], code.size()));
- }
+ const HpackHuffmanTable& table(ObtainHpackHuffmanTable());
+
string buffer;
string test_table[] = {
"\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f",
@@ -405,11 +402,8 @@ TEST(HpackHuffmanTableTest, SpecRequestExamples) {
}
TEST(HpackHuffmanTableTest, SpecResponseExamples) {
- HpackHuffmanTable table;
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(table.Initialize(&code[0], code.size()));
- }
+ const HpackHuffmanTable& table(ObtainHpackHuffmanTable());
+
string buffer;
string test_table[] = {
"\x98\xa7",
@@ -444,11 +438,8 @@ TEST(HpackHuffmanTableTest, SpecResponseExamples) {
}
TEST(HpackHuffmanTableTest, RoundTripIndvidualSymbols) {
- HpackHuffmanTable table;
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(table.Initialize(&code[0], code.size()));
- }
+ const HpackHuffmanTable& table(ObtainHpackHuffmanTable());
+
for (size_t i = 0; i != 256; i++) {
char c = static_cast<char>(i);
char storage[3] = {c, c, c};
@@ -466,11 +457,8 @@ TEST(HpackHuffmanTableTest, RoundTripIndvidualSymbols) {
}
TEST(HpackHuffmanTableTest, RoundTripSymbolSequence) {
- HpackHuffmanTable table;
- {
- std::vector<HpackHuffmanSymbol> code = HpackHuffmanCode();
- EXPECT_TRUE(table.Initialize(&code[0], code.size()));
- }
+ const HpackHuffmanTable& table(ObtainHpackHuffmanTable());
+
char storage[512];
for (size_t i = 0; i != 256; i++) {
storage[i] = static_cast<char>(i);
diff --git a/net/spdy/hpack_input_stream.h b/net/spdy/hpack_input_stream.h
index 4281332..058a4b4 100644
--- a/net/spdy/hpack_input_stream.h
+++ b/net/spdy/hpack_input_stream.h
@@ -6,7 +6,6 @@
#define NET_SPDY_HPACK_INPUT_STREAM_H_
#include <string>
-#include <vector>
#include "base/basictypes.h"
#include "base/macros.h"
@@ -20,11 +19,6 @@
namespace net {
-// TODO(akalin): When we use a callback/delegate instead of a vector,
-// use StringPiece instead of string.
-typedef std::pair<std::string, std::string> HpackHeaderPair;
-typedef std::vector<HpackHeaderPair> HpackHeaderPairVector;
-
// An HpackInputStream handles all the low-level details of decoding
// header fields.
class NET_EXPORT_PRIVATE HpackInputStream {