Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion formwork/src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ public function run(): Response

DynamicFieldValue::$vars = $this->container->call(require $this->config()->get('system.fields.dynamic.vars.file'));

$request = $this->request();

$response = $this->router()->dispatch();

$response->send();
$response->prepare($request)->send();

return $response;
}
Expand Down
4 changes: 2 additions & 2 deletions formwork/src/ErrorHandlers.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public function displayErrorPage(ResponseStatus $responseStatus = ResponseStatus
Response::cleanOutputBuffers();

if ($this->request->isXmlHttpRequest()) {
JsonResponse::error('Error', $responseStatus)->send();
JsonResponse::error('Error', $responseStatus)->prepare($this->request)->send();
} else {
$view = $this->viewFactory->make('errors.error', ['status' => $responseStatus->code(), 'message' => $responseStatus->message(), 'throwable' => $throwable]);
$response = new Response($view->render(), $responseStatus);
$response->send();
$response->prepare($this->request)->send();
// Don't exit, otherwise the error will not be logged
}
}
Expand Down
94 changes: 90 additions & 4 deletions formwork/src/Http/FileResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@

use Formwork\Http\Utils\Header;
use Formwork\Utils\FileSystem;
use RuntimeException;

class FileResponse extends Response
{
protected const CHUNK_SIZE = 512 * 1024;

protected int $fileSize;

protected int $offset = 0;

protected int $length;

/**
* @inheritdoc
*/
public function __construct(string $path, ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = [], bool $download = false)
public function __construct(protected string $path, ResponseStatus $responseStatus = ResponseStatus::OK, array $headers = [], bool $download = false)
{
$this->fileSize = FileSystem::fileSize($path);

$headers += [
'Content-Type' => FileSystem::mimeType($path),
'Content-Disposition' => $download ? Header::make(['attachment', 'filename' => basename($path)]) : 'inline',
'Content-Length' => (string) FileSystem::fileSize($path),
'Content-Length' => (string) $this->fileSize,
];
parent::__construct(FileSystem::read($path), $responseStatus, $headers);

parent::__construct('', $responseStatus, $headers);
}

/**
Expand All @@ -26,6 +38,80 @@ public function __construct(string $path, ResponseStatus $responseStatus = Respo
public function send(): void
{
parent::cleanOutputBuffers();
parent::send();

$this->sendHeaders();

$file = fopen($this->path, 'r');
$output = fopen('php://output', 'w');

if ($output === false) {
throw new RuntimeException('Unable to open output stream');
}

if ($file === false) {
throw new RuntimeException('Unable to open file: ' . $this->path);
}

ignore_user_abort(true);

if ($this->offset > 0) {
fseek($file, $this->offset);
}

$length = $this->length ?? $this->fileSize;

while ($length > 0 && !feof($file)) {
$read = fread($file, self::CHUNK_SIZE);

if ($read === false) {
break;
}

$written = fwrite($output, $read);

if (connection_aborted() || $written === false) {
break;
}

$length -= $written;
}

fclose($output);
fclose($file);
}

public function prepare(Request $request): static
{
parent::prepare($request);

if (!isset($this->headers['Accept-Ranges']) && $request->method() === RequestMethod::GET) {
$this->headers['Accept-Ranges'] = 'bytes';
}

if ($request->method() === RequestMethod::GET && preg_match('/^bytes=(\d+)?-(\d+)?$/', $request->headers()->get('Range', ''), $matches, PREG_UNMATCHED_AS_NULL)) {
[, $start, $end] = $matches;

if ($start === null) {
$start = max(0, $this->fileSize - (int) $end);
$end = $this->fileSize - 1;
} elseif ($end === null || $end > $this->fileSize - 1) {
$end = $this->fileSize - 1;
}

$this->offset = (int) $start;

if ($start > $end) {
$this->length = 0;
$this->responseStatus = ResponseStatus::RangeNotSatisfiable;
$this->headers['Content-Range'] = sprintf('bytes */%s', $this->fileSize);
} else {
$this->length = (int) ($end - $start + 1);
$this->responseStatus = ResponseStatus::PartialContent;
$this->headers['Content-Range'] = sprintf('bytes %s-%s/%s', $start, $end, $this->fileSize);
$this->headers['Content-Length'] = sprintf('%s', $this->length);
}
}

return $this;
}
}
8 changes: 8 additions & 0 deletions formwork/src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public function headers(): array
return $this->headers;
}

/**
* Prepare response according to the given HTTP request
*/
public function prepare(Request $request): static
{
return $this;
}

/**
* Send HTTP status
*/
Expand Down
5 changes: 5 additions & 0 deletions formwork/src/Http/ResponseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public function status(): ResponseStatus;
*/
public function headers(): array;

/**
* Prepare response according to the given HTTP request
*/
public function prepare(Request $request): static;

/**
* Send HTTP status
*/
Expand Down
1 change: 1 addition & 0 deletions formwork/src/Panel/Controllers/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ protected function ensurePermission(string $permission): void
if (!$this->user()->permissions()->has($permission)) {
$this->container->build(ErrorsController::class)
->forbidden()
->prepare($this->request)
->send();
exit;
}
Expand Down
2 changes: 1 addition & 1 deletion formwork/src/Services/Loaders/PanelServiceLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function onResolved(object $service, Container $container): void
if ($service->isLoggedIn() && $this->config->get('system.errors.setHandlers')) {
$errorsController = $this->container->build(ErrorsController::class);
set_exception_handler(function (Throwable $throwable) use ($errorsController): never {
$errorsController->internalServerError($throwable)->send();
$errorsController->internalServerError($throwable)->prepare($this->request)->send();
throw $throwable;
});
}
Expand Down
2 changes: 1 addition & 1 deletion panel/views/fields/partials/filelist.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<?php endif ?>
<?php if ($file->type() === 'video') : ?>
<video class="file-thumbnail">
<source src="<?= $file->uri() ?>" type="<?= $file->mimeType() ?>" />
<source src="<?= $file->uri() ?>" type="<?= $file->mimeType() ?>" preload="metadata" />
</video>
<?php endif ?>
<div class="file-icon"><?= $this->icon(is_null($file->type()) ? 'file' : 'file-' . $file->type()) ?></div>
Expand Down