Browse Source

Add HeaderField.Sensitive, more tests.

Brad Fitzpatrick 11 years ago
parent
commit
16efb5cb09
3 changed files with 111 additions and 88 deletions
  1. 10 7
      hpack/hpack.go
  2. 36 20
      hpack/hpack_test.go
  3. 65 61
      hpack/tables.go

+ 10 - 7
hpack/hpack.go

@@ -36,6 +36,10 @@ func (e InvalidIndexError) Error() string {
 // treated as opaque sequences of octets.
 type HeaderField struct {
 	Name, Value string
+
+	// Sensitive means that this header field should never be
+	// indexed.
+	Sensitive bool
 }
 
 func (hf *HeaderField) size() uint32 {
@@ -58,7 +62,7 @@ func (hf *HeaderField) size() uint32 {
 // header blocks.
 type Decoder struct {
 	dynTab dynamicTable
-	emit   func(f HeaderField, sensitive bool)
+	emit   func(f HeaderField)
 
 	// buf is the unparsed buffer. It's only written to
 	// saveBuf if it was truncated in the middle of a header
@@ -68,7 +72,7 @@ type Decoder struct {
 	saveBuf bytes.Buffer
 }
 
-func NewDecoder(maxSize uint32, emitFunc func(f HeaderField, sensitive bool)) *Decoder {
+func NewDecoder(maxSize uint32, emitFunc func(f HeaderField)) *Decoder {
 	d := &Decoder{
 		emit: emitFunc,
 	}
@@ -163,9 +167,7 @@ func (d *Decoder) DecodeFull(p []byte) ([]HeaderField, error) {
 	var hf []HeaderField
 	saveFunc := d.emit
 	defer func() { d.emit = saveFunc }()
-	d.emit = func(f HeaderField, sensitive bool) {
-		hf = append(hf, f)
-	}
+	d.emit = func(f HeaderField) { hf = append(hf, f) }
 	if _, err := d.Write(p); err != nil {
 		return nil, err
 	}
@@ -278,7 +280,7 @@ func (d *Decoder) parseFieldIndexed() error {
 	if !ok {
 		return DecodingError{InvalidIndexError(idx)}
 	}
-	d.emit(hf, false)
+	d.emit(HeaderField{Name: hf.Name, Value: hf.Value})
 	d.buf = buf
 	return nil
 }
@@ -312,7 +314,8 @@ func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
 	if it.indexed() {
 		d.dynTab.add(hf)
 	}
-	d.emit(hf, it.sensitive())
+	hf.Sensitive = it.sensitive()
+	d.emit(hf)
 	return nil
 }
 

+ 36 - 20
hpack/hpack_test.go

@@ -126,18 +126,18 @@ func (d *Decoder) mustAt(idx int) HeaderField {
 func TestDynamicTableAt(t *testing.T) {
 	d := NewDecoder(4096, nil)
 	at := d.mustAt
-	if got, want := at(2), (HeaderField{":method", "GET"}); got != want {
+	if got, want := at(2), (pair(":method", "GET")); got != want {
 		t.Errorf("at(2) = %q; want %q", got, want)
 	}
-	d.dynTab.add(HeaderField{"foo", "bar"})
-	d.dynTab.add(HeaderField{"blake", "miz"})
-	if got, want := at(len(staticTable)+1), (HeaderField{"blake", "miz"}); got != want {
+	d.dynTab.add(pair("foo", "bar"))
+	d.dynTab.add(pair("blake", "miz"))
+	if got, want := at(len(staticTable)+1), (pair("blake", "miz")); got != want {
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 	}
-	if got, want := at(len(staticTable)+2), (HeaderField{"foo", "bar"}); got != want {
+	if got, want := at(len(staticTable)+2), (pair("foo", "bar")); got != want {
 		t.Errorf("at(dyn 2) = %q; want %q", got, want)
 	}
-	if got, want := at(3), (HeaderField{":method", "POST"}); got != want {
+	if got, want := at(3), (pair(":method", "POST")); got != want {
 		t.Errorf("at(3) = %q; want %q", got, want)
 	}
 }
@@ -148,11 +148,11 @@ func TestDynamicTableSizeEvict(t *testing.T) {
 		t.Fatalf("size = %d; want %d", d.dynTab.size, want)
 	}
 	add := d.dynTab.add
-	add(HeaderField{"blake", "eats pizza"})
+	add(pair("blake", "eats pizza"))
 	if want := uint32(15 + 32); d.dynTab.size != want {
 		t.Fatalf("after pizza, size = %d; want %d", d.dynTab.size, want)
 	}
-	add(HeaderField{"foo", "bar"})
+	add(pair("foo", "bar"))
 	if want := uint32(15 + 32 + 6 + 32); d.dynTab.size != want {
 		t.Fatalf("after foo bar, size = %d; want %d", d.dynTab.size, want)
 	}
@@ -160,37 +160,46 @@ func TestDynamicTableSizeEvict(t *testing.T) {
 	if want := uint32(6 + 32); d.dynTab.size != want {
 		t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
 	}
-	if got, want := d.mustAt(len(staticTable)+1), (HeaderField{"foo", "bar"}); got != want {
+	if got, want := d.mustAt(len(staticTable)+1), (pair("foo", "bar")); got != want {
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 	}
-	add(HeaderField{"long", strings.Repeat("x", 500)})
+	add(pair("long", strings.Repeat("x", 500)))
 	if want := uint32(0); d.dynTab.size != want {
 		t.Fatalf("after big one, size = %d; want %d", d.dynTab.size, want)
 	}
 }
 
 func TestDecoderDecode(t *testing.T) {
-	// TODO: also test state of dynamic table after all these.
 	tests := []struct {
-		name string
-		in   []byte
-		want []HeaderField
+		name       string
+		in         []byte
+		want       []HeaderField
+		wantDynTab []HeaderField // newest entry first
 	}{
 		// C.2.1 Literal Header Field with Indexing
 		// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.1
 		{"C.2.1", dehex("400a 6375 7374 6f6d 2d6b 6579 0d63 7573 746f 6d2d 6865 6164 6572"),
-			[]HeaderField{{"custom-key", "custom-header"}}},
+			[]HeaderField{pair("custom-key", "custom-header")},
+			[]HeaderField{pair("custom-key", "custom-header")},
+		},
 
+		// C.2.2 Literal Header Field without Indexing
+		// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.2
 		{"C.2.2", dehex("040c 2f73 616d 706c 652f 7061 7468"),
-			[]HeaderField{{":path", "/sample/path"}}},
+			[]HeaderField{pair(":path", "/sample/path")},
+			[]HeaderField{}},
 
-		// TODO: test callback happens with sensitive
+		// C.2.3 Literal Header Field never Indexed
+		// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.3
 		{"C.2.3", dehex("1008 7061 7373 776f 7264 0673 6563 7265 74"),
-			[]HeaderField{{"password", "secret"}}},
+			[]HeaderField{{"password", "secret", true}},
+			[]HeaderField{}},
 
-		// Indexed Header Field
+		// C.2.4 Indexed Header Field
 		// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.4
-		{"C.2.4", []byte("\x82"), []HeaderField{{":method", "GET"}}},
+		{"C.2.4", []byte("\x82"),
+			[]HeaderField{pair(":method", "GET")},
+			[]HeaderField{}},
 	}
 	for _, tt := range tests {
 		d := NewDecoder(4096, nil)
@@ -202,6 +211,13 @@ func TestDecoderDecode(t *testing.T) {
 		if !reflect.DeepEqual(hf, tt.want) {
 			t.Errorf("%s: Got %v; want %v", tt.name, hf, tt.want)
 		}
+		gotDynTab := make([]HeaderField, len(d.dynTab.ents))
+		for i := range gotDynTab {
+			gotDynTab[i] = d.dynTab.ents[len(d.dynTab.ents)-1-i]
+		}
+		if !reflect.DeepEqual(gotDynTab, tt.wantDynTab) {
+			t.Errorf("%s: dynamic table after = %v; want %v", tt.name, gotDynTab, tt.wantDynTab)
+		}
 	}
 }
 

+ 65 - 61
hpack/tables.go

@@ -5,69 +5,73 @@
 
 package hpack
 
+func pair(name, value string) HeaderField {
+	return HeaderField{Name: name, Value: value}
+}
+
 // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
 var staticTable = []HeaderField{
-	{":authority", ""}, // index 1 (1-based)
-	{":method", "GET"},
-	{":method", "POST"},
-	{":path", "/"},
-	{":path", "/index.html"},
-	{":scheme", "http"},
-	{":scheme", "https"},
-	{":status", "200"},
-	{":status", "204"},
-	{":status", "206"},
-	{":status", "304"},
-	{":status", "400"},
-	{":status", "404"},
-	{":status", "500"},
-	{"accept-charset", ""},
-	{"accept-encoding", "gzip, deflate"},
-	{"accept-language", ""},
-	{"accept-ranges", ""},
-	{"accept", ""},
-	{"access-control-allow-origin", ""},
-	{"age", ""},
-	{"allow", ""},
-	{"authorization", ""},
-	{"cache-control", ""},
-	{"content-disposition", ""},
-	{"content-encoding", ""},
-	{"content-language", ""},
-	{"content-length", ""},
-	{"content-location", ""},
-	{"content-range", ""},
-	{"content-type", ""},
-	{"cookie", ""},
-	{"date", ""},
-	{"etag", ""},
-	{"expect", ""},
-	{"expires", ""},
-	{"from", ""},
-	{"host", ""},
-	{"if-match", ""},
-	{"if-modified-since", ""},
-	{"if-none-match", ""},
-	{"if-range", ""},
-	{"if-unmodified-since", ""},
-	{"last-modified", ""},
-	{"link", ""},
-	{"location", ""},
-	{"max-forwards", ""},
-	{"proxy-authenticate", ""},
-	{"proxy-authorization", ""},
-	{"range", ""},
-	{"referer", ""},
-	{"refresh", ""},
-	{"retry-after", ""},
-	{"server", ""},
-	{"set-cookie", ""},
-	{"strict-transport-security", ""},
-	{"transfer-encoding", ""},
-	{"user-agent", ""},
-	{"vary", ""},
-	{"via", ""},
-	{"www-authenticate", ""},
+	pair(":authority", ""), // index 1 (1-based)
+	pair(":method", "GET"),
+	pair(":method", "POST"),
+	pair(":path", "/"),
+	pair(":path", "/index.html"),
+	pair(":scheme", "http"),
+	pair(":scheme", "https"),
+	pair(":status", "200"),
+	pair(":status", "204"),
+	pair(":status", "206"),
+	pair(":status", "304"),
+	pair(":status", "400"),
+	pair(":status", "404"),
+	pair(":status", "500"),
+	pair("accept-charset", ""),
+	pair("accept-encoding", "gzip, deflate"),
+	pair("accept-language", ""),
+	pair("accept-ranges", ""),
+	pair("accept", ""),
+	pair("access-control-allow-origin", ""),
+	pair("age", ""),
+	pair("allow", ""),
+	pair("authorization", ""),
+	pair("cache-control", ""),
+	pair("content-disposition", ""),
+	pair("content-encoding", ""),
+	pair("content-language", ""),
+	pair("content-length", ""),
+	pair("content-location", ""),
+	pair("content-range", ""),
+	pair("content-type", ""),
+	pair("cookie", ""),
+	pair("date", ""),
+	pair("etag", ""),
+	pair("expect", ""),
+	pair("expires", ""),
+	pair("from", ""),
+	pair("host", ""),
+	pair("if-match", ""),
+	pair("if-modified-since", ""),
+	pair("if-none-match", ""),
+	pair("if-range", ""),
+	pair("if-unmodified-since", ""),
+	pair("last-modified", ""),
+	pair("link", ""),
+	pair("location", ""),
+	pair("max-forwards", ""),
+	pair("proxy-authenticate", ""),
+	pair("proxy-authorization", ""),
+	pair("range", ""),
+	pair("referer", ""),
+	pair("refresh", ""),
+	pair("retry-after", ""),
+	pair("server", ""),
+	pair("set-cookie", ""),
+	pair("strict-transport-security", ""),
+	pair("transfer-encoding", ""),
+	pair("user-agent", ""),
+	pair("vary", ""),
+	pair("via", ""),
+	pair("www-authenticate", ""),
 }
 
 var huffmanCodes = []uint32{