Skip to content

Commit c025e41

Browse files
committed
fix: Handle 101 Upgrade responses by ignoring Content-Length
1 parent 4fc4c55 commit c025e41

File tree

2 files changed

+29
-16
lines changed

2 files changed

+29
-16
lines changed

src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -147,28 +147,41 @@ private HttpResponseMessage CreateResponseMessage(List<string> responseLines)
147147
}
148148
}
149149

150-
// TODO: We'll need to refactor this in the future.
150+
// Response handling is based on headers and type. The implementation currently covers
151+
// four main cases:
151152
//
152-
// Depending on the request and response (headers), we need to handle the response
153-
// differently. We need to distinguish between four types of responses:
153+
// 1. Chunked transfer encoding (HTTP/1.1 `Transfer-Encoding: chunked`)
154+
// 2. HTTP responses with a `Content-Length` header
155+
// - For 101 Switching Protocols, `Content-Length` is ignored per RFC 9110
156+
// 3. Protocol upgrades (HTTP 101 + `Upgrade: tcp`)
157+
// - e.g., `/containers/{id}/attach` or `/exec/{id}/start`
158+
// 4. Streaming responses without connection upgrade headers
159+
// - e.g., `/containers/{id}/logs`
154160
//
155-
// 1. Chunked transfer encoding
156-
// 2. HTTP with a `Content-Length` header
157-
// 3. Hijacked TCP connections (using the connection upgrade headers)
158-
// - `/containers/{id}/attach`
159-
// - `/exec/{id}/start`
160-
// 4. Streams without the connection upgrade headers
161-
// - `/containers/{id}/logs`
161+
// This separation ensures chunked framing, streaming, and upgraded connections are handled
162+
// correctly, while tolerating proxies that incorrectly send `Content-Length: 0` on upgrades.
163+
164+
var isSwitchingProtocols = response.StatusCode == HttpStatusCode.SwitchingProtocols;
162165

163166
var isConnectionUpgrade = response.Headers.TryGetValues("Upgrade", out var responseHeaderValues)
164-
&& responseHeaderValues.Any(header => "tcp".Equals(header));
167+
&& responseHeaderValues.Any(header => "tcp".Equals(header, StringComparison.OrdinalIgnoreCase));
165168

166169
var isStream = content.Headers.TryGetValues("Content-Type", out var contentHeaderValues)
167-
&& contentHeaderValues.Any(header => DockerStreamHeaders.Contains(header));
170+
&& contentHeaderValues.Any(DockerStreamHeaders.Contains);
168171

169-
var isChunkedTransferEncoding = (response.Headers.TransferEncodingChunked.GetValueOrDefault() && !isStream) || (isStream && !isConnectionUpgrade);
172+
// Treat the response as chunked for standard HTTP chunked or Docker raw-streams,
173+
// but not for upgraded connections.
174+
var isChunkedTransferEncoding = (response.Headers.TransferEncodingChunked.GetValueOrDefault() && !isConnectionUpgrade)
175+
|| (!isConnectionUpgrade && isStream);
170176

171-
content.ResolveResponseStream(chunked: isChunkedTransferEncoding);
177+
if (isSwitchingProtocols && isConnectionUpgrade)
178+
{
179+
content.ResolveResponseStream(false, true);
180+
}
181+
else
182+
{
183+
content.ResolveResponseStream(isChunkedTransferEncoding, isConnectionUpgrade);
184+
}
172185

173186
return response;
174187
}

src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal HttpConnectionResponseContent(HttpConnection connection)
1010
_connection = connection;
1111
}
1212

13-
internal void ResolveResponseStream(bool chunked)
13+
internal void ResolveResponseStream(bool chunked, bool isConnectionUpgrade)
1414
{
1515
if (_responseStream != null)
1616
{
@@ -20,7 +20,7 @@ internal void ResolveResponseStream(bool chunked)
2020
{
2121
_responseStream = new ChunkedReadStream(_connection.Transport);
2222
}
23-
else if (Headers.ContentLength.HasValue)
23+
else if (!isConnectionUpgrade && Headers.ContentLength.HasValue)
2424
{
2525
_responseStream = new ContentLengthReadStream(_connection.Transport, Headers.ContentLength.Value);
2626
}

0 commit comments

Comments
 (0)