DocSpace-client/products/ASC.Files/Server/HttpHandlers/ChunkedUploaderHandler.cs

420 lines
16 KiB
C#

/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Resources;
using ASC.MessagingSystem;
using ASC.Security.Cryptography;
using ASC.Web.Core.Files;
using ASC.Web.Files.Helpers;
using ASC.Web.Files.Utils;
using ASC.Web.Studio.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace ASC.Web.Files.HttpHandlers
{
public class ChunkedUploaderHandler
{
public RequestDelegate Next { get; }
public IServiceProvider ServiceProvider { get; }
public ChunkedUploaderHandler(RequestDelegate next, IServiceProvider serviceProvider)
{
Next = next;
ServiceProvider = serviceProvider;
}
public async Task Invoke(HttpContext context)
{
using var scope = ServiceProvider.CreateScope();
var chunkedUploaderHandlerService = scope.ServiceProvider.GetService<ChunkedUploaderHandlerService>();
await chunkedUploaderHandlerService.Invoke(context);
await Next.Invoke(context);
}
}
public class ChunkedUploaderHandlerService
{
public TenantManager TenantManager { get; }
public FileUploader FileUploader { get; }
public FilesMessageService FilesMessageService { get; }
public AuthManager AuthManager { get; }
public SecurityContext SecurityContext { get; }
public SetupInfo SetupInfo { get; }
public EntryManager EntryManager { get; }
public InstanceCrypto InstanceCrypto { get; }
public ChunkedUploadSessionHolder ChunkedUploadSessionHolder { get; }
public ChunkedUploadSessionHelper ChunkedUploadSessionHelper { get; }
public ILog Logger { get; }
public ChunkedUploaderHandlerService(
IOptionsMonitor<ILog> optionsMonitor,
TenantManager tenantManager,
FileUploader fileUploader,
FilesMessageService filesMessageService,
AuthManager authManager,
SecurityContext securityContext,
SetupInfo setupInfo,
EntryManager entryManager,
InstanceCrypto instanceCrypto,
ChunkedUploadSessionHolder chunkedUploadSessionHolder,
ChunkedUploadSessionHelper chunkedUploadSessionHelper)
{
TenantManager = tenantManager;
FileUploader = fileUploader;
FilesMessageService = filesMessageService;
AuthManager = authManager;
SecurityContext = securityContext;
SetupInfo = setupInfo;
EntryManager = entryManager;
InstanceCrypto = instanceCrypto;
ChunkedUploadSessionHolder = chunkedUploadSessionHolder;
ChunkedUploadSessionHelper = chunkedUploadSessionHelper;
Logger = optionsMonitor.CurrentValue;
}
public async Task Invoke(HttpContext context)
{
var uploadSession = ChunkedUploadSessionHolder.GetSession(context.Request.Query["uid"]);
if (uploadSession as ChunkedUploadSession<int> != null)
{
await Invoke<int>(context);
return;
}
await Invoke<string>(context);
}
public async Task Invoke<T>(HttpContext context)
{
try
{
var request = new ChunkedRequestHelper<T>(context.Request);
if (!TryAuthorize(request))
{
await WriteError(context, "Can't authorize given initiate session request or session with specified upload id already expired");
return;
}
if (TenantManager.GetCurrentTenant().Status != TenantStatus.Active)
{
await WriteError(context, "Can't perform upload for deleted or transfering portals");
return;
}
switch (request.Type(InstanceCrypto))
{
case ChunkedRequestType.Abort:
FileUploader.AbortUpload<T>(request.UploadId);
await WriteSuccess(context, null);
return;
case ChunkedRequestType.Initiate:
var createdSession = FileUploader.InitiateUpload(request.FolderId, request.FileId, request.FileName, request.FileSize, request.Encrypted);
await WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(createdSession, true));
return;
case ChunkedRequestType.Upload:
var resumedSession = FileUploader.UploadChunk<T>(request.UploadId, request.ChunkStream, request.ChunkSize);
if (resumedSession.BytesUploaded == resumedSession.BytesTotal)
{
await WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
FilesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title);
}
else
{
await WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(resumedSession));
}
return;
default:
await WriteError(context, "Unknown request type.");
return;
}
}
catch (FileNotFoundException error)
{
Logger.Error(error);
await WriteError(context, FilesCommonResource.ErrorMassage_FileNotFound);
}
catch (Exception error)
{
Logger.Error(error);
await WriteError(context, error.Message);
}
}
private bool TryAuthorize<T>(ChunkedRequestHelper<T> request)
{
if (request.Type(InstanceCrypto) == ChunkedRequestType.Initiate)
{
TenantManager.SetCurrentTenant(request.TenantId);
SecurityContext.AuthenticateMe(AuthManager.GetAccountByID(TenantManager.GetCurrentTenant().TenantId, request.AuthKey(InstanceCrypto)));
var cultureInfo = request.CultureInfo(SetupInfo);
if (cultureInfo != null)
Thread.CurrentThread.CurrentUICulture = cultureInfo;
return true;
}
if (!string.IsNullOrEmpty(request.UploadId))
{
var uploadSession = ChunkedUploadSessionHolder.GetSession(request.UploadId);
if (uploadSession != null)
{
TenantManager.SetCurrentTenant(uploadSession.TenantId);
SecurityContext.AuthenticateMe(AuthManager.GetAccountByID(TenantManager.GetCurrentTenant().TenantId, uploadSession.UserId));
var culture = SetupInfo.EnabledCulturesPersonal.Find(c => string.Equals(c.Name, uploadSession.CultureName, StringComparison.InvariantCultureIgnoreCase));
if (culture != null)
Thread.CurrentThread.CurrentUICulture = culture;
return true;
}
}
return false;
}
private static async Task WriteError(HttpContext context, string message)
{
await WriteResponse(context, false, null, message, (int)HttpStatusCode.OK);
}
private static async Task WriteSuccess(HttpContext context, object data, int statusCode = (int)HttpStatusCode.OK)
{
await WriteResponse(context, true, data, string.Empty, statusCode);
}
private static async Task WriteResponse(HttpContext context, bool success, object data, string message, int statusCode)
{
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message }));
}
private static object ToResponseObject<T>(File<T> file)
{
return new
{
id = file.ID,
folderId = file.FolderID,
version = file.Version,
title = file.Title,
provider_key = file.ProviderKey,
uploaded = true
};
}
}
public enum ChunkedRequestType
{
None,
Initiate,
Abort,
Upload
}
[DebuggerDisplay("{Type} ({UploadId})")]
public class ChunkedRequestHelper<T>
{
private readonly HttpRequest _request;
private IFormFile _file;
private int? _tenantId;
private long? _fileContentLength;
private Guid? _authKey;
private CultureInfo _cultureInfo;
public ChunkedRequestType Type(InstanceCrypto instanceCrypto)
{
if (_request.Query["initiate"] == "true" && IsAuthDataSet(instanceCrypto) && IsFileDataSet())
return ChunkedRequestType.Initiate;
if (_request.Query["abort"] == "true" && !string.IsNullOrEmpty(UploadId))
return ChunkedRequestType.Abort;
return !string.IsNullOrEmpty(UploadId)
? ChunkedRequestType.Upload
: ChunkedRequestType.None;
}
public string UploadId
{
get { return _request.Query["uid"]; }
}
public int TenantId
{
get
{
if (!_tenantId.HasValue)
{
if (int.TryParse(_request.Query["tid"], out var v))
_tenantId = v;
else
_tenantId = -1;
}
return _tenantId.Value;
}
}
public Guid AuthKey(InstanceCrypto instanceCrypto)
{
if (!_authKey.HasValue)
{
_authKey = !string.IsNullOrEmpty(_request.Query["userid"])
? new Guid(instanceCrypto.Decrypt(_request.Query["userid"]))
: Guid.Empty;
}
return _authKey.Value;
}
public T FolderId
{
get { return (T)Convert.ChangeType(_request.Query[FilesLinkUtility.FolderId], typeof(T)); }
}
public T FileId
{
get { return (T)Convert.ChangeType(_request.Query[FilesLinkUtility.FileId], typeof(T)); }
}
public string FileName
{
get { return _request.Query[FilesLinkUtility.FileTitle]; }
}
public long FileSize
{
get
{
if (!_fileContentLength.HasValue)
{
long.TryParse(_request.Query["fileSize"], out var v);
_fileContentLength = v;
}
return _fileContentLength.Value;
}
}
public long ChunkSize
{
get { return File.Length; }
}
public Stream ChunkStream
{
get { return File.OpenReadStream(); }
}
public CultureInfo CultureInfo(SetupInfo setupInfo)
{
if (_cultureInfo != null)
return _cultureInfo;
var culture = _request.Query["culture"];
if (string.IsNullOrEmpty(culture)) culture = "en-US";
return _cultureInfo = setupInfo.EnabledCulturesPersonal.Find(c => string.Equals(c.Name, culture, StringComparison.InvariantCultureIgnoreCase));
}
public bool Encrypted
{
get { return _request.Query["encrypted"] == "true"; }
}
private IFormFile File
{
get
{
if (_file != null)
return _file;
if (_request.Form.Files.Count > 0)
return _file = _request.Form.Files[0];
throw new Exception("HttpRequest.Files is empty");
}
}
public ChunkedRequestHelper(HttpRequest request)
{
_request = request ?? throw new ArgumentNullException("request");
}
private bool IsAuthDataSet(InstanceCrypto instanceCrypto)
{
return TenantId > -1 && AuthKey(instanceCrypto) != Guid.Empty;
}
private bool IsFileDataSet()
{
return !string.IsNullOrEmpty(FileName) && !FolderId.Equals(default(T));
}
}
public static class ChunkedUploaderHandlerExtention
{
public static DIHelper AddChunkedUploaderHandlerService(this DIHelper services)
{
services.TryAddScoped<ChunkedUploaderHandlerService>();
return services
.AddTenantManagerService()
.AddFileUploaderService()
.AddFilesMessageService()
.AddAuthManager()
.AddSecurityContextService()
.AddSetupInfo()
.AddEntryManagerService()
.AddInstanceCryptoService()
.AddChunkedUploadSessionHolderService()
.AddChunkedUploadSessionHelperService();
}
public static IApplicationBuilder UseChunkedUploaderHandler(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ChunkedUploaderHandler>();
}
}
}