Blake Senftner
Blake Senftner

Reputation: 786

Posting with libcurl in C++, write callback params are garbage

Developing on Win64 with Visual Studio 2013 Community, deploying to both Win64 and Linux with cross platform wxWidgets. I am trying to emulate the following curl.exe command line with C++ using libcurl:

curl.exe -X POST -g "single-url-string"

This is for an IoT feature of an app, where an end-user supplies the single-url-string to control their device. The reason this logic is not just executing curl.exe as an external process is because this logic runs in its own thread, and wxWidgets does not support launching external executables when outside the main thread.

Normally when performing a POST with curl.exe, the post data is supplied as an option. This tells curl.exe the operation is a POST to the supplied url, and here is the data for that POST. As you can see, what I'm trying to do is a GET style url (with the parameters embedded in the url) but then changing the operation to a POST. It's done this way because research shows asking end-users to supply two separate url and data strings is simply too complex for them. So we came up with this easier single string end-users must supply, which is usually just copying a string from their device manual without having to interpret the string, much less break it into separate meaningful strings.

So, the issue at hand is: I have my simple C++ libcurl POST routine in two versions, but in both versions the parameters received by the write callback are bad. The two versions are a POST with a single url string, and a POST with the post data provided as a separate option to the url string.

The problems are 1) using the single string version does not execute a POST, and it's write callback params are bad; and 2) using the two string version does execute a POST, but the write callback params are bad, in a different way.

The data pointer parameter in the write callback points to memory address 1 in both versions, the size parameter appears good in both versions, but the nmemb parameter is either a huge random value (single string version) or zero (two string POST version).

Here's my code, and yes I call curl_global_init() at app start.

size_t CX_IOT_THREAD::curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
  // storage for transferred data:
  const int dataStoreSize = CURL_MAX_WRITE_SIZE + 1;
  char dataStore[dataStoreSize];
  memset(dataStore, 0, dataStoreSize); // zeroed out

  size_t dataSize = size * nmemb; // bytes sent

  if (dataSize)
  {
    memcpy(dataStore, ptr, dataSize); // copy into buffer sized so we'll have a terminating NULL char

    wxString msg = wxString::Format(wxT("%s"), dataStore); // send as event, eventually to the log
    mp_queue->Report(CX_IOTTHR_CMD_ACCESS_JOB, msg);

    // must return byte count processed for libcurl to be happy:
    return dataSize; /**/
  }

return size; // should be dataSize, but because nmemb is bad, I’m using size; it works. 
} 

cx_int CX_IOT_THREAD::Post(std::string& url)
{
  if (url.length() == 0)
     return -1;

  char errBuf[CURL_ERROR_SIZE];
  errBuf[0] = '\0';
  static const char *postthis = "name=Bloke&age=67";

  CURLcode ret;
  CURL *hnd = curl_easy_init();
  curl_easy_setopt(hnd, CURLOPT_URL, url.c_str());
  curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, postthis);
  curl_easy_setopt(hnd, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis));
  curl_easy_setopt(hnd, CURLOPT_ERRORBUFFER, errBuf);
  curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, &CX_IOT_THREAD::curl_write_callback);
  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, NULL);
  curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
  curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.49.1");
  curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
//  curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST");
  ret = curl_easy_perform(hnd);
  curl_easy_cleanup(hnd);

   if (ret != CURLE_OK)
   {
      wxString msg = wxString::Format(wxT("Attempted POST failed, libcurl return code '%d'."), (cx_int)ret);
      mp_queue->Report(CX_IOTTHR_CMD_ACCESS_JOB, msg, (cx_int)ret);

      cx_int len = strlen(errBuf);
      if (len > 0)
         msg = wxString::Format("%s%s", errBuf, ((errBuf[len - 1] != '\n') ? "\n" : ""));
      else msg = wxString::Format("%s\n", curl_easy_strerror(ret));
      mp_queue->Report(CX_IOTTHR_CMD_ACCESS_JOB, msg, (cx_int)ret);
    }
    return (cx_int)ret;
}

Any ideas why the write callback parameters are bad? Any idea why the single string version does not even do a post? (The single string version is the above with the 2 POSTFIELDS options commented out and the CUSTOMREQUEST one enabled.)

Upvotes: 0

Views: 813

Answers (1)

Blake Senftner
Blake Senftner

Reputation: 786

As Igor Tandetnik points out, the callback must be static.

Upvotes: 1

Related Questions