Skip to content

Commit

Permalink
Prevent double-closing file when serving partial content
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed Jan 5, 2025
1 parent 6f3886c commit 65eb268
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 3 deletions.
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Web change log

## ?.?.? / ????-??-??

* Added logic to prevent double-closing file when serving partial content
(@thekid)

## 4.5.1 / 2024-09-29

* Fixed error *[] operator not supported for strings* when handling array
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/web/io/StaticContent.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public function serve($request, $response, $target, $mimeType= null) {
$out->write($trailer);
}
} finally {
$file->close();
$file->isOpen() && $file->close();
$out->close();
}
}
Expand Down
33 changes: 31 additions & 2 deletions src/test/php/web/unittest/io/StaticContentTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ class StaticContentTest {
* @param ?io.File $file
* @param ?string $mimeType
* @param ?web.io.TestInput $input
* @param function(Iterator): void $writer
* @return string
*/
private function serve($content, $file, $mimeType= null, $input= null) {
private function serve($content, $file, $mimeType= null, $input= null, $writer= 'iterator_count') {
$res= new Response(new TestOutput());
$req= new Request($input ?? new TestInput('GET', '/'));

try {
foreach ($content->serve($req, $res, $file, $mimeType) ?? [] as $_) { }
$writer($content->serve($req, $res, $file, $mimeType));
} finally {
$res->end();
}
Expand Down Expand Up @@ -274,4 +275,32 @@ public function range_unsatisfiable($range) {
]))
);
}

#[Test]
public function file_closed_after_first_chunk() {
$chunk= StaticContent::CHUNKSIZE;
$headers= ['Range' => "bytes=0-{$chunk}"];
$file= (new TempFile(self::class))->containing(str_repeat('*', $chunk + 1));

// Close files after having written
$writer= function($it) use($file) {
$it->next();
$file->close();
while ($it->valid()) $it->next();
};

// Assert first chunk has been written
Assert::equals(
"HTTP/1.1 206 Partial Content\r\n".
"Accept-Ranges: bytes\r\n".
"Last-Modified: <Date>\r\n".
"X-Content-Type-Options: nosniff\r\n".
"Content-Type: application/octet-stream\r\n".
"Content-Range: bytes 0-{$chunk}/".($chunk + 1)."\r\n".
"Content-Length: ".($chunk + 1)."\r\n".
"\r\n".
str_repeat('*', $chunk + 1),
$this->serve(new StaticContent(), $file, null, new TestInput('GET', '/', $headers), $writer)
);
}
}

0 comments on commit 65eb268

Please sign in to comment.