Skip to content

Commit 5431beb

Browse files
author
Pushkar Kulkarni
committed
Handle the special case of LZ77 window size of 8
The server_max_window_bits value sent by the client in the negotiation offer is used to configure the deflater on the server. This acceptance is also notified to the client by returning the server_max_window_bits header in the negotiation response. A special case arises when we receive server_max_window_bits = 8. There's an open zlib issue with the window size of 8. For zlib streams, the library silently changes the window size to 9 and informs the inflater via the zlib header. However, this apparent hack is not feasible for raw deflate streams, which are used in WebSockets. To take care of this fact, zlib has been patched to reject a window size of 8, via a deflateInit2() failure. Details here: madler/zlib#171 As a result, we need to handle a window size request of 8 as a special case. We silently change it to 9 and inform the client via a suitable header in the negotiation response.
1 parent 52614a2 commit 5431beb

File tree

1 file changed

+51
-10
lines changed

1 file changed

+51
-10
lines changed

Sources/KituraWebSocket/PermessageDeflate.swift

+51-10
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,35 @@ class PermessageDeflate: WebSocketProtocolExtension {
3535
// * server_no_context_takeover: prevent the server from using context-takeover
3636
// * client_no_context_takeover: prevent the client from using context-takeover
3737
for parameter in header.components(separatedBy: "; ") {
38-
// If we receieved a valid value for server_max_window_bits, configure the deflater to use it
38+
// If we receieved a valid value for server_max_window_bits, use it to configure the deflater
3939
if parameter.hasPrefix("server_max_window_bits") {
4040
let maxWindowBits = parameter.components(separatedBy: "=")
4141
guard maxWindowBits.count == 2 else { continue }
42-
if let mwBits = Int32(maxWindowBits[1]) {
43-
if mwBits >= 8 && mwBits <= 15 {
44-
deflaterMaxWindowBits = mwBits
45-
}
42+
guard let mwBits = Int32(maxWindowBits[1]) else { continue }
43+
if mwBits >= 8 && mwBits <= 15 {
44+
// We received a valid value. However there's a special case here:
45+
//
46+
// There's an open zlib issue which does not set the window size
47+
// to 8 if that is the requested value. The value is set to 9 instead.
48+
// However, this apparent hack works only with zlib streams. WebSockets
49+
// use raw deflate streams. For raw deflate streams, zlib has been patched
50+
// to ignore the windowBits value 8.
51+
// More details here: https://github.com/madler/zlib/issues/171
52+
//
53+
// So, if the server requested for server_max_window_bits=8, we are
54+
// going to use server_max_window_bits=9 instead and notify this in
55+
// our negotiation response too.
56+
deflaterMaxWindowBits = mwBits == 8 ? 9 : mwBits
4657
}
4758
}
4859

49-
// If we receieved a valid value for server_max_window_bits, configure the inflater to use it
60+
// If we received a valid client_max_window_bits value, use it to configure the inflater
5061
if parameter.hasPrefix("client_max_window_bits") {
5162
let maxWindowBits = parameter.components(separatedBy: "=")
5263
guard maxWindowBits.count == 2 else { continue }
53-
if let mwBits = Int32(maxWindowBits[1]) {
54-
if mwBits >= 8 && mwBits <= 15 {
55-
inflaterMaxWindowBits = mwBits
56-
}
64+
guard let mwBits = Int32(maxWindowBits[1]) else { continue }
65+
if mwBits >= 8 && mwBits <= 15 {
66+
inflaterMaxWindowBits = mwBits
5767
}
5868
}
5969

@@ -85,6 +95,37 @@ class PermessageDeflate: WebSocketProtocolExtension {
8595
if parameter == "server_no_context_takeover" {
8696
response.append("; server_no_context_takeover")
8797
}
98+
99+
// If we receive a valid value for server_max_window_bits, we accept it and return if
100+
// in the response. If we receive an invalid value, we default to 15 and return the
101+
// same in the response. If we receive no value, we ignore this header.
102+
if parameter.hasPrefix("server_max_window_bits") {
103+
let maxWindowBits = parameter.components(separatedBy: "=")
104+
guard maxWindowBits.count == 2 else { continue }
105+
guard let mwBits = Int32(maxWindowBits[1]) else { continue }
106+
if mwBits >= 8 && mwBits <= 15 {
107+
// We received a valid value. However there's a special case here:
108+
//
109+
// There's an open zlib issue which does not set the window size
110+
// to 8 if that is the requested value. The value is set to 9 instead.
111+
// However, this apparent hack works only with zlib streams. WebSockets
112+
// use raw deflate streams. For raw deflate streams, zlib has been patched
113+
// to ignore the windowBits value 8.
114+
// More details here: https://github.com/madler/zlib/issues/171
115+
//
116+
// So, if the server requested for server_max_window_bits=8, we are
117+
// going to use server_max_window_bits=9 instead and notify this in
118+
// our negotiation response too.
119+
if mwBits == 8 {
120+
response.append("; server_max_window_bits=9")
121+
} else {
122+
response.append("; \(parameter)")
123+
}
124+
} else {
125+
// we received an invalid value
126+
response.append("; server_max_window_bits=15")
127+
}
128+
}
88129
}
89130
return response
90131
}

0 commit comments

Comments
 (0)