Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ compile_commands.json

# macOS
.DS_Store

# CLion
.idea/
21 changes: 12 additions & 9 deletions cmake/clang-tidy.cmake
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Include this file if and only if you want to use clang-tidy linter.

if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy)
if (NOT ${CPR_LINTER_PATH} STREQUAL "")
get_filename_component(CLANG_TIDY_HINT_FILENAME ${CPR_LINTER_PATH} NAME)
get_filename_component(CLANG_TIDY_HINT_PATH ${CPR_LINTER_PATH} DIRECTORY)
endif ()

find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy ${CLANG_TIDY_HINT_FILENAME} HINTS ${CLANG_TIDY_HINT_PATH} REQUIRED)
mark_as_advanced(CLANG_TIDY_EXECUTABLE)

if (${CLANG_TIDY_EXECUTABLE})
message(FATAL_ERROR "Clang-tidy not found")
else()
message(STATUS "Enabling clang-tidy")
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE};-warnings-as-errors=*")
endif()
else()
message(STATUS "Enabling clang-tidy: ${CLANG_TIDY_EXECUTABLE}")
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE};-warnings-as-errors=*")
else ()
message(FATAL_ERROR "Clang-tidy is not supported when building for windows")
endif()
endif ()
18 changes: 13 additions & 5 deletions cmake/cppcheck.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck)
# Include this file if and only if you want to use cppcheck.

if(CMAKE_CXX_CPPCHECK)
list(APPEND CMAKE_CXX_CPPCHECK "--xml"
if (NOT ${CPR_CPPCHECK_PATH} STREQUAL "")
get_filename_component(CPPCHECK_HINT_FILENAME ${CPR_CPPCHECK_PATH} NAME)
get_filename_component(CPPCHECK_HINT_PATH ${CPR_CPPCHECK_PATH} DIRECTORY)
endif ()

find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck ${CPPCHECK_HINT_FILENAME} HINTS ${CPPCHECK_HINT_PATH} REQUIRED)
message(STATUS "Found cppcheck: ${CMAKE_CXX_CPPCHECK}")

list(APPEND CMAKE_CXX_CPPCHECK
"--xml"
"--error-exitcode=1"
"--enable=warning,style"
"--force"
Expand All @@ -11,5 +19,5 @@ if(CMAKE_CXX_CPPCHECK)
"--cppcheck-build-dir=${PROJECT_BINARY_DIR}"
"--suppress-xml=${PROJECT_SOURCE_DIR}/cppcheck-suppressions.xml"
"--output-file=${PROJECT_BINARY_DIR}/cppcheck.xml"
"--check-level=normal")
endif()
"--check-level=normal"
)
5 changes: 5 additions & 0 deletions cppcheck-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
<id>y2038-unsafe-call</id>
<fileName>*/include/cpr/low_speed.h</fileName>
</suppress>
<!-- No reason to check third party includes -->
<suppress>
<id>y2038-unsafe-call</id>
<fileName>*/curl/curl.h</fileName>
</suppress>
<suppress>
<id>normalCheckLevelMaxBranches</id>
</suppress>
Expand Down
22 changes: 21 additions & 1 deletion cpr/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
Expand All @@ -26,6 +28,7 @@
#include "cpr/auth.h"
#include "cpr/bearer.h"
#include "cpr/body.h"
#include "cpr/body_view.h"
#include "cpr/callback.h"
#include "cpr/connect_timeout.h"
#include "cpr/cookies.h"
Expand Down Expand Up @@ -289,6 +292,10 @@ void Session::RemoveContent() {
curl_mime_free(curl_->multipart);
curl_->multipart = nullptr;
}
} else if (std::holds_alternative<cpr::BodyView>(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_POSTFIELDS, nullptr);
}
content_ = std::monostate{};
}
Expand Down Expand Up @@ -488,6 +495,12 @@ void Session::SetBody(Body&& body) {
content_ = std::move(body);
}

// cppcheck-suppress passedByValue
void Session::SetBodyView(BodyView body) {
static_assert(std::is_trivially_copyable_v<BodyView>, "BodyView expected to be trivially copyable otherwise will need some std::move across codebase");
content_ = body;
}

void Session::SetLowSpeed(const LowSpeed& low_speed) {
curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_LIMIT, low_speed.limit);
curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_TIME, low_speed.time); // cppcheck-suppress y2038-unsafe-call
Expand Down Expand Up @@ -971,6 +984,11 @@ void Session::prepareBodyPayloadOrMultipart() const {
const std::string& body = std::get<cpr::Body>(content_).str();
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body.c_str());
} else if (std::holds_alternative<cpr::BodyView>(content_)) {
const std::string_view body = std::get<cpr::BodyView>(content_).str();
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.length()));
// NOLINTNEXTLINE (bugprone-suspicious-stringview-data-usage)
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, body.data());
} else if (std::holds_alternative<cpr::Multipart>(content_)) {
// Make sure, we have a empty multipart to start with:
if (curl_->multipart) {
Expand Down Expand Up @@ -1020,7 +1038,7 @@ void Session::prepareBodyPayloadOrMultipart() const {
}

[[nodiscard]] bool Session::hasBodyOrPayload() const {
return std::holds_alternative<cpr::Body>(content_) || std::holds_alternative<cpr::Payload>(content_);
return std::holds_alternative<cpr::Body>(content_) || std::holds_alternative<cpr::BodyView>(content_) || std::holds_alternative<cpr::Payload>(content_);
}

// clang-format off
Expand Down Expand Up @@ -1057,6 +1075,8 @@ void Session::SetOption(const Redirect& redirect) { SetRedirect(redirect); }
void Session::SetOption(const Cookies& cookies) { SetCookies(cookies); }
void Session::SetOption(const Body& body) { SetBody(body); }
void Session::SetOption(Body&& body) { SetBody(std::move(body)); }
// cppcheck-suppress passedByValue
void Session::SetOption(BodyView body) { SetBodyView(body); }
void Session::SetOption(const LowSpeed& low_speed) { SetLowSpeed(low_speed); }
void Session::SetOption(const VerifySsl& verify) { SetVerifySsl(verify); }
void Session::SetOption(const Verbose& verbose) { SetVerbose(verbose); }
Expand Down
1 change: 1 addition & 0 deletions include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target_sources(cpr PRIVATE
cpr/auth.h
cpr/bearer.h
cpr/body.h
cpr/body_view.h
cpr/buffer.h
cpr/cert_info.h
cpr/cookies.h
Expand Down
1 change: 0 additions & 1 deletion include/cpr/body.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include <fstream>
#include <initializer_list>
#include <string>
#include <vector>

#include "cpr/buffer.h"
#include "cpr/cprtypes.h"
Expand Down
36 changes: 36 additions & 0 deletions include/cpr/body_view.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef CPR_BODY_VIEW_H
#define CPR_BODY_VIEW_H

#include <string_view>

#include "cpr/buffer.h"

namespace cpr {

class BodyView final {
public:
BodyView() = default;
// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
BodyView(std::string_view body) : m_body(body) {}
// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
BodyView(const char* body) : m_body(body) {}
BodyView(const char* str, size_t len) : m_body(str, len) {}
// NOLINTNEXTLINE(google-explicit-constructor, cppcoreguidelines-pro-type-reinterpret-cast)
BodyView(const Buffer& buffer) : m_body(reinterpret_cast<const char*>(buffer.data), static_cast<size_t>(buffer.datalen)) {}

BodyView(const BodyView& other) = default;
BodyView(BodyView&& old) noexcept = default;
~BodyView() = default;

BodyView& operator=(BodyView&& old) noexcept = default;
BodyView& operator=(const BodyView& other) = default;

[[nodiscard]] std::string_view str() const { return m_body; }

private:
std::string_view m_body;
};

} // namespace cpr

#endif
5 changes: 4 additions & 1 deletion include/cpr/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "cpr/auth.h"
#include "cpr/bearer.h"
#include "cpr/body.h"
#include "cpr/body_view.h"
#include "cpr/callback.h"
#include "cpr/connect_timeout.h"
#include "cpr/cookies.h"
Expand Down Expand Up @@ -46,7 +47,7 @@
namespace cpr {

using AsyncResponse = AsyncWrapper<Response>;
using Content = std::variant<std::monostate, cpr::Payload, cpr::Body, cpr::Multipart>;
using Content = std::variant<std::monostate, cpr::Payload, cpr::Body, cpr::BodyView, cpr::Multipart>;

class Interceptor;
class MultiPerform;
Expand Down Expand Up @@ -90,6 +91,7 @@ class Session : public std::enable_shared_from_this<Session> {
void SetCookies(const Cookies& cookies);
void SetBody(Body&& body);
void SetBody(const Body& body);
void SetBodyView(BodyView body);
void SetLowSpeed(const LowSpeed& low_speed);
void SetVerifySsl(const VerifySsl& verify);
void SetUnixSocket(const UnixSocket& unix_socket);
Expand Down Expand Up @@ -154,6 +156,7 @@ class Session : public std::enable_shared_from_this<Session> {
void SetOption(const Cookies& cookies);
void SetOption(Body&& body);
void SetOption(const Body& body);
void SetOption(BodyView body);
void SetOption(const ReadCallback& read);
void SetOption(const HeaderCallback& header);
void SetOption(const WriteCallback& write);
Expand Down
14 changes: 14 additions & 0 deletions test/raw_body_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ TEST(BodyPostTests, StringMoveBodyTest) {
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(BodyPostTests, BodyViewTest) {
const Url url{server->GetBaseUrl() + "/url_post.html"};
Response response = cpr::Post(url, BodyView{"x=5"});
const std::string expected_text{
"{\n"
" \"x\": 5\n"
"}"};
EXPECT_EQ(expected_text, response.text);
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]);
EXPECT_EQ(201, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(server);
Expand Down
17 changes: 17 additions & 0 deletions test/session_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,23 @@ TEST(BodyTests, SetBodyValueTest) {
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(BodyTests, SetBodyViewTest) {
const Url url{server->GetBaseUrl() + "/url_post.html"};
Session session;
session.SetUrl(url);
session.SetBodyView(BodyView{"x=5"});
Response response = session.Post();
const std::string expected_text{
"{\n"
" \"x\": 5\n"
"}"};
EXPECT_EQ(expected_text, response.text);
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]);
EXPECT_EQ(201, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DigestTests, SetDigestTest) {
Url url{server->GetBaseUrl() + "/digest_auth.html"};
Session session;
Expand Down
Loading