Skip to content

DownloadProgressChanged reports 100% immediately when server omits Content-Length #230

@jinyiouzhen

Description

@jinyiouzhen

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:

  1. Subscribing to ChunkDownloadProgressChanged instead of DownloadProgressChanged to get ReceivedBytesSize updates
  2. Ignoring ProgressPercentage when TotalBytesToReceive equals ReceivedBytesSize (or when TotalBytesToReceive was initially 0)
  3. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions