3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00
Files
snipe-it/app/Http/Requests/UploadFileRequest.php
snipe 5478c671ab Fixes #10387, #12460, and #19389 - better validation for text files
Files whose sniff yields nothing usable (empty, octet-stream, INI-shaped) now pass.
2026-07-28 21:16:39 +01:00

121 lines
3.6 KiB
PHP

<?php
namespace App\Http\Requests;
use App\Helpers\Helper;
use App\Http\Traits\ConvertsBase64ToFiles;
use App\Rules\AllowedUploadExtension;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class UploadFileRequest extends Request
{
use ConvertsBase64ToFiles;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
// AllowedUploadExtension replaces Laravel's `mimes:` rule because
// `mimes:` content-sniffs, reverse-maps the detected MIME to a
// single extension, and rejects anything the guesser can't map,
// even when the client extension is on the allowlist. That was
// rejecting legitimate uploads (empty .txt, INI-shaped text,
// Windows-sniffed .csv reporting octet-stream) with a generic
// "check the form below" error. See issues #12460 and #10387.
return [
'file.*' => [
'bail',
'required',
'file',
new AllowedUploadExtension(config('filesystems.allowed_upload_extensions_array')),
'max:'.Helper::file_upload_max_size(),
],
];
}
/**
* Sanitizes (if needed) and Saves a file to the appropriate location
* Returns the 'short' (storage-relative) filename
*/
public function handleFile(string $dirname, string $name_prefix, $file): string
{
$extension = $file->getClientOriginalExtension();
// Prefer the content-sniffed extension for the stored name so a
// rename can't hide the real content type from the filesystem.
// Fall back to the client extension when finfo returns nothing,
// otherwise the stored filename ends in a bare "." and the
// eventual download has no extension.
$stored_extension = $file->guessExtension() ?: strtolower($extension);
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$stored_extension;
// Check for SVG and sanitize it
if ($file->getMimeType() === 'image/svg+xml') {
$uploaded_file = $this->handleSVG($file);
} else {
$uploaded_file = file_get_contents($file);
}
try {
Storage::put($dirname.$file_name, $uploaded_file);
} catch (\Exception $e) {
Log::debug($e);
}
return $file_name;
}
public function handleSVG($file)
{
$sanitizer = new Sanitizer;
$dirtySVG = file_get_contents($file->getRealPath());
return $sanitizer->sanitize($dirtySVG);
}
/**
* Get the validation error messages that apply to the request, but
* replace the attribute name with the name of the file that was attempted and failed
* to make it clearer to the user which file is the bad one.
*/
public function attributes(): array
{
$attributes = [];
if (($this->file) && (is_array($this->file))) {
for ($i = 0; $i < count($this->file); $i++) {
try {
if ($this->file[$i]) {
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
}
} catch (\Exception $e) {
$attributes['file.'.$i] = 'Invalid file';
}
}
}
return $attributes;
}
}