DownloadProgressChanged reports 100% immediately when server omits Content-Length
Description
When downloading from a server that does not return Content-Length or Accept-Ranges headers, the DownloadProgressChanged event reports ProgressPercentage = 100% immediately after the first chunk is received, even though the download is still in progress.
In this scenario, TotalBytesToReceive is incorrectly set equal to ReceivedBytesToReceive, making ProgressPercentage always compute to 100%. This is misleading — the download is far from complete.
Expected Behavior
When Content-Length is unknown:
TotalBytesToReceive should remain 0 (indicating unknown total size)
ProgressPercentage should remain 0 (cannot be calculated)
ReceivedBytesSize should continue to update normally
- The
DownloadProgressChanged event should continue to fire as data is received
Actual Behavior
TotalBytesToReceive is set to the same value as ReceivedBytesSize after the first chunk
ProgressPercentage is always 100%
- The
DownloadProgressChanged event fires only once or very few times, then stops
Root Cause
When the server does not provide Content-Length, the library has no way to know the total file size. Instead of keeping TotalBytesToReceive = 0, the library dynamically sets TotalBytesToReceive = ReceivedBytesSize, which causes ProgressPercentage to always equal 100%.
Reproduction
The PHP Windows downloads server (using myracloud CDN) does not return Content-Length or Accept-Ranges:
$ curl -sI "https://downloads.php.net/~windows/releases/php-8.2.31-nts-Win32-vs16-x64.zip"
HTTP/1.1 200 OK
Server: myracloud
Content-Type: application/zip
ETag: "1fd0494-651150d907d00"
Note: no Content-Length, no Accept-Ranges.
Minimal Repro Program
using System;
using System.IO;
using Downloader;
var url = "https://downloads.php.net/~windows/releases/php-8.2.31-nts-Win32-vs16-x64.zip";
var dest = Path.Combine(Path.GetTempPath(), "test-download.zip");
var config = new DownloadConfiguration
{
ChunkCount = 1,
ParallelDownload = false,
MaxTryAgainOnFailure = 3,
FileExistPolicy = FileExistPolicy.Delete,
DownloadFileExtension = ".download",
};
var downloader = new DownloadService(config);
var startTime = DateTime.Now;
downloader.DownloadStarted += (_, args) =>
{
Console.WriteLine($"[STARTED] TotalBytes={args.TotalBytesToReceive}");
};
downloader.DownloadProgressChanged += (_, args) =>
{
var elapsed = (DateTime.Now - startTime).TotalSeconds;
Console.WriteLine(
$"[PROGRESS] {args.ProgressPercentage:F1}% " +
$"Received={args.ReceivedBytesSize} " +
$"Total={args.TotalBytesToReceive} " +
$"Elapsed={elapsed:F1}s");
};
downloader.DownloadFileCompleted += (_, args) =>
{
var elapsed = (DateTime.Now - startTime).TotalSeconds;
if (args.Error != null)
Console.WriteLine($"[ERROR] {args.Error.GetBaseException().Message} Elapsed={elapsed:F1}s");
else if (args.Cancelled)
Console.WriteLine($"[CANCELLED] Elapsed={elapsed:F1}s");
else
Console.WriteLine($"[COMPLETED] Elapsed={elapsed:F1}s");
};
await downloader.DownloadFileTaskAsync(url, dest);
if (File.Exists(dest))
Console.WriteLine($"[FILE SIZE] {new FileInfo(dest).Length} bytes");
Observed Output
[STARTED] TotalBytes=0
[PROGRESS] 100.0% Received=3776 Total=3776 Elapsed=3.0s
After the first progress event, TotalBytesToReceive is set to 3776 (same as ReceivedBytesSize), and ProgressPercentage is 100%. The actual file is ~33MB and is still being written to disk, but no further DownloadProgressChanged events are fired.
Note: ChunkDownloadProgressChanged does continue to fire with correct ReceivedBytesSize values, but DownloadProgressChanged stops after the first report.
Workaround
Consumers can work around this by:
- Subscribing to
ChunkDownloadProgressChanged instead of DownloadProgressChanged to get ReceivedBytesSize updates
- Ignoring
ProgressPercentage when TotalBytesToReceive equals ReceivedBytesSize (or when TotalBytesToReceive was initially 0)
- Displaying "received X MB" instead of a percentage when total size is unknown
Environment
- Downloader version: 5.5.0
- .NET version: 10.0
- OS: Windows 10
DownloadProgressChanged reports 100% immediately when server omits Content-Length
Description
When downloading from a server that does not return
Content-LengthorAccept-Rangesheaders, theDownloadProgressChangedevent reportsProgressPercentage = 100%immediately after the first chunk is received, even though the download is still in progress.In this scenario,
TotalBytesToReceiveis incorrectly set equal toReceivedBytesToReceive, makingProgressPercentagealways compute to 100%. This is misleading — the download is far from complete.Expected Behavior
When
Content-Lengthis unknown:TotalBytesToReceiveshould remain0(indicating unknown total size)ProgressPercentageshould remain0(cannot be calculated)ReceivedBytesSizeshould continue to update normallyDownloadProgressChangedevent should continue to fire as data is receivedActual Behavior
TotalBytesToReceiveis set to the same value asReceivedBytesSizeafter the first chunkProgressPercentageis always100%DownloadProgressChangedevent fires only once or very few times, then stopsRoot Cause
When the server does not provide
Content-Length, the library has no way to know the total file size. Instead of keepingTotalBytesToReceive = 0, the library dynamically setsTotalBytesToReceive = ReceivedBytesSize, which causesProgressPercentageto always equal 100%.Reproduction
The PHP Windows downloads server (using myracloud CDN) does not return
Content-LengthorAccept-Ranges:Note: no
Content-Length, noAccept-Ranges.Minimal Repro Program
Observed Output
After the first progress event,
TotalBytesToReceiveis set to 3776 (same asReceivedBytesSize), andProgressPercentageis 100%. The actual file is ~33MB and is still being written to disk, but no furtherDownloadProgressChangedevents are fired.Note:
ChunkDownloadProgressChangeddoes continue to fire with correctReceivedBytesSizevalues, butDownloadProgressChangedstops after the first report.Workaround
Consumers can work around this by:
ChunkDownloadProgressChangedinstead ofDownloadProgressChangedto getReceivedBytesSizeupdatesProgressPercentagewhenTotalBytesToReceiveequalsReceivedBytesSize(or whenTotalBytesToReceivewas initially 0)Environment