@@ -77,11 +77,19 @@ public static string CreateResponseKey(string requestKey)
7777 }
7878
7979 // 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 )
8182 {
8283 bool hasServerMaxWindowBits = false ;
8384 bool hasClientMaxWindowBits = false ;
85+ bool hasClientNoContext = false ;
86+ bool hasServerNoContext = false ;
8487 response = null ;
88+ parsedOptions = new WebSocketDeflateOptions ( )
89+ {
90+ ServerContextTakeover = serverContextTakeover ,
91+ ServerMaxWindowBits = serverMaxWindowBits
92+ } ;
8593 var builder = new StringBuilder ( WebSocketDeflateConstants . MaxExtensionLength ) ;
8694 builder . Append ( WebSocketDeflateConstants . Extension ) ;
8795
@@ -90,83 +98,139 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
9098 int end = extension . IndexOf ( ';' ) ;
9199 ReadOnlySpan < char > value = ( end >= 0 ? extension [ ..end ] : extension ) . Trim ( ) ;
92100
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 ) )
94174 {
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 )
96180 {
97- options . ClientContextTakeover = false ;
98- builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientNoContextTakeover ) ;
181+ return false ;
99182 }
100- else if ( value . SequenceEqual ( WebSocketDeflateConstants . ServerNoContextTakeover ) )
183+
184+ hasServerMaxWindowBits = true ;
185+ if ( ! ParseWindowBits ( value , WebSocketDeflateConstants . ServerMaxWindowBits , out var parsedServerMaxWindowBits ) )
101186 {
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 ;
108188 }
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 )
110192 {
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 ;
132194 }
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 ( ) ) )
134220 {
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 ] ;
149222 }
150223
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 )
152227 {
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 ;
169230 }
231+
232+ parsedValue = windowBits ;
233+ return true ;
170234 }
171235
172236 if ( end < 0 )
@@ -176,30 +240,16 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
176240 extension = extension [ ( end + 1 ) ..] ;
177241 }
178242
179- if ( ! options . ServerContextTakeover )
243+ if ( ! parsedOptions . ServerContextTakeover )
180244 {
181245 builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ServerNoContextTakeover ) ;
182246 }
183247
184- if ( hasServerMaxWindowBits || options . ServerMaxWindowBits != 15 )
248+ if ( hasServerMaxWindowBits || parsedOptions . ServerMaxWindowBits != 15 )
185249 {
186250 builder . Append ( "; " )
187251 . 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 ) ) ;
203253 }
204254
205255 response = builder . ToString ( ) ;
0 commit comments