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.Diagnostics;
|
|
|
|
using System.Globalization;
|
|
|
|
using System.IO;
|
|
|
|
using System.Net;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
2020-02-26 10:01:12 +00:00
|
|
|
using ASC.Common;
|
2020-02-05 15:37:31 +00:00
|
|
|
using ASC.Common.Logging;
|
2020-01-27 11:15:18 +00:00
|
|
|
using ASC.Core;
|
|
|
|
using ASC.Core.Tenants;
|
|
|
|
using ASC.Files.Core;
|
2020-02-18 08:39:28 +00:00
|
|
|
using ASC.Files.Resources;
|
2020-01-27 11:15:18 +00:00
|
|
|
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;
|
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-26 10:01:12 +00:00
|
|
|
public ChunkedUploadSessionHelper ChunkedUploadSessionHelper { 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,
|
2020-02-26 10:01:12 +00:00
|
|
|
ChunkedUploadSessionHolder chunkedUploadSessionHolder,
|
|
|
|
ChunkedUploadSessionHelper chunkedUploadSessionHelper)
|
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-26 10:01:12 +00:00
|
|
|
ChunkedUploadSessionHelper = chunkedUploadSessionHelper;
|
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);
|
2020-02-26 10:01:12 +00:00
|
|
|
WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(createdSession, true));
|
2020-01-27 11:15:18 +00:00
|
|
|
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
|
|
|
|
{
|
2020-02-26 10:01:12 +00:00
|
|
|
WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(resumedSession));
|
2020-01-27 11:15:18 +00:00
|
|
|
}
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-26 10:01:12 +00:00
|
|
|
|
|
|
|
public static class ChunkedUploaderHandlerExtention
|
|
|
|
{
|
|
|
|
public static DIHelper AddChunkedUploaderHandlerService(this DIHelper services)
|
|
|
|
{
|
|
|
|
services.TryAddScoped<ChunkedUploaderHandler>();
|
|
|
|
return services
|
|
|
|
.AddTenantManagerService()
|
|
|
|
.AddFileUploaderService()
|
|
|
|
.AddFilesMessageService()
|
|
|
|
.AddAuthManager()
|
|
|
|
.AddSecurityContextService()
|
|
|
|
.AddSetupInfo()
|
|
|
|
.AddEntryManagerService()
|
|
|
|
.AddInstanceCryptoService()
|
|
|
|
.AddChunkedUploadSessionHolderService()
|
|
|
|
.AddChunkedUploadSessionHelperService();
|
|
|
|
}
|
|
|
|
}
|
2020-01-27 11:15:18 +00:00
|
|
|
}
|