mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
6e47d76ba6
Client file sharing, rebuilt from the ground up: a private area per client, resumable uploads, folders, groups and categories, sharing with expiry dates and download limits, comments, file versions, an activity log, a REST API, and sixteen languages. This repository begins here. ProjectSend 2 was developed privately, and that development history is not published — the previous generation remains available, with its own history, at projectsend/legacy. Free software under the GNU General Public License v2, or (at your option) any later version.
76 lines
2.3 KiB
PHP
76 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Files\Uploads;
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Audit\Action;
|
|
use App\Modules\Audit\ActivityLogger;
|
|
use App\Modules\Files\Models\File;
|
|
|
|
/**
|
|
* The single place a stored payload becomes a File record — shared by
|
|
* the plain intake and the chunked-upload completion, so metadata
|
|
* persistence and auditing never diverge.
|
|
*/
|
|
class StoreUploadedFile
|
|
{
|
|
public function __construct(
|
|
private readonly ActivityLogger $activity,
|
|
) {}
|
|
|
|
public function create(
|
|
User $uploader,
|
|
string $originalName,
|
|
string $path,
|
|
string $mimeType,
|
|
int $size,
|
|
string $checksum,
|
|
?string $name = null,
|
|
?string $description = null,
|
|
?int $folderId = null,
|
|
string $disk = 'files',
|
|
Action $action = Action::FileUploaded,
|
|
): File {
|
|
$originalName = self::sanitizeFilename($originalName);
|
|
|
|
$file = File::query()->create([
|
|
'uploaded_by' => $uploader->id,
|
|
'folder_id' => $folderId,
|
|
'name' => $name !== null && $name !== ''
|
|
? $name
|
|
: pathinfo($originalName, PATHINFO_FILENAME),
|
|
'description' => $description,
|
|
'original_name' => $originalName,
|
|
'path' => $path,
|
|
'disk' => $disk,
|
|
'mime_type' => $mimeType,
|
|
'size' => $size,
|
|
'checksum' => $checksum,
|
|
]);
|
|
|
|
$this->activity->log($action, $uploader, $file);
|
|
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* An uploader picks original_name freely — it is validated for length
|
|
* and nothing else — and it is echoed back in the Content-Disposition
|
|
* header of every download, preview and thumbnail response. A CR or LF
|
|
* in a header value is header injection; PHP's header() refuses to
|
|
* emit one, so today that fails closed as a 500 rather than a split
|
|
* response, but a filename should not be able to break the response at
|
|
* all. Strip the whole C0/C1 control range once, here, rather than
|
|
* hoping each of the five emission sites remembers to.
|
|
*/
|
|
public static function sanitizeFilename(string $name): string
|
|
{
|
|
$clean = preg_replace('/[\x00-\x1F\x7F]/u', '', $name) ?? $name;
|
|
$clean = trim($clean);
|
|
|
|
return $clean === '' ? 'file' : $clean;
|
|
}
|
|
}
|