diff mbox series

[v2] fix: do not let channel_get_file resume an interrupted range request

Message ID 20260824193812.95168-1-caleb.hensley@lvt.com
State New
Headers show
Series [v2] fix: do not let channel_get_file resume an interrupted range request | expand

Commit Message

Caleb Hensley Aug. 24, 2026, 7:38 p.m. UTC
Previously, channel_get_file() retried an interrupted transfer by setting
CURLOPT_RESUME_FROM_LARGE to the number of bytes received so far. That is
correct for the cached_file case it was written for, where the count is an
offset in the file being downloaded, but the delta chunks downloader uses the
same function with CURLOPT_RANGE set, and curl_easy_reset() only runs at the end
of the call. libcurl builds the Range header from CURLOPT_RESUME_FROM when it is
set, so a retry replaces the requested ranges with

    Range: bytes=<bytes received for this request>-

which is neither the right offset nor bounded, and not correct for the delta
handler. Report the error instead of resuming here. The handler can implement
a range reconstruction strategy to recover from the interrupted request.

Signed-off-by: Caleb Hensley <caleb.hensley@lvt.com>
---
 corelib/channel_curl.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
diff mbox series

Patch

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index bae44d85..34a99e81 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -1422,6 +1422,25 @@  channel_op_res_t channel_get_file(channel_t *this, void *data)
 	 */
 	do {
 		if (try_count > 0) {
+			/*
+			 * A range request cannot be resumed here. `total_bytes_downloaded`
+			 * counts the body bytes of this request, multipart boundaries and
+			 * part headers included, and is not an offset in the file. libcurl
+			 * builds the Range header from CURLOPT_RESUME_FROM when it is set,
+			 * dropping the CURLOPT_RANGE set in channel_set_options(). Without
+			 * this check, that retry would ask for "bytes=<count>-" and collect
+			 * unrelated data up to the end of the file. Pass the curl error to
+			 * the caller and let it resconstruct a new range request.
+			 */
+			if (channel_data->range) {
+				ERROR("Channel range request interrupted after "
+				      "%llu bytes (%d): '%s'",
+				      total_bytes_downloaded, curlrc,
+				      curl_easy_strerror(curlrc));
+				result = channel_map_curl_error(curlrc);
+				goto cleanup_file;
+			}
+
 			if (channel_data->retries == 0) {
 				ERROR(
 				    "Channel get operation failed (%d): '%s'\n",