mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-09 16:24:59 +00:00
Implements StoredObjectManager for local storage
This commit is contained in:
@@ -11,49 +11,200 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
|
||||
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
class StoredObjectManager implements StoredObjectManagerInterface
|
||||
{
|
||||
private readonly string $baseDir;
|
||||
|
||||
private readonly Filesystem $filesystem;
|
||||
|
||||
public function __construct(
|
||||
ParameterBagInterface $parameterBag,
|
||||
private readonly KeyGenerator $keyGenerator,
|
||||
) {
|
||||
$this->baseDir = $parameterBag->get('chill_doc_store')['local_storage']['base_dir'];
|
||||
$this->filesystem = new Filesystem();
|
||||
}
|
||||
|
||||
public function getLastModified(StoredObject|StoredObjectVersion $document): \DateTimeInterface
|
||||
{
|
||||
// TODO: Implement getLastModified() method.
|
||||
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||
|
||||
if (null === $version) {
|
||||
throw StoredObjectManagerException::storedObjectDoesNotContainsVersion();
|
||||
}
|
||||
|
||||
$path = $this->buildPath($version->getFilename());
|
||||
|
||||
if (false === $ts = filemtime($path)) {
|
||||
throw StoredObjectManagerException::unableToReadDocumentOnDisk($path);
|
||||
}
|
||||
|
||||
return \DateTimeImmutable::createFromFormat('U', (string) $ts);
|
||||
}
|
||||
|
||||
public function getContentLength(StoredObject|StoredObjectVersion $document): int
|
||||
{
|
||||
// TODO: Implement getContentLength() method.
|
||||
return strlen($this->read($document));
|
||||
}
|
||||
|
||||
public function exists(StoredObject|StoredObjectVersion $document): bool
|
||||
{
|
||||
// TODO: Implement exists() method.
|
||||
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||
|
||||
if (null === $version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $this->buildPath($version->getFilename());
|
||||
|
||||
return $this->filesystem->exists($path);
|
||||
}
|
||||
|
||||
public function read(StoredObject|StoredObjectVersion $document): string
|
||||
{
|
||||
// TODO: Implement read() method.
|
||||
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||
|
||||
if (null === $version) {
|
||||
throw StoredObjectManagerException::storedObjectDoesNotContainsVersion();
|
||||
}
|
||||
|
||||
$path = $this->buildPath($version->getFilename());
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw StoredObjectManagerException::unableToFindDocumentOnDisk($path);
|
||||
}
|
||||
|
||||
if (false === $content = file_get_contents($path)) {
|
||||
throw StoredObjectManagerException::unableToReadDocumentOnDisk($path);
|
||||
}
|
||||
|
||||
if (!$this->isVersionEncrypted($version)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$clearData = openssl_decrypt(
|
||||
$content,
|
||||
self::ALGORITHM,
|
||||
// TODO: Why using this library and not use base64_decode() ?
|
||||
Base64Url::decode($version->getKeyInfos()['k']),
|
||||
\OPENSSL_RAW_DATA,
|
||||
pack('C*', ...$version->getIv())
|
||||
);
|
||||
|
||||
if (false === $clearData) {
|
||||
throw StoredObjectManagerException::unableToDecrypt(openssl_error_string());
|
||||
}
|
||||
|
||||
return $clearData;
|
||||
}
|
||||
|
||||
public function write(StoredObject $document, string $clearContent, ?string $contentType = null): StoredObjectVersion
|
||||
{
|
||||
// TODO: Implement write() method.
|
||||
$newIv = $document->isEncrypted() ? $document->getIv() : $this->keyGenerator->generateIv();
|
||||
$newKey = $document->isEncrypted() ? $document->getKeyInfos() : $this->keyGenerator->generateKey(self::ALGORITHM);
|
||||
$newType = $contentType ?? $document->getType();
|
||||
$version = $document->registerVersion(
|
||||
$newIv,
|
||||
$newKey,
|
||||
$newType
|
||||
);
|
||||
|
||||
$encryptedContent = $this->isVersionEncrypted($version)
|
||||
? openssl_encrypt(
|
||||
$clearContent,
|
||||
self::ALGORITHM,
|
||||
// TODO: Why using this library and not use base64_decode() ?
|
||||
Base64Url::decode($version->getKeyInfos()['k']),
|
||||
\OPENSSL_RAW_DATA,
|
||||
pack('C*', ...$version->getIv())
|
||||
)
|
||||
: $clearContent;
|
||||
|
||||
if (false === $encryptedContent) {
|
||||
throw StoredObjectManagerException::unableToEncryptDocument((string) openssl_error_string());
|
||||
}
|
||||
|
||||
$fullPath = $this->buildPath($version->getFilename());
|
||||
$dir = Path::getDirectory($fullPath);
|
||||
|
||||
if (!$this->filesystem->exists($dir)) {
|
||||
$this->filesystem->mkdir($dir);
|
||||
}
|
||||
|
||||
$result = file_put_contents($fullPath, $encryptedContent);
|
||||
|
||||
if (false === $result) {
|
||||
throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
private function buildPath(string $filename): string
|
||||
{
|
||||
$dirs = [$this->baseDir];
|
||||
|
||||
for ($i = 0; $i < min(strlen($filename), 8); ++$i) {
|
||||
$dirs[] = $filename[$i];
|
||||
}
|
||||
|
||||
$dirs[] = $filename;
|
||||
|
||||
return Path::canonicalize(implode(DIRECTORY_SEPARATOR, $dirs));
|
||||
}
|
||||
|
||||
public function delete(StoredObjectVersion $storedObjectVersion): void
|
||||
{
|
||||
// TODO: Implement delete() method.
|
||||
if (!$this->exists($storedObjectVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $this->buildPath($storedObjectVersion->getFilename());
|
||||
|
||||
$this->filesystem->remove($path);
|
||||
$this->removeDirectoriesRecursively(Path::getDirectory($path));
|
||||
}
|
||||
|
||||
private function removeDirectoriesRecursively(string $path): void
|
||||
{
|
||||
if ($path === $this->baseDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = scandir($path);
|
||||
|
||||
// if it does contains only "." and "..", we can remove the directory
|
||||
if (2 === count($files) && in_array('.', $files, true) && in_array('..', $files, true)) {
|
||||
$this->filesystem->remove($path);
|
||||
$this->removeDirectoriesRecursively(Path::getDirectory($path));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws StoredObjectManagerException
|
||||
*/
|
||||
public function etag(StoredObject|StoredObjectVersion $document): string
|
||||
{
|
||||
// TODO: Implement etag() method.
|
||||
return md5($this->read($document));
|
||||
}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
// TODO: Implement clearCache() method.
|
||||
// there is no cache: nothing to do here !
|
||||
}
|
||||
|
||||
private function isVersionEncrypted(StoredObjectVersion $storedObjectVersion): bool
|
||||
{
|
||||
return $storedObjectVersion->isEncrypted();
|
||||
}
|
||||
}
|
||||
|
@@ -25,8 +25,6 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
final class StoredObjectManager implements StoredObjectManagerInterface
|
||||
{
|
||||
private const ALGORITHM = 'AES-256-CBC';
|
||||
|
||||
private array $inMemory = [];
|
||||
|
||||
public function __construct(
|
||||
@@ -362,6 +360,6 @@ final class StoredObjectManager implements StoredObjectManagerInterface
|
||||
|
||||
private function isVersionEncrypted(StoredObjectVersion $storedObjectVersion): bool
|
||||
{
|
||||
return ([] !== $storedObjectVersion->getKeyInfos()) && ([] !== $storedObjectVersion->getIv());
|
||||
return $storedObjectVersion->isEncrypted();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user