Skip to content

Commit 523c3fa

Browse files
authored
Merge pull request php-curl-class#411 from zachborboa/master
Add additional resume Curl::download() tests
2 parents e38820a + 6b63561 commit 523c3fa

File tree

3 files changed

+69
-25
lines changed

3 files changed

+69
-25
lines changed

tests/PHPCurlClass/ContentRangeServer.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ class ContentRangeServer
88
{
99
public function serve($path)
1010
{
11-
$range = new RangeHeader($_SERVER['HTTP_RANGE']);
12-
1311
$filesize = filesize($path);
1412
$fp = fopen($path, 'r');
1513

@@ -19,16 +17,25 @@ public function serve($path)
1917
header('Accept-Ranges: bytes');
2018
fpassthru($fp);
2119
} else {
20+
$range = new RangeHeader($_SERVER['HTTP_RANGE'], $path);
21+
22+
if (!$range->isValid()) {
23+
header('HTTP/1.1 416 Requested Range Not Satisfiable');
24+
header('Content-Range: ' . $range->getContentRangeHeader());
25+
exit;
26+
}
27+
28+
$length = $range->getLength();
29+
2230
header('HTTP/1.1 206 Partial Content');
23-
header('Content-Length: ' . $range->getLength($filesize));
24-
header('Content-Range: ' . $range->getContentRangeHeader($filesize));
31+
header('Content-Length: ' . $length);
32+
header('Content-Range: ' . $range->getContentRangeHeader());
2533

26-
$start = $range->getFirstBytePosition($filesize);
34+
$start = $range->getFirstBytePosition();
2735
if ($start > 0) {
2836
fseek($fp, $start, SEEK_SET);
2937
}
3038

31-
$length = $range->getLength($filesize);
3239
$chunk_size = 4096;
3340
while ($length) {
3441
$read = $length > $chunk_size ? $chunk_size : $length;

tests/PHPCurlClass/PHPCurlClassTest.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,24 @@ public function testDownloadRange()
692692
$filesize - 3,
693693
$filesize - 2,
694694
$filesize - 1,
695+
696+
// A partial temporary file having the exact same file size as the complete source file should only
697+
// occur under certain circumstances (almost never). When the download successfully completed, the
698+
// temporary file should have been moved to the download destination save path. However, it is possible
699+
// that a larger file download was interrupted after which the source file was updated and now has the
700+
// exact same file size as the partial temporary. When resuming the download, the range is now
701+
// unsatisfiable as the first byte position exceeds the available range. The entire file should be
702+
// downloaded again.
703+
$filesize - 0,
704+
705+
// A partial temporary file having a larger file size than the complete source file should only occur
706+
// under certain circumstances. This is possible when a download was interrupted after which the source
707+
// file was updated with a smaller file. When resuming the download, the range is now unsatisfiable as
708+
// the first byte position exceeds the the available range. The entire file should be downloaded again.
709+
$filesize + 1,
710+
$filesize + 2,
711+
$filesize + 3,
712+
695713
) as $length) {
696714

697715
$source = Test::TEST_URL;
@@ -749,10 +767,12 @@ public function testDownloadRange()
749767
$this->assertEquals($expected_bytes_downloaded, $bytes_downloaded);
750768
}
751769
$this->assertEquals($expected_http_status_code, $curl->httpStatusCode);
752-
$this->assertEquals($filesize, filesize($destination));
753770

754-
unlink($destination);
755-
$this->assertFalse(file_exists($destination));
771+
if (!$curl->error) {
772+
$this->assertEquals($filesize, filesize($destination));
773+
unlink($destination);
774+
$this->assertFalse(file_exists($destination));
775+
}
756776
}
757777

758778
// Remove server file.

tests/PHPCurlClass/RangeHeader.php

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,65 @@ class RangeHeader
66
{
77
private $first_byte;
88
private $last_byte;
9+
private $filesize;
10+
private $is_valid = true;
911

10-
public function __construct($http_range_header)
12+
public function __construct($http_range_header, $file_path)
1113
{
1214
// Simulate basic support for the Content-Range header.
1315
preg_match('/bytes=(\d+)?-(\d+)?/', $http_range_header, $matches);
1416
$this->first_byte = isset($matches['1']) ? (int)$matches['1'] : null;
1517
$this->last_byte = isset($matches['2']) ? (int)$matches['2'] : null;
18+
19+
$this->filesize = filesize($file_path);
20+
21+
// Start position begins after end of file.
22+
if ($this->first_byte >= $this->filesize) {
23+
$this->is_valid = false;
24+
}
25+
26+
// "If the last-byte-pos value is present, it MUST be greater than or equal to the first-byte-pos in that
27+
// byte-range-spec, or the byte- range-spec is syntactically invalid."
28+
if (!($this->last_byte === null) && !($this->last_byte >= $this->first_byte)) {
29+
$this->is_valid = false;
30+
}
1631
}
1732

18-
public function getFirstBytePosition($file_size)
33+
public function getFirstBytePosition()
1934
{
20-
$size = (int)$file_size;
21-
2235
if ($this->first_byte === null) {
23-
return $size - 1 - $this->last_byte;
36+
return $this->filesize - 1 - $this->last_byte;
2437
}
2538

2639
return $this->first_byte;
2740
}
2841

29-
public function getLastBytePosition($file_size)
42+
public function getLastBytePosition()
3043
{
31-
$size = (int)$file_size;
32-
3344
if ($this->last_byte === null) {
34-
return $size - 1;
45+
return $this->filesize - 1;
3546
}
3647

3748
return $this->last_byte;
3849
}
3950

40-
public function getLength($file_size)
51+
public function getLength()
52+
{
53+
return $this->getLastBytePosition() - $this->getFirstBytePosition() + 1;
54+
}
55+
56+
public function getByteRangeSpec()
4157
{
42-
$size = (int)$file_size;
58+
return $this->is_valid ? $this->getFirstBytePosition() . '-' . $this->getLastBytePosition() : '*';
59+
}
4360

44-
return $this->getLastBytePosition($size) - $this->getFirstBytePosition($size) + 1;
61+
public function getContentRangeHeader()
62+
{
63+
return 'bytes ' . $this->getByteRangeSpec() . '/' . $this->filesize;
4564
}
4665

47-
public function getContentRangeHeader($file_size)
66+
public function isValid()
4867
{
49-
return
50-
'bytes ' . $this->getFirstBytePosition($file_size) . '-' . $this->getLastBytePosition($file_size) . '/' .
51-
$file_size;
68+
return $this->is_valid;
5269
}
5370
}

0 commit comments

Comments
 (0)