btcpayserver/BTCPayServer/Storage/Services/FileService.cs

158 lines
6.3 KiB
C#
Raw Normal View History

2021-03-06 13:36:36 +09:00
#nullable enable
2020-06-28 21:44:35 -05:00
using System;
using System.Collections.Generic;
using System.Globalization;
2022-03-31 11:54:25 +02:00
using System.IO;
using System.Linq;
2022-03-31 11:54:25 +02:00
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using BTCPayServer.Abstractions.Contracts;
2022-03-02 18:05:16 +01:00
using BTCPayServer.Abstractions.Extensions;
2022-03-31 11:54:25 +02:00
using BTCPayServer.Configuration;
using BTCPayServer.Services;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers;
using Microsoft.AspNetCore.Http;
2022-03-31 11:54:25 +02:00
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Storage.Services
{
public class FileService : IFileService
{
2022-03-31 11:54:25 +02:00
private readonly StoredFileRepository _fileRepository;
private readonly IEnumerable<IStorageProviderService> _providers;
2022-03-31 11:54:25 +02:00
private readonly SettingsRepository _settingsRepository;
private readonly IOptions<DataDirectories> _dataDirectories;
private readonly IHttpClientFactory _httpClientFactory;
2022-03-31 11:54:25 +02:00
public FileService(StoredFileRepository fileRepository,
SettingsRepository settingsRepository,
IEnumerable<IStorageProviderService> providers,
IHttpClientFactory httpClientFactory,
IOptions<DataDirectories> dataDirectories)
{
2022-03-31 11:54:25 +02:00
_fileRepository = fileRepository;
_providers = providers;
2022-03-31 11:54:25 +02:00
_settingsRepository = settingsRepository;
_httpClientFactory = httpClientFactory;
_dataDirectories = dataDirectories;
}
public async Task<bool> IsAvailable()
{
2022-03-31 11:54:25 +02:00
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
return settings is not null;
}
public async Task<IStoredFile> AddFile(IFormFile file, string userId)
{
2022-03-31 11:54:25 +02:00
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
2021-03-06 13:36:36 +09:00
if (settings is null)
throw new InvalidOperationException("StoreSettings not configured");
2021-03-19 18:55:07 +09:00
if (!file.FileName.IsValidFileName())
throw new InvalidOperationException("Invalid file name");
2022-03-31 11:54:25 +02:00
var provider = GetProvider(settings);
var storedFile = await provider.AddFile(file, settings);
storedFile.ApplicationUserId = userId;
2022-03-31 11:54:25 +02:00
await _fileRepository.AddFile(storedFile);
return storedFile;
}
public async Task<IStoredFile> AddFile(Uri url, string userId)
{
if (!await IsAvailable())
throw new InvalidOperationException("StoreSettings not configured");
var fileName = Sanitize(Path.GetFileName(url.AbsolutePath));
2022-03-31 11:54:25 +02:00
if (!fileName.IsValidFileName())
throw new InvalidOperationException("Invalid file name");
// download
var filePath = Path.Join(_dataDirectories.Value.TempDir, fileName);
var httClient = _httpClientFactory.CreateClient();
using var resp = await httClient.GetAsync(url);
await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
await resp.Content.CopyToAsync(stream);
var file = new FormFile(stream, 0, stream.Length, fileName, fileName)
{
Headers = new HeaderDictionary(),
ContentType = GetContentType(filePath)
};
await stream.FlushAsync();
var storedFile = await AddFile(file, userId);
// cleanup
File.Delete(filePath);
return storedFile;
}
2021-03-06 13:36:36 +09:00
public async Task<string?> GetFileUrl(Uri baseUri, string fileId)
{
2022-03-31 11:54:25 +02:00
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
2021-03-06 13:36:36 +09:00
if (settings is null)
return null;
var provider = GetProvider(settings);
2022-03-31 11:54:25 +02:00
var storedFile = await _fileRepository.GetFile(fileId);
2020-06-28 17:55:27 +09:00
return storedFile == null ? null : await provider.GetFileUrl(baseUri, storedFile, settings);
}
2020-06-28 17:55:27 +09:00
2021-03-06 13:36:36 +09:00
public async Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
bool isDownload)
{
2022-03-31 11:54:25 +02:00
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
2021-03-06 13:36:36 +09:00
if (settings is null)
return null;
var provider = GetProvider(settings);
2022-03-31 11:54:25 +02:00
var storedFile = await _fileRepository.GetFile(fileId);
2020-06-28 17:55:27 +09:00
return storedFile == null ? null : await provider.GetTemporaryFileUrl(baseUri, storedFile, settings, expiry, isDownload);
}
public async Task RemoveFile(string fileId, string userId)
{
2022-03-31 11:54:25 +02:00
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
2021-03-06 13:36:36 +09:00
if (settings is null)
return;
var provider = GetProvider(settings);
2022-03-31 11:54:25 +02:00
var storedFile = await _fileRepository.GetFile(fileId);
if (string.IsNullOrEmpty(userId) ||
storedFile.ApplicationUserId.Equals(userId, StringComparison.InvariantCultureIgnoreCase))
{
await provider.RemoveFile(storedFile, settings);
2022-03-31 11:54:25 +02:00
await _fileRepository.RemoveFile(storedFile);
}
}
private IStorageProviderService GetProvider(StorageSettings storageSettings)
{
2021-12-27 13:15:43 +09:00
return _providers.First((service) => service.StorageProvider().Equals(storageSettings.Provider));
}
2022-03-31 11:54:25 +02:00
private static string GetContentType(string filePath)
{
var mimeProvider = new FileExtensionContentTypeProvider();
if (!mimeProvider.TryGetContentType(filePath, out string? contentType))
{
contentType = "application/octet-stream";
}
return contentType;
}
private static string Sanitize(string fileName)
{
var invalid = Path.GetInvalidFileNameChars().Concat(":").ToArray();
// replace invalid chars in url-decoded filename
var name = string.Join("_", HttpUtility.UrlDecode(fileName)
.Split(invalid, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.');
// replace multiple underscores with just one
return Regex.Replace(name, @"_+", "_");
}
}
}