Skip to content

Commit 91d8d6e

Browse files
committed
Range requests: Added test cases to cover functionality
Fixed some found issues in the process.
1 parent d947625 commit 91d8d6e

File tree

2 files changed

+111
-10
lines changed

2 files changed

+111
-10
lines changed

app/Http/RangeSupportedStream.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
*/
1414
class RangeSupportedStream
1515
{
16-
protected string $sniffContent;
17-
protected array $responseHeaders;
16+
protected string $sniffContent = '';
17+
protected array $responseHeaders = [];
1818
protected int $responseStatus = 200;
1919

2020
protected int $responseLength = 0;
@@ -53,16 +53,20 @@ public function outputAndClose(): void
5353
}
5454

5555
$outStream = fopen('php://output', 'w');
56-
$sniffOffset = strlen($this->sniffContent);
56+
$sniffLength = strlen($this->sniffContent);
57+
$bytesToWrite = $this->responseLength;
5758

58-
if (!empty($this->sniffContent) && $this->responseOffset < $sniffOffset) {
59-
$sniffOutput = substr($this->sniffContent, $this->responseOffset, min($sniffOffset, $this->responseLength));
59+
if ($sniffLength > 0 && $this->responseOffset < $sniffLength) {
60+
$sniffEnd = min($sniffLength, $bytesToWrite + $this->responseOffset);
61+
$sniffOutLength = $sniffEnd - $this->responseOffset;
62+
$sniffOutput = substr($this->sniffContent, $this->responseOffset, $sniffOutLength);
6063
fwrite($outStream, $sniffOutput);
64+
$bytesToWrite -= $sniffOutLength;
6165
} else if ($this->responseOffset !== 0) {
6266
fseek($this->stream, $this->responseOffset);
6367
}
6468

65-
stream_copy_to_stream($this->stream, $outStream, $this->responseLength);
69+
stream_copy_to_stream($this->stream, $outStream, $bytesToWrite);
6670

6771
fclose($this->stream);
6872
fclose($outStream);
@@ -124,10 +128,6 @@ protected function getRangeFromRequest(Request $request): ?array
124128
$start = (int) $start;
125129
}
126130

127-
if ($start > $end) {
128-
return null;
129-
}
130-
131131
$end = min($end, $this->fileSize - 1);
132132
return [$start, $end];
133133
}

tests/Uploads/AttachmentTest.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,105 @@ public function test_file_upload_works_when_local_secure_restricted_is_in_use()
316316
$this->assertFileExists(storage_path($attachment->path));
317317
$this->files->deleteAllAttachmentFiles();
318318
}
319+
320+
public function test_file_get_range_access()
321+
{
322+
$page = $this->entities->page();
323+
$this->asAdmin();
324+
$attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', 'abc123456', 'text/plain');
325+
326+
// Download access
327+
$resp = $this->get($attachment->getUrl(), ['Range' => 'bytes=3-5']);
328+
$resp->assertStatus(206);
329+
$resp->assertStreamedContent('123');
330+
$resp->assertHeader('Content-Length', '3');
331+
$resp->assertHeader('Content-Range', 'bytes 3-5/9');
332+
333+
// Inline access
334+
$resp = $this->get($attachment->getUrl(true), ['Range' => 'bytes=5-7']);
335+
$resp->assertStatus(206);
336+
$resp->assertStreamedContent('345');
337+
$resp->assertHeader('Content-Length', '3');
338+
$resp->assertHeader('Content-Range', 'bytes 5-7/9');
339+
340+
$this->files->deleteAllAttachmentFiles();
341+
}
342+
343+
public function test_file_head_range_returns_no_content()
344+
{
345+
$page = $this->entities->page();
346+
$this->asAdmin();
347+
$attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', 'abc123456', 'text/plain');
348+
349+
$resp = $this->head($attachment->getUrl(), ['Range' => 'bytes=0-9']);
350+
$resp->assertStreamedContent('');
351+
$resp->assertHeader('Content-Length', '9');
352+
$resp->assertStatus(200);
353+
354+
$this->files->deleteAllAttachmentFiles();
355+
}
356+
357+
public function test_file_head_range_edge_cases()
358+
{
359+
$page = $this->entities->page();
360+
$this->asAdmin();
361+
362+
// Mime-type "sniffing" happens on first 2k bytes, hence this content (2005 bytes)
363+
$content = '01234' . str_repeat('a', 1990) . '0123456789';
364+
$attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', $content, 'text/plain');
365+
366+
// Test for both inline and download attachment serving
367+
foreach ([true, false] as $isInline) {
368+
// No end range
369+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=5-']);
370+
$resp->assertStreamedContent(substr($content, 5));
371+
$resp->assertHeader('Content-Length', '2000');
372+
$resp->assertHeader('Content-Range', 'bytes 5-2004/2005');
373+
$resp->assertStatus(206);
374+
375+
// End only range
376+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=-10']);
377+
$resp->assertStreamedContent('0123456789');
378+
$resp->assertHeader('Content-Length', '10');
379+
$resp->assertHeader('Content-Range', 'bytes 1995-2004/2005');
380+
$resp->assertStatus(206);
381+
382+
// Range across sniff point
383+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=1997-2002']);
384+
$resp->assertStreamedContent('234567');
385+
$resp->assertHeader('Content-Length', '6');
386+
$resp->assertHeader('Content-Range', 'bytes 1997-2002/2005');
387+
$resp->assertStatus(206);
388+
389+
// Range up to sniff point
390+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=0-1997']);
391+
$resp->assertHeader('Content-Length', '1998');
392+
$resp->assertHeader('Content-Range', 'bytes 0-1997/2005');
393+
$resp->assertStreamedContent(substr($content, 0, 1998));
394+
$resp->assertStatus(206);
395+
396+
// Range beyond sniff point
397+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=2001-2003']);
398+
$resp->assertStreamedContent('678');
399+
$resp->assertHeader('Content-Length', '3');
400+
$resp->assertHeader('Content-Range', 'bytes 2001-2003/2005');
401+
$resp->assertStatus(206);
402+
403+
// Range beyond content
404+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=0-2010']);
405+
$resp->assertStreamedContent($content);
406+
$resp->assertHeader('Content-Length', '2005');
407+
$resp->assertHeaderMissing('Content-Range');
408+
$resp->assertStatus(200);
409+
410+
// Range start before end
411+
$resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=50-10']);
412+
$resp->assertStreamedContent($content);
413+
$resp->assertHeader('Content-Length', '2005');
414+
$resp->assertHeader('Content-Range', 'bytes */2005');
415+
$resp->assertStatus(416);
416+
}
417+
418+
$this->files->deleteAllAttachmentFiles();
419+
}
319420
}

0 commit comments

Comments
 (0)