@samitouri / QOSAMI-WSL / commits / fb10e1b2

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.