Skip to content

Instantly share code, notes, and snippets.

@jay
Last active August 18, 2026 20:14
Show Gist options
  • Select an option

  • Save jay/e963eed374ce5fc1a5c8ce5b4230dbc7 to your computer and use it in GitHub Desktop.

Select an option

Save jay/e963eed374ce5fc1a5c8ce5b4230dbc7 to your computer and use it in GitHub Desktop.
Use libcurl to test and trace mime POSTs.
/* Use libcurl to test and trace mime POSTs.
Usage: mimediag2 [datasize [readsize]]
Issue: "Multi-part uploads never send more than one buffersize"
https://github.com/curl/curl/issues/22586
Reporter says certain size mime POSTs are failing to complete on an embedded
system. The default datasize and readsize (max size to read per callback call)
can be changed by using command line arguments.
* Copyright (C) 2026 Jay Satiro <raysatiro@yahoo.com>
https://curl.haxx.se/docs/copyright.html
https://gist.github.com/jay/e963eed374ce5fc1a5c8ce5b4230dbc7
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <sys/time.h>
#include <unistd.h>
#endif
#include <curl/curl.h>
#ifdef _WIN32
#define SIZE_T_FMT "Iu"
#define WAITMS(x) Sleep(x)
#else
#define SIZE_T_FMT "zu"
#define WAITMS(x) usleep((x) * 1000)
#endif
#if defined(MULTI_POLL) || defined(MULTI_WAIT) || defined(MULTI_LEGACY)
#ifndef TEST_MULTI
#define TEST_MULTI
#endif
#endif
/* AAAAAA data tracking for the mime part read by the read callback */
struct data {
size_t datasize, readsize, position;
};
size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
struct data *d = (struct data *)userdata;
size_t readsize = (size * nmemb);
size_t remaining = d->datasize - d->position;
fprintf(stderr, "BEGIN: read_callback requests %" SIZE_T_FMT " bytes\n",
readsize);
if(readsize > d->readsize)
readsize = d->readsize;
if(readsize > remaining)
readsize = remaining;
if(readsize) {
memset(ptr, 'A', readsize);
d->position += readsize;
}
fprintf(stderr, "END: read_callback got %" SIZE_T_FMT " bytes\n", readsize);
return readsize;
}
int seek_callback(void *userdata, curl_off_t offset, int origin)
{
struct data *d = (struct data *)userdata;
fprintf(stderr, "BEGIN: seek_callback requests offset "
"%" CURL_FORMAT_CURL_OFF_T " from origin %s\n",
offset,
(origin == SEEK_CUR ? "SEEK_CUR" :
origin == SEEK_END ? "SEEK_END" :
origin == SEEK_SET ? "SEEK_SET" :
"Unknown"));
if(origin == SEEK_CUR)
offset += (curl_off_t)d->position;
else if(origin == SEEK_END)
offset += (curl_off_t)d->datasize;
else if(origin != SEEK_SET) {
fprintf(stderr, "END: seek_callback FAILED, unknown origin: %d\n", origin);
return CURL_SEEKFUNC_FAIL;
}
if(offset < 0 || offset > (curl_off_t)d->datasize) {
fprintf(stderr, "END: seek_callback FAILED, "
"new absolute position out of range: "
"%" CURL_FORMAT_CURL_OFF_T "\n", offset);
return CURL_SEEKFUNC_FAIL;
}
d->position = (size_t)offset;
fprintf(stderr, "END: seek_callback new absolute position "
"%" SIZE_T_FMT "\n", d->position);
return CURL_SEEKFUNC_OK;
}
int main(int argc, char *argv[])
{
CURLM *multi = NULL;
CURL *curl;
CURLcode result;
curl_mime *mime;
curl_mimepart *part;
struct data d;
int i, failed;
char errbuf[CURL_ERROR_SIZE] = { 0, };
/* disable output buffering to lessen the chance of partial lines mixing */
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
/* default size of AAAAAA mime part read by the read callback */
d.readsize = 1024;
d.datasize = d.readsize + 1;
d.position = 0;
printf("Usage: mimediag2 [datasize [readsize]]\n");
if(argc > 1) {
if(!strcmp("-h", argv[1]) || !strcmp("--help", argv[1]))
return 0;
d.datasize = (size_t)atoi(argv[1]);
if(argc > 2)
d.readsize = (size_t)atoi(argv[2]);
else
d.readsize = d.datasize ? d.datasize : 1;
}
if(!d.readsize) {
fprintf(stderr, "Error: readsize can't be 0\n");
return 1;
}
if(d.readsize < 100) {
fprintf(stderr, "WARNING: your readsize is small enough to cause "
"server i/o timeout (eg httpbin HTTP response code 400)\n");
}
printf("Using datasize %" SIZE_T_FMT "\n", d.datasize);
printf("Using readsize %" SIZE_T_FMT "\n", d.readsize);
curl_global_trace("all");
curl_global_init(CURL_GLOBAL_ALL);
atexit(curl_global_cleanup);
printf("curl_version(): %s\n", curl_version());
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.io/post");
mime = curl_mime_init(curl);
part = curl_mime_addpart(mime);
curl_mime_name(part, "payload");
curl_mime_filename(part, "payload.bin");
curl_mime_type(part, "application/octet-stream");
curl_mime_data_cb(part, (curl_off_t)d.datasize,
read_callback, seek_callback, NULL, &d);
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
#ifdef TEST_MULTI
multi = curl_multi_init();
#endif
/* run the transfer at least twice to test data and connection reuse */
for(i = 1, failed = 0; i <= 2 && !failed; ++i) {
fprintf(stderr, "BEGIN: TRANSFER #%d\n", i);
#ifdef TEST_MULTI
{
int still_running = 1;
curl_multi_add_handle(multi, curl);
while(still_running && !failed) {
CURLMcode mc;
mc = curl_multi_perform(multi, &still_running);
if(mc) {
fprintf(stderr, "Error: curl_multi_perform() failed, code %d: %s\n",
mc, curl_multi_strerror(mc));
failed = 1;
break;
}
if(!still_running)
break;
#ifdef MULTI_POLL
{
int numfds = 0;
mc = curl_multi_poll(multi, NULL, 0, 1000, &numfds);
if(mc) {
fprintf(stderr, "Error: curl_multi_poll() failed, code %d: %s\n",
mc, curl_multi_strerror(mc));
failed = 1;
break;
}
fprintf(stderr, "curl_multi_poll returned numfds %d\n", numfds);
}
#elif defined(MULTI_WAIT)
/* Test of curl_multi_wait. Normally we'd use curl_multi_poll without
this additional code. */
{
int numfds = 0;
int repeats = 0;
mc = curl_multi_wait(multi, NULL, 0, 1000, &numfds);
if(mc) {
fprintf(stderr, "Error: curl_multi_wait() failed, code %d: %s\n",
mc, curl_multi_strerror(mc));
failed = 1;
break;
}
fprintf(stderr, "curl_multi_wait returned numfds %d\n", numfds);
/* 'numfds' being zero means either a timeout or no file descriptors
to wait for. Try timeout on first occurrence, then assume no file
descriptors and no file descriptors to wait for means wait for
100 milliseconds. */
if(!numfds) {
repeats++; /* count number of repeated zero numfds */
if(repeats > 1) {
fprintf(stderr, "sleeping 100ms\n");
WAITMS(100); /* sleep 100 milliseconds */
}
}
else
repeats = 0;
}
#elif defined(MULTI_LEGACY)
/* Test of select. Normally we'd use curl_multi_poll without this
additional code. This code is from docs/examples/multi-legacy.c */
{
struct timeval timeout;
int rc; /* select() return code */
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;
long curl_timeo = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
/* set a suitable timeout to play around with */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
mc = curl_multi_timeout(multi, &curl_timeo);
if(mc) {
fprintf(stderr, "Error: curl_multi_timeout() failed, "
"code %d: %s\n",
mc, curl_multi_strerror(mc));
failed = 1;
break;
}
if(curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if(timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (int)(curl_timeo % 1000) * 1000;
}
/* get file descriptors from the transfers */
mc = curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd);
if(mc) {
fprintf(stderr, "Error: curl_multi_fdset() failed, "
"code %d: %s\n",
mc, curl_multi_strerror(mc));
failed = 1;
break;
}
/* On success the value of maxfd is guaranteed to be >= -1. We call
select(maxfd + 1, ...); specially in case of (maxfd == -1) there
are no fds ready yet so we sleep 100ms, which is the minimum
suggested value in the curl_multi_fdset() doc. */
if(maxfd == -1) {
fprintf(stderr, "sleeping 100ms\n");
WAITMS(100);
rc = 0;
}
else {
/* Note that on some platforms 'timeout' may be modified by
select(). If you need access to the original value save a copy
beforehand. */
rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
}
if(rc == -1) {
int err = errno;
fprintf(stderr, "Error: select failed, "
"errno %d: %s\n",
err, strerror(err));
failed = 1;
break;
}
fprintf(stderr, "select returned %s\n", (rc ? "action" : "timeout"));
}
#else
#error "define one of MULTI_POLL, MULTI_WAIT, MULTI_LEGACY to test multi"
#endif
}
/* get the result of our lone transfer in the multi */
result = CURLE_FAILED_INIT;
if(!failed) {
for(;;) {
int qc;
struct CURLMsg *m = curl_multi_info_read(multi, &qc);
if(!m)
break;
if(m->easy_handle == curl && m->msg == CURLMSG_DONE) {
result = m->data.result;
break;
}
}
}
curl_multi_remove_handle(multi, curl);
}
#else
result = curl_easy_perform(curl);
#endif
if(!result && !failed) {
long code = 0;
double upload_size = 0, upload_speed = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if(code != 200) {
fprintf(stderr, "Error: Unexpected HTTP response code %ld != 200\n",
code);
failed = 1;
}
curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD, &upload_speed);
curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &upload_size);
fprintf(stderr, "Upload rate: %.0f KB/sec\n", upload_speed / 1024);
/* upload_size should be greater than the mime part datasize because
it includes the mime boundary markers */
if((size_t)upload_size <= d.datasize) {
fprintf(stderr, "Error: Unexpected upload size %" SIZE_T_FMT
" is less than or equal to datasize %" SIZE_T_FMT "\n",
(size_t)upload_size, d.datasize);
failed = 1;
}
}
else {
fprintf(stderr, "Error: (%d) %s\n", (int)result,
errbuf[0] ? errbuf : curl_easy_strerror(result));
failed = 1;
}
fprintf(stderr, "END: TRANSFER #%d, %s\n", i, failed ? "FAILED" : "OK");
}
curl_mime_free(mime);
if(multi)
curl_multi_remove_handle(multi, curl);
curl_easy_cleanup(curl);
if(multi)
curl_multi_cleanup(multi);
fprintf(stderr, "%s\n", failed ? "FAIL" : "PASS");
return failed ? 1 : 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment