Index: llvm/include/llvm/Support/CURLClient.h =================================================================== --- /dev/null +++ llvm/include/llvm/Support/CURLClient.h @@ -0,0 +1,51 @@ +//===-- llvm/Support/CURLClient.h - CURL 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 declaration of the CurlHTTPRequest class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_CURL_CLIENT_H +#define LLVM_SUPPORT_CURL_CLIENT_H + +#include "llvm/Support/Error.h" +#include "llvm/Support/HTTPClient.h" +#include "llvm/Support/MemoryBuffer.h" +#include + +namespace llvm { + +class CurlHTTPRequest : public HTTPRequest { + template void curlSetOpt(CURLoption Option, T Parameter) { + curl_easy_setopt(this->Curl, Option, Parameter); + } + void curlCleanup(); + +protected: + CURL *Curl = nullptr; + virtual Error curlInit(); + +public: + Error ErrorState = Error::success(); + + size_t curlHandleHeaderLine(char *Contents, size_t Size, size_t NMemb, + void * /*Unused*/ Ptr); + size_t curlHandleBodyChunk(char *Contents, size_t Size, size_t NMemb, + void * /*Unused*/ Ptr); + + using HTTPRequest::HTTPRequest; + ~CurlHTTPRequest(); + HTTPRequestConfig Config; + Error setConfiguration(HTTPRequestConfig Config) override; + virtual Error performRequest() override; +}; + +} // end namespace llvm + +#endif // LLVM_SUPPORT_CURL_CLIENT_H Index: llvm/lib/Support/CMakeLists.txt =================================================================== --- llvm/lib/Support/CMakeLists.txt +++ llvm/lib/Support/CMakeLists.txt @@ -74,6 +74,11 @@ set(system_libs ${system_libs} ${Z3_LIBRARIES}) endif() +# Link LibCURL if the user wants it +if (LLVM_ENABLE_CURL) + set(system_libs ${system_libs} ${CURL_LIBRARIES}) +endif () + # Override the C runtime allocator on Windows and embed it into LLVM tools & libraries if(LLVM_INTEGRATED_CRT_ALLOC) if (CMAKE_BUILD_TYPE AND NOT ${LLVM_USE_CRT_${uppercase_CMAKE_BUILD_TYPE}} MATCHES "^(MT|MTd)$") @@ -134,6 +139,7 @@ ConvertUTF.cpp ConvertUTFWrapper.cpp CrashRecoveryContext.cpp + CURLClient.cpp DataExtractor.cpp Debug.cpp DebugCounter.cpp Index: llvm/lib/Support/CURLClient.cpp =================================================================== --- /dev/null +++ llvm/lib/Support/CURLClient.cpp @@ -0,0 +1,107 @@ +//===-- llvm/Support/CURLClient.cpp - CURL 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 CurlHTTPRequest class. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CURLClient.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 "CURLClient" + +size_t CurlHTTPRequest::curlHandleHeaderLine(char *Contents, size_t Size, + size_t NMemb, + void * /*Unused*/ Ptr) { + assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION " + "should always be 1."); + Expected SizeOrErr = + Handler->handleHeaderLine(StringRef(Contents, NMemb)); + + if (Error Err = SizeOrErr.takeError()) { + ErrorState = joinErrors(std::move(ErrorState), std::move(Err)); + return 0; + } + return *SizeOrErr; +} + +size_t CurlHTTPRequest::curlHandleBodyChunk(char *Contents, size_t Size, + size_t NMemb, + void * /*Unused*/ Ptr) { + Expected SizeOrErr = + Handler->handleBodyChunk(StringRef(Contents, Size * NMemb)); + if (Error Err = SizeOrErr.takeError()) { + ErrorState = joinErrors(std::move(ErrorState), std::move(Err)); + return 0; + } + return *SizeOrErr; +} + +Error CurlHTTPRequest::curlInit() { + if (!Curl) + Curl = curl_easy_init(); + if (!Curl) + return createStringError(errc::io_error, "Error initializing curl."); + return std::move(ErrorState); +} + +void CurlHTTPRequest::curlCleanup() { curl_easy_cleanup(Curl); } + +CurlHTTPRequest::~CurlHTTPRequest() { curlCleanup(); } + +static size_t curlHeaderLineMethodHook(char *Contents, size_t Size, + size_t NMemb, CurlHTTPRequest *Request) { + return Request->curlHandleHeaderLine(Contents, Size, NMemb, nullptr); +} + +static size_t curlBodyChunkMethodHook(char *Contents, size_t Size, size_t NMemb, + CurlHTTPRequest *Request) { + return Request->curlHandleBodyChunk(Contents, Size, NMemb, nullptr); +} + +Error CurlHTTPRequest::setConfiguration(HTTPRequestConfig Config) { + this->Config = Config; + if (Config.Method != HTTPMethod::GET) + return createStringError(errc::invalid_argument, + "Unsupported CURL request method."); + if (Error Err = curlInit()) + return joinErrors(std::move(ErrorState), std::move(Err)); + + curlSetOpt(CURLOPT_URL, Config.Url.c_str()); + curlSetOpt(CURLOPT_FOLLOWLOCATION, Config.FollowRedirects); + curlSetOpt(CURLOPT_WRITEFUNCTION, curlBodyChunkMethodHook); + curlSetOpt(CURLOPT_HEADERFUNCTION, curlHeaderLineMethodHook); + curlSetOpt(CURLOPT_WRITEDATA, this); + curlSetOpt(CURLOPT_HEADERDATA, this); + return std::move(ErrorState); +} + +Error CurlHTTPRequest::performRequest() { + if (!Curl) + return createStringError(errc::inappropriate_io_control_operation, + "CURL is uninitialized."); + CURLcode CurlRes = curl_easy_perform(Curl); + if (CurlRes != CURLE_OK) + return joinErrors(std::move(ErrorState), + createStringError(errc::io_error, + "curl_easy_perform() failed: %s\n", + curl_easy_strerror(CurlRes))); + + unsigned Code; + curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); + Handler->handleResponseCode(Code); + return std::move(ErrorState); +} Index: llvm/unittests/Support/HTTPClient.cpp =================================================================== --- llvm/unittests/Support/HTTPClient.cpp +++ llvm/unittests/Support/HTTPClient.cpp @@ -128,3 +128,126 @@ EXPECT_EQ(Handler.State.BodyBytesTotal, 7u); EXPECT_EQ(Handler.State.ResponseCode, 200u); } + +#ifdef LLVM_ENABLE_CURL +#include "llvm/Support/CURLClient.h" + +struct CurlHTTPRequestSimulatorState { + bool Initialized = false; + bool Performed = false; + bool CleanedUp = false; +}; + +class CurlHTTPRequestSimulator : public CurlHTTPRequest { + Error curlInit() override { + State.Initialized = true; + State.CleanedUp = false; + return CurlHTTPRequest::curlInit(); + } + +public: + CurlHTTPRequestSimulatorState &State; + + CurlHTTPRequestSimulator(HTTPResponseHandler *Handler, + CurlHTTPRequestSimulatorState &State) + : CurlHTTPRequest::CurlHTTPRequest(Handler), State(State) {} + + virtual Error performRequest() override { + if (!Curl) + return createStringError(errc::inappropriate_io_control_operation, + "CURL is uninitialized."); + State.Performed = true; + Handler->handleResponseCode(200); + if (Error Err = Handler->handleHeaderLine("Content-Length: 17").takeError()) + return joinErrors(std::move(ErrorState), std::move(Err)); + if (Error Err = Handler->handleBodyChunk("This is the body.").takeError()) + return joinErrors(std::move(ErrorState), std::move(Err)); + return std::move(ErrorState); + } + + ~CurlHTTPRequestSimulator() { + State.Initialized = false; + State.CleanedUp = true; + } +}; + +class CurlHTTPRequestBrokenConnectionSimulator + : public CurlHTTPRequestSimulator { +public: + Error performRequest() override { + if (!Curl) + return createStringError(errc::inappropriate_io_control_operation, + "CURL is uninitialized."); + State.Performed = true; + Handler->handleResponseCode(200); + if (Error Err = Handler->handleHeaderLine("Content-Length: 17").takeError()) + return joinErrors(std::move(ErrorState), std::move(Err)); + if (Error Err = Handler->handleBodyChunk("This is").takeError()) + return joinErrors(std::move(ErrorState), std::move(Err)); + return joinErrors( + std::move(ErrorState), + createStringError(errc::io_error, "Simulated network failure")); + } + using CurlHTTPRequestSimulator::CurlHTTPRequestSimulator; +}; + +TEST(CURLClientTests, curlRequestBrokenConnectionTest) { + HTTPResponseHandlerSimulator Handler; + CurlHTTPRequestSimulatorState State; + CurlHTTPRequestBrokenConnectionSimulator Request(&Handler, State); + + HTTPRequestConfig Config; + EXPECT_THAT_ERROR(Request.setConfiguration(Config), Succeeded()); + EXPECT_THAT_ERROR(Request.performRequest(), Failed()); +} + +TEST(CURLClientTests, curlRequestLifecycleTest) { + HTTPResponseHandlerSimulator Handler; + CurlHTTPRequestSimulatorState State; + { + CurlHTTPRequestSimulator Request(&Handler, State); + EXPECT_EQ(State.Initialized, false); + EXPECT_EQ(State.Performed, false); + EXPECT_EQ(State.CleanedUp, false); + + HTTPRequestConfig Config; + EXPECT_THAT_ERROR(Request.setConfiguration(Config), Succeeded()); + EXPECT_EQ(State.Initialized, true); + EXPECT_EQ(State.Performed, false); + EXPECT_EQ(State.CleanedUp, false); + EXPECT_EQ(Request.Config, Config); + + EXPECT_THAT_ERROR(Request.performRequest(), Succeeded()); + EXPECT_EQ(State.Initialized, true); + EXPECT_EQ(State.Performed, true); + EXPECT_EQ(State.CleanedUp, false); + } + // destructor of CurlHTTPRequest should invoke curlCleanup + EXPECT_EQ(State.Initialized, false); + EXPECT_EQ(State.Performed, true); + EXPECT_EQ(State.CleanedUp, true); + EXPECT_EQ(Handler.State.BodyBytesTotal, 17u); + EXPECT_EQ(Handler.State.HeaderLinesTotal, 1u); + EXPECT_EQ(Handler.State.ResponseCode, 200u); +} + +TEST(CURLClientTests, curlHTTPRequestMultipleInitTest) { + // multiple calls to curlInit should do nothing + HTTPResponseHandlerSimulator Handler; + CurlHTTPRequestSimulatorState State; + HTTPRequestConfig Config; + CurlHTTPRequestSimulator Request(&Handler, State); + EXPECT_EQ(State.Initialized, false); + EXPECT_EQ(State.Performed, false); + EXPECT_EQ(State.CleanedUp, false); + EXPECT_THAT_ERROR(Request.setConfiguration(Config), Succeeded()); + EXPECT_EQ(State.Initialized, true); + EXPECT_EQ(State.Performed, false); + EXPECT_EQ(State.CleanedUp, false); + EXPECT_THAT_ERROR(Request.setConfiguration(Config), Succeeded()); + EXPECT_EQ(State.Initialized, true); + EXPECT_EQ(State.Performed, false); + EXPECT_EQ(State.CleanedUp, false); +} + +#endif