EDIT: After running a few more commands, it seems that only lines starting with * are being shown. Any way to stop that?
I'm trying to use the libcurl library with my C++ program to recieve emails using the IMAP protocol. The issue is that the response from the CURL calls are only returning one line, and cutting off the rest of what is supposed to be returned. I know that the request itself is working because when I turn on verbose mode with curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);, it has the full result within the output.
For example, when I use curl_easy_setopt(curl, "FETCH 10000 (FLAGS BODY[HEADER.FIELDS (DATE FROM SUBJECT)])"); the response is:
* 10000 FETCH (FLAGS (\Seen) BODY[HEADER.FIELDS (DATE FROM SUBJECT)] {140}
When I turn on verbose mode, the console includes the full result:
< A003 OK [READ-WRITE] INBOX selected. (Success)
> A004 FETCH 10000 (FLAGS BODY[HEADER.FIELDS (DATE FROM SUBJECT)])
< * 10000 FETCH (FLAGS (\Seen) BODY[HEADER.FIELDS (DATE FROM SUBJECT)] {140}
< Date: Wed, 1 Apr 2020 01:59:45 -0500
< From: [REMOVED NAME] <[REMOVED EMAIL]>
< Subject: Testing subject here
<
< )
< A004 OK Success
I have tried multiple ways of outputting the data: to stdout, an external file, and in memory (then printed). I feel like the CURL result just isn't returning more than the first line for some reason. Here is the code if it helps (I'm using wxWidgets):
App.cpp - Ignore most of this, just calls the functions
#include "App.h"
#include "Frame.h"
#include "Config.h"
#include "IMAP.h"
wxIMPLEMENT_APP(App);
bool App::OnInit() {
curl_global_init(CURL_GLOBAL_SSL);
try {
Config config("settings.conf");
for(const auto& account : config["accounts"]) {
const char* url = account["incoming_server"].asCString();
const char* username = account["username"].asCString();
const char* password = account["password"].asCString();
IMAP connection(url, IMAP_DEFAULT_PORT);
if(connection.auth(username, password) == IMAP_SUCCESS) {
connection.select("INBOX");
auto folders = connection.getFolders();
}
}
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
exit(0);
}
curl_global_cleanup();
exit(1);
Frame *frame = new Frame("MailDuck", wxDefaultPosition, wxSize(100, 100));
frame->Show(true);
frame->Centre();
return true;
}
App.cpp
#include "IMAP.h"
IMAP::IMAP(const char *url, int port) {
this->url = url;
this->port = port;
}
IMAP_CODE IMAP::auth(const char *username, const char *password) {
this->username = username;
this->password = password;
if(execute().size == -1) return IMAP_FAILED;
else return IMAP_SUCCESS;
}
std::vector<Folder> IMAP::getFolders() {
std::vector<Folder> folders;
memory mem = execute("FETCH 10000 (FLAGS BODY[HEADER.FIELDS (DATE FROM SUBJECT)])");
std::cout << mem.response << std::endl;
free(mem.response);
return folders;
}
IMAP::memory IMAP::execute(const char *command) {
CURL *curl;
CURLcode res = CURLE_OK;
struct memory chunk { (char *) malloc(1), 0 };
curl = curl_easy_init();
std::string finalURL;
finalURL.append(this->url);
finalURL.append("/");
finalURL.append(this->mailbox);
if(curl) {
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
curl_easy_setopt(curl, CURLOPT_USERNAME, this->username);
curl_easy_setopt(curl, CURLOPT_PASSWORD, this->password);
curl_easy_setopt(curl, CURLOPT_URL, finalURL.c_str());
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, command);
res = curl_easy_perform(curl);
if(res != CURLE_OK) chunk.size = -1;
curl_easy_cleanup(curl);
} else chunk.size = -1;
if(chunk.size == -1) std::cerr << "ERROR >> Unable to call command: " << command << std::endl;
return chunk;
}
size_t IMAP::write_function(void *data, size_t size, size_t nmemb, void *userp) {
size_t real_size = size * nmemb;
auto *mem = (struct memory *) userp;
char *ptr = (char *) realloc(mem->response, mem->size + real_size + 1);
if(ptr == NULL) return 0;
mem->response = ptr;
memcpy(&(mem->response[mem->size]), data, real_size);
mem->size += real_size;
mem->response[mem->size] = 0;
return real_size;
}
void IMAP::select(const char *box) {
this->mailbox = box;
}
IMAP.h
#ifndef MAILDUCK_IMAP_H
#define MAILDUCK_IMAP_H
#include <map>
#include <vector>
#include <string>
#include <json/json.h>
#include <curl/curl.h>
#include <iostream>
#include <istream>
enum {
IMAP_DEFAULT_PORT = 993,
};
enum IMAP_CODE {
IMAP_SUCCESS = 1,
IMAP_FAILED = 0
};
class Folder : public std::vector<Folder> {
public:
std::string name;
};
class IMAP {
private:
const char *url, *username, *password, *mailbox = "";
int port;
struct memory { char *response; size_t size; };
memory execute(const char *command = nullptr);
static size_t write_function(void *data, size_t size, size_t nmemb, void *userp);
public:
IMAP(const char *url, int port);
IMAP_CODE auth(const char* username, const char* password);
std::vector<Folder> getFolders();
void select(const char *mailbox);
};
#endif
I've been stuck on this for a bit, so I appreciate any insight anyone could provide.
Figured it out! For all who get stuck like me:
Apparently the rest of the data is stuck in the header data, so add this:
struct memory chunk { (char *) malloc(1), 0 };
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&chunk);
And then my write function is exactly like the other one...
size_t IMAP::header_callback(char *data, size_t size, size_t nmemb, void *userp) {
size_t real_size = size * nmemb;
auto *mem = (struct memory *) userp;
char *ptr = (char *) realloc(mem->response, mem->size + real_size + 1);
if(ptr == NULL) return 0;
mem->response = ptr;
memcpy(&(mem->response[mem->size]), data, real_size);
mem->size += real_size;
mem->response[mem->size] = 0;
return real_size;
}
Now I'm getting the full response.
Related
Goal:
To modify the libcurl example for HTTP/2 multiplexing that can be found here to save a payload response as it arrives into some buffer, instead of writing it to a file like the aforementioned example currently does. The payload within the buffer would then be available for things such as printing, searching for strings etc.
Expected output:
Program should print out each received payload response to stdout whenever the callback function detects that one has been delivered.
Actual output:
Sometimes the program works as expected for a small number of transfers (see line of code int num_transfers = 3 in main() further below). If the number of transfers is increased to say 8 or 10, sometimes the program doesn't function properly and the program will still print the output to stdout, but in the default format that libcurl will do if no CURL_WRITEFUNCTION/CURL_WRITEDATA has been included in the code, possibly suggesting nothing is being received by the callback function? Also in this scenario, an incorrect number of responses will be printed.
In the main do...while loop within main(), I set chunk.memory and chunk.size equal to 0 after they had been printed out. Without doing this, every time a new response was received these would continue growing. I'm unsure if this was the correct approach, however.
Current attempt:
Using the libcurl example that can be found here, I have attempted to mimic the functionality of writing the output to a callback function as seen below (instead of writing each response payload to a file).
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* somewhat unix-specific */
#include <sys/time.h>
#include <unistd.h>
/* curl stuff */
#include <curl/curl.h>
#include <curl/mprintf.h>
#ifndef CURLPIPE_MULTIPLEX
#define CURLPIPE_MULTIPLEX 0
#endif
struct CURLMsg *msg;
struct transfer {
CURL *easy;
unsigned int num;
FILE *out;
};
struct MemoryStruct {
char *memory;
size_t size;
};
struct MemoryStruct chunk;
#define NUM_HANDLES 1000
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
char *ptr = (char*)realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
/* out of memory! */
std::cout << "not enough memory (realloc returned NULL)" << std::endl;
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
static void setup(struct transfer *t, int num)
{
CURL *hnd;
hnd = t->easy = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, (void *)&chunk);
/* set the same URL */
curl_easy_setopt(hnd, CURLOPT_URL, "https://someurl.xyz");
/* HTTP/2 please */
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
/* we use a self-signed test server, skip verification during debugging */
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
#if (CURLPIPE_MULTIPLEX > 0)
/* wait for pipe connection to confirm */
curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
#endif
}
int main() {
struct transfer trans[NUM_HANDLES];
CURLM *multi_handle;
int i;
int still_running = 0; /* keep number of running handles */
int num_transfers = 3;
chunk.memory = (char*)malloc(1);
chunk.size = 0;
/* init a multi stack */
multi_handle = curl_multi_init();
for(i = 0; i < num_transfers; i++) {
setup(&trans[i], i);
/* add the individual transfer */
curl_multi_add_handle(multi_handle, trans[i].easy);
}
curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
// Main loop
do {
CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
if(still_running) {
/* wait for activity, timeout or "nothing" */
mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
}
if(mc) {
break;
}
// Get response
do {
int queued;
msg = curl_multi_info_read(multi_handle, &queued);
if ((msg) && (msg->msg == CURLMSG_DONE) && (msg->data.result == CURLE_OK)) {
// Print the response payload
std::cout << "size: " << chunk.size << std::endl;
std::cout << chunk.memory << std::endl;
chunk.memory = 0;
chunk.size = 0;
}
} while (msg);
} while (still_running);
for(i = 0; i < num_transfers; i++) {
curl_multi_remove_handle(multi_handle, trans[i].easy);
curl_easy_cleanup(trans[i].easy);
}
free(chunk.memory);
curl_multi_cleanup(multi_handle);
return 0;
}
Summary question:
Q1. How can I modify the above program to correctly save a received payload response into a struct or a buffer asynchronously so that it can be available for functionality such as printing to stdout or searching for strings?
First, you should associate the transfer struct with a way of accessing the output:
struct transfer {
CURL *easy;
unsigned int num;
std::string contents;
};
and associate the CURLOPT_WRITEDATA with the pointer:
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, (void *)t);
Then, WriteMemoryCallback becomes:
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
transfer *t = (transfer *)userp;
size_t realsize = size * nmemb;
t->contents.append((const char *)contents, realsize);
return realsize;
}
Afterwards you can find the contents in the trans[i].contents variables.
I have written program, that sends 5 emails through my gmail adress, with 1 minute pause between. This program works on my laptop, but not works on Raspberry Pi. On Raspberry Pi it sends only 1 email.
main.cpp:
#include <string>
#include <thread>
#include <chrono>
#include "email.c"
void threadFunction()
{
for (int i = 0; i < 5; i++)
{
std::string message = /*"Hello my name is Dmitry\r\nTest1\r\nTest2\r\n" + */std::to_string(i) + "\r\n";
int status = sent_email("*********", "*********", "Test message", message.c_str(), "smtps://smtp.gmail.com", "*********", "*********");
std::this_thread::sleep_for(std::chrono::minutes(1));
status++;
}
}
int main()
{
/*std::thread thr(threadFunction);
thr.join();*/
threadFunction();
return 0;
}
email.c:
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
struct upload_information {
const char *data;
size_t data_length;
size_t sent;
};
size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream);
int sent_email(const char *FROM, const char *TO, const char *SUBJECT, const char *message, const char *server, const char *login, const char *password)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct curl_slist *recipients = NULL;
size_t length = strlen(message) + strlen(FROM) + strlen(TO) + strlen(SUBJECT) + 32; //32 is FROM: <>/n/r, TO: <>/n/r length and SUBJECT + new line(after header - SMTP standart) + /0
char *data;
data = (char*)malloc(sizeof(char) * length);
strcat(data, "FROM: <");
strcat(data, FROM);
strcat(data, ">\r\n");
strcat(data, "TO: <");
strcat(data, TO);
strcat(data, ">\r\n");
strcat(data, "SUBJECT: ");
strcat(data, SUBJECT);
strcat(data, "\r\n\r\n");
strcat(data, message);
struct upload_information upload_info;
upload_info.data = data;
upload_info.data_length = length;
upload_info.sent = 0;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_USERNAME, login);
curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
curl_easy_setopt(curl, CURLOPT_URL, server);
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
recipients = curl_slist_append(recipients, TO);
//recipients = curl_slist_append(recipients, CC);
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_info);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_slist_free_all(recipients);
curl_easy_cleanup(curl);
}
free(data);
return (int)res;
}
size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream)
{
struct upload_information *upload_info = (struct upload_information *)instream;
if ((size == 0) || (nitems == 0) || (size * nitems < 1))
return 0;
if (upload_info->sent < upload_info->data_length)
{
size_t length = size * nitems;
if (length > upload_info->data_length - upload_info->sent)
length = upload_info->data_length - upload_info->sent;
memcpy(buffer, upload_info->data + upload_info->sent, length);
upload_info->sent += length;
return length;
}
return 0;
}
Error message on Raspberry Pi:
*** glibc detected *** ./Mailer: double free or corruption (!prev): 0x01d0da38 ***
P.S. Program on raspberry pi send 1 correct message and part of second message.
P.S.S Post was updated
The buffer you're allocating for the message is 1 byte to short. The total length of all the constant strings you're adding is 29, but you also need to add one more byte for the '\0' character that strcat will terminate the string with.
If you run the following code it shows you that concatenated length of all the string literals is 30:
#include <stdio.h>
int
main() {
printf("%d\n", (int) sizeof("FROM: <" ">\r\n"
"TO: <" ">\r\n"
"SUBJECT: " "\r\n"));
}
There are two other problem with the message. One is that you're not putting a blank line between the headers and the message as required. The other is that you're not terminating the final line of the message with \r\n. This might confuse the curl library as it will need to add the end of line terminator itself. If it doesn't the message won't be sent through SMTP correctly.
I have solved my problem. Problem was because of when i freeв memory i am not reset memory.
I add *data = '\0'; befor strcat.
When i put this code in main and put it static it works fine but when i separate them to another class. This problem comes out. Does anybody know why?
#ifndef __PathToExile__ConnectHandler__
#define __PathToExile__ConnectHandler__
#include <curl/curl.h>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
class ConnectHandler{
public:
ConnectHandler();
size_t data_write(void* buf, size_t size, size_t nmemb, void* userp);
CURLcode curl_read(const std::string& url, std::ostream& os, long timeout = 30);
void writeWebToFile(std::string filename);
};
#endif /* defined(__PathToExile__ConnectHandler__) */
#include "ConnectHandler.h"
ConnectHandler::ConnectHandler(){
curl_global_init(CURL_GLOBAL_ALL);
}
size_t ConnectHandler::data_write(void* buf, size_t size, size_t nmemb, void* userp)
{
if(userp)
{
std::ostream& os = *static_cast<std::ostream*>(userp);
std::streamsize len = size * nmemb;
if(os.write(static_cast<char*>(buf), len)) //EROR: Thread 1: Exe_Bad_access (code = 1, address = 0x10)
return len;
}
return 0;
}
CURLcode ConnectHandler::curl_read(const std::string& url, std::ostream& os, long timeout)
{
CURLcode code(CURLE_FAILED_INIT);
CURL* curl = curl_easy_init();
if(curl)
{
if(CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &ConnectHandler::data_write))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &os))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str())))
{
code = curl_easy_perform(curl);
}
curl_easy_cleanup(curl);
}
return code;
}
void ConnectHandler::writeWebToFile(std::string filename){
std::ofstream ofs(filename);
if(CURLE_OK == ConnectHandler::curl_read("http://www.google.com", ofs))
{
std::cout << "Successfully write web to file " << filename << std::endl;
}
}
So in the main file i call it like normal:
Connecthandler c()
c.writeWebToFile("haha-masupilami.txt")
the problem occur at function size_t data_write(..) i already comment the line problem occur. Does anybody know what went wrong. 'Cause it works file when i put i static function in main file.
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;
}
I've created a PHP and C# app that gets the users Xbox 360 Profile ID from their gamertag.
I was just wondering how I do a web request and put the page contents of the url into a string in C++.
Thanks.
Here's a down-and-dirty C++ HTTP fetcher I wrote using libCURL some time back.
It takes a URL and returns a string with the contents of a get request. Modify and flavor to taste:
/*
* HttpFetcher.h
*/
#ifndef _http_fetcher_
#define _http_fetcher_
#include <stdlib.h>
#include <string>
class HttpFetcher {
public:
std::string simpleGetRequest(std::string url);
private:
// Private methods to handle getting
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data);
};
#endif // ifndef _http_fetcher_
/*
* HttpFetcher.cpp
* Much of this is quite similar to examples on the libcURL site
*/
#include "HttpFetcher.h"
#include "curl.h"
#include "easy.h"
#include <iostream>
size_t HttpFetcher::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)data;
mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
if (mem->memory == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
exit(EXIT_FAILURE);
}
memcpy(&(mem->memory[mem->size]), ptr, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
std::string HttpFetcher::simpleGetRequest(std::string url)
{
std::string toReturn = "";
CURL *curl = NULL;
struct MemoryStruct chunk;
chunk.memory = (char*)malloc(1);
chunk.size = 0;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&chunk);
// Some requests fail without a user agent.
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
toReturn = std::string(chunk.memory);
}
free(chunk.memory);
return toReturn;
}