Index: llvm/include/llvm/Support/HTTPClient.h =================================================================== --- /dev/null +++ llvm/include/llvm/Support/HTTPClient.h @@ -0,0 +1,72 @@ +//===-- llvm/Support/HTTPClient.h - HTTP client library ---*- C++ -------*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declarations of the HTTPClient, HTTPMethod, +/// HTTPResponseHandler, and BufferedHTTPResponseHandler classes, as well as +/// the HTTPResponseBuffer and HTTPRequest structs. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_HTTP_CLIENT_H +#define LLVM_SUPPORT_HTTP_CLIENT_H + +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace llvm { + +enum class HTTPMethod { GET }; + +class HTTPResponseHandler { +public: + virtual ~HTTPResponseHandler(){}; + virtual Expected handleHeaderLine(StringRef HeaderLine) = 0; + virtual Expected handleBodyChunk(StringRef BodyChunk) = 0; + virtual void handleResponseCode(unsigned Code) = 0; +}; + +struct HTTPResponseBuffer { + unsigned Code = 0; + std::unique_ptr Body; +}; + +class BufferedHTTPResponseHandler : public HTTPResponseHandler { + size_t BufferOffset = 0; + +public: + HTTPResponseBuffer ResponseBuffer; + Expected handleHeaderLine(StringRef HeaderLine) override; + Expected handleBodyChunk(StringRef BodyChunk) override; + void handleResponseCode(unsigned Code) override; +}; + +struct HTTPRequest { + SmallString<128> Url; + HTTPMethod Method = HTTPMethod::GET; + bool FollowRedirects = true; + bool operator==(const HTTPRequest &O) const { + return Url == O.Url && Method == O.Method && + FollowRedirects == O.FollowRedirects; + } + HTTPRequest(const Twine &Url); +}; + +class HTTPClient { + +public: + virtual Error perform(const HTTPRequest &Request, + HTTPResponseHandler &Handler); + Expected perform(const HTTPRequest &Request); + Expected get(const Twine &Url); + virtual ~HTTPClient(){}; +}; + +} // end namespace llvm + +#endif // LLVM_SUPPORT_HTTP_CLIENT_H Index: llvm/lib/Support/CMakeLists.txt =================================================================== --- llvm/lib/Support/CMakeLists.txt +++ llvm/lib/Support/CMakeLists.txt @@ -155,6 +155,7 @@ GlobPattern.cpp GraphWriter.cpp Hashing.cpp + HTTPClient.cpp InitLLVM.cpp InstructionCost.cpp IntEqClasses.cpp Index: llvm/lib/Support/HTTPClient.cpp =================================================================== --- /dev/null +++ llvm/lib/Support/HTTPClient.cpp @@ -0,0 +1,79 @@ +//===-- llvm/Support/HTTPClient.cpp - HTTP client library -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// +/// This file defines the BufferedHTTPResponseHandler class. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/HTTPClient.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" + +using namespace llvm; + +#define DEBUG_TYPE "HTTPClient" + +static bool parseContentLengthHeader(StringRef LineRef, + unsigned long long &ContentLength) { + // Content-Length is a mandatory header, and the only one we handle. + return LineRef.consume_front("Content-Length: ") && + !getAsUnsignedInteger(LineRef.trim(), 10, ContentLength); +} + +Expected +BufferedHTTPResponseHandler::handleHeaderLine(StringRef HeaderLine) { + if (ResponseBuffer.Body) + return HeaderLine.size(); + + unsigned long long ContentLength; + if (parseContentLengthHeader(HeaderLine, ContentLength)) + ResponseBuffer.Body = + WritableMemoryBuffer::getNewUninitMemBuffer(ContentLength); + + return HeaderLine.size(); +} + +Expected +BufferedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) { + if (!ResponseBuffer.Body) + return createStringError(errc::io_error, + "Unallocated response buffer -- HTTP Body data " + "recieved before Content-Length Header."); + + if (BufferOffset + BodyChunk.size() > ResponseBuffer.Body->getBufferSize()) + return createStringError(errc::io_error, + "Content is larger than response buffer."); + + memcpy(ResponseBuffer.Body->getBufferStart() + BufferOffset, BodyChunk.data(), + BodyChunk.size()); + BufferOffset += BodyChunk.size(); + return BodyChunk.size(); +} + +void BufferedHTTPResponseHandler::handleResponseCode(unsigned Code) { + ResponseBuffer.Code = Code; +} + +HTTPRequest::HTTPRequest(const Twine &Url) { this->Url = Url.str(); } + +Expected HTTPClient::perform(const HTTPRequest &Request) { + BufferedHTTPResponseHandler Handler; + if (Error Err = perform(Request, Handler)) + return Err; + return std::move(Handler.ResponseBuffer); +} + +Expected HTTPClient::get(const Twine &Url) { + HTTPRequest Request(Url); + return perform(Request); +} Index: llvm/unittests/Support/CMakeLists.txt =================================================================== --- llvm/unittests/Support/CMakeLists.txt +++ llvm/unittests/Support/CMakeLists.txt @@ -41,6 +41,7 @@ GlobPatternTest.cpp HashBuilderTest.cpp Host.cpp + HTTPClient.cpp IndexedAccessorTest.cpp InstructionCostTest.cpp ItaniumManglingCanonicalizerTest.cpp Index: llvm/unittests/Support/HTTPClient.cpp =================================================================== --- /dev/null +++ llvm/unittests/Support/HTTPClient.cpp @@ -0,0 +1,130 @@ +//===-- llvm/unittest/Support/HTTPClient.cpp - unit tests -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/HTTPClient.h" +#include "llvm/Support/Errc.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(HTTPClientTests, bufferedHTTPResponseHandlerLifecycleTest) { + BufferedHTTPResponseHandler Handler; + // test initial state + EXPECT_EQ(Handler.ResponseBuffer.Code, 0u); + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + + // a body chunk passed before the content-length header should return Error + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("body"), + Failed()); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("a header line"), HasValue(13)); + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("body"), + Failed()); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: 36\r\n"), + HasValue(20)); + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("body:"), HasValue(5)); + EXPECT_THAT_EXPECTED( + Handler.handleBodyChunk("this puts the total at 36 chars"), HasValue(31)); + EXPECT_EQ(memcmp(Handler.ResponseBuffer.Body->getBufferStart(), + "body:this puts the total at 36 chars", + Handler.ResponseBuffer.Body->getBufferSize()), + 0); + + // additional content should be rejected by the handler + EXPECT_THAT_EXPECTED( + Handler.handleBodyChunk("extra content past the content-length"), + Failed()); + + // response code handling + Handler.handleResponseCode(200u); + EXPECT_EQ(Handler.ResponseBuffer.Code, 200u); +} + +TEST(HTTPClientTests, bufferedHTTPResponseHandlerZeroContentLengthTest) { + BufferedHTTPResponseHandler Handler; + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: 0"), + HasValue(17)); + EXPECT_NE(Handler.ResponseBuffer.Body, nullptr); + EXPECT_EQ(Handler.ResponseBuffer.Body->getBufferSize(), 0u); + + // all content should be rejected by the handler + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("non-empty body content"), + Failed()); +} + +TEST(HTTPClientTests, bufferedHTTPResponseHandlerMalformedContentLengthTest) { + // confirm getAsUnsignedInteger behaves as expected + // in particular, getAsUnsignedInteger returns false + // for valid inputs + unsigned long long ContentLength; + EXPECT_EQ(llvm::getAsUnsignedInteger("fff", 10, ContentLength), true); + EXPECT_EQ(llvm::getAsUnsignedInteger("", 10, ContentLength), true); + EXPECT_EQ(llvm::getAsUnsignedInteger("-1", 10, ContentLength), true); + EXPECT_EQ(llvm::getAsUnsignedInteger("0111", 10, ContentLength), false); + EXPECT_EQ(llvm::getAsUnsignedInteger("0", 10, ContentLength), false); + EXPECT_EQ(ContentLength, 0u); + + // check several invalid content lengths are ignored + BufferedHTTPResponseHandler Handler; + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: fff"), + HasValue(19)); + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: "), + HasValue(19)); + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + EXPECT_THAT_EXPECTED( + Handler.handleHeaderLine(StringRef("Content-Length: \0\0\0", 19)), + HasValue(19)); + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: -11"), + HasValue(19)); + EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr); + + // all content should be rejected by the handler because + // no valid content length has been received + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("non-empty body content"), + Failed()); +} + +class HTTPResponseHandlerSimulator : public HTTPResponseHandler { +public: + struct { + size_t BodyBytesTotal = 0; + size_t HeaderLinesTotal = 0; + unsigned ResponseCode = 0; + } State; + + Expected handleHeaderLine(StringRef HeaderLine) { + State.HeaderLinesTotal++; + return HeaderLine.size(); + }; + + Expected handleBodyChunk(StringRef BodyChunk) { + State.BodyBytesTotal += BodyChunk.size(); + return BodyChunk.size(); + }; + + void handleResponseCode(unsigned Code) { State.ResponseCode = Code; }; +}; + +TEST(HTTPClientTests, simulatedHTTPResponseHandlerTest) { + HTTPResponseHandlerSimulator Handler; + EXPECT_EQ(Handler.State.HeaderLinesTotal, 0u); + EXPECT_EQ(Handler.State.BodyBytesTotal, 0u); + EXPECT_EQ(Handler.State.ResponseCode, 0u); + Handler.handleResponseCode(200); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Content-Length: 0"), + HasValue(17)); + EXPECT_THAT_EXPECTED(Handler.handleHeaderLine("Another-Header: foo"), + HasValue(19)); + EXPECT_THAT_EXPECTED(Handler.handleBodyChunk("1234567"), HasValue(7)); + EXPECT_EQ(Handler.State.HeaderLinesTotal, 2u); + EXPECT_EQ(Handler.State.BodyBytesTotal, 7u); + EXPECT_EQ(Handler.State.ResponseCode, 200u); +}