Skip to content

Commit

Permalink
Merge pull request #748 from jmhersc/curlopt_localport_and_range
Browse files Browse the repository at this point in the history
Support for CURLOPT_LOCALPORT and CURLOPT_LOCALPORTRANGE
  • Loading branch information
COM8 committed May 27, 2022
2 parents aae967b + 2b6be00 commit e17dec0
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 0 deletions.
14 changes: 14 additions & 0 deletions cpr/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class Session::Impl {
void SetVerbose(const Verbose& verbose);
void SetSslOptions(const SslOptions& options);
void SetInterface(const Interface& iface);
void SetLocalPort(const LocalPort& local_port);
void SetLocalPortRange(const LocalPortRange& local_port_range);
void SetHttpVersion(const HttpVersion& version);
void SetRange(const Range& range);
void SetMultiRange(const MultiRange& multi_range);
Expand Down Expand Up @@ -283,6 +285,14 @@ void Session::Impl::SetInterface(const Interface& iface) {
}
}

void Session::Impl::SetLocalPort(const LocalPort& local_port) {
curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORT, local_port);
}

void Session::Impl::SetLocalPortRange(const LocalPortRange& local_port_range) {
curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORTRANGE, local_port_range);
}

// Only supported with libcurl >= 7.61.0.
// As an alternative use SetHeader and add the token manually.
#if LIBCURL_VERSION_NUM >= 0x073D00
Expand Down Expand Up @@ -906,6 +916,8 @@ void Session::SetUnixSocket(const UnixSocket& unix_socket) { pimpl_->SetUnixSock
void Session::SetSslOptions(const SslOptions& options) { pimpl_->SetSslOptions(options); }
void Session::SetVerbose(const Verbose& verbose) { pimpl_->SetVerbose(verbose); }
void Session::SetInterface(const Interface& iface) { pimpl_->SetInterface(iface); }
void Session::SetLocalPort(const LocalPort& local_port) { pimpl_->SetLocalPort(local_port); }
void Session::SetLocalPortRange(const LocalPortRange& local_port_range) { pimpl_->SetLocalPortRange(local_port_range); }
void Session::SetHttpVersion(const HttpVersion& version) { pimpl_->SetHttpVersion(version); }
void Session::SetRange(const Range& range) { pimpl_->SetRange(range); }
void Session::SetMultiRange(const MultiRange& multi_range) { pimpl_->SetMultiRange(multi_range); }
Expand Down Expand Up @@ -947,6 +959,8 @@ void Session::SetOption(const Verbose& verbose) { pimpl_->SetVerbose(verbose); }
void Session::SetOption(const UnixSocket& unix_socket) { pimpl_->SetUnixSocket(unix_socket); }
void Session::SetOption(const SslOptions& options) { pimpl_->SetSslOptions(options); }
void Session::SetOption(const Interface& iface) { pimpl_->SetInterface(iface); }
void Session::SetOption(const LocalPort& local_port) { pimpl_->SetLocalPort(local_port); }
void Session::SetOption(const LocalPortRange& local_port_range) { pimpl_->SetLocalPortRange(local_port_range); }
void Session::SetOption(const HttpVersion& version) { pimpl_->SetHttpVersion(version); }
void Session::SetOption(const Range& range) { pimpl_->SetRange(range); }
void Session::SetOption(const MultiRange& multi_range) { pimpl_->SetMultiRange(multi_range); }
Expand Down
2 changes: 2 additions & 0 deletions include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ target_sources(cpr PRIVATE
cpr/curlholder.h
cpr/error.h
cpr/limit_rate.h
cpr/local_port.h
cpr/local_port_range.h
cpr/multipart.h
cpr/parameters.h
cpr/payload.h
Expand Down
2 changes: 2 additions & 0 deletions include/cpr/cpr.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "cpr/interceptor.h"
#include "cpr/interface.h"
#include "cpr/limit_rate.h"
#include "cpr/local_port.h"
#include "cpr/local_port_range.h"
#include "cpr/low_speed.h"
#include "cpr/multipart.h"
#include "cpr/parameters.h"
Expand Down
23 changes: 23 additions & 0 deletions include/cpr/local_port.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef CPR_LOCAL_PORT_H
#define CPR_LOCAL_PORT_H

#include <cstdint>

namespace cpr {

class LocalPort {
public:
// NOLINTNEXTLINE(google-explicit-constructor)
LocalPort(const std::uint16_t p_localport) : localport_(p_localport) {}

operator std::uint16_t() const {
return localport_;
}

private:
std::uint16_t localport_ = 0;
};

} // namespace cpr

#endif
23 changes: 23 additions & 0 deletions include/cpr/local_port_range.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef CPR_LOCAL_PORT_RANGE_H
#define CPR_LOCAL_PORT_RANGE_H

#include <cstdint>

namespace cpr {

class LocalPortRange {
public:
// NOLINTNEXTLINE(google-explicit-constructor)
LocalPortRange(const std::uint16_t p_localportrange) : localportrange_(p_localportrange) {}

operator std::uint16_t() const {
return localportrange_;
}

private:
std::uint16_t localportrange_ = 0;
};

} // namespace cpr

#endif
6 changes: 6 additions & 0 deletions include/cpr/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "cpr/http_version.h"
#include "cpr/interface.h"
#include "cpr/limit_rate.h"
#include "cpr/local_port.h"
#include "cpr/local_port_range.h"
#include "cpr/low_speed.h"
#include "cpr/multipart.h"
#include "cpr/parameters.h"
Expand Down Expand Up @@ -79,6 +81,8 @@ class Session {
void SetDebugCallback(const DebugCallback& debug);
void SetVerbose(const Verbose& verbose);
void SetInterface(const Interface& iface);
void SetLocalPort(const LocalPort& local_port);
void SetLocalPortRange(const LocalPortRange& local_port_range);
void SetHttpVersion(const HttpVersion& version);
void SetRange(const Range& range);
void SetMultiRange(const MultiRange& multi_range);
Expand Down Expand Up @@ -122,6 +126,8 @@ class Session {
void SetOption(const UnixSocket& unix_socket);
void SetOption(const SslOptions& options);
void SetOption(const Interface& iface);
void SetOption(const LocalPort& local_port);
void SetOption(const LocalPortRange& local_port_range);
void SetOption(const HttpVersion& version);
void SetOption(const Range& range);
void SetOption(const MultiRange& multi_range);
Expand Down
13 changes: 13 additions & 0 deletions test/httpServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -820,9 +820,22 @@ void HttpServer::OnRequest(mg_connection* conn, http_message* msg) {
OnRequestPatchNotAllowed(conn, msg);
} else if (uri == "/download_gzip.html") {
OnRequestDownloadGzip(conn, msg);
} else if (uri == "/local_port.html") {
OnRequestLocalPort(conn, msg);
} else {
OnRequestNotFound(conn, msg);
}
}

void HttpServer::OnRequestLocalPort(mg_connection* conn, http_message* /*msg*/) {
// send source port number as response for checking SetLocalPort/SetLocalPortRange
std::string headers = "Content-Type: text/plain";
char portbuf[8];
mg_conn_addr_to_str(conn, portbuf, sizeof(portbuf),
MG_SOCK_STRINGIFY_PORT | MG_SOCK_STRINGIFY_REMOTE);
std::string response = portbuf;
mg_send_head(conn, 200, response.length(), headers.c_str());
mg_send(conn, response.c_str(), response.length());
}

} // namespace cpr
1 change: 1 addition & 0 deletions test/httpServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class HttpServer : public AbstractServer {
static void OnRequestPatch(mg_connection* conn, http_message* msg);
static void OnRequestPatchNotAllowed(mg_connection* conn, http_message* msg);
static void OnRequestDownloadGzip(mg_connection* conn, http_message* msg);
static void OnRequestLocalPort(mg_connection* conn, http_message* msg);

protected:
mg_connection* initServer(mg_mgr* mgr, MG_CB(mg_event_handler_t event_handler, void* user_data)) override;
Expand Down
64 changes: 64 additions & 0 deletions test/session_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,70 @@ TEST(CurlHolderManipulateTests, CustomOptionTest) {
}
}

TEST(LocalPortTests, SetLocalPortTest) {
Url url{server->GetBaseUrl() + "/local_port.html"};
Session session;
session.SetUrl(url);
std::uint16_t const local_port = 60250; // beware of HttpServer::GetPort when changing
std::uint16_t const local_port_range = 50;
session.SetLocalPort(local_port);
session.SetLocalPortRange(local_port_range);
// expected response: body contains port number in specified range
// NOTE: even when trying up to 50 ports there is the chance that all of them are occupied.
// It would be possible to also check here for ErrorCode::INTERNAL_ERROR but that somehow seems
// wrong as then this test would pass in case SetLocalPort does not work at all
// or in other words: we have to assume that at least one port in the specified range is free.
Response response = session.Get();
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
unsigned long port_from_response = std::strtoul(response.text.c_str(), nullptr, 10);
EXPECT_EQ(errno, 0);
EXPECT_GE(port_from_response, local_port);
EXPECT_LE(port_from_response, local_port + local_port_range);
}

TEST(LocalPortTests, SetOptionTest) {
Url url{server->GetBaseUrl() + "/local_port.html"};
Session session;
session.SetUrl(url);
std::uint16_t const local_port = 60550; // beware of HttpServer::GetPort when changing
std::uint16_t const local_port_range = 50;
session.SetOption(LocalPort(local_port));
session.SetOption(LocalPortRange(local_port_range));
// expected response: body contains port number in specified range
// NOTE: even when trying up to 50 ports there is the chance that all of them are occupied.
// It would be possible to also check here for ErrorCode::INTERNAL_ERROR but that somehow seems
// wrong as then this test would pass in case SetOption(LocalPort) does not work at all
// or in other words: we have to assume that at least one port in the specified range is free.
Response response = session.Get();
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
unsigned long port_from_response = std::strtoul(response.text.c_str(), nullptr, 10);
EXPECT_EQ(errno, 0);
EXPECT_GE(port_from_response, local_port);
EXPECT_LE(port_from_response, local_port + local_port_range);
}

TEST(LocalPortTests, SetLocalPortTestOccupied) {
Url url{server->GetBaseUrl() + "/local_port.html"};
Session session;
session.SetUrl(url);
session.SetLocalPort(server->GetPort());
// expected response: request cannot be made as port is already occupied
Response response = session.Get();
EXPECT_EQ(ErrorCode::INTERNAL_ERROR, response.error.code);
}

TEST(LocalPortTests, SetOptionTestOccupied) {
Url url{server->GetBaseUrl() + "/local_port.html"};
Session session;
session.SetUrl(url);
session.SetOption(LocalPort(server->GetPort()));
// expected response: request cannot be made as port is already occupied
Response response = session.Get();
EXPECT_EQ(ErrorCode::INTERNAL_ERROR, response.error.code);
}

TEST(BasicTests, ReserveResponseString) {
Url url{server->GetBaseUrl() + "/hello.html"};
Session session;
Expand Down

0 comments on commit e17dec0

Please sign in to comment.