using System.Text; using FluentFTP.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Wabbajack.BuildServer; using Wabbajack.Downloaders; using Wabbajack.Downloaders.Interfaces; using Wabbajack.DTOs; using Wabbajack.DTOs.DownloadStates; using Wabbajack.Hashing.xxHash64; using Wabbajack.Paths.IO; using Wabbajack.VFS; namespace Wabbajack.Server.Controllers; [ApiController] [Route("/proxy")] public class Proxy : ControllerBase { private readonly ILogger _logger; private readonly DownloadDispatcher _dispatcher; private readonly TemporaryFileManager _tempFileManager; private readonly AppSettings _appSettings; private readonly FileHashCache _hashCache; public Proxy(ILogger logger, DownloadDispatcher dispatcher, TemporaryFileManager tempFileManager, FileHashCache hashCache, AppSettings appSettings) { _logger = logger; _dispatcher = dispatcher; _tempFileManager = tempFileManager; _appSettings = appSettings; _hashCache = hashCache; } [HttpGet] public async Task ProxyGet(CancellationToken token, [FromQuery] Uri uri, [FromQuery] string? name, [FromQuery] string? hash) { var shouldMatch = hash != null ? Hash.FromHex(hash) : default; _logger.LogInformation("Got proxy request for {Uri}", uri); var state = _dispatcher.Parse(uri); var cacheName = (await Encoding.UTF8.GetBytes(uri.ToString()).Hash()).ToHex(); var cacheFile = _appSettings.ProxyPath.Combine(cacheName); if (state == null) { return BadRequest(new {Type = "Could not get state from Uri", Uri = uri.ToString()}); } var archive = new Archive { Name = name ?? "", State = state, Hash = shouldMatch }; var downloader = _dispatcher.Downloader(archive); if (downloader is not IProxyable) { return BadRequest(new {Type = "Downloader is not IProxyable", Downloader = downloader.GetType().FullName}); } if (cacheFile.FileExists() && (DateTime.Now - cacheFile.LastModified()) > TimeSpan.FromHours(4)) { try { var verify = await _dispatcher.Verify(archive, token); if (verify) cacheFile.Touch(); } catch (Exception ex) { _logger.LogInformation("When trying to verify cached file"); } } if (cacheFile.FileExists() && (DateTime.Now - cacheFile.LastModified()) > TimeSpan.FromHours(24)) { try { cacheFile.Delete(); } catch (Exception ex) { _logger.LogError(ex, "When trying to delete expired file"); } } if (cacheFile.FileExists()) { if (hash != default) { var hashResult = await _hashCache.FileHashCachedAsync(cacheFile, token); if (hashResult != shouldMatch) return BadRequest(new {Type = "Unmatching Hashes", Expected = shouldMatch.ToHex(), Found = hashResult.ToHex()}); } var ret = new PhysicalFileResult(cacheFile.ToString(), "application/octet-stream"); if (name != null) ret.FileDownloadName = name; return ret; } _logger.LogInformation("Downloading proxy request for {Uri}", uri); var tempFile = _tempFileManager.CreateFile(deleteOnDispose:false); var result = await _dispatcher.Download(archive, tempFile.Path, token); if (hash != default && result != shouldMatch) { if (tempFile.Path.FileExists()) tempFile.Path.Delete(); return BadRequest(new {Type = "Unmatching Hashes", Expected = shouldMatch.ToHex(), Found = result.ToHex()}); } await tempFile.Path.MoveToAsync(cacheFile, true, token); _logger.LogInformation("Returning proxy request for {Uri} {Size}", uri, cacheFile.Size().FileSizeToString()); return new PhysicalFileResult(cacheFile.ToString(), "application/binary"); } }