Use strict detection of http line ending for docker response (#40932)
yao-msft committed
Jun 29, 2026 at 19:39 UTC
fb10e1b2402183f27cf7d15f4df19fb96c8e7c9e
4 files changed
+86
-21
src/windows/common/HttpHeaderEndDetector.h
new
+44
@@ -0,0 +1,44 @@
1
+// Copyright (C) Microsoft Corporation. All rights reserved.
2
+
3
+#pragma once
4
+
5
+#include <algorithm>
6
+#include <deque>
7
+#include <string_view>
8
+
9
+namespace wsl::windows::common {
10
+
11
+// Detects the end-of-header marker ("\r\n\r\n") in an HTTP message
12
+class HttpHeaderEndDetector
13
+{
14
+public:
15
+ // Returns true once the full "\r\n\r\n" terminator has been consumed.
16
+ bool Consume(char byte)
17
+ {
18
+ if (m_done)
19
+ {
20
+ return true;
21
+ }
22
+
23
+ m_last4Bytes.push_back(byte);
24
+ if (m_last4Bytes.size() > 4)
25
+ {
26
+ m_last4Bytes.pop_front();
27
+ }
28
+
29
+ static constexpr std::string_view c_terminator = "\r\n\r\n";
30
+ m_done = std::ranges::equal(m_last4Bytes, c_terminator);
31
+ return m_done;
32
+ }
33
+
34
+ bool IsDone() const noexcept
35
+ {
36
+ return m_done;
37
+ }
38
+
39
+private:
40
+ std::deque<char> m_last4Bytes;
41
+ bool m_done = false;
42
+};
43
+
44
+} // namespace wsl::windows::common
src/windows/wslcsession/DockerHTTPClient.cpp
+6
-20
@@ -663,16 +663,9 @@ void DockerHTTPClient::DockerHttpResponseHandle::OnRead(const gsl::span<char>& C
663
{
664
// Otherwise keep parsing the HTTP response header.
665
size_t i{};
666
- for (i = 0; i < Content.size() && LineFeeds < 2; i++)
666
+ for (i = 0; i < Content.size() && !HeaderEnd.IsDone(); i++)
667
{
668
- if (Content[i] == '\n')
669
- {
670
- LineFeeds++;
671
- }
672
- else if (Content[i] != '\r')
673
- {
674
- LineFeeds = 0;
675
- }
668
+ HeaderEnd.Consume(Content[i]);
669
}
670
671
// Feed the parser up to the end of the header.
@@ -805,7 +798,7 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::
798
parser.eager(false);
799
parser.skip(false);
800
808
- size_t lineFeeds = 0;
801
+ HttpHeaderEndDetector headerEnd;
802
// Consume the socket until the header end is reached
803
while (!parser.is_header_done())
804
{
@@ -822,16 +815,9 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::
815
816
// Scan only the newly peeked bytes [Offset, Offset + bytesRead)
817
size_t i = 0;
825
- for (i = Offset; i < bytesRead + Offset && lineFeeds < 2; i++)
818
+ for (i = Offset; i < bytesRead + Offset && !headerEnd.IsDone(); i++)
819
{
827
- if (buffer[i] == '\n')
828
- {
829
- lineFeeds++;
830
- }
831
- else if (buffer[i] != '\r')
832
- {
833
- lineFeeds = 0;
834
- }
820
+ headerEnd.Consume(buffer[i]);
821
}
822
823
WI_ASSERT(i >= Offset);
@@ -847,7 +833,7 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::
833
Offset += bytesRead;
834
buffer.resize(Offset);
835
850
- if (lineFeeds == 2) // Header is complete, feed it to the parser.
836
+ if (headerEnd.IsDone()) // Header is complete, feed it to the parser.
837
{
838
839
#ifdef WSLC_HTTP_DEBUG
src/windows/wslcsession/DockerHTTPClient.h
+2
-1
@@ -20,6 +20,7 @@ Abstract:
20
#include <boost/beast/http.hpp>
21
#include "relay.hpp"
22
#include "docker_schema.h"
23
+#include "HttpHeaderEndDetector.h"
24
25
#define THROW_DOCKER_USER_ERROR_MSG(_Ex, _Msg, ...) \
26
if ((_Ex).HasErrorMessage()) \
@@ -199,7 +200,7 @@ public:
200
std::function<void(const gsl::span<char>&)> OnResponse;
201
std::function<void()> OnCompleted;
202
boost::beast::http::response_parser<boost::beast::http::buffer_body> Parser;
202
- size_t LineFeeds = 0;
203
+ common::HttpHeaderEndDetector HeaderEnd;
204
std::optional<size_t> RemainingContentLength;
205
std::optional<common::io::HTTPChunkBasedReadHandle> ResponseParser;
206
};
test/windows/WSLCTests.cpp
+34
@@ -22,6 +22,7 @@ Abstract:
22
#include "hcs.hpp"
23
#include "ContainerNameGenerator.h"
24
#include "wslc/e2e/WSLCE2EHelpers.h"
25
+#include "HttpHeaderEndDetector.h"
26
#include <nlohmann/json.hpp>
27
28
using namespace std::literals::chrono_literals;
@@ -9220,6 +9221,39 @@ class WSLCTests
9221
VERIFY_IS_TRUE(payload == output);
9222
}
9223
9224
+ TEST_METHOD(HttpHeaderEndDetector)
9225
+ {
9226
+ // Returns the index of the byte of header end, or -1 if the header never ends.
9227
+ const auto headerEndIndex = [](std::string_view input) {
9228
+ wsl::windows::common::HttpHeaderEndDetector detector;
9229
+ for (size_t i = 0; i < input.size(); i++)
9230
+ {
9231
+ if (detector.Consume(input[i]))
9232
+ {
9233
+ return static_cast<int>(i);
9234
+ }
9235
+ }
9236
+
9237
+ return -1;
9238
+ };
9239
+
9240
+ VERIFY_ARE_EQUAL(3, headerEndIndex("\r\n\r\n"));
9241
+ VERIFY_ARE_EQUAL(4, headerEndIndex("a\r\n\r\n"));
9242
+ VERIFY_ARE_EQUAL(7, headerEndIndex("a\r\nb\r\n\r\n"));
9243
+ VERIFY_ARE_EQUAL(4, headerEndIndex("\r\r\n\r\n"));
9244
+ VERIFY_ARE_EQUAL(3, headerEndIndex("\r\n\r\nbody"));
9245
+
9246
+ VERIFY_ARE_EQUAL(-1, headerEndIndex(""));
9247
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("Header: value\r\n"));
9248
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("HTTP/1.1 200 OK\r\n"));
9249
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("\r\n\r"));
9250
+
9251
+ // Detection is strict.
9252
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("\n\n"));
9253
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("\r\n\n"));
9254
+ VERIFY_ARE_EQUAL(-1, headerEndIndex("\n\r\n"));
9255
+ }
9256
+
9257
WSLC_TEST_METHOD(ContainerRecoveryFromStorage)
9258
{
9259
auto restore = ResetTestSession(); // Required to access the storage folder.