My Question is closed so I have to update this.
1- My purpose is to send my compressed file google cloud storage URL.
2- To do that I have generated a postman request. I have stored my file to my google cloud storage by using the postman tool and the tool has generated the following code.
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_URL, "my URL");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type:
application/octet-stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl,CURLOPT_POSTFIELDS,"<file contents here>");
res = curl_easy_perform(curl);
}
curl_easy_cleanup(curl);
3- Then I have copied the code above into my c++ project to send my compressed file to the URL.
4- To create CURLOPT_POSTFIELDS content I did implement the following code;
std::ifstream ifs;
ifs.open ("./compressed.gz", std::ios::binary |
std::ios::ate);
PRINT("Size ->", ifs.tellg());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &ifs);
And when I compile and run my code, the request returns me the 200 success response.
But when I checked the google storage dashboard, it just contains 6 bytes of data. Actually, the size of my ifstream data is 1090.
So my problem is that why my request uploads all bytes of the compressed file to cloud storage? Whats wrong in my code ?
How to read compressed files c++
Compressed files are generally binary formats, they are not null terminated text. You cannot use strlen to get their length because that requires a null terminated text as input.
You can use any UnformattedInputFunction to read binary data. Don't forget to open any stream in binary mode.
I need to put the compressed file in a char pointer to put it to the server by using libcurl.
You don't need to read the file in order to do that. You can let libcurl take care of reading the file by passing a FILE* to CURLOPT_READDATA.
This way it won't be necessary to store the entire file in memory. Reading the entire file could be a problem if the file is very large.
You can do something like that when you want to read binary data:
std::ifstream file("./compressed.gz", std::ios::binary);
if (!file.is_open())
ERROR();
std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(file), {});
Then you will have it in the vector buffer. Access the size via buffer.size() and to get raw data use buffer.data() Note that since buffer is an std::vector if it goes outside of scope it will be destructed so the data will be deleted.
Related
Using the CURL at the command line:
curl -d #%1 -X GET https://blah-blah
This is in a Windows batch file and I pass the name of the file on the command line. Using this, I can issue a service call sending in a file with a whole lot of input parameters and I receive a substantial output.
For the life of me, when I try pro \grammatically, I can't make it work. It must be possible, since it can be done on the command. However, when I set
curl_easy_setopt(m_Curl, CURLOPT_HTTPGET, 1);
I can't upload a file, even if I set the callback
If I use:
curl_easy_setopt(m_Curl, CURLOPT_UPLOAD, 1L);
the call becomes 'PUT', even though I try and force the header to be a GET. I follow the documentation and can see that this is the documented behavior. However, what is the pathway for avoiding this default?
Any guidance would be most appreciated.
Thanks,
Stan
AFAIK it's not strictly forbidden but you shouldn't send a body with a GET request. On the other side a server should be able to handle a GET request with body but the response shouldn't be dependent of the content of the body.
If you really need to send a request containing a body with GET you can change the value of the method with CURLOPT_CUSTOMREQUEST. This won't change the behavior of curl. This snippet will upload data with GET:
CURL* curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_PORT, port);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
const auto* file = fopen(filename.c_str(), "r");
curl_easy_setopt(curl, CURLOPT_READDATA, file);
curl_easy_setopt(
curl, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(filesize));
curl_easy_perform(curl);
}
You can also use the CURLOPT_POSTFIELDS command, to paste the body as a string. Afterwards you change the request to GET like in #Thomas Sablik s answer.
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
// Set target URL
curl_easy_setopt(curl, CURLOPT_URL, requestURL.c_str());
// Set HTTP version
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_3);
// Set POST fields
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestBody.c_str());
// Set request to get
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
// Set response target
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
// Perform CURL request
CURLcode res = curl_easy_perform(curl);
I'm working in C++ with libcurl writing a program to interact with some APIs and I'm stuck when it comes to adding authentication info in the headers. I am new to libcurl and APIs with a basic knowledge of C++. Authentication requires an API key and a nonce hashed with HMAC_SHA256, each of which is then placed in the headers. A very simple JSON message is then sent. I've tried searching through this site but most examples seem to be in javascript or command line, and I don't see any relevant answers in them.
When I send my POST message to the server, I get a response 402 - Invalid ApiKey. My API key is 100% correct so I suspect it's something to do with the formatting or the way I've included it in the header. The site is BlinkTrade and their documentation is here, which gives some info about the header requirements.
Code snippet below:
char* message="{\"MsgType\": \"U2\",\"BalanceReqID\": 1}";
curl_easy_setopt(curl, CURLOPT_URL, "https://api.blinktrade.com/tapi/v1/message");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, pFile2);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message);
struct curl_slist *header = NULL;
header = curl_slist_append(header, "APIKey:0000000000000000000000000000000000000000");
header = curl_slist_append(header, "Nonce:1");
header = curl_slist_append(header, "Signature:1");
header = curl_slist_append(header, "Content-Type:application/json");
transfer = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
cout << transfer << endl;
transfer = curl_easy_perform(curl);
cout << transfer << endl;
And I get the return code 0 for curl_easy_setopt and curl_easy_perform. I've swapped the actual characters from the API key for a load of 0's, but otherwise everything is the same in terms of formatting etc. I've not actually used a hashed signature, I'll do that after I can sort out this error. I've tried adding a space after the colon and enclosing string and/or value in double quotes but I get the same response. What am I doing wrong that means my headers aren't actually recognised by the server?
Solved: the Blinktrade server returns "Invalid APIKey" not when your API key is incorrect, but when the signature is incorrect. A rather annoying mislabelling.
I am writing an simple file downloader with a help of libcurl. Here's the code for downloading the file from HTTP server:
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
std::wstring result; //result with polish letters (ą, ę etc.)
CURL *curl;
CURLcode res;
std::string readBuffer;
curl = curl_easy_init();
ERROR_HANDLE(curl, L"CURL could not been inited.", MOD_INTERNET);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(curl, CURLOPT_USERPWD, (login + ":" + password).c_str()); //e.g.: "login:password"
curl_easy_setopt(curl, CURLOPT_POST, true);
//curl_easy_setopt(curl, CURLOPT_ENCODING, "UTF-8"); //does not change anything
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
result = C::toWString(readBuffer);
return res == 0; //0 = OK
It works fine when the file I want to download is encoded as ANSI (according to e.g. Notepad++). But when I try to download the UTF-8 file (UTF-8 without BOM), I get an error with some characters (e.g. polish letters) due to encoding problem.
For example, I run the code for two files with the same text ("to jest teść to") and saved it to std::wstring. The result is from ANSI file and result2 (problematic) from UTF-8 version:
Both files opened on server with e.g. Notepad++ displays the right text.
So, how can I get the UTF-8 file content with libcurl and save it to std::wstring with the proper encoding (so the debugger of Visual Studio will show it as to jest teść to)?
This is not a libcurl issue. You are storing the raw data in a std::string and then converting that to a std::wstring after the download is finished. You have to look at the charset reported in the HTTP response and decode the data to std::wstring accordingly. C::toWString() has no concept of charsets, so you should use something else, like ICONV or ICU. Or, if you know the data is always UTF-8, do the conversion manually (UTF conversions are easy to code by hand), or use C++11's built in UTF conversions using the std::wstring_convert class.
libcurl won't convert or translate the contents for you. It will deliver the exact bytes to your application that the server sent out.
You can use HTTP Accept headers etc to affect what the server responds, but then you need to check the received charset and convert accordingly by yourself if you're not satisfied with what you get.
I'm having trouble getting an image from a URL using curl. It works if I pass the URL in as the constructor of an ImageMagick Image object. But using curl I'm not having much luck and I need to use curl.
Right now I'm doing...
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlCallback);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);
And then
size_t curlCallback(char* buf, size_t size, size_t nmemb, void* up)
{
ofstream out;
out.open("/home/name/Desktop/img.png");
out.write(buf, nmemb * size);
return size * nmemb;
}
It does seem to get the start of a PNG, but not the whole thing. It only returns 251 bytes (header info or something maybe??). An image viewer will open it as a png and know its resolution, but the image itself is blank. If I print the buffer to console, I see ?PNG and then the binary data symbol.
I know its not a problem with the remote host because if I use ImageMagick:
Image image = Image(url);
Then I get the image in its entirety and can save it and it's just fine.
The function set with CURLOPT_WRITEFUNCTION (curlCallback in your case) can be called multiple times during the download (see the docs).
Using CURLOPT_WRITEDATA passing in a FILE* might be easier.
I am transferring a binary file (.exe) with FTP using libcurl, and saving it to a local file. The problem is that after the file is transferred, it is altered and is no longer a valid Win32 application, and doesn't run. Here's how I'm doing it:
CURL *curl;
curl = curl_easy_init();
FILE* f = fopen("C:\\blah.exe", "w");
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://ftp.mysite.com");
curl_easy_setopt(curl, CURLOPT_USERPWD, "blah:blah");
curl_easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &f);
} else {
fclose(f);
return CURL_EASY_INIT_FAIL;
}
fclose(f);
The file is written but is bigger than it is on the FTP server. Like I said, trying to run it results in the "%1 is not a valid Win32 application" error. Did I forget to set an option or something?
You forgot the binary flag.
This is the correct code:
FILE* f = fopen("C:\\blah.exe", "wb");
The reason is that you transfer as ASCII and not as binary. So your end of lines might get broken. Of there are CRs in the binary they might turn into CR LF or the other way around. Tune CURL to make a binary transfer.