2022-02-08 23:52:01 +00:00
|
|
|
using System.Net;
|
2022-01-25 23:39:51 +00:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
using VoidCat.Model;
|
2022-09-07 14:52:40 +00:00
|
|
|
using VoidCat.Model.Payments;
|
2022-02-16 16:33:00 +00:00
|
|
|
using VoidCat.Services.Abstractions;
|
2022-07-25 17:59:32 +00:00
|
|
|
using VoidCat.Services.Files;
|
2022-01-25 23:39:51 +00:00
|
|
|
|
|
|
|
namespace VoidCat.Controllers;
|
|
|
|
|
|
|
|
[Route("d")]
|
|
|
|
public class DownloadController : Controller
|
|
|
|
{
|
2022-09-09 09:42:33 +00:00
|
|
|
private readonly VoidSettings _settings;
|
2022-07-25 17:59:32 +00:00
|
|
|
private readonly FileStoreFactory _storage;
|
2022-09-06 21:32:22 +00:00
|
|
|
private readonly FileInfoManager _fileInfo;
|
2022-09-07 14:52:40 +00:00
|
|
|
private readonly IPaymentOrderStore _paymentOrders;
|
2022-02-08 23:52:01 +00:00
|
|
|
private readonly ILogger<DownloadController> _logger;
|
2022-01-25 23:39:51 +00:00
|
|
|
|
2022-09-06 21:32:22 +00:00
|
|
|
public DownloadController(FileStoreFactory storage, ILogger<DownloadController> logger, FileInfoManager fileInfo,
|
2022-09-09 09:42:33 +00:00
|
|
|
IPaymentOrderStore paymentOrderStore, VoidSettings settings)
|
2022-01-25 23:39:51 +00:00
|
|
|
{
|
|
|
|
_storage = storage;
|
2022-02-08 23:52:01 +00:00
|
|
|
_logger = logger;
|
2022-02-27 13:54:25 +00:00
|
|
|
_fileInfo = fileInfo;
|
2022-09-07 14:52:40 +00:00
|
|
|
_paymentOrders = paymentOrderStore;
|
2022-09-09 09:42:33 +00:00
|
|
|
_settings = settings;
|
2022-01-25 23:39:51 +00:00
|
|
|
}
|
|
|
|
|
2022-02-08 23:52:01 +00:00
|
|
|
[HttpOptions]
|
|
|
|
[Route("{id}")]
|
|
|
|
public Task DownloadFileOptions([FromRoute] string id)
|
|
|
|
{
|
|
|
|
var gid = id.FromBase58Guid();
|
|
|
|
return SetupDownload(gid);
|
|
|
|
}
|
2022-02-08 23:56:35 +00:00
|
|
|
|
2022-03-08 13:47:42 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Download a specific file by Id
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="id"></param>
|
2022-01-28 00:18:27 +00:00
|
|
|
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 86400)]
|
2022-01-25 23:39:51 +00:00
|
|
|
[HttpGet]
|
|
|
|
[Route("{id}")]
|
|
|
|
public async Task DownloadFile([FromRoute] string id)
|
|
|
|
{
|
|
|
|
var gid = id.FromBase58Guid();
|
2022-02-17 15:52:49 +00:00
|
|
|
var voidFile = await SetupDownload(gid);
|
2022-02-21 09:39:59 +00:00
|
|
|
if (voidFile == default) return;
|
2022-02-08 23:52:01 +00:00
|
|
|
|
2022-09-07 14:52:40 +00:00
|
|
|
var egressReq = new EgressRequest(gid, GetRanges(Request, (long) voidFile!.Metadata!.Size));
|
2022-02-08 23:52:01 +00:00
|
|
|
if (egressReq.Ranges.Count() > 1)
|
|
|
|
{
|
|
|
|
_logger.LogWarning("Multi-range request not supported!");
|
|
|
|
// downgrade to full send
|
|
|
|
egressReq = egressReq with
|
|
|
|
{
|
|
|
|
Ranges = Enumerable.Empty<RangeRequest>()
|
|
|
|
};
|
|
|
|
}
|
2022-02-08 23:56:35 +00:00
|
|
|
else if (egressReq.Ranges.Count() == 1)
|
2022-02-08 23:52:01 +00:00
|
|
|
{
|
2022-09-07 14:52:40 +00:00
|
|
|
Response.StatusCode = (int) HttpStatusCode.PartialContent;
|
2022-02-16 23:19:31 +00:00
|
|
|
if (egressReq.Ranges.Sum(a => a.Size) == 0)
|
|
|
|
{
|
2022-09-07 14:52:40 +00:00
|
|
|
Response.StatusCode = (int) HttpStatusCode.RequestedRangeNotSatisfiable;
|
2022-02-16 23:19:31 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-02-08 23:52:01 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Response.Headers.AcceptRanges = "bytes";
|
|
|
|
}
|
2022-02-08 23:56:35 +00:00
|
|
|
|
2022-02-08 23:52:01 +00:00
|
|
|
foreach (var range in egressReq.Ranges)
|
|
|
|
{
|
|
|
|
Response.Headers.Add("content-range", range.ToContentRange());
|
|
|
|
Response.ContentLength = range.Size;
|
|
|
|
}
|
|
|
|
|
2022-07-25 18:46:14 +00:00
|
|
|
var preResult = await _storage.StartEgress(egressReq);
|
|
|
|
if (preResult.Redirect != null)
|
|
|
|
{
|
2022-09-07 14:52:40 +00:00
|
|
|
Response.StatusCode = (int) HttpStatusCode.Redirect;
|
2022-07-25 18:46:14 +00:00
|
|
|
Response.Headers.Location = preResult.Redirect.ToString();
|
|
|
|
Response.ContentLength = 0;
|
|
|
|
return;
|
|
|
|
}
|
2022-07-25 19:38:58 +00:00
|
|
|
|
2022-02-08 23:52:01 +00:00
|
|
|
var cts = HttpContext.RequestAborted;
|
|
|
|
await Response.StartAsync(cts);
|
|
|
|
await _storage.Egress(egressReq, Response.Body, cts);
|
|
|
|
await Response.CompleteAsync();
|
|
|
|
}
|
|
|
|
|
2022-02-17 15:52:49 +00:00
|
|
|
private async Task<PublicVoidFile?> SetupDownload(Guid id)
|
2022-02-08 23:52:01 +00:00
|
|
|
{
|
2022-02-27 13:54:25 +00:00
|
|
|
var meta = await _fileInfo.Get(id);
|
2022-01-25 23:39:51 +00:00
|
|
|
if (meta == null)
|
|
|
|
{
|
|
|
|
Response.StatusCode = 404;
|
2022-02-21 12:54:57 +00:00
|
|
|
return default;
|
2022-01-25 23:39:51 +00:00
|
|
|
}
|
2022-02-08 23:56:35 +00:00
|
|
|
|
2022-09-07 14:52:40 +00:00
|
|
|
// check payment order
|
2022-09-07 15:25:31 +00:00
|
|
|
if (meta.Payment != default && meta.Payment.Service != PaymentServices.None && meta.Payment.Required)
|
2022-02-21 09:39:59 +00:00
|
|
|
{
|
2022-02-21 12:54:57 +00:00
|
|
|
var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"];
|
|
|
|
if (!await IsOrderPaid(orderId))
|
2022-02-21 09:39:59 +00:00
|
|
|
{
|
2022-09-07 14:52:40 +00:00
|
|
|
Response.StatusCode = (int) HttpStatusCode.PaymentRequired;
|
2022-02-21 12:54:57 +00:00
|
|
|
return default;
|
2022-02-21 09:39:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-09 09:42:33 +00:00
|
|
|
|
|
|
|
// prevent hot-linking viruses
|
|
|
|
var origin = Request.Headers.Origin.Count > 0 ? new Uri(Request.Headers.Origin.First()) : null;
|
|
|
|
var originWrong = !origin?.Host.Equals(_settings.SiteUrl.Host, StringComparison.InvariantCultureIgnoreCase) ??
|
|
|
|
false;
|
|
|
|
if (meta.VirusScan?.IsVirus == true && originWrong)
|
|
|
|
{
|
|
|
|
Response.StatusCode = (int) HttpStatusCode.Redirect;
|
|
|
|
Response.Headers.Location = $"/{id.ToBase58()}";
|
|
|
|
return default;
|
|
|
|
}
|
2022-02-21 09:39:59 +00:00
|
|
|
|
2022-02-03 23:06:39 +00:00
|
|
|
Response.Headers.XFrameOptions = "SAMEORIGIN";
|
|
|
|
Response.Headers.ContentDisposition = $"inline; filename=\"{meta?.Metadata?.Name}\"";
|
2022-01-25 23:39:51 +00:00
|
|
|
Response.ContentType = meta?.Metadata?.MimeType ?? "application/octet-stream";
|
2022-02-08 23:52:01 +00:00
|
|
|
|
|
|
|
return meta;
|
|
|
|
}
|
|
|
|
|
2022-02-21 12:54:57 +00:00
|
|
|
private async ValueTask<bool> IsOrderPaid(string orderId)
|
|
|
|
{
|
|
|
|
if (Guid.TryParse(orderId, out var oid))
|
|
|
|
{
|
2022-09-07 14:52:40 +00:00
|
|
|
var order = await _paymentOrders.Get(oid);
|
|
|
|
if (order?.Status == PaymentOrderStatus.Paid)
|
2022-02-21 12:54:57 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-02-08 23:52:01 +00:00
|
|
|
private IEnumerable<RangeRequest> GetRanges(HttpRequest request, long totalSize)
|
|
|
|
{
|
|
|
|
foreach (var rangeHeader in request.Headers.Range)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(rangeHeader))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:48:42 +00:00
|
|
|
foreach (var h in RangeRequest.Parse(rangeHeader, totalSize))
|
2022-02-08 23:52:01 +00:00
|
|
|
{
|
2022-03-01 16:48:42 +00:00
|
|
|
yield return h;
|
2022-02-08 23:52:01 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-25 23:39:51 +00:00
|
|
|
}
|
2022-09-07 14:52:40 +00:00
|
|
|
}
|