4
4
using System ;
5
5
using System . Net . Http ;
6
6
using System . Net . Http . HPack ;
7
- using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http ;
8
7
9
8
namespace Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http2
10
9
{
@@ -13,57 +12,105 @@ internal static class HPackHeaderWriter
13
12
/// <summary>
14
13
/// Begin encoding headers in the first HEADERS frame.
15
14
/// </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 )
17
16
{
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 ) )
19
26
{
20
27
throw new HPackEncodingException ( SR . net_http_hpack_encode_failure ) ;
21
28
}
29
+ length += statusCodeLength ;
22
30
23
31
if ( ! headersEnumerator . MoveNext ( ) )
24
32
{
25
- length = statusCodeLength ;
26
33
return true ;
27
34
}
28
35
29
36
// We're ok with not throwing if no headers were encoded because we've already encoded the status.
30
37
// 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 ;
34
40
return done ;
35
41
}
36
42
37
43
/// <summary>
38
44
/// Begin encoding headers in the first HEADERS frame.
39
45
/// </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 )
41
47
{
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
+
42
56
if ( ! headersEnumerator . MoveNext ( ) )
43
57
{
44
- length = 0 ;
45
58
return true ;
46
59
}
47
60
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 ;
49
64
}
50
65
51
66
/// <summary>
52
67
/// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
53
68
/// </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 )
55
70
{
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
+ }
57
92
}
58
93
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 )
60
95
{
61
96
var currentLength = 0 ;
62
97
do
63
98
{
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 ) )
65
112
{
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.
67
114
// Throw an error to avoid an infinite loop of attempting to write large header.
68
115
if ( currentLength == 0 && throwIfNoneEncoded )
69
116
{
@@ -79,79 +126,48 @@ private static bool EncodeHeaders(Http2HeadersEnumerator headersEnumerator, Span
79
126
while ( headersEnumerator . MoveNext ( ) ) ;
80
127
81
128
length = currentLength ;
82
-
83
129
return true ;
84
130
}
85
131
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 )
87
133
{
88
- var hPackStaticTableId = GetResponseHeaderStaticTableId ( knownHeaderType ) ;
89
-
90
- if ( hPackStaticTableId == - 1 )
134
+ HeaderEncodingHint hint ;
135
+ if ( IsSensitive ( staticTableId , name ) )
91
136
{
92
- return HPackEncoder . EncodeLiteralHeaderFieldWithoutIndexingNewName ( name , value , buffer , out length ) ;
137
+ hint = HeaderEncodingHint . NeverIndex ;
138
+ }
139
+ else if ( IsNotDynamicallyIndexed ( staticTableId ) )
140
+ {
141
+ hint = HeaderEncodingHint . IgnoreIndex ;
93
142
}
94
143
else
95
144
{
96
- return HPackEncoder . EncodeLiteralHeaderFieldWithoutIndexing ( hPackStaticTableId , value , buffer , out length ) ;
145
+ hint = HeaderEncodingHint . Index ;
97
146
}
147
+
148
+ return hint ;
98
149
}
99
150
100
- private static int GetResponseHeaderStaticTableId ( KnownHeaderType responseHeaderType )
151
+ private static bool IsSensitive ( int staticTableIndex , string name )
101
152
{
102
- switch ( responseHeaderType )
153
+ // Set-Cookie could contain sensitive data.
154
+ if ( staticTableIndex == H2StaticTable . SetCookie )
103
155
{
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 ;
154
161
}
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 ;
155
171
}
156
172
}
157
173
}
0 commit comments