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

393 lines
15 KiB
C#
Raw Normal View History

2020-01-27 11:15:18 +00:00
/*
*
* (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.
*
*/
2020-02-05 15:37:31 +00:00
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common.Logging;
2020-01-27 11:15:18 +00:00
using ASC.Core;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.MessagingSystem;
using ASC.Security.Cryptography;
using ASC.Web.Core.Files;
using ASC.Web.Files.Helpers;
using ASC.Web.Files.Resources;
using ASC.Web.Files.Utils;
using ASC.Web.Studio.Core;
2020-02-05 15:37:31 +00:00
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
2020-01-27 11:15:18 +00:00
using Newtonsoft.Json;
2020-02-05 15:37:31 +00:00
2020-01-27 11:15:18 +00:00
using File = ASC.Files.Core.File;
namespace ASC.Web.Files.HttpHandlers
{
2020-02-05 15:37:31 +00:00
public class ChunkedUploaderHandler //: AbstractHttpAsyncHandler
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
public RequestDelegate Next { get; }
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; }
2020-02-11 15:10:30 +00:00
public ChunkedUploadSessionHolder ChunkedUploadSessionHolder { get; }
2020-02-05 15:37:31 +00:00
public ILog Logger { get; }
public ChunkedUploaderHandler(
RequestDelegate next,
IOptionsMonitor<ILog> optionsMonitor,
TenantManager tenantManager,
FileUploader fileUploader,
FilesMessageService filesMessageService,
AuthManager authManager,
SecurityContext securityContext,
SetupInfo setupInfo,
EntryManager entryManager,
2020-02-11 15:10:30 +00:00
InstanceCrypto instanceCrypto,
ChunkedUploadSessionHolder chunkedUploadSessionHolder)
2020-02-05 15:37:31 +00:00
{
Next = next;
TenantManager = tenantManager;
FileUploader = fileUploader;
FilesMessageService = filesMessageService;
AuthManager = authManager;
SecurityContext = securityContext;
SetupInfo = setupInfo;
EntryManager = entryManager;
InstanceCrypto = instanceCrypto;
2020-02-11 15:10:30 +00:00
ChunkedUploadSessionHolder = chunkedUploadSessionHolder;
2020-02-05 15:37:31 +00:00
Logger = optionsMonitor.CurrentValue;
}
public async Task Invoke(HttpContext context)
2020-01-27 11:15:18 +00:00
{
try
{
var request = new ChunkedRequestHelper(context.Request);
if (!TryAuthorize(request))
{
WriteError(context, "Can't authorize given initiate session request or session with specified upload id already expired");
return;
}
2020-02-05 15:37:31 +00:00
if (TenantManager.GetCurrentTenant().Status != TenantStatus.Active)
2020-01-27 11:15:18 +00:00
{
WriteError(context, "Can't perform upload for deleted or transfering portals");
return;
}
2020-02-05 15:37:31 +00:00
switch (request.Type(InstanceCrypto))
2020-01-27 11:15:18 +00:00
{
case ChunkedRequestType.Abort:
FileUploader.AbortUpload(request.UploadId);
WriteSuccess(context, null);
return;
case ChunkedRequestType.Initiate:
var createdSession = FileUploader.InitiateUpload(request.FolderId, request.FileId, request.FileName, request.FileSize, request.Encrypted);
WriteSuccess(context, ToResponseObject(createdSession, true));
return;
case ChunkedRequestType.Upload:
var resumedSession = FileUploader.UploadChunk(request.UploadId, request.ChunkStream, request.ChunkSize);
2020-02-05 15:37:31 +00:00
2020-01-27 11:15:18 +00:00
if (resumedSession.BytesUploaded == resumedSession.BytesTotal)
{
2020-02-05 15:37:31 +00:00
WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
FilesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title);
2020-01-27 11:15:18 +00:00
}
else
{
WriteSuccess(context, ToResponseObject(resumedSession));
}
return;
default:
WriteError(context, "Unknown request type.");
return;
}
}
catch (FileNotFoundException error)
{
2020-02-05 15:37:31 +00:00
Logger.Error(error);
2020-01-27 11:15:18 +00:00
WriteError(context, FilesCommonResource.ErrorMassage_FileNotFound);
}
catch (Exception error)
{
2020-02-05 15:37:31 +00:00
Logger.Error(error);
2020-01-27 11:15:18 +00:00
WriteError(context, error.Message);
}
2020-02-05 15:37:31 +00:00
await Next.Invoke(context);
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
private bool TryAuthorize(ChunkedRequestHelper request)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
if (request.Type(InstanceCrypto) == ChunkedRequestType.Initiate)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
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;
2020-01-27 11:15:18 +00:00
return true;
}
if (!string.IsNullOrEmpty(request.UploadId))
{
var uploadSession = ChunkedUploadSessionHolder.GetSession(request.UploadId);
if (uploadSession != null)
{
2020-02-05 15:37:31 +00:00
TenantManager.SetCurrentTenant(uploadSession.TenantId);
SecurityContext.AuthenticateMe(AuthManager.GetAccountByID(TenantManager.GetCurrentTenant().TenantId, uploadSession.UserId));
2020-01-27 14:58:33 +00:00
var culture = SetupInfo.EnabledCulturesPersonal.Find(c => string.Equals(c.Name, uploadSession.CultureName, StringComparison.InvariantCultureIgnoreCase));
2020-01-27 11:15:18 +00:00
if (culture != null)
Thread.CurrentThread.CurrentUICulture = culture;
return true;
}
}
return false;
}
private static void WriteError(HttpContext context, string message)
{
2020-02-05 15:37:31 +00:00
WriteResponse(context, false, null, message, (int)HttpStatusCode.OK);
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
private static void WriteSuccess(HttpContext context, object data, int statusCode = (int)HttpStatusCode.OK)
2020-01-27 11:15:18 +00:00
{
WriteResponse(context, true, data, string.Empty, statusCode);
}
private static void WriteResponse(HttpContext context, bool success, object data, string message, int statusCode)
{
context.Response.StatusCode = statusCode;
2020-02-05 15:37:31 +00:00
context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message })).Wait();
2020-01-27 11:15:18 +00:00
context.Response.ContentType = "application/json";
}
2020-02-05 15:37:31 +00:00
public object ToResponseObject(ChunkedUploadSession session, bool appendBreadCrumbs = false)
2020-01-27 11:15:18 +00:00
{
var pathFolder = appendBreadCrumbs
? EntryManager.GetBreadCrumbs(session.FolderId).Select(f =>
{
//todo: check how?
if (f == null)
{
2020-02-05 15:37:31 +00:00
Logger.ErrorFormat("GetBreadCrumbs {0} with null", session.FolderId);
2020-01-27 11:15:18 +00:00
return string.Empty;
}
return f.ID;
})
2020-02-05 15:37:31 +00:00
: new List<object> { session.FolderId };
2020-01-27 11:15:18 +00:00
return new
2020-02-05 15:37:31 +00:00
{
id = session.Id,
path = pathFolder,
created = session.Created,
expired = session.Expired,
location = session.Location,
bytes_uploaded = session.BytesUploaded,
bytes_total = session.BytesTotal
};
2020-01-27 11:15:18 +00:00
}
private static object ToResponseObject(File file)
{
return new
2020-02-05 15:37:31 +00:00
{
id = file.ID,
folderId = file.FolderID,
version = file.Version,
title = file.Title,
provider_key = file.ProviderKey,
uploaded = true
};
2020-01-27 11:15:18 +00:00
}
private enum ChunkedRequestType
{
None,
Initiate,
Abort,
Upload
}
[DebuggerDisplay("{Type} ({UploadId})")]
private class ChunkedRequestHelper
{
private readonly HttpRequest _request;
2020-02-05 15:37:31 +00:00
private IFormFile _file;
2020-01-27 11:15:18 +00:00
private int? _tenantId;
private long? _fileContentLength;
private Guid? _authKey;
private CultureInfo _cultureInfo;
2020-02-05 15:37:31 +00:00
public ChunkedRequestType Type(InstanceCrypto instanceCrypto)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
if (_request.Query["initiate"] == "true" && IsAuthDataSet(instanceCrypto) && IsFileDataSet())
return ChunkedRequestType.Initiate;
2020-01-27 11:15:18 +00:00
2020-02-05 15:37:31 +00:00
if (_request.Query["abort"] == "true" && !string.IsNullOrEmpty(UploadId))
return ChunkedRequestType.Abort;
2020-01-27 11:15:18 +00:00
2020-02-05 15:37:31 +00:00
return !string.IsNullOrEmpty(UploadId)
? ChunkedRequestType.Upload
: ChunkedRequestType.None;
2020-01-27 11:15:18 +00:00
}
public string UploadId
{
2020-02-05 15:37:31 +00:00
get { return _request.Query["uid"]; }
2020-01-27 11:15:18 +00:00
}
public int TenantId
{
get
{
if (!_tenantId.HasValue)
{
2020-02-05 15:37:31 +00:00
if (int.TryParse(_request.Query["tid"], out var v))
2020-01-27 11:15:18 +00:00
_tenantId = v;
else
_tenantId = -1;
}
return _tenantId.Value;
}
}
2020-02-05 15:37:31 +00:00
public Guid AuthKey(InstanceCrypto instanceCrypto)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
if (!_authKey.HasValue)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
_authKey = !string.IsNullOrEmpty(_request.Query["userid"])
? new Guid(instanceCrypto.Decrypt(_request.Query["userid"]))
: Guid.Empty;
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
return _authKey.Value;
2020-01-27 11:15:18 +00:00
}
public string FolderId
{
2020-02-05 15:37:31 +00:00
get { return _request.Query[FilesLinkUtility.FolderId]; }
2020-01-27 11:15:18 +00:00
}
public string FileId
{
2020-02-05 15:37:31 +00:00
get { return _request.Query[FilesLinkUtility.FileId]; }
2020-01-27 11:15:18 +00:00
}
public string FileName
{
2020-02-05 15:37:31 +00:00
get { return _request.Query[FilesLinkUtility.FileTitle]; }
2020-01-27 11:15:18 +00:00
}
public long FileSize
{
get
{
if (!_fileContentLength.HasValue)
{
2020-02-05 15:37:31 +00:00
long.TryParse(_request.Query["fileSize"], out var v);
2020-01-27 11:15:18 +00:00
_fileContentLength = v;
}
return _fileContentLength.Value;
}
}
public long ChunkSize
{
2020-02-05 15:37:31 +00:00
get { return File.Length; }
2020-01-27 11:15:18 +00:00
}
public Stream ChunkStream
{
2020-02-05 15:37:31 +00:00
get { return File.OpenReadStream(); }
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
public CultureInfo CultureInfo(SetupInfo setupInfo)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
if (_cultureInfo != null)
return _cultureInfo;
2020-01-27 11:15:18 +00:00
2020-02-05 15:37:31 +00:00
var culture = _request.Query["culture"];
if (string.IsNullOrEmpty(culture)) culture = "en-US";
2020-01-27 11:15:18 +00:00
2020-02-05 15:37:31 +00:00
return _cultureInfo = setupInfo.EnabledCulturesPersonal.Find(c => string.Equals(c.Name, culture, StringComparison.InvariantCultureIgnoreCase));
2020-01-27 11:15:18 +00:00
}
public bool Encrypted
{
2020-02-05 15:37:31 +00:00
get { return _request.Query["encrypted"] == "true"; }
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
private IFormFile File
2020-01-27 11:15:18 +00:00
{
get
{
if (_file != null)
return _file;
2020-02-11 15:10:30 +00:00
if (_request.Form.Files.Count > 0)
return _file = _request.Form.Files[0];
2020-01-27 11:15:18 +00:00
throw new Exception("HttpRequest.Files is empty");
}
}
public ChunkedRequestHelper(HttpRequest request)
{
2020-02-05 15:37:31 +00:00
_request = request ?? throw new ArgumentNullException("request");
2020-01-27 11:15:18 +00:00
}
2020-02-05 15:37:31 +00:00
private bool IsAuthDataSet(InstanceCrypto instanceCrypto)
2020-01-27 11:15:18 +00:00
{
2020-02-05 15:37:31 +00:00
return TenantId > -1 && AuthKey(instanceCrypto) != Guid.Empty;
2020-01-27 11:15:18 +00:00
}
private bool IsFileDataSet()
{
return !string.IsNullOrEmpty(FileName) && !string.IsNullOrEmpty(FolderId);
}
}
}
}