I am writing code to upload file on file server along with two other string variables as part of HTTP post request.
Idea to use multi interface here is to upload multiple file in future.
Libcurl version being used here is: 7.44
Here is my program:
#include <iostream>
#include <string>
#include <curl/curl.h>
const auto TimeoutInMS = 1000;
const auto FileDescriptorZero = 0;
bool HTTPPostSuccessful(long httpResponseCode)
{
bool httpRequestSuccessful = false;
if (httpResponseCode == 200)
{
httpRequestSuccessful = true;
}
return httpRequestSuccessful;
}
size_t WriteCallback(void * buffer, size_t size, size_t count, void * userp)
{
size_t numBytes = size * count;
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), numBytes);
return numBytes;
}
void HTTPPost(const std::string& value1, const std::string& value2, const std::string& filePath)
{
struct curl_slist *pHTTPRequestHeaders = nullptr;
struct curl_httppost* pFormpost = nullptr;
struct curl_httppost* pLastptr = nullptr;
uint16_t httpResponseCode = 0;
int stillSendingFile = 0;
CURL* pCurlEasyHandle = curl_easy_init();
CURLM *pCurlMultiHandle = curl_multi_init();
std::string responseData{};
if (pCurlEasyHandle && pCurlMultiHandle)
{
pHTTPRequestHeaders = curl_slist_append(pHTTPRequestHeaders, "Content-Type: multipart/form-data");
curl_easy_setopt(pCurlEasyHandle, CURLOPT_HTTPHEADER, pHTTPRequestHeaders);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_URL, "https://http_.org/logs/readers");
curl_easy_setopt(pCurlEasyHandle, CURLOPT_VERBOSE, 1L);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "fileName",
CURLFORM_FILE, filePath.c_str(),
CURLFORM_CONTENTTYPE, "text/csv",
CURLFORM_END);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "Value1",
CURLFORM_COPYCONTENTS, value1.c_str(),
CURLFORM_END);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "Value2",
CURLFORM_COPYCONTENTS, value2.c_str(),
CURLFORM_END);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_HTTPPOST, pFormpost);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_WRITEDATA, &responseData);
curl_multi_add_handle(pCurlMultiHandle, pCurlEasyHandle);
do
{
curl_multi_perform(pCurlMultiHandle, &stillSendingFile);
if (stillSendingFile)
{
curl_multi_wait(pCurlMultiHandle, nullptr, FileDescriptorZero, TimeoutInMS, nullptr);
}
}
while(stillSendingFile);
CURLcode res = curl_easy_getinfo(pCurlEasyHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode);
if (HTTPPostSuccessful(httpResponseCode) && res == CURLE_OK)
{
std::cout << "File sent Successfully, HTTP response code: " << httpResponseCode << ", ResponseData: "<< responseData<< std::endl;
}
else
{
std::cerr << "Error during request: " << curl_easy_strerror(res) << ", Failure HTTP response code: " << httpResponseCode << std::endl;
}
if (pCurlMultiHandle && pCurlEasyHandle)
{
std::cout << "Clean up for curl_multi_remove_handle " << std::endl;
curl_multi_remove_handle(pCurlMultiHandle, pCurlEasyHandle);
}
if (pCurlEasyHandle)
{
std::cout << "Clean up for pCurlEasyHandle " << std::endl;
curl_easy_cleanup(pCurlEasyHandle);
pCurlEasyHandle = nullptr;
}
if (pCurlMultiHandle)
{
std::cout << "Clean up for pCurlMultiHandle " << std::endl;
curl_multi_cleanup(pCurlMultiHandle);
pCurlMultiHandle = nullptr;
}
if (pHTTPRequestHeaders)
{
std::cout << "Clean up pHTTPRequestHeaders " << std::endl;
curl_slist_free_all(pHTTPRequestHeaders);
pHTTPRequestHeaders = nullptr;
}
if (pFormpost)
{
std::cout << "Clean up pFormpost " << std::endl;
curl_formfree(pFormpost);
pFormpost = nullptr;
pLastptr = nullptr;
}
}
}
bool UploadFile(const std::string& value1, const std::string& value2, const std::string& filePath)
{
curl_global_init(CURL_GLOBAL_ALL);
HTTPPost(value1, value2, filePath);
curl_global_cleanup();
return true;
}
int main ()
{
UploadFile("1", "1", "/tmp/UploadFIle/testDoc.txt");
}
above code works fine in my machine, when I commit it on Jenkins build server, I get following error:
7: HTTPClient Initialization is successful.
7: * Could not resolve host: http_.org
7: * Closing connection 0
7: Error during request: No error, Failure HTTP response code: 0
7: Clean up for curl_multi_remove_handle
7: Clean up for pCurlEasyHandle
7: Clean up for pCurlMultiHandle
7: Clean up pHTTPRequestHeaders
7: Clean up pFormpost
7/30 Test #7: **** ....................................***Exception: SegFault 0.29 sec
In Few cases also seeing following error:
8: HTTPClient Initialization is successful.
8: * Could not resolve host: http_.org
8: * Closing connection 0
8: Error during request: No error, Failure HTTP response code: 0
8: Clean up for curl_multi_remove_handle
8: Clean up for pCurlEasyHandle
8: Clean up for pCurlMultiHandle
8: Clean up pHTTPRequestHeaders
8: Clean up pFormpost
8: ==11267== Invalid read of size 8
8: ==11267== at 0x4E48018: curl_formfree (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.4.0)
Can somebody please inform me what is wrong here, new to Curl.
The first issue resulting in UB is in WriteCallback
:
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), 0, numBytes);
You have chosen the overloaded member function
basic_string& append( const basic_string& str,
size_type pos, size_type count )
that creates std::string
from a not null-terminated data in the buffer
.
You should use the other overloaded member function
basic_string& append( const CharT* s, size_type count )
therefore the correct call is
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), numBytes);
The second issue is uint16_t httpResponseCode
, whereas CURLINFO_RESPONSE_CODE
requires a pointer to a long value, you pass &httpResponseCode
, a pointer to a short value, this is yet another UB. Particularly it corrupts data in the local variables, writes zeros in 2 or 6 bytes outside the httpResponseCode
storage, probably in pFormpost
bytes. It should be
long httpResponseCode = 0;