femto-backend/Femto.Modules.Media/Infrastructure/FilesystemStorageProvider.cs
2025-05-04 21:46:24 +02:00

62 lines
1.9 KiB
C#

using Femto.Modules.Media.Data;
namespace Femto.Modules.Media.Infrastructure;
internal class FilesystemStorageProvider : IStorageProvider
{
private readonly string _storageRoot;
public FilesystemStorageProvider(string storageRoot)
{
if (string.IsNullOrWhiteSpace(storageRoot))
throw new ArgumentException(
"Storage root cannot be null or empty.",
nameof(storageRoot)
);
this._storageRoot = Path.GetFullPath(storageRoot);
if (!Directory.Exists(this._storageRoot))
{
Directory.CreateDirectory(this._storageRoot);
}
}
public async Task SaveBlob(string id, Stream data)
{
string filePath = this.GetSafeFilePath(id);
// Ensure the directory exists
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
await data.CopyToAsync(fileStream);
}
public async Task<Stream> LoadBlob(string id)
{
string filePath = this.GetSafeFilePath(id);
if (!File.Exists(filePath))
throw new FileNotFoundException("The blob with the specified ID was not found.", id);
Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return fileStream;
}
private string GetSafeFilePath(string id)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentException("Blob ID cannot be null or empty.", nameof(id));
// Sanitize and validate the path
string combinedPath = Path.Combine(this._storageRoot, id);
string fullPath = Path.GetFullPath(combinedPath);
if (!fullPath.StartsWith(this._storageRoot, StringComparison.Ordinal))
throw new UnauthorizedAccessException("Access to the path is denied.");
return fullPath;
}
}