@@ -76,8 +76,11 @@ public static string CreateResponseKey(string requestKey)
7676 return Convert . ToBase64String ( hashedBytes ) ;
7777 }
7878
79+ // https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
7980 public static bool ParseDeflateOptions ( ReadOnlySpan < char > extension , WebSocketDeflateOptions options , [ NotNullWhen ( true ) ] out string ? response )
8081 {
82+ bool hasServerMaxWindowBits = false ;
83+ bool hasClientMaxWindowBits = false ;
8184 response = null ;
8285 var builder = new StringBuilder ( WebSocketDeflateConstants . MaxExtensionLength ) ;
8386 builder . Append ( WebSocketDeflateConstants . Extension ) ;
@@ -91,40 +94,58 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
9194 {
9295 if ( value . SequenceEqual ( WebSocketDeflateConstants . ClientNoContextTakeover ) )
9396 {
94- // REVIEW: If someone specifies true for server options, do we allow client to override this?
9597 options . ClientContextTakeover = false ;
9698 builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientNoContextTakeover ) ;
9799 }
98100 else if ( value . SequenceEqual ( WebSocketDeflateConstants . ServerNoContextTakeover ) )
99101 {
100- options . ServerContextTakeover = false ;
101- builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ServerNoContextTakeover ) ;
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+ }
102108 }
103109 else if ( value . StartsWith ( WebSocketDeflateConstants . ClientMaxWindowBits ) )
104110 {
105111 var clientMaxWindowBits = ParseWindowBits ( value , WebSocketDeflateConstants . ClientMaxWindowBits ) ;
106- if ( clientMaxWindowBits > options . ClientMaxWindowBits )
112+ // 8 is a valid value according to the spec, but our zlib implementation does not support it
113+ if ( clientMaxWindowBits == 8 )
107114 {
108115 return false ;
109116 }
110- // if client didn't send a value for ClientMaxWindowBits use the value the server set
111- options . ClientMaxWindowBits = clientMaxWindowBits ?? options . ClientMaxWindowBits ;
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.
112130 builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ClientMaxWindowBits ) . Append ( '=' )
113131 . Append ( options . ClientMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
114132 }
115133 else if ( value . StartsWith ( WebSocketDeflateConstants . ServerMaxWindowBits ) )
116134 {
135+ hasServerMaxWindowBits = true ;
117136 var serverMaxWindowBits = ParseWindowBits ( value , WebSocketDeflateConstants . ServerMaxWindowBits ) ;
118- if ( serverMaxWindowBits > options . ServerMaxWindowBits )
137+ // 8 is a valid value according to the spec, but our zlib implementation does not support it
138+ if ( serverMaxWindowBits == 8 )
119139 {
120140 return false ;
121141 }
122- // if client didn't send a value for ServerMaxWindowBits use the value the server set
123- options . ServerMaxWindowBits = serverMaxWindowBits ?? options . ServerMaxWindowBits ;
124142
125- builder . Append ( "; " )
126- . Append ( WebSocketDeflateConstants . ServerMaxWindowBits ) . Append ( '=' )
127- . Append ( options . ServerMaxWindowBits . ToString ( CultureInfo . InvariantCulture ) ) ;
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 ) ;
128149 }
129150
130151 static int ? ParseWindowBits ( ReadOnlySpan < char > value , string propertyName )
@@ -138,7 +159,7 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
138159 }
139160
140161 if ( ! int . TryParse ( value [ ( startIndex + 1 ) ..] , NumberStyles . Integer , CultureInfo . InvariantCulture , out int windowBits ) ||
141- windowBits < 9 ||
162+ windowBits < 8 ||
142163 windowBits > 15 )
143164 {
144165 throw new WebSocketException ( WebSocketError . HeaderError , $ "invalid { propertyName } used: { value [ ( startIndex + 1 ) ..] . ToString ( ) } ") ;
@@ -155,6 +176,32 @@ public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDe
155176 extension = extension [ ( end + 1 ) ..] ;
156177 }
157178
179+ if ( ! options . ServerContextTakeover )
180+ {
181+ builder . Append ( "; " ) . Append ( WebSocketDeflateConstants . ServerNoContextTakeover ) ;
182+ }
183+
184+ if ( hasServerMaxWindowBits || options . ServerMaxWindowBits != 15 )
185+ {
186+ builder . Append ( "; " )
187+ . 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 ;
203+ }
204+
158205 response = builder . ToString ( ) ;
159206
160207 return true ;
0 commit comments