Joining a variable inside a JSON formatted post request C++ - c++

So I have this put request which submits to a service running on localhost.
Doing it the way below works just fine, note I have replaced the actual acc name with ACC_NAME and password with ACC_PASSWORD.
curl_easy_setopt(curl_account_login, CURLOPT_POSTFIELDS, "{\r\n \"username\": \ACC_NAME\,\r\n \"password\": \"ACC_PASSWORD\,\r\n \"persistLogin\": false\r\n}\r\n");
However when I wanted to pass in a variable containing the acc_name and acc_password, it does not work, I get an error response from server.
The below request is using the variable joined inside the JSON string, which gives me the error response.
curl_easy_setopt(curl_account_login, CURLOPT_POSTFIELDS, "{\r\n \"username\": \""+acc_name+"\,\r\n \"password\": \""+acc_password+"\,\r\n \"persistLogin\": false\r\n}\r\n");
I can't figure out what I am doing wrong when I am joining the string variable into the request.
It works just fine by plain, if I write the account credentials directly into the request and not in a variable.
Regards

C-style string literals (like "something") are of type const char [] (this is a null-terminated character array), which decays into a const char* (a pointer). Thus, + (as in "something" + "another") is just adding two addresses, resulting in an invalid pointer value. You cannot concatenate C-style strings by simply using + that way.
Assuming you are using C++ (not C), as indicated by your question being tagged c++, I suggest C++ string objects instead, this will allow easy concatenation.
Also, there's a note in curl documentation about CURLOPT_POSTFIELDS, mentioning that the data pointed to is not copied, thus requiring you to make sure the pointer remains valid until the associated transfer finishes. Because of that, I would prefer using CURLOPT_COPYPOSTFIELDS instead.
To sum up, do something like this:
#include <string>
// ...
std::string postfields
{
std::string{ "{" }
+ R"("username":")" + ACC_NAME
+ R"(","password":")" + ACC_PASSWORD
+ R"(","persistLogin":false})"
};
curl_easy_setopt(curl_account_login, CURLOPT_COPYPOSTFIELDS, postfields.data());

Related

libcurl, how to use string as a password?

Following this example:
https://github.com/curl/curl/blob/master/docs/examples/smtp-tls.c
I managed to get it to work by setting the password as such:
curl_easy_setopt(curl, CURLOPT_PASSWORD, "password");
However, I want to set the password up as a string, i.e.:
string myPassword = "password";
curl_easy_setopt(curl, CURLOPT_PASSWORD, myPassword);
When I do this however, I get an error
curl_easy_perform() failed: Login denied
I double checked and have the program print the values of the original password and the string before it sends. They are exactly the same. I have two functions to test this on. They are almost entirely identical except that one uses a hardcoded password and the other uses the string. The hard coded one works fine, but the one that uses a string is having login issues. How could I fix this?
The CURLOPT_PASSWORD option expects a char*, not a std::string. You are passing the wrong type. The library is interpreting your data in the "wrong" way and getting nonsense.
This is not diagnosed because curl_easy_setopt uses varargs to take arbitrary parameters of varying types, depending upon the option being set. (Ideally your compiler would have rejected or at least warned about the program, but that's the cost of working with type-unsafe C libraries!)
You can pass the C-string version of your string using std::string::c_str(), like this:
curl_easy_setopt(curl, CURLOPT_PASSWORD, myPassword.c_str());
There exists a project named curlpp which purports to be a type-safe wrapper around libcurl, and this might be worth a look.
It fails because curl_easy_setopt() is expecting a C style string, (i.e. char *).
What you should do in this case is convert the std::string to a C style string using .c_str().
So you should pass password by doing:
std::string myPassword = "password";
curl_easy_setopt(curl, CURLOPT_PASSWORD, myPassword.c_str());

Escape url parameters for cURL

I have a url like that:
http://localhost:3000/get_agencies?zipcodecity=&zipcode=30048&city=kraków&
As you can see there city param is equal to kraków. When I pass such URL into curl I receive it somehow encoded in inappropriate way:
curl = curl_easy_init();
// Some code here
curl_easy_setopt(curl, CURLOPT_URL, url);
On the server side I get city=kraków. I tried to use curl_easy_escape(curl, url, strlen(url)); but it just encodes everything. So how can I parse only param values of a query string?
(sorry, either you significantly edited your original question, or i read it wrong the first time, let me try again)
well, i guess you can kindof repair it, guessing where the data name and value starts and ends based on the = and & characters. it's NOT foolproof, if & or ? is wrongly encoded, or if you encounter an unicode character using the equivalent bytes for their character (edit: this last part is fixable by switching to a unicode string search function), this won't be enough, but except for those 2 scenarios, something like this should work:
std::string patchInappropriatelyEncodedURL(CURL *curl, std::string url){
size_t pos=url.find("?");
size_t pos2;
if(pos==url.npos){
return url;
}
std::string ret=url.substr(0,pos+1);
std::string tmpstr;
char *escapedstr;
url=url.substr(pos+1,url.npos);
std::string type="=";
do{
pos=url.find("=");
pos2=url.find("&");
if(pos == url.npos && pos2 == url.npos){
break;
}
if(pos<pos2){
type="=";
}else{
type="&";
pos=pos2;
}
tmpstr=url.substr(0,pos);
url=url.substr(pos+1,url.npos);
escapedstr=curl_easy_escape(curl,tmpstr.c_str(),tmpstr.length());
ret.append(escapedstr);
ret.append(type);
curl_free(escapedstr);
}while(true);
escapedstr=curl_easy_escape(curl,url.c_str(),url.length());
ret.append(escapedstr);
curl_free(escapedstr);
return ret;
}
note that this function is based on guessing, and is not by any means foolproof. i suppose the guessing could improved with a dictionary for your target language or something, though.. but your time would probably be better spent on fixing the bug causing you to receive malformed urls in your program in the first place.
i deliberately omitted error checking because i'm lazy. curl_easy_escape can fail (out of memory), and when it does, it returns a nullptr. you should fix that before the code enters production, i'm too lazy.
you should put those curl_free's in a finally{} block, else you may encounter memory leaks if the string functions throw exceptions (like substr may throw bad_alloc exceptions), but again, i'm too lazy to fix it.
this is why we have curl_easy_escape.
char *escaped_string=curl_easy_escape(ch,"kraków",0);
(however, when the string is known at compile time, you could hardcode the encoded version instead of encoding it at runtime, in this case, the hardcoded version is krak%C3%B3w - your browser's javascript console can be used to figure that out, just write encodeURIComponent("kraków"); to see what the urlencoded version looks like)
gotchas:
when the 3rd paramater is 0, curl use strlen() to determine the size. this is safe when using utf8 text, but not safe with binary data. if you're encoding binary data, make sure to specify the length manually, as strlen() will stop once it finds a null byte. (other than that, curl_easy_escape, and urlencoded data is binary safe)
don't forget to curl_free(escaped_string); when you're done with it, else you'll end up with memory leaks.

C++ character encoding when converting from string to const char* for Ruby FFI interface

I am using an external C++ lib that does some HTTPS communication and provides the XML server response. On serverside the response is encoded via ISO-8859-15 and I get a std::string that represents that response out of the API. When I print it out / write it to a file it looks proper.
The std::string and an int error code have to be passed to my external caller. So I return both values inside a struct:
extern "C" {
struct FoobarResponse {
const char* responseText;
int returnCode;
};
}
Unfortunately I have to convert the std::string response into a const char* C-style string representation with help of std::c_str() before. Reason: My caller is a Ruby script making use of Ruby FFI to communicate with my C++ lib, and interlanguage type conversion here is Ruby::string -> C::const char*.
Interesting here: If I std::cout the converted string after I put it into the struct, it is still ok.
The problem: When handling the server response on Ruby side, it is broken. Instead of the original answer like:
<?xml version="1.0" encoding="ISO-8859-15"?>
<Foobar xmlns="http://www.foobar.com/2012/XMLSchema">
...
</Foobar>
I receive a string obviously containing non printable characters which is always broken at the beginning and at the end.
?O[
l version="1.0" encoding="ISO-8859-15"?>
<Foobar xmlns="http://www.foobar.com/2012/XMLSchema">
</Fo??
In fact the string contains linebreaks, carriage returns and tabs at least, maybe more.
I tried to :force_encoding the string on Ruby side as ASCII-8BIT, ISO-8859-15 and UTF-8, no change.
I tried to base64 encode on C++ side before putting the string into the struct and base64 decode on Ruby side using this code, no change.
I had countless attepts to convert the string using Iconv as well, no change.
I also tried to remove non printable characters from the string before putting it into the struct, but I failed on that.
I have no idea what is going on here and running out of options.
Can someone point me into the right direction?
Regards
Felix
The value returned by c_str() is destroyed as soon as the std::string goes out of scope.
If you intend to pass this value to your script you should allocate memory and copy the string into your newly allocated space. See this example: http://www.cplusplus.com/reference/string/string/c_str/
You should also ensure the ruby script will correctly release memory.
I think this is what is explained there: https://github.com/ffi/ffi/wiki/Examples.
Example with a struct passed to Ruby from C:
https://github.com/ffi/ffi/wiki/Examples#-structs

libCurl : curl_easy_setopt in one method and curl_easy_perform in another does not work

I have a code, where, in one local function I use curl_easy_setopt to set the proxy URL. And in another local function I call curl_easy_perform. But when te control moves from one function to another, the proxy url set using local variable contains junk characters and the DNS query returns an error. The libcurl help page says that when we do setopt the string values is copied by the curl library. But I feel the library just referes to that value whenever it needs it. It doesn't copy the string. So if local variable is used to set proxy url, it will contain junk by the time I call curl_easy_perform.
Following is the example code snippet.
void funcSetOpt
{
char ProxyUrl[] = "someproxy";
curl_easy_setopt(curlHandle, CURLOPT_PROXY, ProxyUrl);
}
void funcPerform
{
curl_easy_perform(curlHandle);
}
That would imply that you're using a fairly old libcurl version and the following section from the curl_easy_setopt man page might affect you:
Before version 7.17.0, strings were not copied. Instead the user was
forced keep them available until libcurl no longer needed them.

Curlpp, incomplete data from request

I am using Curlpp to send requests to various webservices to send and receive data.
So far this has worked fine since i have only used it for sending/receiving JSON data.
Now i have a situation where a webservice returns a zip file in binary form. This is where i encountered a problem where the data received is not complete.
I first had Curl set to write any data to a ostringstream by using the option WriteStream, but this proved not to be the correct approach since the data contained null characters, and thus the data stopped at the first null char.
After that, instead of using WriteStream i used WriteFunction with a callback function.
The problem in this case is that this function is always called 2 or 3 times, regardless of the amount of data.
This results in always having a few chunks of data that don't seem to be the first part of the file, although the data always contains PK as the first 2 characters, indicating a zip file.
I used several tools to verify that the data is entirely being sent to my application so this is not a problem of the webservice.
Here the code. Do note that the options like hostname, port, headers and postfields are set elsewhere.
string requestData;
size_t WriteStringCallback(char* ptr, size_t size, size_t nmemb)
{
requestData += ptr;
int totalSize= size*nmemb;
return totalSize;
}
const string CurlRequest::Perform()
{
curlpp::options::WriteFunction wf(WriteStringCallback);
this->request.setOpt( wf );
this->request.perform();
return requestData;
}
I hope anyone can help me out with this issue because i've run dry of any leads on how to fix this, also because curlpp is poorly documented(and even worse since the curlpp website disappeared).
The problem with the code is that the data is put into a std::string, despite having the data in binary (ZIP) format. I'd recommend to put the data into a stream (or a binary array).
You can also register a callback to retrieve the response headers and act in the WriteCallback according to the "Content-type".
curlpp::options::HeaderFunction to register a callback to retrieve response-headers.
std::string is not a problem, but the concatenation is:
requestData += ptr;
C string (ptr) is terminated with zero, if the input contains any zero bytes, the input will be truncated. You should wrap it into a string which knows the length of its data:
requestData += std::string(ptr, size*nmemb);