Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/windows/common/HttpHeaderEndDetector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

HttpHeaderEndDetector.h

Abstract:

This file contains a small state machine that detects the end-of-header
marker ("\r\n\r\n") in an HTTP message, one byte at a time.

--*/

Comment on lines +1 to +15
#pragma once

namespace wsl::windows::common {

// Detects the end-of-header marker ("\r\n\r\n") in an HTTP message
class HttpHeaderEndDetector
{
public:
// Returns true once the full "\r\n\r\n" terminator has been consumed.
bool Consume(char byte) noexcept
{
switch (m_state)
{
case State::Start:
m_state = (byte == '\r') ? State::Cr : State::Start;
break;
case State::Cr:
m_state = (byte == '\n') ? State::CrLf : ((byte == '\r') ? State::Cr : State::Start);
break;
case State::CrLf:
m_state = (byte == '\r') ? State::CrLfCr : State::Start;
break;
case State::CrLfCr:
m_state = (byte == '\n') ? State::Done : ((byte == '\r') ? State::Cr : State::Start);
break;
case State::Done:
break;
}

return m_state == State::Done;
}

bool IsDone() const noexcept
{
return m_state == State::Done;
}

private:
enum class State
{
Start,
Cr,
CrLf,
CrLfCr,
Done
};

State m_state = State::Start;
};

} // namespace wsl::windows::common
26 changes: 6 additions & 20 deletions src/windows/wslcsession/DockerHTTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -663,16 +663,9 @@ void DockerHTTPClient::DockerHttpResponseHandle::OnRead(const gsl::span<char>& C
{
// Otherwise keep parsing the HTTP response header.
size_t i{};
for (i = 0; i < Content.size() && LineFeeds < 2; i++)
for (i = 0; i < Content.size() && !HeaderEnd.IsDone(); i++)
{
if (Content[i] == '\n')
{
LineFeeds++;
}
else if (Content[i] != '\r')
{
LineFeeds = 0;
}
HeaderEnd.Consume(Content[i]);
}

// Feed the parser up to the end of the header.
Expand Down Expand Up @@ -805,7 +798,7 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::
parser.eager(false);
parser.skip(false);

size_t lineFeeds = 0;
HttpHeaderEndDetector headerEnd;
// Consume the socket until the header end is reached
while (!parser.is_header_done())
{
Expand All @@ -822,16 +815,9 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::

// Scan only the newly peeked bytes [Offset, Offset + bytesRead)
size_t i = 0;
for (i = Offset; i < bytesRead + Offset && lineFeeds < 2; i++)
for (i = Offset; i < bytesRead + Offset && !headerEnd.IsDone(); i++)
{
if (buffer[i] == '\n')
{
lineFeeds++;
}
else if (buffer[i] != '\r')
{
lineFeeds = 0;
}
headerEnd.Consume(buffer[i]);
}

WI_ASSERT(i >= Offset);
Expand All @@ -847,7 +833,7 @@ std::pair<DockerHTTPClient::HTTPResponse, wil::unique_socket> DockerHTTPClient::
Offset += bytesRead;
buffer.resize(Offset);

if (lineFeeds == 2) // Header is complete, feed it to the parser.
if (headerEnd.IsDone()) // Header is complete, feed it to the parser.
{

#ifdef WSLC_HTTP_DEBUG
Expand Down
3 changes: 2 additions & 1 deletion src/windows/wslcsession/DockerHTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Module Name:
#include <boost/beast/http.hpp>
#include "relay.hpp"
#include "docker_schema.h"
#include "HttpHeaderEndDetector.h"

#define THROW_DOCKER_USER_ERROR_MSG(_Ex, _Msg, ...) \
if ((_Ex).HasErrorMessage()) \
Expand Down Expand Up @@ -199,7 +200,7 @@ class DockerHTTPClient
std::function<void(const gsl::span<char>&)> OnResponse;
std::function<void()> OnCompleted;
boost::beast::http::response_parser<boost::beast::http::buffer_body> Parser;
size_t LineFeeds = 0;
common::HttpHeaderEndDetector HeaderEnd;
std::optional<size_t> RemainingContentLength;
std::optional<common::io::HTTPChunkBasedReadHandle> ResponseParser;
};
Expand Down
34 changes: 34 additions & 0 deletions test/windows/WSLCTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Module Name:
#include "hcs.hpp"
#include "ContainerNameGenerator.h"
#include "wslc/e2e/WSLCE2EHelpers.h"
#include "HttpHeaderEndDetector.h"
#include <nlohmann/json.hpp>

using namespace std::literals::chrono_literals;
Expand Down Expand Up @@ -9220,6 +9221,39 @@ class WSLCTests
VERIFY_IS_TRUE(payload == output);
}

TEST_METHOD(HttpHeaderEndDetector)
{
// Returns the index of the byte of header end, or -1 if the header never ends.
const auto headerEndIndex = [](std::string_view input) {
wsl::windows::common::HttpHeaderEndDetector detector;
for (size_t i = 0; i < input.size(); i++)
{
if (detector.Consume(input[i]))
{
return static_cast<int>(i);
}
}

return -1;
};

VERIFY_ARE_EQUAL(3, headerEndIndex("\r\n\r\n"));
VERIFY_ARE_EQUAL(4, headerEndIndex("a\r\n\r\n"));
VERIFY_ARE_EQUAL(7, headerEndIndex("a\r\nb\r\n\r\n"));
VERIFY_ARE_EQUAL(4, headerEndIndex("\r\r\n\r\n"));
VERIFY_ARE_EQUAL(3, headerEndIndex("\r\n\r\nbody"));

VERIFY_ARE_EQUAL(-1, headerEndIndex(""));
VERIFY_ARE_EQUAL(-1, headerEndIndex("Header: value\r\n"));
VERIFY_ARE_EQUAL(-1, headerEndIndex("HTTP/1.1 200 OK\r\n"));
VERIFY_ARE_EQUAL(-1, headerEndIndex("\r\n\r"));

// Detection is strict.
VERIFY_ARE_EQUAL(-1, headerEndIndex("\n\n"));
VERIFY_ARE_EQUAL(-1, headerEndIndex("\r\n\n"));
VERIFY_ARE_EQUAL(-1, headerEndIndex("\n\r\n"));
}

WSLC_TEST_METHOD(ContainerRecoveryFromStorage)
{
auto restore = ResetTestSession(); // Required to access the storage folder.
Expand Down