@@ -77,11 +77,19 @@ public static string CreateResponseKey(string requestKey)
77
77
}
78
78
79
79
// https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
80
- public static bool ParseDeflateOptions ( ReadOnlySpan < char > extension , WebSocketDeflateOptions options , [ NotNullWhen ( true ) ] out string ? response )
80
+ public static bool ParseDeflateOptions ( ReadOnlySpan < char > extension , bool serverContextTakeover ,
81
+ int serverMaxWindowBits , out WebSocketDeflateOptions parsedOptions , [ NotNullWhen ( true ) ] out string ? response )
81
82
{
82
83
bool hasServerMaxWindowBits = false ;
83
84
bool hasClientMaxWindowBits = false ;
85
+ bool hasClientNoContext = false ;
86
+ bool hasServerNoContext = false ;
84
87
response = null ;
88
+ parsedOptions = new WebSocketDeflateOptions ( )
89
+ {
90
+ ServerContextTakeover = serverContextTakeover ,
91
+ ServerMaxWindowBits = serverMaxWindowBits
92
+ } ;
85
93
var builder = new StringBuilder ( WebSocketDeflateConstants . MaxExtensionLength ) ;
86
94
builder . Append ( WebSocketDeflateConstants . Extension ) ;
87
95
@@ -90,83 +98,139 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
90
98
int end = extension . IndexOf ( ';' ) ;
91
99
ReadOnlySpan < char > value = ( end >= 0 ? extension [ ..end ] : extension ) . Trim ( ) ;
92
100
93
- if ( value . Length > 0 )
101
+ if ( value . Length == 0 )
102
+ {
103
+ break ;
104
+ }
105
+
106
+ if ( value . SequenceEqual ( WebSocketDeflateConstants . ClientNoContextTakeover ) )
107
+ {
108
+ // https://datatracker.ietf.org/doc/html/rfc7692#section-7
109
+ // MUST decline if:
110
+ // The negotiation offer contains multiple extension parameters with
111
+ // the same name.
112
+ if ( hasClientNoContext )
113
+ {
114
+ return false ;
115
+ }
116
+
117
+ hasClientNoContext = true ;
118
+ parsedOptions . ClientContextTakeover = false ;
119
+ builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientNoContextTakeover ) ;
120
+ }
121
+ else if ( value . SequenceEqual ( WebSocketDeflateConstants . ServerNoContextTakeover ) )
122
+ {
123
+ // https://datatracker.ietf.org/doc/html/rfc7692#section-7
124
+ // MUST decline if:
125
+ // The negotiation offer contains multiple extension parameters with
126
+ // the same name.
127
+ if ( hasServerNoContext )
128
+ {
129
+ return false ;
130
+ }
131
+
132
+ hasServerNoContext = true ;
133
+ parsedOptions . ServerContextTakeover = false ;
134
+ }
135
+ else if ( value . StartsWith ( WebSocketDeflateConstants . ClientMaxWindowBits ) )
136
+ {
137
+ // https://datatracker.ietf.org/doc/html/rfc7692#section-7
138
+ // MUST decline if:
139
+ // The negotiation offer contains multiple extension parameters with
140
+ // the same name.
141
+ if ( hasClientMaxWindowBits )
142
+ {
143
+ return false ;
144
+ }
145
+
146
+ hasClientMaxWindowBits = true ;
147
+ if ( ! ParseWindowBits ( value , WebSocketDeflateConstants . ClientMaxWindowBits , out var clientMaxWindowBits ) )
148
+ {
149
+ return false ;
150
+ }
151
+
152
+ // 8 is a valid value according to the spec, but our zlib implementation does not support it
153
+ if ( clientMaxWindowBits == 8 )
154
+ {
155
+ return false ;
156
+ }
157
+
158
+ // https://tools.ietf.org/html/rfc7692#section-7.1.2.2
159
+ // the server may either ignore this
160
+ // value or use this value to avoid allocating an unnecessarily big LZ77
161
+ // sliding window by including the "client_max_window_bits" extension
162
+ // parameter in the corresponding extension negotiation response to the
163
+ // offer with a value equal to or smaller than the received value.
164
+ parsedOptions . ClientMaxWindowBits = clientMaxWindowBits ?? 15 ;
165
+
166
+ // If a received extension negotiation offer doesn't have the
167
+ // "client_max_window_bits" extension parameter, the corresponding
168
+ // extension negotiation response to the offer MUST NOT include the
169
+ // "client_max_window_bits" extension parameter.
170
+ builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientMaxWindowBits ) . Append ( '=' )
171
+ . Append ( parsedOptions . ClientMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
172
+ }
173
+ else if ( value . StartsWith ( WebSocketDeflateConstants . ServerMaxWindowBits ) )
94
174
{
95
- if ( value . SequenceEqual ( WebSocketDeflateConstants . ClientNoContextTakeover ) )
175
+ // https://datatracker.ietf.org/doc/html/rfc7692#section-7
176
+ // MUST decline if:
177
+ // The negotiation offer contains multiple extension parameters with
178
+ // the same name.
179
+ if ( hasServerMaxWindowBits )
96
180
{
97
- options . ClientContextTakeover = false ;
98
- builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientNoContextTakeover ) ;
181
+ return false ;
99
182
}
100
- else if ( value . SequenceEqual ( WebSocketDeflateConstants . ServerNoContextTakeover ) )
183
+
184
+ hasServerMaxWindowBits = true ;
185
+ if ( ! ParseWindowBits ( value , WebSocketDeflateConstants . ServerMaxWindowBits , out var parsedServerMaxWindowBits ) )
101
186
{
102
- // REVIEW: Do we want to reject it?
103
- // Client requests no context takeover but options passed in specified context takeover, so reject the negotiate offer
104
- if ( options . ServerContextTakeover )
105
- {
106
- return false ;
107
- }
187
+ return false ;
108
188
}
109
- else if ( value . StartsWith ( WebSocketDeflateConstants . ClientMaxWindowBits ) )
189
+
190
+ // 8 is a valid value according to the spec, but our zlib implementation does not support it
191
+ if ( parsedServerMaxWindowBits == 8 )
110
192
{
111
- var clientMaxWindowBits = ParseWindowBits ( value , WebSocketDeflateConstants . ClientMaxWindowBits ) ;
112
- // 8 is a valid value according to the spec, but our zlib implementation does not support it
113
- if ( clientMaxWindowBits == 8 )
114
- {
115
- return false ;
116
- }
117
-
118
- // https://tools.ietf.org/html/rfc7692#section-7.1.2.2
119
- // the server may either ignore this
120
- // value or use this value to avoid allocating an unnecessarily big LZ77
121
- // sliding window by including the "client_max_window_bits" extension
122
- // parameter in the corresponding extension negotiation response to the
123
- // offer with a value equal to or smaller than the received value.
124
- options . ClientMaxWindowBits = Math . Min ( clientMaxWindowBits ?? 15 , options . ClientMaxWindowBits ) ;
125
-
126
- // If a received extension negotiation offer doesn't have the
127
- // "client_max_window_bits" extension parameter, the corresponding
128
- // extension negotiation response to the offer MUST NOT include the
129
- // "client_max_window_bits" extension parameter.
130
- builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientMaxWindowBits ) . Append ( '=' )
131
- . Append ( options . ClientMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
193
+ return false ;
132
194
}
133
- else if ( value . StartsWith ( WebSocketDeflateConstants . ServerMaxWindowBits ) )
195
+
196
+ // https://tools.ietf.org/html/rfc7692#section-7.1.2.1
197
+ // A server accepts an extension negotiation offer with this parameter
198
+ // by including the "server_max_window_bits" extension parameter in the
199
+ // extension negotiation response to send back to the client with the
200
+ // same or smaller value as the offer.
201
+ parsedOptions . ServerMaxWindowBits = Math . Min ( parsedServerMaxWindowBits ?? 15 , serverMaxWindowBits ) ;
202
+ }
203
+
204
+ static bool ParseWindowBits ( ReadOnlySpan < char > value , string propertyName , out int ? parsedValue )
205
+ {
206
+ var startIndex = value . IndexOf ( '=' ) ;
207
+
208
+ // parameters can be sent without a value by the client, we'll use the values set by the app developer or the default of 15
209
+ if ( startIndex < 0 )
210
+ {
211
+ parsedValue = null ;
212
+ return true ;
213
+ }
214
+
215
+ value = value [ ( startIndex + 1 ) ..] . TrimEnd ( ) ;
216
+
217
+ // https://datatracker.ietf.org/doc/html/rfc7692#section-5.2
218
+ // check for value in quotes and pull the value out without the quotes
219
+ if ( value [ 0 ] == '"' && value . EndsWith ( "\" " . AsSpan ( ) ) )
134
220
{
135
- hasServerMaxWindowBits = true ;
136
- var serverMaxWindowBits = ParseWindowBits ( value , WebSocketDeflateConstants . ServerMaxWindowBits ) ;
137
- // 8 is a valid value according to the spec, but our zlib implementation does not support it
138
- if ( serverMaxWindowBits == 8 )
139
- {
140
- return false ;
141
- }
142
-
143
- // https://tools.ietf.org/html/rfc7692#section-7.1.2.1
144
- // A server accepts an extension negotiation offer with this parameter
145
- // by including the "server_max_window_bits" extension parameter in the
146
- // extension negotiation response to send back to the client with the
147
- // same or smaller value as the offer.
148
- options . ServerMaxWindowBits = Math . Min ( serverMaxWindowBits ?? 15 , options . ServerMaxWindowBits ) ;
221
+ value = value [ 1 ..^ 1 ] ;
149
222
}
150
223
151
- static int ? ParseWindowBits ( ReadOnlySpan < char > value , string propertyName )
224
+ if ( ! int . TryParse ( value , NumberStyles . Integer , CultureInfo . InvariantCulture , out int windowBits ) ||
225
+ windowBits < 8 ||
226
+ windowBits > 15 )
152
227
{
153
- var startIndex = value . IndexOf ( '=' ) ;
154
-
155
- // parameters can be sent without a value by the client, we'll use the values set by the app developer or the default of 15
156
- if ( startIndex < 0 )
157
- {
158
- return null ;
159
- }
160
-
161
- if ( ! int . TryParse ( value [ ( startIndex + 1 ) ..] , NumberStyles . Integer , CultureInfo . InvariantCulture , out int windowBits ) ||
162
- windowBits < 8 ||
163
- windowBits > 15 )
164
- {
165
- throw new WebSocketException ( WebSocketError . HeaderError , $ "invalid { propertyName } used: { value [ ( startIndex + 1 ) ..] . ToString ( ) } ") ;
166
- }
167
-
168
- return windowBits ;
228
+ parsedValue = null ;
229
+ return false ;
169
230
}
231
+
232
+ parsedValue = windowBits ;
233
+ return true ;
170
234
}
171
235
172
236
if ( end < 0 )
@@ -176,30 +240,16 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
176
240
extension = extension [ ( end + 1 ) ..] ;
177
241
}
178
242
179
- if ( ! options . ServerContextTakeover )
243
+ if ( ! parsedOptions . ServerContextTakeover )
180
244
{
181
245
builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ServerNoContextTakeover ) ;
182
246
}
183
247
184
- if ( hasServerMaxWindowBits || options . ServerMaxWindowBits != 15 )
248
+ if ( hasServerMaxWindowBits || parsedOptions . ServerMaxWindowBits != 15 )
185
249
{
186
250
builder . Append ( "; " )
187
251
. Append ( WebSocketDeflateConstants . ServerMaxWindowBits ) . Append ( '=' )
188
- . Append ( options . ServerMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
189
- }
190
-
191
- // https://tools.ietf.org/html/rfc7692#section-7.1.2.2
192
- // If a received extension negotiation offer doesn't have the
193
- // "client_max_window_bits" extension parameter, the corresponding
194
- // extension negotiation response to the offer MUST NOT include the
195
- // "client_max_window_bits" extension parameter.
196
- //
197
- // Absence of this extension parameter in an extension negotiation
198
- // response indicates that the server can receive messages compressed
199
- // using an LZ77 sliding window of up to 32,768 bytes.
200
- if ( ! hasClientMaxWindowBits )
201
- {
202
- options . ClientMaxWindowBits = 15 ;
252
+ . Append ( parsedOptions . ServerMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
203
253
}
204
254
205
255
response = builder . ToString ( ) ;
0 commit comments