Search code examples
streamlibcurlmimemultipartform-data

CURLFORM_STREAM pointer not being used by CURLOPT_READFUNCTION


I am trying to send a multipart MIME message with custom streaming (multiple binary files). To this end, I cannot get my CURLOPT_READFUNCTION callback to use a pointer set by CURLFORM_STREAM.

So far as I can tell, the CURLFORM_STREAM documentation automatically calls the CURLOPT_READFUNCTION pointer when it begins to stream data. This is not happening for me.

Here's my current code sample (I've been trying different configurations with no success). CURLCODECHECK and CURLFORMCHECK are macros that throw exceptions on an error. streams is a vector of my own StreamData structs.

        CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_HTTPPOST, 1L));
        CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, ::StreamReadFunction));

        for (auto iter = streams.begin(); iter != streams.end(); ++iter)
        {
            std::string const & name = iter->first;
            auto streamData = iter->second;

            CURLFORMCHECK(curl_formadd(&m_Post, &last,
                CURLFORM_COPYNAME, name.c_str(),
                CURLFORM_FILENAME, streamData->fileName.c_str(),
                CURLFORM_CONTENTTYPE, streamData->mimeType.c_str(),
                CURLFORM_STREAM, (void *) streamData.get(),
                CURLFORM_CONTENTSLENGTH, streamData->size,
                CURLFORM_END));
        }

My ::StreamReadFunction does get called, but unless I call curl_easy_setopt() with CURLOPT_READDATA set, it gets passed a null pointer for the fourth (void * userdata) argument.


Solution

  • In short, CURLOPT_HTTPPOST is not a replacement for CURLOPT_POST. Both must be provided and CURLOPT_POST must be set first.

    I moved my curl_formadd() call to the top of the function, and followed it with:

    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_POST, 1L));
    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE, fieldSize));
    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, ::StreamReadFunction));
    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_HTTPPOST, m_Post));
    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L));
    CURLCODECHECK(curl_easy_setopt(m_Curl, CURLOPT_HEADER, 1L));
    

    This then properly called my ::StreamReadFunction with my stream pointer.

    Note that CURLOPT_POST must be set before CURLOPT_HTTPPOST for the stream callback to use the proper pointer (placing CURLOPT_POST later will cause a null pointer to be passed in).