mirror of
https://github.com/tiennm99/coolify.git
synced 2026-07-19 06:18:13 +00:00
fix(service): block UI editing of file volumes exceeding 5 MiB
Large host files mounted via Docker volumes caused the storages page to become unusable — full file content was stored in the encrypted mediumText column and serialised into the Livewire payload, crashing the browser. - Add MAX_CONTENT_SIZE (5 MiB), BINARY_PLACEHOLDER, and TOO_LARGE_PLACEHOLDER constants to LocalFileVolume - Check remote file size via stat/wc before cat in loadStorageOnServer and saveStorageOnServer; store placeholder instead of content when limit exceeded - Expose is_too_large computed attribute (appended for Livewire serialisation) - Guard submit, instantSave, and syncData in FileStorage Livewire component - Truncate oversized content in Storage::refreshStorages to prevent payload bloat - Show distinct warning banner in file-storage blade; mark textarea readonly and hide Save/Convert buttons for too-large files - Add unit tests covering constants, computed flags, and toArray serialisation Fixes #4701
This commit is contained in:
@@ -10,6 +10,12 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class LocalFileVolume extends BaseModel
|
||||
{
|
||||
public const MAX_CONTENT_SIZE = 5_242_880;
|
||||
|
||||
public const BINARY_PLACEHOLDER = '[binary file]';
|
||||
|
||||
public const TOO_LARGE_PLACEHOLDER = '[file too large to display]';
|
||||
|
||||
protected $casts = [
|
||||
// 'fs_path' => 'encrypted',
|
||||
// 'mount_path' => 'encrypted',
|
||||
@@ -33,7 +39,7 @@ class LocalFileVolume extends BaseModel
|
||||
'is_preview_suffix_enabled',
|
||||
];
|
||||
|
||||
public $appends = ['is_binary'];
|
||||
public $appends = ['is_binary', 'is_too_large'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -46,9 +52,14 @@ class LocalFileVolume extends BaseModel
|
||||
protected function isBinary(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->content === '[binary file]';
|
||||
}
|
||||
get: fn () => $this->content === self::BINARY_PLACEHOLDER
|
||||
);
|
||||
}
|
||||
|
||||
protected function isTooLarge(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->content === self::TOO_LARGE_PLACEHOLDER
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,10 +92,17 @@ class LocalFileVolume extends BaseModel
|
||||
|
||||
$isFile = instant_remote_process(["test -f {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'OK') {
|
||||
if ($this->remoteFileExceedsLimit($escapedPath, $server)) {
|
||||
$this->content = self::TOO_LARGE_PLACEHOLDER;
|
||||
$this->is_directory = false;
|
||||
$this->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$content = instant_remote_process(["cat {$escapedPath}"], $server, false);
|
||||
// Check if content contains binary data by looking for null bytes or non-printable characters
|
||||
if (str_contains($content, "\0") || preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $content)) {
|
||||
$content = '[binary file]';
|
||||
$content = self::BINARY_PLACEHOLDER;
|
||||
}
|
||||
$this->content = $content;
|
||||
$this->is_directory = false;
|
||||
@@ -92,6 +110,18 @@ class LocalFileVolume extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
protected function remoteFileExceedsLimit(string $escapedPath, $server): bool
|
||||
{
|
||||
$sizeOutput = instant_remote_process(
|
||||
["stat -c%s {$escapedPath} 2>/dev/null || wc -c < {$escapedPath}"],
|
||||
$server,
|
||||
false,
|
||||
);
|
||||
$size = (int) trim((string) $sizeOutput);
|
||||
|
||||
return $size > self::MAX_CONTENT_SIZE;
|
||||
}
|
||||
|
||||
public function deleteStorageOnServer()
|
||||
{
|
||||
$this->load(['service']);
|
||||
@@ -173,9 +203,12 @@ class LocalFileVolume extends BaseModel
|
||||
$isFile = instant_remote_process(["test -f {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'OK' && $this->is_directory) {
|
||||
$content = instant_remote_process(["cat {$escapedPath}"], $server, false);
|
||||
if ($this->remoteFileExceedsLimit($escapedPath, $server)) {
|
||||
$this->content = self::TOO_LARGE_PLACEHOLDER;
|
||||
} else {
|
||||
$this->content = instant_remote_process(["cat {$escapedPath}"], $server, false);
|
||||
}
|
||||
$this->is_directory = false;
|
||||
$this->content = $content;
|
||||
$this->save();
|
||||
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
||||
|
||||
Reference in New Issue
Block a user