diff --git a/CMakeLists.txt b/CMakeLists.txt index 30208bcd5..4475298d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake") set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # generate compile_commands.json to be used by other tools (e.g. vs code) else() # Check required c++ standard of parent project if(CMAKE_CXX_STANDARD) diff --git a/cpr/curlholder.cpp b/cpr/curlholder.cpp index 48a6b1e39..3e80ad17e 100644 --- a/cpr/curlholder.cpp +++ b/cpr/curlholder.cpp @@ -1,9 +1,9 @@ #include "cpr/curlholder.h" +#include "cpr/secure_string.h" #include #include #include #include -#include "cpr/secure_string.h" namespace cpr { CurlHolder::CurlHolder() { diff --git a/cpr/session.cpp b/cpr/session.cpp index 34050f376..27a7d2b6d 100644 --- a/cpr/session.cpp +++ b/cpr/session.cpp @@ -268,6 +268,26 @@ void Session::SetLimitRate(const LimitRate& limit_rate) { curl_easy_setopt(curl_->handle, CURLOPT_MAX_SEND_SPEED_LARGE, limit_rate.uprate); } +const Content& Session::GetContent() const { + return content_; +} + +void Session::RemoveContent() { + // inverse function to prepareBodyPayloadOrMultipart() + if (std::holds_alternative(content_) || std::holds_alternative(content_)) { + // set default values, so curl does not send a body in subsequent requests + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, -1); + curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, nullptr); + } else if (std::holds_alternative(content_)) { + if (curl_->multipart) { + // remove multipart data + curl_mime_free(curl_->multipart); + curl_->multipart = nullptr; + } + } + content_ = std::monostate{}; +} + void Session::SetReadCallback(const ReadCallback& read) { cbs_->readcb_ = read; curl_easy_setopt(curl_->handle, CURLOPT_INFILESIZE_LARGE, read.size); @@ -930,7 +950,7 @@ const std::optional Session::intercept() { } void Session::prepareBodyPayloadOrMultipart() const { - // Either a body, multipart or a payload is allowed. + // Either a body, multipart or a payload is allowed. Inverse function to RemoveContent() if (std::holds_alternative(content_)) { const std::string payload = std::get(content_).GetContent(*curl_); diff --git a/include/cpr/session.h b/include/cpr/session.h index db61e2109..93b034838 100644 --- a/include/cpr/session.h +++ b/include/cpr/session.h @@ -46,6 +46,7 @@ namespace cpr { using AsyncResponse = AsyncWrapper; +using Content = std::variant; class Interceptor; class MultiPerform; @@ -112,6 +113,17 @@ class Session : public std::enable_shared_from_this { void SetAcceptEncoding(AcceptEncoding&& accept_encoding); void SetLimitRate(const LimitRate& limit_rate); + /** + * Returns a reference to the content sent in previous request. + **/ + [[nodiscard]] const Content& GetContent() const; + + /** + * Removes the content sent in previous request from internal state, so it will not be sent with the next request. + * Call this before doing a request that is specified not to send a body, e.g. GET. + **/ + void RemoveContent(); + // For cancellable requests void SetCancellationParam(std::shared_ptr param); @@ -237,7 +249,7 @@ class Session : public std::enable_shared_from_this { bool chunkedTransferEncoding_{false}; - std::variant content_{std::monostate{}}; + Content content_{std::monostate{}}; std::shared_ptr curl_; Url url_; Parameters parameters_; diff --git a/test/httpServer.cpp b/test/httpServer.cpp index 249ce76db..cbe8ed901 100644 --- a/test/httpServer.cpp +++ b/test/httpServer.cpp @@ -272,6 +272,18 @@ void HttpServer::OnRequestBasicJson(mg_connection* conn, mg_http_message* /*msg* } void HttpServer::OnRequestHeaderReflect(mg_connection* conn, mg_http_message* msg) { + if (std::string_view{msg->method.ptr, msg->method.len} == "GET") { + if (msg->body.len > 0) { + std::string errorMessage{"Bad Request: GET shouldn't contain a body."}; + SendError(conn, 400, errorMessage); + return; + } else if (msg->chunk.len > 0) { + std::string errorMessage{"Bad Request: GET shouldn't contain a body."}; + SendError(conn, 400, errorMessage); + return; + } + } + std::string response = "Header reflect " + std::string{msg->method.ptr, msg->method.len}; std::string headers; bool hasContentTypeHeader = false; @@ -852,6 +864,7 @@ void HttpServer::OnRequestGetDownloadFileLength(mg_connection* conn, mg_http_mes void HttpServer::OnRequest(mg_connection* conn, mg_http_message* msg) { std::string uri = std::string(msg->uri.ptr, msg->uri.len); + if (uri == "/") { OnRequestRoot(conn, msg); } else if (uri == "/hello.html") { diff --git a/test/prepare_tests.cpp b/test/prepare_tests.cpp index 722b2815b..51d45196d 100644 --- a/test/prepare_tests.cpp +++ b/test/prepare_tests.cpp @@ -68,6 +68,7 @@ TEST(PrepareTests, MultipleDeleteHeadPutGetPostTest) { Session session; for (size_t i = 0; i < 3; ++i) { { + session.RemoveContent(); session.SetUrl(url); session.PrepareDelete(); CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); @@ -91,6 +92,7 @@ TEST(PrepareTests, MultipleDeleteHeadPutGetPostTest) { EXPECT_EQ(ErrorCode::OK, response.error.code); } { + session.RemoveContent(); session.SetUrl(url); session.PrepareGet(); CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); @@ -118,6 +120,7 @@ TEST(PrepareTests, MultipleDeleteHeadPutGetPostTest) { EXPECT_EQ(ErrorCode::OK, response.error.code); } { + session.RemoveContent(); session.SetUrl(url); session.PrepareHead(); CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); diff --git a/test/session_tests.cpp b/test/session_tests.cpp index 28f663f30..589c49500 100644 --- a/test/session_tests.cpp +++ b/test/session_tests.cpp @@ -1,9 +1,9 @@ #include +#include #include #include #include -#include #include #include @@ -1005,9 +1005,34 @@ TEST(DifferentMethodTests, MultipleDeleteHeadPutGetPostTest) { Url url{server->GetBaseUrl() + "/header_reflect.html"}; Url urlPost{server->GetBaseUrl() + "/post_reflect.html"}; Url urlPut{server->GetBaseUrl() + "/put.html"}; + Url urlMultipartPost{server->GetBaseUrl() + "/post_file_upload.html"}; Session session; for (size_t i = 0; i < 10; ++i) { { + session.RemoveContent(); + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.RemoveContent(); + session.SetUrl(urlMultipartPost); + std::string fileContentsBinary{"this is a binary payload"}; + std::string fileExtension = ".myfile"; + session.SetMultipart(cpr::Multipart{{"files", cpr::Buffer{fileContentsBinary.begin(), fileContentsBinary.end(), "myfile.jpg"}}, {"file_types", "[\"" + fileExtension + "\"]"}}); + Response response = session.Post(); + std::string expected_text{"{\n \"files\": \"myfile.jpg=this is a binary payload\",\n \"file_types\": \"[\".myfile\"]\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(urlMultipartPost, response.url); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.RemoveContent(); session.SetUrl(url); Response response = session.Delete(); std::string expected_text{"Header reflect DELETE"}; @@ -1027,6 +1052,7 @@ TEST(DifferentMethodTests, MultipleDeleteHeadPutGetPostTest) { EXPECT_EQ(ErrorCode::OK, response.error.code); } { + session.RemoveContent(); session.SetUrl(url); Response response = session.Get(); std::string expected_text{"Header reflect GET"}; @@ -1050,6 +1076,7 @@ TEST(DifferentMethodTests, MultipleDeleteHeadPutGetPostTest) { EXPECT_EQ(ErrorCode::OK, response.error.code); } { + session.RemoveContent(); session.SetUrl(url); Response response = session.Head(); std::string expected_text{"Header reflect HEAD"};