libnetservices: fix handling of HEAD requests and 204 responses

We were incorrectly reporting a B_IO_ERROR for these requests because we
could not read the content after the headers. There is no content in
these cases.

Add an unit test for both HEAD and 204 status, checking that there is no
content and the headers are correct.

Fixes #16885.

Change-Id: I98fefc5c604253bb2545b50395b7af9f8834def0
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4142
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Niels Sascha Reedijk <niels.reedijk@gmail.com>
This commit is contained in:
Adrien Destugues 2021-07-03 11:04:34 +02:00 committed by Adrien Destugues
parent f32d5c5ca5
commit 3ca5eec002
5 changed files with 126 additions and 16 deletions

View File

@ -684,8 +684,7 @@ BHttpRequest::_MakeRequest()
// example in the case of a chunked transfer, we can't know
// - If the request method is "HEAD" which explicitly asks the
// server to not send any data (only the headers)
if (bytesTotal > 0 && bytesReceived != bytesTotal
&& fRequestMethod != B_HTTP_HEAD) {
if (bytesTotal > 0 && bytesReceived != bytesTotal) {
readError = B_IO_ERROR;
break;
}
@ -752,6 +751,14 @@ BHttpRequest::_MakeRequest()
bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
else
bytesTotal = -1;
if (fRequestMethod == B_HTTP_HEAD
|| fResult.StatusCode() == 204) {
// In the case of a HEAD request or if the server replies
// 204 ("no content"), we don't expect to receive anything
// more, and the socket will be closed.
receiveEnd = true;
}
}
}
@ -968,8 +975,7 @@ BHttpRequest::_MakeRequest()
// example in the case of a chunked transfer, we can't know
// - If the request method is "HEAD" which explicitly asks the
// server to not send any data (only the headers)
if (bytesTotal > 0 && bytesReceived != bytesTotal
&& fRequestMethod != B_HTTP_HEAD) {
if (bytesTotal > 0 && bytesReceived != bytesTotal) {
readError = B_IO_ERROR;
break;
}
@ -1047,6 +1053,14 @@ BHttpRequest::_MakeRequest()
bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
else
bytesTotal = -1;
if (fRequestMethod == B_HTTP_HEAD
|| fResult.StatusCode() == 204) {
// In the case of a HEAD request or if the server replies
// 204 ("no content"), we don't expect to receive anything
// more, and the socket will be closed.
receiveEnd = true;
}
}
}

View File

@ -8,9 +8,6 @@
*/
#include <cstdio>
#ifdef DEBUG
#include <iostream>
#endif
#include <UrlRequest.h>
#include <UrlProtocolListener.h>
@ -111,24 +108,24 @@ BUrlProtocolListener::DebugMessage(BUrlRequest* caller,
#ifdef DEBUG
switch (type) {
case B_URL_PROTOCOL_DEBUG_TEXT:
cout << " ";
fprintf(stderr, " ");
break;
case B_URL_PROTOCOL_DEBUG_ERROR:
cout << "!!!";
fprintf(stderr, "!!!");
break;
case B_URL_PROTOCOL_DEBUG_TRANSFER_IN:
case B_URL_PROTOCOL_DEBUG_HEADER_IN:
cout << "<--";
fprintf(stderr, "<--");
break;
case B_URL_PROTOCOL_DEBUG_TRANSFER_OUT:
case B_URL_PROTOCOL_DEBUG_HEADER_OUT:
cout << "-->";
fprintf(stderr, "-->");
break;
}
cout << " " << caller->Protocol() << ": " << text << endl;
fprintf(stderr, " %s: %s\n", caller->Protocol().String(), text);
#endif
}

View File

@ -168,6 +168,8 @@ template <typename T>
void AddCommonTests(BThreadedTestCaller<T>& testCaller)
{
testCaller.addThread("GetTest", &T::GetTest);
testCaller.addThread("HeadTest", &T::HeadTest);
testCaller.addThread("NoContentTest", &T::NoContentTest);
testCaller.addThread("UploadTest", &T::UploadTest);
testCaller.addThread("BasicAuthTest", &T::AuthBasicTest);
testCaller.addThread("DigestAuthTest", &T::AuthDigestTest);
@ -206,6 +208,96 @@ HttpTest::GetTest()
}
void
HttpTest::HeadTest()
{
BUrl testUrl(fTestServer.BaseUrl(), "/");
BUrlContext* context = new BUrlContext();
context->AcquireReference();
std::string expectedResponseBody("");
HttpHeaderMap expectedResponseHeaders;
expectedResponseHeaders["Content-Encoding"] = "gzip";
expectedResponseHeaders["Content-Length"] = "144";
expectedResponseHeaders["Content-Type"] = "text/plain";
expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT";
expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku";
TestListener listener(expectedResponseBody, expectedResponseHeaders);
ObjectDeleter<BUrlRequest> requestDeleter(
BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener,
context));
BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get());
CPPUNIT_ASSERT(request != NULL);
request->SetAutoReferrer(false);
request->SetMethod("HEAD");
CPPUNIT_ASSERT(request->Run());
while (request->IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, request->Status());
const BHttpResult& result
= dynamic_cast<const BHttpResult&>(request->Result());
CPPUNIT_ASSERT_EQUAL(200, result.StatusCode());
CPPUNIT_ASSERT_EQUAL(BString("OK"), result.StatusText());
CPPUNIT_ASSERT_EQUAL(144, result.Length());
listener.Verify();
CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext());
// This page should not set cookies
context->ReleaseReference();
}
void
HttpTest::NoContentTest()
{
BUrl testUrl(fTestServer.BaseUrl(), "/204");
BUrlContext* context = new BUrlContext();
context->AcquireReference();
std::string expectedResponseBody("");
HttpHeaderMap expectedResponseHeaders;
expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT";
expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku";
TestListener listener(expectedResponseBody, expectedResponseHeaders);
ObjectDeleter<BUrlRequest> requestDeleter(
BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener,
context));
BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get());
CPPUNIT_ASSERT(request != NULL);
request->SetAutoReferrer(false);
CPPUNIT_ASSERT(request->Run());
while (request->IsRunning())
snooze(1000);
CPPUNIT_ASSERT_EQUAL(B_OK, request->Status());
const BHttpResult& result
= dynamic_cast<const BHttpResult&>(request->Result());
CPPUNIT_ASSERT_EQUAL(204, result.StatusCode());
CPPUNIT_ASSERT_EQUAL(BString("No Content"), result.StatusText());
listener.Verify();
CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext());
// This page should not set cookies
context->ReleaseReference();
}
void
HttpTest::ProxyTest()
{

View File

@ -25,6 +25,8 @@ public:
virtual void setUp();
void GetTest();
void HeadTest();
void NoContentTest();
void UploadTest();
void AuthBasicTest();
void AuthDigestTest();

View File

@ -68,10 +68,15 @@ class RequestHandler(http.server.BaseHTTPRequestHandler):
self.send_response(status_code)
if status_code >= 300 and status_code < 400:
self.send_header('Location', '/')
if status_code == 204:
write_response = False
else:
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', str(len(response_body)))
if encoding:
self.send_header('Content-Encoding', encoding)
for header_name, header_value in extra_headers:
self.send_header(header_name, header_value)
self.end_headers()