Saturday, April 9, 2022

[SOLVED] cURL write_callback does not pass userdata argument

Issue

I am trying to collect some data from a URL. If I do not define any CURLOPT_WRITEFUNCTION and CURLOPT_WRITEDATA I can obviously see the output on console. Then I tried to write that data to memory by copiying the example code, however userdata argument of my callback function returned NULL and I got following exception on line:

char* ptr = (char*)realloc(mem->memory, mem->size + realsize + 1);

Exception thrown: read access violation.
mem was nullptr.

Am I doing something wrong?

Here is my code:

struct MemoryStruct {
    char* memory;
    size_t size;
};

//-----------------
// Curl's callback
//-----------------
size_t CurlWrapper::curl_cb(char* data, size_t size, size_t nmemb, void* response)
{
    size_t realsize = size * nmemb;
    std::cout << "CALLBACK CALLED" << std::endl;

    MemoryStruct* mem = (struct MemoryStruct*)response;
    char* ptr = (char*)realloc(mem->memory, mem->size + realsize + 1);
    if (!ptr) {
        /* out of memory! */
        printf("not enough memory (realloc returned NULL)\n");
        return 0;
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), data, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

//--------------------
// Do the curl
//--------------------
void CurlWrapper::curl_api(
      const std::string& url,
            std::string& str_result)
{

    MemoryStruct chunk;

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWrapper::curl_cb);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&chunk);
        // TODO: enable ssh certificate
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // true
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); // 2
        curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "zlib");

        auto res = curl_easy_perform(curl);

        /* Check for errors */
        if (res != CURLE_OK) {
            // nothing
            std::cout << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        }
    }
}

libcurl version: 7.82.0


Solution

Since libcurl is a C library, it does not know anything about C++ member functions or objects. You can overcome this "limitation" with relative ease using for example a static member function that is passed a pointer to the class.

See this example (from the everything curl book).

// f is the pointer to your object.
static size_t YourClass::func(void *buffer, size_t sz, size_t n, void *f)
{
  // Call non-static member function.
  static_cast<YourClass*>(f)->nonStaticFunction();
}

// This is how you pass pointer to the static function:
curl_easy_setopt(hcurl, CURLOPT_WRITEFUNCTION, YourClass::func);
curl_easy_setopt(hcurl, CURLOPT_WRITEDATA, this);


Answered By - Daniel Stenberg
Answer Checked By - David Goodson (WPSolving Volunteer)