use BookStack\Exceptions\FileUploadException;
use Exception;
-use Illuminate\Contracts\Filesystem\Factory as FileSystem;
-use Illuminate\Contracts\Filesystem\FileNotFoundException;
-use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+use Illuminate\Contracts\Filesystem\Filesystem as Storage;
+use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
-use League\Flysystem\Util;
+use League\Flysystem\WhitespacePathNormalizer;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachmentService
{
- protected $fileSystem;
+ protected FilesystemManager $fileSystem;
/**
* AttachmentService constructor.
*/
- public function __construct(FileSystem $fileSystem)
+ public function __construct(FilesystemManager $fileSystem)
{
$this->fileSystem = $fileSystem;
}
/**
* Get the storage that will be used for storing files.
*/
- protected function getStorage(): FileSystemInstance
+ protected function getStorageDisk(): Storage
{
return $this->fileSystem->disk($this->getStorageDiskName());
}
// Change to our secure-attachment disk if any of the local options
// are used to prevent escaping that location.
- if ($storageType === 'local' || $storageType === 'local_secure') {
+ if ($storageType === 'local' || $storageType === 'local_secure' || $storageType === 'local_secure_restricted') {
$storageType = 'local_secure_attachments';
}
*/
protected function adjustPathForStorageDisk(string $path): string
{
- $path = Util::normalizePath(str_replace('uploads/files/', '', $path));
+ $path = (new WhitespacePathNormalizer())->normalizePath(str_replace('uploads/files/', '', $path));
if ($this->getStorageDiskName() === 'local_secure_attachments') {
return $path;
}
/**
- * Get an attachment from storage.
+ * Stream an attachment from storage.
*
- * @throws FileNotFoundException
+ * @return resource|null
*/
- public function getAttachmentFromStorage(Attachment $attachment): string
+ public function streamAttachmentFromStorage(Attachment $attachment)
{
- return $this->getStorage()->get($this->adjustPathForStorageDisk($attachment->path));
+ return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path));
+ }
+
+ /**
+ * Read the file size of an attachment from storage, in bytes.
+ */
+ public function getAttachmentFileSize(Attachment $attachment): int
+ {
+ return $this->getStorageDisk()->size($this->adjustPathForStorageDisk($attachment->path));
}
/**
$link = trim($requestData['link'] ?? '');
if (!empty($link)) {
- $attachment->path = $requestData['link'];
if (!$attachment->external) {
$this->deleteFileInStorage($attachment);
$attachment->external = true;
+ $attachment->extension = '';
}
+ $attachment->path = $requestData['link'];
}
$attachment->save();
- return $attachment;
+ return $attachment->refresh();
}
/**
*/
protected function deleteFileInStorage(Attachment $attachment)
{
- $storage = $this->getStorage();
+ $storage = $this->getStorageDisk();
$dirPath = $this->adjustPathForStorageDisk(dirname($attachment->path));
$storage->delete($this->adjustPathForStorageDisk($attachment->path));
*/
protected function putFileInStorage(UploadedFile $uploadedFile): string
{
- $attachmentData = file_get_contents($uploadedFile->getRealPath());
-
- $storage = $this->getStorage();
+ $storage = $this->getStorageDisk();
$basePath = 'uploads/files/' . date('Y-m-M') . '/';
- $uploadFileName = Str::random(16) . '.' . $uploadedFile->getClientOriginalExtension();
+ $uploadFileName = Str::random(16) . '-' . $uploadedFile->getClientOriginalExtension();
while ($storage->exists($this->adjustPathForStorageDisk($basePath . $uploadFileName))) {
$uploadFileName = Str::random(3) . $uploadFileName;
}
+ $attachmentStream = fopen($uploadedFile->getRealPath(), 'r');
$attachmentPath = $basePath . $uploadFileName;
try {
- $storage->put($this->adjustPathForStorageDisk($attachmentPath), $attachmentData);
+ $storage->writeStream($this->adjustPathForStorageDisk($attachmentPath), $attachmentStream);
} catch (Exception $e) {
Log::error('Error when attempting file upload:' . $e->getMessage());
return $attachmentPath;
}
+
+ /**
+ * Get the file validation rules for attachments.
+ */
+ public function getFileValidationRules(): array
+ {
+ return ['file', 'max:' . (config('app.upload_limit') * 1000)];
+ }
}