Skip to content

Commit

Permalink
Bug 1687903 - Enable stricter HTTP response status line parsing; r=va…
Browse files Browse the repository at this point in the history
…lentin,necko-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D186229
  • Loading branch information
wisniewskit committed Aug 16, 2023
1 parent 59a4efb commit 895aa0d
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 46 deletions.
6 changes: 6 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12613,6 +12613,12 @@
value: true
mirror: always

# If true, will be more strict with status code parsing
- name: network.http.strict_response_status_line_parsing
type: RelaxedAtomicBool
value: true
mirror: always

# If true, remove the resumption token when 0RTT failed.
- name: network.http.remove_resumption_token_when_early_data_failed
type: RelaxedAtomicBool
Expand Down
3 changes: 2 additions & 1 deletion netwerk/protocol/http/InterceptedHttpChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,8 @@ InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus,
statusLine.AppendLiteral(" ");
statusLine.Append(aReason);

mSynthesizedResponseHead->ParseStatusLine(statusLine);
NS_ENSURE_SUCCESS(mSynthesizedResponseHead->ParseStatusLine(statusLine),
NS_ERROR_FAILURE);
return NS_OK;
}

Expand Down
40 changes: 35 additions & 5 deletions netwerk/protocol/http/nsHttpResponseHead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "HttpLog.h"

#include "mozilla/StaticPrefs_network.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Unused.h"
#include "nsHttpResponseHead.h"
#include "nsIHttpHeaderVisitor.h"
Expand Down Expand Up @@ -351,19 +352,18 @@ void nsHttpResponseHead::AssignDefaultStatusText() {
net_GetDefaultStatusTextForCode(mStatus, mStatusText);
}

void nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
nsresult nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
RecursiveMutexAutoLock monitor(mRecursiveMutex);
ParseStatusLine_locked(line);
return ParseStatusLine_locked(line);
}

void nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
nsresult nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
//
// Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
//

const char* start = line.BeginReading();
const char* end = line.EndReading();
const char* p = start;

// HTTP-Version
ParseVersion(start);
Expand All @@ -373,9 +373,38 @@ void nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
if ((mVersion == HttpVersion::v0_9) || (index == -1)) {
mStatus = 200;
AssignDefaultStatusText();
} else if (StaticPrefs::network_http_strict_response_status_line_parsing()) {
// Status-Code: all ASCII digits after any whitespace
const char* p = start + index + 1;
while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
if (p == end || !mozilla::IsAsciiDigit(*p)) {
return NS_ERROR_FAILURE;
}
const char* codeStart = p;
while (p < end && mozilla::IsAsciiDigit(*p)) ++p;

// Only accept 3 digits (including all leading zeros)
// Also if next char isn't whitespace, fail (ie, code is 0x2)
if (p - codeStart > 3 || (p < end && !NS_IsHTTPWhitespace(*p))) {
return NS_ERROR_FAILURE;
}

// At this point the code is between 0 and 999 inclusive
nsDependentCSubstring strCode(codeStart, p - codeStart);
nsresult rv;
mStatus = strCode.ToInteger(&rv);
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}

// Reason-Phrase: whatever remains after any whitespace (even empty)
while (p < end && NS_IsHTTPWhitespace(*p)) ++p;
if (p != end) {
mStatusText = nsDependentCSubstring(p, end - p);
}
} else {
// Status-Code
p += index + 1;
const char* p = start + index + 1;
mStatus = (uint16_t)atoi(p);
if (mStatus == 0) {
LOG(("mal-formed response status; assuming status = 200\n"));
Expand All @@ -394,6 +423,7 @@ void nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {

LOG1(("Have status line [version=%u status=%u statusText=%s]\n",
unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
return NS_OK;
}

nsresult nsHttpResponseHead::ParseHeaderLine(const nsACString& line) {
Expand Down
4 changes: 2 additions & 2 deletions netwerk/protocol/http/nsHttpResponseHead.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class nsHttpResponseHead {
[[nodiscard]] nsresult ParseCachedOriginalHeaders(char* block);

// parse the status line.
void ParseStatusLine(const nsACString& line);
nsresult ParseStatusLine(const nsACString& line);

// parse a header line.
[[nodiscard]] nsresult ParseHeaderLine(const nsACString& line);
Expand Down Expand Up @@ -160,7 +160,7 @@ class nsHttpResponseHead {
nsresult ParseResponseContentLength(const nsACString& aHeaderStr)
MOZ_REQUIRES(mRecursiveMutex);

void ParseStatusLine_locked(const nsACString& line)
nsresult ParseStatusLine_locked(const nsACString& line)
MOZ_REQUIRES(mRecursiveMutex);
[[nodiscard]] nsresult ParseHeaderLine_locked(const nsACString& line,
bool originalFromNetHeaders)
Expand Down
10 changes: 7 additions & 3 deletions netwerk/protocol/http/nsHttpTransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1961,8 +1961,10 @@ nsresult nsHttpTransaction::ParseLine(nsACString& line) {
nsresult rv = NS_OK;

if (!mHaveStatusLine) {
mResponseHead->ParseStatusLine(line);
mHaveStatusLine = true;
rv = mResponseHead->ParseStatusLine(line);
if (NS_SUCCEEDED(rv)) {
mHaveStatusLine = true;
}
// XXX this should probably never happen
if (mResponseHead->Version() == HttpVersion::v0_9) mHaveAllHeaders = true;
} else {
Expand Down Expand Up @@ -2086,7 +2088,9 @@ nsresult nsHttpTransaction::ParseHead(char* buf, uint32_t count,
// Treat any 0.9 style response of a put as a failure.
if (mRequestHead->IsPut()) return NS_ERROR_ABORT;

mResponseHead->ParseStatusLine(""_ns);
if (NS_FAILED(mResponseHead->ParseStatusLine(""_ns))) {
return NS_ERROR_FAILURE;
}
mHaveStatusLine = true;
mHaveAllHeaders = true;
return NS_OK;
Expand Down
32 changes: 29 additions & 3 deletions netwerk/test/gtest/TestHttpResponseHead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "mozilla/net/PHttpChannelParams.h"
#include "mozilla/Unused.h"
#include "nsHttp.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"

namespace mozilla {
namespace net {
Expand Down Expand Up @@ -44,7 +46,7 @@ TEST(TestHttpResponseHead, Bug1636930)
{
nsHttpResponseHead head;

head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
Unused << head.ParseHeaderLine("etag: Just testing"_ns);
Unused << head.ParseHeaderLine("cache-control: max-age=99999"_ns);
Expand All @@ -61,7 +63,7 @@ TEST(TestHttpResponseHead, bug1649807)
{
nsHttpResponseHead head;

head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
Unused << head.ParseHeaderLine("etag: Just testing"_ns);
Unused << head.ParseHeaderLine("cache-control: age=99999"_ns);
Expand All @@ -81,7 +83,7 @@ TEST(TestHttpResponseHead, bug1660200)
{
nsHttpResponseHead head;

head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseStatusLine("HTTP/1.1 200 OK"_ns);
Unused << head.ParseHeaderLine("content-type: text/plain"_ns);
Unused << head.ParseHeaderLine("etag: Just testing"_ns);
Unused << head.ParseHeaderLine("cache-control: no-cache"_ns);
Expand All @@ -94,6 +96,30 @@ TEST(TestHttpResponseHead, bug1660200)
AssertRoundTrips(head);
}

TEST(TestHttpResponseHead, bug1687903)
{
nsHttpResponseHead head;

bool usingStrictParsing = false;
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->GetBoolPref("network.http.strict_response_status_line_parsing",
&usingStrictParsing);
}

nsresult expectation = usingStrictParsing ? NS_ERROR_FAILURE : NS_OK;

ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 "_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 BLAH"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 1000 BOO"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0200 BOO"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 60200 200"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 131072 HIOK"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 -200 OK"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 0x9 OK"_ns));
ASSERT_EQ(expectation, head.ParseStatusLine("HTTP/1.1 C8 OK"_ns));
}

TEST(TestHttpResponseHead, atoms)
{
// Test that the resolving the content-type atom returns the initial static
Expand Down
1 change: 1 addition & 0 deletions testing/web-platform/meta/fetch/h1-parsing/__dir__.ini
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
leak-threshold: [default:3020800]
prefs: [network.http.strict_response_status_line_parsing:true]
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,3 @@
expected:
if (os == "android") and debug and fission: [TIMEOUT, OK]
if (os == "android") and debug and not fission: [OK, TIMEOUT]
[HTTP/1.1 (network error)]
expected: FAIL

[HTTP/1.1 BLAH (network error)]
expected: FAIL

[HTTP/1.1 0 OK ]
expected: FAIL

[HTTP/1.1 200 ]
expected: FAIL

[HTTP/1.1 1000 BOO (network error)]
expected: FAIL

[HTTP/1.1 0200 BOO (network error)]
expected: FAIL

[HTTP/1.1 65736 NOT 200 OR SOME SUCH (network error)]
expected: FAIL

[HTTP/1.1 131072 HI (network error)]
expected: FAIL

[HTTP/1.1 -200 TEST (network error)]
expected: FAIL

[HTTP/1.1 0xA (network error)]
expected: FAIL

[HTTP/1.1 C8 (network error)]
expected: FAIL

0 comments on commit 895aa0d

Please sign in to comment.