diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 91c77d6..bb78b4f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -63,3 +63,11 @@ parameters: message: "#^Calling sha1\\(\\) is forbidden, use hash\\(\\) with at least SHA\\-256 for secure hash, or password_hash\\(\\) for passwords\\.$#" count: 1 path: src/FileUploadControl/Storage/Metadata/FileUploadMetadata.php + - # false-positive + message: "#^Method Nepada\\\\FileUploadControl\\\\Storage\\\\ContentRange\\:\\:getRangeSize\\(\\) should return int\\<0, max\\> but returns int\\.$#" + count: 1 + path: src/FileUploadControl/Storage/ContentRange.php + - # should not happen + message: "#^Parameter \\#1 \\$size of static method Nepada\\\\FileUploadControl\\\\Storage\\\\ContentRange\\:\\:ofSize\\(\\) expects int\\<0, max\\>, int given\\.$#" + count: 1 + path: src/FileUploadControl/Storage/FileUploadChunk.php diff --git a/src/FileUploadControl/Storage/ContentRange.php b/src/FileUploadControl/Storage/ContentRange.php index 729ee3d..ea66d81 100644 --- a/src/FileUploadControl/Storage/ContentRange.php +++ b/src/FileUploadControl/Storage/ContentRange.php @@ -11,10 +11,19 @@ final class ContentRange use Nette\SmartObject; + /** + * @var int<0, max> + */ private int $start; + /** + * @var int<0, max> + */ private int $end; + /** + * @var int<0, max> + */ private int $size; private function __construct(int $start, int $end, int $size) @@ -25,17 +34,23 @@ private function __construct(int $start, int $end, int $size) if ($end < $start) { throw new \InvalidArgumentException("Start ($start) cannot be larger than end ($end)."); } - if ($size <= $end) { - throw new \InvalidArgumentException("End ($end) cannot be larger or equal to size ($size)."); + if ($size < $end) { + throw new \InvalidArgumentException("End ($end) cannot be larger than size ($size)."); + } + if ($size > 0 && $size === $end) { + throw new \InvalidArgumentException("End ($end) cannot be equal to size ($size)."); } $this->start = $start; $this->end = $end; $this->size = $size; } + /** + * @param int<0, max> $size + */ public static function ofSize(int $size): self { - return new self(0, $size - 1, $size); + return new self(0, $size === 0 ? 0 : $size - 1, $size); } public static function fromHttpHeaderValue(string $header): self @@ -47,21 +62,33 @@ public static function fromHttpHeaderValue(string $header): self return new self((int) $match[1], (int) $match[2], (int) $match[3]); } + /** + * @return int<0, max> + */ public function getStart(): int { return $this->start; } + /** + * @return int<0, max> + */ public function getEnd(): int { return $this->end; } + /** + * @return int<0, max> + */ public function getRangeSize(): int { - return $this->end - $this->start + 1; + return $this->size === 0 ? 0 : $this->end - $this->start + 1; } + /** + * @return int<0, max> + */ public function getSize(): int { return $this->size; @@ -74,7 +101,7 @@ public function containsFirstByte(): bool public function containsLastByte(): bool { - return $this->end === ($this->size - 1); + return $this->end === max(0, ($this->size - 1)); } } diff --git a/tests/FileUploadControl/Storage/ContentRangeTest.phpt b/tests/FileUploadControl/Storage/ContentRangeTest.phpt index 110504b..b138cd9 100644 --- a/tests/FileUploadControl/Storage/ContentRangeTest.phpt +++ b/tests/FileUploadControl/Storage/ContentRangeTest.phpt @@ -16,6 +16,28 @@ require_once __DIR__ . '/../../bootstrap.php'; class ContentRangeTest extends TestCase { + public function testOfZeroSize(): void + { + $contentRange = ContentRange::ofSize(0); + Assert::same(0, $contentRange->getSize()); + Assert::same(0, $contentRange->getStart()); + Assert::same(0, $contentRange->getEnd()); + Assert::same(0, $contentRange->getRangeSize()); + Assert::true($contentRange->containsFirstByte()); + Assert::true($contentRange->containsLastByte()); + } + + public function testOfOneByteSize(): void + { + $contentRange = ContentRange::ofSize(1); + Assert::same(1, $contentRange->getSize()); + Assert::same(0, $contentRange->getStart()); + Assert::same(0, $contentRange->getEnd()); + Assert::same(1, $contentRange->getRangeSize()); + Assert::true($contentRange->containsFirstByte()); + Assert::true($contentRange->containsLastByte()); + } + public function testOfSize(): void { $contentRange = ContentRange::ofSize(42); @@ -62,9 +84,13 @@ class ContentRangeTest extends TestCase 'headerValue' => 'bflmpsvz', 'expectedError' => "Malformed content-range header 'bflmpsvz'.", ], + [ + 'headerValue' => 'bytes 0-10/5', + 'expectedError' => 'End (10) cannot be larger than size (5).', + ], [ 'headerValue' => 'bytes 0-10/10', - 'expectedError' => 'End (10) cannot be larger or equal to size (10).', + 'expectedError' => 'End (10) cannot be equal to size (10).', ], [ 'headerValue' => 'bytes 10-5/10',