44using System ;
55using System . Net . Http ;
66using System . Net . Http . HPack ;
7- using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http ;
87
98namespace Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http2
109{
@@ -13,57 +12,105 @@ internal static class HPackHeaderWriter
1312 /// <summary>
1413 /// Begin encoding headers in the first HEADERS frame.
1514 /// </summary>
16- public static bool BeginEncodeHeaders ( int statusCode , Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
15+ public static bool BeginEncodeHeaders ( int statusCode , HPackEncoder hpackEncoder , Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
1716 {
18- if ( ! HPackEncoder . EncodeStatusHeader ( statusCode , buffer , out var statusCodeLength ) )
17+ length = 0 ;
18+
19+ if ( ! hpackEncoder . EnsureDynamicTableSizeUpdate ( buffer , out var sizeUpdateLength ) )
20+ {
21+ throw new HPackEncodingException ( SR . net_http_hpack_encode_failure ) ;
22+ }
23+ length += sizeUpdateLength ;
24+
25+ if ( ! EncodeStatusHeader ( statusCode , hpackEncoder , buffer . Slice ( length ) , out var statusCodeLength ) )
1926 {
2027 throw new HPackEncodingException ( SR . net_http_hpack_encode_failure ) ;
2128 }
29+ length += statusCodeLength ;
2230
2331 if ( ! headersEnumerator . MoveNext ( ) )
2432 {
25- length = statusCodeLength ;
2633 return true ;
2734 }
2835
2936 // We're ok with not throwing if no headers were encoded because we've already encoded the status.
3037 // There is a small chance that the header will encode if there is no other content in the next HEADERS frame.
31- var done = EncodeHeaders ( headersEnumerator , buffer . Slice ( statusCodeLength ) , throwIfNoneEncoded : false , out var headersLength ) ;
32- length = statusCodeLength + headersLength ;
33-
38+ var done = EncodeHeadersCore ( hpackEncoder , headersEnumerator , buffer . Slice ( length ) , throwIfNoneEncoded : false , out var headersLength ) ;
39+ length += headersLength ;
3440 return done ;
3541 }
3642
3743 /// <summary>
3844 /// Begin encoding headers in the first HEADERS frame.
3945 /// </summary>
40- public static bool BeginEncodeHeaders ( Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
46+ public static bool BeginEncodeHeaders ( HPackEncoder hpackEncoder , Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
4147 {
48+ length = 0 ;
49+
50+ if ( ! hpackEncoder . EnsureDynamicTableSizeUpdate ( buffer , out var sizeUpdateLength ) )
51+ {
52+ throw new HPackEncodingException ( SR . net_http_hpack_encode_failure ) ;
53+ }
54+ length += sizeUpdateLength ;
55+
4256 if ( ! headersEnumerator . MoveNext ( ) )
4357 {
44- length = 0 ;
4558 return true ;
4659 }
4760
48- return EncodeHeaders ( headersEnumerator , buffer , throwIfNoneEncoded : true , out length ) ;
61+ var done = EncodeHeadersCore ( hpackEncoder , headersEnumerator , buffer . Slice ( length ) , throwIfNoneEncoded : true , out var headersLength ) ;
62+ length += headersLength ;
63+ return done ;
4964 }
5065
5166 /// <summary>
5267 /// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
5368 /// </summary>
54- public static bool ContinueEncodeHeaders ( Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
69+ public static bool ContinueEncodeHeaders ( HPackEncoder hpackEncoder , Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , out int length )
5570 {
56- return EncodeHeaders ( headersEnumerator , buffer , throwIfNoneEncoded : true , out length ) ;
71+ return EncodeHeadersCore ( hpackEncoder , headersEnumerator , buffer , throwIfNoneEncoded : true , out length ) ;
72+ }
73+
74+ private static bool EncodeStatusHeader ( int statusCode , HPackEncoder hpackEncoder , Span < byte > buffer , out int length )
75+ {
76+ switch ( statusCode )
77+ {
78+ case 200 :
79+ case 204 :
80+ case 206 :
81+ case 304 :
82+ case 400 :
83+ case 404 :
84+ case 500 :
85+ // Status codes which exist in the HTTP/2 StaticTable.
86+ return HPackEncoder . EncodeIndexedHeaderField ( H2StaticTable . StatusIndex [ statusCode ] , buffer , out length ) ;
87+ default :
88+ const string name = ":status" ;
89+ var value = StatusCodes . ToStatusString ( statusCode ) ;
90+ return hpackEncoder . EncodeHeader ( buffer , H2StaticTable . Status200 , HeaderEncodingHint . Index , name , value , out length ) ;
91+ }
5792 }
5893
59- private static bool EncodeHeaders ( Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , bool throwIfNoneEncoded , out int length )
94+ private static bool EncodeHeadersCore ( HPackEncoder hpackEncoder , Http2HeadersEnumerator headersEnumerator , Span < byte > buffer , bool throwIfNoneEncoded , out int length )
6095 {
6196 var currentLength = 0 ;
6297 do
6398 {
64- if ( ! EncodeHeader ( headersEnumerator . KnownHeaderType , headersEnumerator . Current . Key , headersEnumerator . Current . Value , buffer . Slice ( currentLength ) , out int headerLength ) )
99+ var staticTableId = headersEnumerator . HPackStaticTableId ;
100+ var name = headersEnumerator . Current . Key ;
101+ var value = headersEnumerator . Current . Value ;
102+
103+ var hint = ResolveHeaderEncodingHint ( staticTableId , name ) ;
104+
105+ if ( ! hpackEncoder . EncodeHeader (
106+ buffer . Slice ( currentLength ) ,
107+ staticTableId ,
108+ hint ,
109+ name ,
110+ value ,
111+ out var headerLength ) )
65112 {
66- // The the header wasn't written and no headers have been written then the header is too large.
113+ // If the header wasn't written, and no headers have been written, then the header is too large.
67114 // Throw an error to avoid an infinite loop of attempting to write large header.
68115 if ( currentLength == 0 && throwIfNoneEncoded )
69116 {
@@ -79,79 +126,48 @@ private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span
79126 while ( headersEnumerator . MoveNext ( ) ) ;
80127
81128 length = currentLength ;
82-
83129 return true ;
84130 }
85131
86- private static bool EncodeHeader ( KnownHeaderType knownHeaderType , string name , string value , Span < byte > buffer , out int length )
132+ private static HeaderEncodingHint ResolveHeaderEncodingHint ( int staticTableId , string name )
87133 {
88- var hPackStaticTableId = GetResponseHeaderStaticTableId ( knownHeaderType ) ;
89-
90- if ( hPackStaticTableId == - 1 )
134+ HeaderEncodingHint hint ;
135+ if ( IsSensitive ( staticTableId , name ) )
91136 {
92- return HPackEncoder . EncodeLiteralHeaderFieldWithoutIndexingNewName ( name , value , buffer , out length ) ;
137+ hint = HeaderEncodingHint . NeverIndex ;
138+ }
139+ else if ( IsNotDynamicallyIndexed ( staticTableId ) )
140+ {
141+ hint = HeaderEncodingHint . IgnoreIndex ;
93142 }
94143 else
95144 {
96- return HPackEncoder . EncodeLiteralHeaderFieldWithoutIndexing ( hPackStaticTableId , value , buffer , out length ) ;
145+ hint = HeaderEncodingHint . Index ;
97146 }
147+
148+ return hint ;
98149 }
99150
100- private static int GetResponseHeaderStaticTableId ( KnownHeaderType responseHeaderType )
151+ private static bool IsSensitive ( int staticTableIndex , string name )
101152 {
102- switch ( responseHeaderType )
153+ // Set-Cookie could contain sensitive data.
154+ if ( staticTableIndex == H2StaticTable . SetCookie )
103155 {
104- case KnownHeaderType . CacheControl :
105- return H2StaticTable . CacheControl ;
106- case KnownHeaderType . Date :
107- return H2StaticTable . Date ;
108- case KnownHeaderType . TransferEncoding :
109- return H2StaticTable . TransferEncoding ;
110- case KnownHeaderType . Via :
111- return H2StaticTable . Via ;
112- case KnownHeaderType . Allow :
113- return H2StaticTable . Allow ;
114- case KnownHeaderType . ContentType :
115- return H2StaticTable . ContentType ;
116- case KnownHeaderType . ContentEncoding :
117- return H2StaticTable . ContentEncoding ;
118- case KnownHeaderType . ContentLanguage :
119- return H2StaticTable . ContentLanguage ;
120- case KnownHeaderType . ContentLocation :
121- return H2StaticTable . ContentLocation ;
122- case KnownHeaderType . ContentRange :
123- return H2StaticTable . ContentRange ;
124- case KnownHeaderType . Expires :
125- return H2StaticTable . Expires ;
126- case KnownHeaderType . LastModified :
127- return H2StaticTable . LastModified ;
128- case KnownHeaderType . AcceptRanges :
129- return H2StaticTable . AcceptRanges ;
130- case KnownHeaderType . Age :
131- return H2StaticTable . Age ;
132- case KnownHeaderType . ETag :
133- return H2StaticTable . ETag ;
134- case KnownHeaderType . Location :
135- return H2StaticTable . Location ;
136- case KnownHeaderType . ProxyAuthenticate :
137- return H2StaticTable . ProxyAuthenticate ;
138- case KnownHeaderType . RetryAfter :
139- return H2StaticTable . RetryAfter ;
140- case KnownHeaderType . Server :
141- return H2StaticTable . Server ;
142- case KnownHeaderType . SetCookie :
143- return H2StaticTable . SetCookie ;
144- case KnownHeaderType . Vary :
145- return H2StaticTable . Vary ;
146- case KnownHeaderType . WWWAuthenticate :
147- return H2StaticTable . WwwAuthenticate ;
148- case KnownHeaderType . AccessControlAllowOrigin :
149- return H2StaticTable . AccessControlAllowOrigin ;
150- case KnownHeaderType . ContentLength :
151- return H2StaticTable . ContentLength ;
152- default :
153- return - 1 ;
156+ return true ;
157+ }
158+ if ( string . Equals ( name , "Content-Disposition" , StringComparison . OrdinalIgnoreCase ) )
159+ {
160+ return true ;
154161 }
162+
163+ return false ;
164+ }
165+
166+ private static bool IsNotDynamicallyIndexed ( int staticTableIndex )
167+ {
168+ // Content-Length is added to static content. Content length is different for each
169+ // file, and is unlikely to be reused because of browser caching.
170+ return staticTableIndex == H2StaticTable . ContentLength ;
155171 }
156172 }
157173}
0 commit comments