Skip to content

libcurl 8.3.0 erroneously closes paused transfers with unknown content-length #11982

Closed
@piru

Description

@piru

I did this

/* Proof of Concept for libcurl 8.3.0 bug where paused downloads
 * with unknown Content-Length suddendly get errornously reported
 * as completed.
 *
 * Issue discovered and PoC written by Harry Sintonen <sintonen@iki.fi>
 *
 * Loosely based on https://curl.se/libcurl/c/multi-app.html
 */

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <curl/curl.h>

#define HANDLECOUNT 2

int err(void)
{
  fprintf(stderr, "something unexpected went wrong - bailing out!\n");
  exit(2);
}

struct handle
{
  int idx;
  int paused;
  CURL *h;
};

static size_t cb(void *data, size_t size, size_t nmemb, void *clientp)
{
  size_t realsize = size * nmemb;
  struct handle *handle = (struct handle *) clientp;
  curl_off_t totalsize;

  printf("handle %d write %lu bytes\n", handle->idx, realsize);

  if(curl_easy_getinfo(handle->h, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &totalsize) == CURLE_OK)
    printf("handle %d Content-Length %"CURL_FORMAT_CURL_OFF_T"\n", handle->idx, totalsize);

  handle->paused = 1;

  return CURL_WRITEFUNC_PAUSE;
}

int main(void)
{
  struct handle handles[HANDLECOUNT];
  CURLM *multi_handle;
  int i, still_running = 1, msgs_left, numfds;
  CURLMsg *msg;
  int rounds = 0;
  int rc = 1;

  curl_global_init(CURL_GLOBAL_DEFAULT);

  for(i = 0; i<HANDLECOUNT; i++) {
    handles[i].idx = i;
    handles[i].paused = 0;
    handles[i].h = curl_easy_init();
    if(!handles[i].h ||
      curl_easy_setopt(handles[i].h, CURLOPT_WRITEFUNCTION, cb) != CURLE_OK ||
      curl_easy_setopt(handles[i].h, CURLOPT_WRITEDATA, &handles[i]) != CURLE_OK ||
      curl_easy_setopt(handles[i].h, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK ||
      curl_easy_setopt(handles[i].h, CURLOPT_URL,
        "https://www.meteoblue.com/en") != CURLE_OK) {
      err();
    }
  }

  multi_handle = curl_multi_init();
  if (!multi_handle)
    err();

  for(i = 0; i<HANDLECOUNT; i++) {
    if(curl_multi_add_handle(multi_handle, handles[i].h) != CURLM_OK)
      err();
  }

  for(;;) {
    if(curl_multi_perform(multi_handle, &still_running) != CURLM_OK)
      err();

    if(!still_running)
      break;

    if(curl_multi_poll(multi_handle, NULL, 0, 100, &numfds) != CURLM_OK)
      err();

    while((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
      if(msg->msg == CURLMSG_DONE) {
        for(i = 0; i<HANDLECOUNT; i++) {
          if(msg->easy_handle == handles[i].h) {
            printf("handle %d done, result %d - wtf?\n", i, msg->data.result);
            goto out;
          }
        }
      }
    }
    /* Successfully paused? */
    if(handles[0].paused && handles[1].paused && ++rounds > 10) {
      /* Both handles are paused and transfer hasn't completed */
      printf("the test program ran as expected\n");
      rc = 0;
      break;
    }
  }
  out:

  for(i = 0; i<HANDLECOUNT; i++) {
    curl_multi_remove_handle(multi_handle, handles[i].h);
    curl_easy_cleanup(handles[i].h);
  }

  curl_multi_cleanup(multi_handle);

  curl_global_cleanup();

  return rc;
}

I expected the following

Both connections to get paused and neither complete.

curl/libcurl version

libcurl 8.3.0

operating system

Linux hostname 6.3.0-2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.3.11-1 (2023-07-01) x86_64 GNU/Linux

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions