formatting array of data into multipart/form-data

Viewed 504

I have a code where I receive a encrypted data in an array. I'm struggling to create a multipart/form-data using the curl mime api. Can anyone help me/guide me on how to create a multipart/form-data. Thank you. I'm new to curl and never worked with curl before. I get an error saying "Failed to open/read local data from file/application".

#include <string>
#include <vector>
#include <iostream>
#include <curl/curl.h>
#include <curl/easy.h>

#define SYSLOG_INFO std::clog

struct Post {
    std::string url, chunk;
    static constexpr auto HeaderSize = 10;
    size_t static callBackFunk(char* buffer, size_t size, size_t nitmes,
                               void* userdata)
    { /*ignore?*/
        SYSLOG_INFO << __PRETTY_FUNCTION__ << "\n";
        return 0;
    }

    std::string libcurl(const std::vector<uint8_t>& cmdData)
    {
        CURL*              curl{nullptr};
        CURLcode           res;
        curl_mime*         form    = NULL;
        curl_mimepart*     field   = NULL;
        struct curl_slist* headers = NULL;
        static const char  buf[]   = "Expect:";

        std::string data = std::string(cmdData.begin(), cmdData.end());

        curl = curl_easy_init();
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());

        form  = curl_mime_init(curl);
        field = curl_mime_addpart(form);
        curl_mime_name(field, "upfile");
        curl_mime_filedata(field, data.c_str());

        field = curl_mime_addpart(form);
        curl_mime_name(field, "filename");
        curl_mime_filedata(field, data.c_str());

        std::string header =
            std::string(cmdData.begin(), cmdData.begin() + HeaderSize);
        SYSLOG_INFO << "header = " << header << std::endl;
        std::string headerAuth = "SCPv2: " + header;

        headers = curl_slist_append(headers, headerAuth.c_str());
        headers = curl_slist_append(headers, buf);

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        curl_easy_setopt(curl, CURLOPT_HEADERDATA, (std::string*)&chunk);
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, callBackFunk);

        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            std::cout << stderr << " curl_easy_perform() failed "
                      << curl_easy_strerror(res) << std::endl;
        }

        curl_easy_cleanup(curl);
        curl_mime_free(form);
        curl_slist_free_all(headers);

        return chunk;
    }
};

int main() {
    Post post {"http://www.example.com", "hello world"};

    post.libcurl({1,2,3,4});
}

The commands

curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "cache-control:", CURLFORM_COPYCONTENTS, "no-cache", CURLFORM_END);

   curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "content-type:", CURLFORM_COPYCONTENTS, "multipart/form-data", CURLFORM_END);

   curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "file", CURLFORM_BUFFER, "data", CURLFORM_BUFFERPTR, cmdData.data(), CURLFORM_BUFFERLENGTH, cmdData.size(), CURLFORM_END);
1 Answers

With curl_mime_filedata() function, curl will try to load the content from file path named data.c_str(), if this file was not found, curl shown the error message above. If you want to upload data content, you should use curl_mime_data() instead:

filename points to the null-terminated file's path name. The pointer can be NULL to detach the previous part contents settings. Filename storage can be safely be reused after this call.

...

The contents of the file is read during the file transfer in a streaming manner to allow huge files to get transferred without using much memory. It therefore requires that the file is kept intact during the entire request.

The codes for upload a file named "example.dat" with content set by data string should be (modified from curl example here):

  CURL *curl;
  CURLM *multi_handle;

  int still_running = 0;
  curl_mime *form = NULL;
  curl_mimepart *field = NULL;
  struct curl_slist *headerlist = NULL;

  std::string data = {"1234"};

  //... add your headers definition ....

  curl = curl_easy_init();
  multi_handle = curl_multi_init();

  if(curl && multi_handle) {

    /* Create the form */
    form = curl_mime_init(curl);

    /* Fill in the file upload field */
    field = curl_mime_addpart(form);
    curl_mime_name(field, "upfile");
    curl_mime_filedata(field, data.c_str(), data.size() );

    /* Fill in the filename field */
    field = curl_mime_addpart(form);
    curl_mime_name(field, "filename");
    curl_mime_data(field, "example.dat", CURL_ZERO_TERMINATED);

    //... set your headers here ...

    /* what URL that receives this POST */
    curl_easy_setopt(curl, CURLOPT_URL, "https://www.example.com/");
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
    curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
    curl_multi_add_handle(multi_handle, curl);
    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;
    } while(still_running);
    curl_multi_cleanup(multi_handle);
    /* always cleanup */
    curl_easy_cleanup(curl);
    /* then cleanup the form */
    curl_mime_free(form);
    /* free slist */
    curl_slist_free_all(headerlist);
  }

If the program get error, it means the server side does not accept "upfile" or "filename" variables. You should check the server side part for further informations.

The codes shown above are the same as following curl command:

curl -F 'upfile=1234' -F 'filename=example.dat' http://www.example.com
Related