Package: libcurl4t64
Version: 8.7.1-2
Severity: important
Tags: upstream patch
Forwarded: https://github.com/curl/curl/issues/13269

Dear Maintainer,

curl 8.7 no longer fills in the request_size field. This has been
reported upstream in the following issue:

https://github.com/curl/curl/issues/13269

This causes at least ruby-ethon, a Ruby library that wraps libcurl via
FFI, to fail its test suite (after fixing it to not hardcode libcurl4 as
a dependency), like this:

----------------8<----------------8<----------------8<-----------------
Failures:

  1) Ethon::Easy::Informations#request_size returns 53
     Failure/Error: expect(easy.request_size).to eq(53)

       expected: 53
            got: 0

       (compared using ==)
     # ./spec/ethon/easy/informations_spec.rb:92:in `block (3 levels) in <top 
(required)>'

Finished in 5.06 seconds (files took 0.80166 seconds to load)
578 examples, 1 failure, 2 pending

Failed examples:

rspec ./spec/ethon/easy/informations_spec.rb:91 # 
Ethon::Easy::Informations#request_size returns 53
----------------8<----------------8<----------------8<-----------------

(the same test suite passes just fine against libcurl4 8.6.0-3 from testing.)

I have tested the patch in https://github.com/curl/curl/pull/13275 and
it indeed fixes this. I'm including a patch against the Debian package
in the archive that includes this patch in debian/patches, with the
fuzzyness already removed, and updates debian/patches/series accordingly.

-- System Information:
Debian Release: trixie/sid
  APT prefers testing
  APT policy: (900, 'testing'), (500, 'stable-security'), (500, 'unstable'), 
(1, 'experimental')
Architecture: arm64 (aarch64)

Kernel: Linux 6.6.15-arm64 (SMP w/32 CPU threads)
Locale: LANG=pt_BR.UTF-8, LC_CTYPE=pt_BR.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages libcurl4t64 depends on:
ii  libbrotli1        1.1.0-2+b3
ii  libc6             2.37-15
ii  libgssapi-krb5-2  1.20.1-5+b1
ii  libidn2-0         2.3.7-2
ii  libldap-2.5-0     2.5.13+dfsg-5+b3
ii  libnghttp2-14     1.59.0-1
pn  libpsl5t64        <none>
ii  librtmp1          2.4+20151223.gitfa8646d.1-2+b2
pn  libssh2-1t64      <none>
pn  libssl3t64        <none>
ii  libzstd1          1.5.5+dfsg2-2
ii  zlib1g            1:1.3.dfsg-3+b1

Versions of packages libcurl4t64 recommends:
ii  ca-certificates  20240203

libcurl4t64 suggests no packages.
diff -Nru curl-8.7.1/debian/patches/Fix_CURLINFO_REQUEST_SIZE.patch curl-8.7.1/debian/patches/Fix_CURLINFO_REQUEST_SIZE.patch
--- curl-8.7.1/debian/patches/Fix_CURLINFO_REQUEST_SIZE.patch	1970-01-01 00:00:00.000000000 +0000
+++ curl-8.7.1/debian/patches/Fix_CURLINFO_REQUEST_SIZE.patch	2024-04-19 13:18:39.000000000 +0000
@@ -0,0 +1,210 @@
+From 2793acbfc5e89fb130b1d4e045cb6cd7b6549412 Mon Sep 17 00:00:00 2001
+From: Stefan Eissing <ste...@eissing.org>
+Date: Thu, 4 Apr 2024 11:06:06 +0200
+Subject: [PATCH] Fix CURLINFO_REQUEST_SIZE, add tests for transfer infos
+ reported
+
+- refs #13269
+- tests for 'size_request' and other stats reported, for
+  presence and consistency
+---
+ lib/transfer.c              |   3 +
+ tests/http/test_16_info.py  | 162 ++++++++++++++++++++++++++++++++++++
+ tests/http/testenv/httpd.py |   1 +
+ 3 files changed, 166 insertions(+)
+ create mode 100644 tests/http/test_16_info.py
+
+Index: curl-8.7.1/lib/transfer.c
+===================================================================
+--- curl-8.7.1.orig/lib/transfer.c
++++ curl-8.7.1/lib/transfer.c
+@@ -1221,6 +1221,9 @@ CURLcode Curl_xfer_send(struct Curl_easy
+     result = CURLE_OK;
+     *pnwritten = 0;
+   }
++  else if(!result && *pnwritten)
++    data->info.request_size += *pnwritten;
++
+   return result;
+ }
+ 
+Index: curl-8.7.1/tests/http/test_16_info.py
+===================================================================
+--- /dev/null
++++ curl-8.7.1/tests/http/test_16_info.py
+@@ -0,0 +1,162 @@
++#!/usr/bin/env python3
++# -*- coding: utf-8 -*-
++#***************************************************************************
++#                                  _   _ ____  _
++#  Project                     ___| | | |  _ \| |
++#                             / __| | | | |_) | |
++#                            | (__| |_| |  _ <| |___
++#                             \___|\___/|_| \_\_____|
++#
++# Copyright (C) Daniel Stenberg, <dan...@haxx.se>, et al.
++#
++# This software is licensed as described in the file COPYING, which
++# you should have received as part of this distribution. The terms
++# are also available at https://curl.se/docs/copyright.html.
++#
++# You may opt to use, copy, modify, merge, publish, distribute and/or sell
++# copies of the Software, and permit persons to whom the Software is
++# furnished to do so, under the terms of the COPYING file.
++#
++# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
++# KIND, either express or implied.
++#
++# SPDX-License-Identifier: curl
++#
++###########################################################################
++#
++import difflib
++import filecmp
++import logging
++import os
++from datetime import timedelta
++import pytest
++
++from testenv import Env, CurlClient, LocalClient, ExecResult
++
++
++log = logging.getLogger(__name__)
++
++
++class TestInfo:
++
++    @pytest.fixture(autouse=True, scope='class')
++    def _class_scope(self, env, httpd, nghttpx):
++        if env.have_h3():
++            nghttpx.start_if_needed()
++        httpd.clear_extra_configs()
++        httpd.reload()
++
++    @pytest.fixture(autouse=True, scope='class')
++    def _class_scope(self, env, httpd):
++        indir = httpd.docs_dir
++        env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024)
++        env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024)
++        env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024)
++
++    # download plain file
++    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
++    def test_16_01_info_download(self, env: Env, httpd, nghttpx, repeat, proto):
++        if proto == 'h3' and not env.have_h3():
++            pytest.skip("h3 not supported")
++        count = 2
++        curl = CurlClient(env=env)
++        url = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
++        r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True)
++        r.check_stats(count=count, http_status=200)
++        for s in r.stats:
++            self.check_stat(s, dl_size=30, ul_size=0)
++
++    # download plain file with a 302 redirect
++    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
++    def test_16_02_info_302_download(self, env: Env, httpd, nghttpx, repeat, proto):
++        if proto == 'h3' and not env.have_h3():
++            pytest.skip("h3 not supported")
++        count = 2
++        curl = CurlClient(env=env)
++        url = f'https://{env.authority_for(env.domain1, proto)}/data.json.302?[0-{count-1}]'
++        r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True, extra_args=[
++            '--location'
++        ])
++        r.check_stats(count=count, http_status=200)
++        for s in r.stats:
++            self.check_stat(s, dl_size=30, ul_size=0)
++
++    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
++    def test_16_03_info_upload(self, env: Env, httpd, nghttpx, proto, repeat):
++        if proto == 'h3' and not env.have_h3():
++            pytest.skip("h3 not supported")
++        count = 2
++        fdata = os.path.join(env.gen_dir, 'data-100k')
++        fsize = 100 * 1024
++        curl = CurlClient(env=env)
++        url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
++        r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
++                             with_headers=True)
++        r.check_response(count=count, http_status=200)
++        r.check_stats(count=count, http_status=200)
++        for s in r.stats:
++            self.check_stat(s, dl_size=fsize, ul_size=fsize)
++
++    # download plain file via http: ('time_appconnect' is 0)
++    @pytest.mark.parametrize("proto", ['http/1.1'])
++    def test_16_04_info_http_download(self, env: Env, httpd, nghttpx, repeat, proto):
++        count = 2
++        curl = CurlClient(env=env)
++        url = f'http://{env.domain1}:{env.http_port}/data.json?[0-{count-1}]'
++        r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True)
++        r.check_stats(count=count, http_status=200)
++        for s in r.stats:
++            self.check_stat(s, dl_size=30, ul_size=0)
++
++    def check_stat(self, s, dl_size=None, ul_size=None):
++        self.check_stat_times(s)
++        # we always send something
++        self.check_stat_positive(s, 'size_request')
++        # we always receive response headers
++        self.check_stat_positive(s, 'size_header')
++        if ul_size is not None:
++            assert s['size_upload'] == ul_size  # the file we sent
++        assert s['size_request'] >= s['size_upload'], f'"size_request" smaller than "size_upload", {s}'
++        if dl_size is not None:
++            assert s['size_download'] == dl_size  # the file we received
++
++    def check_stat_positive(self, s, key):
++        assert key in s, f'stat "{key}" missing: {s}'
++        assert s[key] > 0, f'stat "{key}" not positive: {s}'
++
++    def check_stat_zero(self, s, key):
++        assert key in s, f'stat "{key}" missing: {s}'
++        assert s[key] == 0, f'stat "{key}" not zero: {s}'
++
++    def check_stat_times(self, s):
++        # check timings reported on a transfer for consistency
++        url = s['url_effective']
++        # all stat keys which reporting timings
++        all_keys = set([
++            'time_appconnect', 'time_connect', 'time_redirect',
++            'time_pretransfer', 'time_starttransfer', 'time_total'
++        ])
++        # stat keys where we expect a positive value
++        pos_keys = set(['time_pretransfer', 'time_starttransfer', 'time_total'])
++        if s['num_connects'] > 0:
++            pos_keys.add('time_connect')
++            if url.startswith('https:'):
++                pos_keys.add('time_appconnect')
++        if s['num_redirects'] > 0:
++            pos_keys.add('time_redirect')
++        zero_keys = all_keys - pos_keys
++        # assert all zeros are zeros and the others are positive
++        for key in zero_keys:
++            self.check_stat_zero(s, key)
++        for key in pos_keys:
++            self.check_stat_positive(s, key)
++        # assert that all timers before "time_pretransfer" are less or equal
++        for key in ['time_appconnect', 'time_connect', 'time_namelookup']:
++            assert s[key] < s['time_pretransfer'], f'time "{key}" larger than' \
++                f'"time_pretransfer": {s}'
++        # assert transfer start is after pretransfer
++        assert s['time_pretransfer'] <= s['time_starttransfer'], f'"time_pretransfer" '\
++            f'greater than "time_starttransfer", {s}'
++        # assert that transfer start is before total
++        assert s['time_starttransfer'] <= s['time_total'], f'"time_starttransfer" '\
++            f'greater than "time_total", {s}'
+\ No newline at end of file
+Index: curl-8.7.1/tests/http/testenv/httpd.py
+===================================================================
+--- curl-8.7.1.orig/tests/http/testenv/httpd.py
++++ curl-8.7.1/tests/http/testenv/httpd.py
+@@ -372,6 +372,7 @@ class Httpd:
+         lines = []
+         if Httpd.MOD_CURLTEST is not None:
+             lines.extend([
++                f'    Redirect 302 /data.json.302 /data.json',
+                 f'    Redirect 301 /curltest/echo301 /curltest/echo',
+                 f'    Redirect 302 /curltest/echo302 /curltest/echo',
+                 f'    Redirect 303 /curltest/echo303 /curltest/echo',
diff -Nru curl-8.7.1/debian/patches/series curl-8.7.1/debian/patches/series
--- curl-8.7.1/debian/patches/series	2024-04-03 17:59:41.000000000 +0000
+++ curl-8.7.1/debian/patches/series	2024-04-19 13:18:20.000000000 +0000
@@ -9,6 +9,7 @@
 make-manpages-reproducible.patch
 fix-regression-on-chunked-post.patch
 test1901_verify_chunked_POST_from_callback_with_CURLOPT_POSTFIELDSIZ.patch
+Fix_CURLINFO_REQUEST_SIZE.patch
 
 # Do not add patches below.
 # Used to generate packages for the other crypto libraries.

Attachment: signature.asc
Description: PGP signature

Reply via email to