diff --git a/README.md b/README.md index 3ec3cfc9..93271acc 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,25 @@ RestClient::Response r = RestClient::patch("http://url.com/patch", "application/ RestClient::Response r = RestClient::del("http://url.com/delete") RestClient::Response r = RestClient::head("http://url.com") RestClient::Response r = RestClient::options("http://url.com") + +// Post Form Upload +/* Filling information about the form in a RestClient::FormData object */ +RestClient::FormData uploadInfo; +/* "submitted" is the name of the "file" input and "TestPostForm.txt" +is the location of the file to submit. + +*/ +uploadInfo.addFormFile("submitted", "TestPostForm.txt"); +/* In some rare cases, some fields related to the form can be filled with +addFormContent(), the 1st argument is the name of the input element and +the 2nd argument is the value assigned to it. + + +*/ +uploadInfo.addFormContent("filename", "myfile.cpp"); +uploadInfo.addFormContent("submit", "send"); +/* Performing a post form upload with the information provided above */ +RestClient::Response res = RestClient::post("http://posttestserver.com/post.php?dir=restclientcpptests", uploadInfo); ``` The response is of type [RestClient::Response][restclient_response] and has @@ -295,6 +314,23 @@ conn->SetHeaders(headers); auto resp = conn->get("/images/json"); ``` +## HTTP Proxy Tunneling Support + +An HTTP Proxy can be set to use for the upcoming request. +To specify a port number, append :[port] to the end of the host name. If not specified, `libcurl` will default to using port 1080 for proxies. The proxy string may be prefixed with `http://` or `https://`. If no HTTP(S) scheme is specified, the address provided to `libcurl` will be prefixed with `http://` to specify an HTTP proxy. A proxy host string can embedded user + password. +The operation will be tunneled through the proxy as curl option `CURLOPT_HTTPPROXYTUNNEL` is enabled by default. +A numerical IPv6 address must be written within [brackets]. + +```cpp +// set CURLOPT_PROXY +conn->SetProxy("https://37.187.100.23:3128"); +/* or you can set it without the protocol scheme and +http:// will be prefixed by default */ +conn->SetProxy("37.187.100.23:3128"); +/* the following request will be tunneled through the proxy */ +RestClient::Response res = conn->get("/get"); +``` + ## Dependencies - [libcurl][] diff --git a/include/restclient-cpp/connection.h b/include/restclient-cpp/connection.h index c39c022a..30089915 100644 --- a/include/restclient-cpp/connection.h +++ b/include/restclient-cpp/connection.h @@ -224,6 +224,8 @@ class Connection { RestClient::Response get(const std::string& uri); RestClient::Response post(const std::string& uri, const std::string& data); + RestClient::Response post(const std::string& uri, + const FormData& data); RestClient::Response put(const std::string& uri, const std::string& data); RestClient::Response patch(const std::string& uri, diff --git a/include/restclient-cpp/helpers.h b/include/restclient-cpp/helpers.h index 79d161bd..c26a077d 100644 --- a/include/restclient-cpp/helpers.h +++ b/include/restclient-cpp/helpers.h @@ -9,6 +9,8 @@ #ifndef INCLUDE_RESTCLIENT_CPP_HELPERS_H_ #define INCLUDE_RESTCLIENT_CPP_HELPERS_H_ +#include + #include #include #include diff --git a/include/restclient-cpp/restclient.h b/include/restclient-cpp/restclient.h index 601e7f2a..8622e202 100644 --- a/include/restclient-cpp/restclient.h +++ b/include/restclient-cpp/restclient.h @@ -13,6 +13,7 @@ #include #include +#include "restclient-cpp/helpers.h" #include "restclient-cpp/version.h" /** @@ -40,6 +41,26 @@ typedef struct { HeaderFields headers; } Response; +/** @class FormData + * @brief This class represents the form information to send on + * POST Form requests + */ +class FormData { + struct curl_httppost* formPtr; + struct curl_httppost* lastFormPtr; + public: + FormData(); + ~FormData(); + /* Fill in the file upload field */ + void addFormFile(const std::string& fieldName, + const std::string& fieldValue); + /* Fill in the filename or the submit field */ + void addFormContent(const std::string& fieldName, + const std::string& fieldValue); + /* Get Form pointer */ + struct curl_httppost* getFormPtr() const { return formPtr; } +}; + // init and disable functions int init(); void disable(); @@ -53,6 +74,8 @@ Response get(const std::string& url); Response post(const std::string& url, const std::string& content_type, const std::string& data); +Response post(const std::string& url, + const FormData& data); Response put(const std::string& url, const std::string& content_type, const std::string& data); diff --git a/source/connection.cc b/source/connection.cc index 54118633..39c24ab1 100644 --- a/source/connection.cc +++ b/source/connection.cc @@ -94,6 +94,8 @@ RestClient::Connection::GetInfo() { ret.uriProxy = this->uriProxy; ret.unixSocketPath = this->unixSocketPath; + ret.uriProxy = this->uriProxy; + return ret; } @@ -298,12 +300,16 @@ RestClient::Connection::SetKeyPassword(const std::string& keyPassword) { */ void RestClient::Connection::SetProxy(const std::string& uriProxy) { + if (uriProxy.empty()) { + return; + } + std::string uriProxyUpper = uriProxy; // check if the provided address is prefixed with "http" std::transform(uriProxyUpper.begin(), uriProxyUpper.end(), uriProxyUpper.begin(), ::toupper); - if ((uriProxy.length() > 0) && (uriProxyUpper.compare(0, 4, "HTTP") != 0)) { + if (uriProxyUpper.compare(0, 4, "HTTP") != 0) { this->uriProxy = "http://" + uriProxy; } else { this->uriProxy = uriProxy; @@ -483,6 +489,32 @@ RestClient::Connection::performCurlRequest(const std::string& uri, this->keyPassword.c_str()); } + // set web proxy address + if (!this->uriProxy.empty()) { + curl_easy_setopt(getCurlHandle(), CURLOPT_PROXY, + uriProxy.c_str()); + curl_easy_setopt(getCurlHandle(), CURLOPT_HTTPPROXYTUNNEL, + 1L); + } + // set key password + if (!this->keyPassword.empty()) { + curl_easy_setopt(getCurlHandle(), CURLOPT_KEYPASSWD, + this->keyPassword.c_str()); + } + + // set web proxy address + if (!this->uriProxy.empty()) { + curl_easy_setopt(getCurlHandle(), CURLOPT_PROXY, + uriProxy.c_str()); + curl_easy_setopt(getCurlHandle(), CURLOPT_HTTPPROXYTUNNEL, + 1L); + } + // set key password + if (!this->keyPassword.empty()) { + curl_easy_setopt(getCurlHandle(), CURLOPT_KEYPASSWD, + this->keyPassword.c_str()); + } + // set web proxy address if (!this->uriProxy.empty()) { curl_easy_setopt(getCurlHandle(), CURLOPT_PROXY, @@ -580,6 +612,26 @@ RestClient::Connection::post(const std::string& url, return this->performCurlRequest(url); } +/** + * @brief HTTP POST Form method + * + * @param url to query + * @param data form info + * + * @return response struct + */ +RestClient::Response +RestClient::Connection::post(const std::string& url, + const FormData& data) { + /** Now specify we want to POST data */ + curl_easy_setopt(getCurlHandle(), CURLOPT_POST, 1L); + /* stating that Expect: 100-continue is not wanted */ + AppendHeader("Expect", ""); + /** set post form */ + curl_easy_setopt(getCurlHandle(), CURLOPT_HTTPPOST, data.getFormPtr()); + + return this->performCurlRequest(url); +} /** * @brief HTTP PUT method * @@ -620,7 +672,7 @@ RestClient::Connection::put(const std::string& url, */ RestClient::Response RestClient::Connection::patch(const std::string& url, - const std::string& data) { + const std::string& data) { /** initialize upload object */ RestClient::Helpers::UploadObject up_obj; up_obj.data = data.c_str(); diff --git a/source/helpers.cc b/source/helpers.cc index aa51bf58..eb62cac5 100644 --- a/source/helpers.cc +++ b/source/helpers.cc @@ -36,6 +36,7 @@ size_t RestClient::Helpers::write_callback(void *data, size_t size, * @param size of data * @param nmemb memblock * @param userdata pointer to user data object to save headr data + * * @return size * nmemb; */ size_t RestClient::Helpers::header_callback(void *data, size_t size, diff --git a/source/restclient.cc b/source/restclient.cc index ed0fe861..1cc638d0 100644 --- a/source/restclient.cc +++ b/source/restclient.cc @@ -49,16 +49,10 @@ void RestClient::disable() { * @return response struct */ RestClient::Response RestClient::get(const std::string& url) { -#if __cplusplus >= 201402L - auto conn = std::make_unique(""); - return conn->get(url); -#else RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - ret = conn->get(url); - delete conn; + RestClient::Connection conn(""); + ret = conn.get(url); return ret; -#endif } /** @@ -73,18 +67,27 @@ RestClient::Response RestClient::get(const std::string& url) { RestClient::Response RestClient::post(const std::string& url, const std::string& ctype, const std::string& data) { -#if __cplusplus >= 201402L - auto conn = std::make_unique(""); - conn->AppendHeader("Content-Type", ctype); - return conn->post(url, data); -#else RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - conn->AppendHeader("Content-Type", ctype); - ret = conn->post(url, data); - delete conn; + RestClient::Connection conn(""); + conn.AppendHeader("Content-Type", ctype); + ret = conn.post(url, data); + return ret; +} + +/** + * @brief HTTP POST Form method + * + * @param url to query + * @param data post form information + * + * @return response struct + */ +RestClient::Response RestClient::post(const std::string& url, + const FormData& data) { + RestClient::Response ret; + RestClient::Connection conn(""); + ret = conn.post(url, data); return ret; -#endif } /** @@ -99,18 +102,11 @@ RestClient::Response RestClient::post(const std::string& url, RestClient::Response RestClient::put(const std::string& url, const std::string& ctype, const std::string& data) { -#if __cplusplus >= 201402L - auto conn = std::make_unique(""); - conn->AppendHeader("Content-Type", ctype); - return conn->put(url, data); -#else RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - conn->AppendHeader("Content-Type", ctype); - ret = conn->put(url, data); - delete conn; + RestClient::Connection conn(""); + conn.AppendHeader("Content-Type", ctype); + ret = conn.put(url, data); return ret; -#endif } /** @@ -123,13 +119,12 @@ RestClient::Response RestClient::put(const std::string& url, * @return response struct */ RestClient::Response RestClient::patch(const std::string& url, - const std::string& ctype, - const std::string& data) { + const std::string& ctype, + const std::string& data) { RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - conn->AppendHeader("Content-Type", ctype); - ret = conn->patch(url, data); - delete conn; + RestClient::Connection conn(""); + conn.AppendHeader("Content-Type", ctype); + ret = conn.patch(url, data); return ret; } @@ -141,16 +136,10 @@ RestClient::Response RestClient::patch(const std::string& url, * @return response struct */ RestClient::Response RestClient::del(const std::string& url) { -#if __cplusplus >= 201402L - auto conn = std::make_unique(""); - return conn->del(url); -#else RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - ret = conn->del(url); - delete conn; + RestClient::Connection conn(""); + ret = conn.del(url); return ret; -#endif } /** @@ -161,16 +150,10 @@ RestClient::Response RestClient::del(const std::string& url) { * @return response struct */ RestClient::Response RestClient::head(const std::string& url) { -#if __cplusplus >= 201402L - auto conn = std::make_unique(""); - return conn->head(url); -#else RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - ret = conn->head(url); - delete conn; + RestClient::Connection conn(""); + ret = conn.head(url); return ret; -#endif } /** @@ -182,8 +165,55 @@ RestClient::Response RestClient::head(const std::string& url) { */ RestClient::Response RestClient::options(const std::string& url) { RestClient::Response ret; - RestClient::Connection *conn = new RestClient::Connection(""); - ret = conn->options(url); - delete conn; + RestClient::Connection conn(""); + ret = conn.options(url); return ret; } + +/** + * @brief FormData constructor + */ +RestClient::FormData::FormData() + : formPtr(NULL), lastFormPtr(NULL) { +} + +/** + * @brief FormData destructor + */ +RestClient::FormData::~FormData() { + // cleanup the formpost chain + if (this->formPtr) { + curl_formfree(this->formPtr); + this->formPtr = NULL; + this->lastFormPtr = NULL; + } +} + +/** + * @brief set the name and the value of the HTML "file" form's input + * + * @param fieldName name of the "file" input + * @param fieldValue path to the file to upload + */ +void RestClient::FormData::addFormFile(const std::string& fieldName, + const std::string& fieldValue) { + curl_formadd(&this->formPtr, &this->lastFormPtr, + CURLFORM_COPYNAME, fieldName.c_str(), + CURLFORM_FILE, fieldValue.c_str(), + CURLFORM_END); +} + +/** + * @brief set the name and the value of an HTML form's input + * (other than "file" like "text", "hidden" or "submit") + * + * @param fieldName name of the input element + * @param fieldValue value to be assigned to the input element + */ +void RestClient::FormData::addFormContent(const std::string& fieldName, + const std::string& fieldValue) { + curl_formadd(&this->formPtr, &this->lastFormPtr, + CURLFORM_COPYNAME, fieldName.c_str(), + CURLFORM_COPYCONTENTS, fieldValue.c_str(), + CURLFORM_END); +} diff --git a/test/test_connection.cc b/test/test_connection.cc index d41bed7b..e54e5955 100644 --- a/test/test_connection.cc +++ b/test/test_connection.cc @@ -286,7 +286,7 @@ TEST_F(ConnectionTest, TestSetProgress) TEST_F(ConnectionTestRemote, TestProxy) { - conn->SetProxy(RestClient::TestProxyUrl); + conn->SetProxy(RestClient::TestProxy); RestClient::Response res = conn->get("/get"); EXPECT_EQ(200, res.code); } @@ -379,3 +379,4 @@ TEST_F(ConnectionTest, TestSetWriteFunction) EXPECT_EQ(200, res.code); EXPECT_EQ(ret, lineReceived.size() + lines); } + diff --git a/test/test_restclient.cc b/test/test_restclient.cc index cf64b6b2..f4fbfad7 100644 --- a/test/test_restclient.cc +++ b/test/test_restclient.cc @@ -1,7 +1,10 @@ #include "restclient-cpp/restclient.h" #include #include +#include +#include #include +#include #include "tests.h" @@ -120,6 +123,36 @@ TEST_F(RestClientTest, TestRestClientPOSTBody) EXPECT_EQ(RestClient::TestUrl+"/post", root.get("url", "no url set").asString()); EXPECT_EQ("restclient-cpp/" RESTCLIENT_VERSION, root["headers"].get("User-Agent", "no url set").asString()); } +TEST_F(RestClientTest, TestRestClientPostForm) +{ + // generating a file name with a timestamp + std::ostringstream fileName; + time_t rawtime; + tm * timeinfo; + time(&rawtime); + timeinfo = localtime( &rawtime ); + + fileName << "TestPostForm_" << (timeinfo->tm_year)+1900 << "_" << timeinfo->tm_mon+1 + << "_" << timeinfo->tm_mday << "-" << timeinfo->tm_hour + << "_"<< timeinfo->tm_min << "_" << timeinfo->tm_sec << ".txt"; + + // creating a dummy file to upload via a post form request + std::ofstream ofDummyFile(fileName.str().c_str()); + ASSERT_TRUE(static_cast(ofDummyFile)); + ofDummyFile << "Dummy file for the unit test 'TestRestClientPostForm' of the restclient-cpp Project."; + ASSERT_TRUE(static_cast(ofDummyFile)); + ofDummyFile.close(); + + // uploading the dummy file + RestClient::FormData UploadInfo; + UploadInfo.addFormFile("submitted", fileName.str()); + UploadInfo.addFormContent("filename", fileName.str()); + RestClient::Response res = RestClient::post("http://ptsv2.com/t/restclientcpptests/post", UploadInfo); + EXPECT_EQ(200, res.code); + + // remove dummy file + remove(fileName.str().c_str()); +} // check for failure TEST_F(RestClientTest, TestRestClientPOSTFailureCode) diff --git a/test/tests.cpp b/test/tests.cpp index e94ecccb..8b513908 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -9,6 +9,7 @@ namespace RestClient std::string TestNonExistantUrl; std::string TestServer; std::string TestUrl; + std::string TestProxy; std::string TestProxyUrl; }; @@ -19,8 +20,9 @@ int main(int argc, char **argv) RestClient::TestNonExistantUrl = "http://nonexistent"; RestClient::TestServer = "127.0.0.1:8998"; RestClient::TestUrl = "http://" + RestClient::TestServer; - RestClient::TestProxyUrl = "http://127.0.0.1:3128"; + RestClient::TestProxy = "127.0.0.1:3128"; + RestClient::TestProxyUrl = "http://" + RestClient::TestProxy; - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/test/tests.h b/test/tests.h index 484cdf61..92113980 100644 --- a/test/tests.h +++ b/test/tests.h @@ -8,6 +8,7 @@ namespace RestClient extern std::string TestNonExistantUrl; extern std::string TestServer; extern std::string TestUrl; + extern std::string TestProxy; extern std::string TestProxyUrl; };