Libcurl: image is damaged when using fstream::write instead of fwrite - c++

I'm trying to download image file from a url. I followed the example using fwrite and it succeeded. Now I'm trying to use fstream::write to save the data (ios::binary), but the data is damaged. Here is my code:
#include"stdafx.h"
#include<fstream>
#include<iostream>
#include <curl/curl.h>
#include <string.h>
using namespace std;
size_t write_data(void *ptr, size_t size, size_t nmemb, char* out) {
//void *ptr, size_t size, size_t nmemb, File* fp
fstream file;
if (file.is_open()){
file.close();
file.clear();
}
file.open(out, ios::out | ios::binary);
if (file.is_open()){
cout << "open successfully\n" << endl;
file.write((char*)ptr, nmemb*size); // Does it correct?
};
// fwrite(ptr,size,nmemb,fp);
file.close();
file.clear();
cout <<"\n sizeof(ptr): " << sizeof(ptr) //size of ptr[0]?
<<"\n sizeof(char): " << sizeof(char)
<<"\n size: " << size
<<"\n nmemb: " << nmemb<< endl;
return size*nmemb;
}
I'm confused about the parameters in write_data.
According to CURLOPT_WRITEFUNCTION
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
"ptr points to the delivered data, and the size of that data is size multiplied with nmemb."
......so what are the meanings of size and nmemb?
When tried to download the data from a website,I printed first 3 parameters. It seems that char*ptr is the memory address that data stored (as 'char a[]'?), and size is element's size, nmemb is number of the elements. So the data size = size * nmemb. Am I correct?
The output is confusing too:
open successfully
sizeof(ptr):4
sizeof(char):1
size:1
nmemb:2715
open successfully
sizeof(ptr):4
sizeof(char):1
size:1
nmemb:4865
download successfully
When download the same url, nmemb and files' open times often change.
I'm also confused about 'sizeof(ptr)', it returns '4'(size of int?). How can I use the 'sizeof' to get the size of data memory, so that I can proof that data size is 'size * nmemb'?
CURLcode download(char* url,char* out){
CURL *curl = NULL;
//FILE *fp = NULL;
CURLcode res;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, out); //fp
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return res;
}
else
{
return CURLE_FAILED_INIT;
}
}
int main()
{
CURLcode res = download("http://XXXXXX.gif", "D:\\test.gif");
if (CURLE_OK == res)
cout << "download successfully.\n" << endl;
else
cout<<"cannot download.\n"<<endl;
return 0;
}
Thanks! :)

This callback can be multiple times per file. You should NOT create a new file stream every time the function is called - you should pass it in using the user data parameter. Otherwise you will just keep overwriting the data at the beginning of the file.
Here is an example implementation:
size_t write_data(char *ptr, size_t size, size_t nmemb, void *userdata)
{
std::ofstream *out = static_cast<std::ofstream *>(userdata);
size_t nbytes = size * nmemb;
out->write(ptr, nbytes);
return nbytes;
}
You also need to adjust the call to curl_easy_setopt with the parameter CURLOPT_WRITEDATA to actually pass your file stream. Make sure that the stream does not go out of scope while the functions run!
CURLcode download(char* url, char* out) {
CURL *curl = NULL;
std::ofstream output(out, ios::binary);
CURLcode res;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return res;
}
else
{
return CURLE_FAILED_INIT;
}
}

Related

Retrieving response text from curl in cpp

we need to receive response string from curl in CPP ,I tried following options but nothing worked.js uses xhr.responseText for this.I need to do in cpp.
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
const char* sp = static_cast<const char*>(contents);
readBuffer.append(sp, realsize);
return realsize;
}
CURLcode res;
char* http_error= new char[100];
readBuffer.clear();
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
CURLcode code(CURLE_FAILED_INIT);
code = curl_easy_perform(curl);
cout << "Curl response msg CURLOPT_WRITEDATA: "<<curl_easy_strerror(code)<< " respose :"<<readBuffer;
res=curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code) ;
cout << "Curl response msg: "<< curl_easy_strerror(res);
Change your WriteCallback function to this:
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
userp->append((char*) contents, size * nmemb);
return size * nmemb;
}
Remember, you are passing in &readBuffer as the CURL_WRITEDATA option. This shows up as the fourth parameter in the WriteCallback. Since the type of &readBuffer is std::string*, you can use that as the signature in your callback.
Additionally, since it's a std::string* and not a std::string, you have to access append through the pointer, hence the -> instead of .. After curl_easy_perform, readBuffer should hold the response from the curl request.
To get the response code, you can grab that after making the request:
long http_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

How to pass a pointer to object method?

I'm trying to make a CurlResponse object encapsulating libcurl response. My implementation of curl options WRITEFUNCTION and HEADERFUNCTION is mostly the same, the only difference being that in first case I'm calling response->appendBody and in the second - response->appendHeader. I would like to have one function and pass a pointer to appropriate method as a parameter, e.g. WRITEDATA would be response->appendBody, and I could call writer(data). However when I execute the below code, I get an error:
error: cannot pass objects of non-trivially-copyable type ‘struct std::_Bind<std::_Mem_fn<void (CurlResponse::*)(std::basic_string<char>)>(CurlResponse*, std::_Placeholder<1>)>’ through ‘...’
...
#include <iostream>
#include <string>
#include <functional>
#include <curl/curl.h>
using namespace std;
class CurlResponse {
public:
void appendBody(string data) {
cout << "Append body " << data << endl;
}
void appendHeader(string data) {
cout << "Append header " << data << endl;
}
};
//size_t WriteMemoryCallback(char * contents, size_t size, size_t nmemb, CurlResponse* response)
size_t WriteMemoryCallback(char * contents, size_t size, size_t nmemb, function<void(string)> writer)
{
size_t realsize = size * nmemb;
string data(contents, realsize);
// response->appendBody(data);
writer(data);
return realsize;
}
size_t WriteHeaderCallback(char * contents, size_t size, size_t nmemb, CurlResponse* response)
{
size_t realsize = size * nmemb;
string data(contents, realsize);
response->appendHeader(data);
return realsize;
}
int main() {
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if (! curl) return 1;
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost");
CurlResponse* response = new CurlResponse();
auto writeBody = std::bind(&CurlResponse::appendBody, response, placeholders::_1);
writeBody("Test writing to body");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, writeBody);
// curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, WriteHeaderCallback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, response);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
return 0;
}
How do I fix this? Does this mean I can use std::binded functions in the same function, but can't pass them anywhere?
The problem is that you are trying to pass a complex object through ..., as the compiler already tells you. The probably best solution is to first wrap the std::bind in a std::function object, to avoid having to repeat the complete type:
function<void(string)> writeBodyPass(writeBody);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeBodyPass);
You then have to fix the signature (and body) of your callback, as you are now receiving a pointer to the std::function object:
size_t WriteMemoryCallback(char * contents, size_t size, size_t nmemb, function<void(string)> *writer)
{
size_t realsize = size * nmemb;
string data(contents, realsize);
(*writer)(data);
return realsize;
}

C++ Finish Current task before moving on to the next

Im trying to find a way to download a file and have the program wait for the download to finish then move onto the next task which in this case would be checking to see if the file exist
#include <stdio.h>
#include <curl/curl.h>
#include <curl/easy.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <fstream>
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t written = fwrite(ptr, size, nmemb, stream);
return written;
}
using namespace std;
int main(void) {
CURL *curl;
FILE *fp;
CURLcode res;
char *url = "http://199.91.153.180/y1plsynnb11g/e9g9mcd3fi5txov/UHC.jar";
char outfilename[FILENAME_MAX] = "UHC.jar";
curl = curl_easy_init();
if (curl) {
fp = fopen(outfilename,"wb");
curl_easy_setopt(curl, CURLOPT_URL, "http://199.91.153.180/y1plsynnb11g/e9g9mcd3fi5txov/UHC.jar");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
fclose(fp);
}
ofstream launch;
launch.open("UHC.jar");
if (launch.is_open()){
cout<< "File Downloaded";
}
else {
cout << "Download Failed";
}
}
The HTTP request may have failed. Try adding error checking before /* always cleanup */
if (res)
cout << curl_easy_strerror(res) << endl;
I realized the return value of write_data is wrong. But it should have caused an error...
Anyway, please try:
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t written = fwrite(ptr, size, nmemb, stream);
return written * size;
}
I just realized what was wrong when it to check for the file existing using ofstream it was trying to write to it so it would make the file empty. Just changing it from ofsteam to fstream it works perfectly.

Why this is not working ? libcurl & c++

I am new to libcurl and am trying to get filelist from a server using ftp, going through the examples and some other posts here, I have come up with the following code. But when I run it, it returns error message :
Failed writing body (4294967295 != 129)
in the error string set by CURLOPT_ERRORBUFFER. The curl_easy_strerror( res ) returns:
Failed writing received data to disk/application
struct FtpFile
{
const char *filename;
FILE *stream;
};
static size_t fileWrite( void *buffer, size_t size, size_t nmemb, void *stream )
{
struct FtpFile *out=(struct FtpFile *)stream;
if(out && !out->stream)
{
out->stream=fopen(out->filename, "wb");
if(!out->stream)
{
cout << out->filename << " open failure [fileWrite] " << endl;
return -1;
}
}
size_t written = fwrite(buffer, size, nmemb, out->stream);
cout << written << endl;
if(written <= 0)
cout << "Nothing written : " << written;
return written;
}
void getFileList( const char* url, const char* fname )
{
CURL *curl;
CURLcode res;
FILE *ftpfile;
const char *errmsg;
ftpfile = fopen(fname, "wb");
if ( ftpfile == NULL )
{
cout << fname << " open failure [getFileList] " << endl ;
return;
}
curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fileWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ftpfile);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg);
curl_easy_setopt(curl, CURLOPT_USERNAME, "username");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "password");
curl_easy_setopt(curl, CURLOPT_FTPLISTONLY,TRUE);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
{
fprintf( stderr, "curl_easy_perform() failed: %s\nError Message: %s\n", curl_easy_strerror( res ), errmsg );
}
curl_easy_cleanup(curl);
}
fclose(ftpfile);
}
int main(int argc, char *argv[])
{
getFileList( "ftp://ftp.example.com/public/somefolder/", "file-list.txt" );
system("PAUSE");
return EXIT_SUCCESS;
}
4294967295 is the 32 bit unsigned version of -1, which probably is what your callback returned and thus libcurl considered it an error and stopped everything.
Where is ->filename assigned?
You pass in a FILE * to CURLOPT_WRITEDATA and yet your callback reads it as a struct FtpFile *...
First, your fileWrite function should return the number of bytes written. fwrite returns the number of objects written. So you have to return written * size;
Then, as Daniel Stenberg notes, you are passing a FILE * to fileWrite but using it as a struct FtpFile *. Try this one instead:
static size_t fileWrite( void *buffer, size_t size, size_t nmemb, void *stream )
{
size_t written = fwrite(buffer, size, nmemb, (FILE*)stream);
std::cout << written * size << std::endl;
if(written <= 0)
std::cout << "Nothing written : " << written;
return written * size;
}

Save cURL content result into a string in C++

int main(void)
{
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
_getch();
return 0;
}
string contents = "";
I would like to save the result of the curl html content in a string, how do I do this?
It's a silly question but unfortunately, I couldn't find anywhere in the cURL examples for C++
thanks!
You will have to use CURLOPT_WRITEFUNCTION to set a callback for writing. I can't test to compile this right now, but the function should look something close to;
static std::string readBuffer;
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
readBuffer.append(contents, realsize);
return realsize;
}
Then call it by doing;
readBuffer.clear();
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
// ...other curl options
res = curl_easy_perform(curl);
After the call, readBuffershould have your contents.
Edit: You can use CURLOPT_WRITEDATA to pass the buffer string instead of making it static. In this case I just made it static for simplicity. A good page to look (besides the linked example above) is here for an explanation of the options.
Edit2: As requested, here's a complete working example without the static string buffer;
#include <iostream>
#include <string>
#include <curl/curl.h>
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;
}
int main(void)
{
CURL *curl;
CURLcode res;
std::string readBuffer;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
std::cout << readBuffer << std::endl;
}
return 0;
}
On my blog I have published a simple wrapper class to perform this task.
Usage example:
#include "HTTPDownloader.hpp"
int main(int argc, char** argv) {
HTTPDownloader downloader;
std::string content = downloader.download("https://stackoverflow.com");
std::cout << content << std::endl;
}
Here's the header file:
/**
* HTTPDownloader.hpp
*
* A simple C++ wrapper for the libcurl easy API.
*
* Written by Uli Köhler (techoverflow.net)
* Published under CC0 1.0 Universal (public domain)
*/
#ifndef HTTPDOWNLOADER_HPP
#define HTTPDOWNLOADER_HPP
#include <string>
/**
* A non-threadsafe simple libcURL-easy based HTTP downloader
*/
class HTTPDownloader {
public:
HTTPDownloader();
~HTTPDownloader();
/**
* Download a file using HTTP GET and store in in a std::string
* #param url The URL to download
* #return The download result
*/
std::string download(const std::string& url);
private:
void* curl;
};
#endif /* HTTPDOWNLOADER_HPP */
Here's the source code:
/**
* HTTPDownloader.cpp
*
* A simple C++ wrapper for the libcurl easy API.
*
* Written by Uli Köhler (techoverflow.net)
* Published under CC0 1.0 Universal (public domain)
*/
#include "HTTPDownloader.hpp"
#include <curl/curl.h>
#include <curl/easy.h>
#include <curl/curlbuild.h>
#include <sstream>
#include <iostream>
using namespace std;
size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
string data((const char*) ptr, (size_t) size * nmemb);
*((stringstream*) stream) << data;
return size * nmemb;
}
HTTPDownloader::HTTPDownloader() {
curl = curl_easy_init();
}
HTTPDownloader::~HTTPDownloader() {
curl_easy_cleanup(curl);
}
string HTTPDownloader::download(const std::string& url) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
/* example.com is redirected, so we tell libcurl to follow redirection */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "deflate");
std::stringstream out;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out);
/* Perform the request, res will get the return code */
CURLcode res = curl_easy_perform(curl);
/* Check for errors */
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
return out.str();
}
Using the 'new' C++11 lambda functionality, this can be done in a few lines of code.
#ifndef WIN32 #define __stdcall "" #endif //For compatibility with both Linux and Windows
std::string resultBody { };
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resultBody);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast<size_t (__stdcall *)(char*, size_t, size_t, void*)>(
[](char* ptr, size_t size, size_t nmemb, void* resultBody){
*(static_cast<std::string*>(resultBody)) += std::string {ptr, size * nmemb};
return size * nmemb;
}
));
CURLcode curlResult = curl_easy_perform(curl);
std::cout << "RESULT BODY:\n" << resultBody << std::endl;
// Cleanup etc
Note the __stdcall cast is needed to comply to the C calling convention (cURL is a C library)
This might not work right away but should give you an idea:
#include <string>
#include <curl.h>
#include <stdio.h>
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t written;
written = fwrite(ptr, size, nmemb, stream);
return written;
}
int main() {
std::string tempname = "temp";
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
FILE *fp = fopen(tempname.c_str(),"wb");
curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
fp = fopen(tempname.c_str(),"rb");
fseek (fp , 0 , SEEK_END);
long lSize = ftell (fp);
rewind(fp);
char *buffer = new char[lSize+1];
fread (buffer, 1, lSize, fp);
buffer[lSize] = 0;
fclose(fp);
std::string content(buffer);
delete [] buffer;
}
}
Came out with useful, yet simple solution, which overloads std::ostream::operator<<
#include <ostream>
#include <curl/curl.h>
size_t curlCbToStream (
char * buffer,
size_t nitems,
size_t size,
std::ostream * sout
)
{
*sout << buffer;
return nitems * size;
}
std::ostream & operator<< (
std::ostream & sout,
CURL * request
)
{
::curl_easy_setopt(request, CURLOPT_WRITEDATA, & sout);
::curl_easy_setopt(request, CURLOPT_WRITEFUNCTION, curlCbToStream);
::curl_easy_perform(request);
return sout;
}
Possible drawback of taken approach could be:
typedef void CURL;
That means it covers all known pointer types.
Based on #JoachimIsaksson answer, here is a more verbose output that handles out-of-memory and has a limit for the maximum output from curl (as CURLOPT_MAXFILESIZE limits only based on header information and not on the actual size transferred ).
#DEFINE MAX_FILE_SIZE = 10485760 //10 MiB
size_t curl_to_string(void *ptr, size_t size, size_t count, void *stream)
{
if(((string*)stream)->size() + (size * count) > MAX_FILE_SIZE)
{
cerr<<endl<<"Could not allocate curl to string, output size (current_size:"<<((string*)stream)->size()<<"bytes + buffer:"<<(size * count) << "bytes) would exceed the MAX_FILE_SIZE ("<<MAX_FILE_SIZE<<"bytes)";
return 0;
}
int retry=0;
while(true)
{
try{
((string*)stream)->append((char*)ptr, 0, size*count);
break;// successful
}catch (const std::bad_alloc&) {
retry++;
if(retry>100)
{
cerr<<endl<<"Could not allocate curl to string, probably not enough memory, aborting after : "<<retry<<" tries at 10s apart";
return 0;
}
cerr<<endl<<"Could not allocate curl to string, probably not enough memory, sleeping 10s, try:"<<retry;
sleep(10);
}
}
return size*count;
}
I use Joachim Isaksson's answer with a modern C++ adaptation of CURLOPT_WRITEFUNCTION.
No nagging by the compiler for C-style casts.
static auto WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
static_cast<string*>(userdata)->append(ptr, size * nmemb);
return size * nmemb;
}