private static async Task <string> HandleCreationOfConcatFiles(ContextAdapter context, Models.Concatenation.UploadConcat uploadConcat,
                                                                       ITusConcatenationStore tusConcatenationStore, long uploadLength, string metadata,
                                                                       CancellationToken cancellationToken)
        {
            string fileId;

            if (uploadConcat.Type is FileConcatPartial)
            {
                fileId = await tusConcatenationStore
                         .CreatePartialFileAsync(uploadLength, metadata, cancellationToken);
            }
            else
            {
                var finalConcat = (FileConcatFinal)uploadConcat.Type;
                fileId = await tusConcatenationStore.CreateFinalFileAsync(finalConcat.Files, metadata, cancellationToken);

                // Run callback that the final file is completed.
                if (context.Configuration.OnUploadCompleteAsync != null)
                {
                    await context.Configuration.OnUploadCompleteAsync(fileId, context.Configuration.Store, cancellationToken);
                }
            }

            return(fileId);
        }
        public static IntentHandler DetermineIntent(ContextAdapter context)
        {
            var httpMethod = GetHttpMethod(context.Request);

            if (RequestRequiresTusResumableHeader(httpMethod))
            {
                if (context.Request.GetHeader(HeaderConstants.TusResumable) == null)
                {
                    return(IntentHandler.NotApplicable);
                }
            }

            if (MethodRequiresFileIdUrl(httpMethod))
            {
                if (!UrlMatchesFileIdUrl(context.Request.RequestUri, context.Configuration.UrlPath))
                {
                    return(IntentHandler.NotApplicable);
                }
            }
            else if (!UrlMatchesUrlPath(context.Request.RequestUri, context.Configuration.UrlPath))
            {
                return(IntentHandler.NotApplicable);
            }

            return(httpMethod switch
            {
                "post" => DetermineIntentForPost(context),
                "patch" => DetermineIntentForPatch(context),
                "head" => DetermineIntentForHead(context),
                "options" => DetermineIntentForOptions(context),
                "delete" => DetermineIntentForDelete(context),
                _ => IntentHandler.NotApplicable,
            });
        private void VerifyRequestUploadLengthAsync(ContextAdapter context, string uploadLengthHeader)
        {
            var request = context.Request;

            if (uploadLengthHeader == null)
            {
                BadRequest($"Missing {HeaderConstants.UploadLength} header");
                return;
            }

            if (!long.TryParse(request.Headers[HeaderConstants.UploadLength].First(), out long uploadLength))
            {
                BadRequest($"Could not parse {HeaderConstants.UploadLength}");
                return;
            }

            if (uploadLength < 0)
            {
                BadRequest($"Header {HeaderConstants.UploadLength} must be a positive number");
                return;
            }

            if (uploadLength > context.Configuration.MaxAllowedUploadSizeInBytes)
            {
                StatusCode   = HttpStatusCode.RequestEntityTooLarge;
                ErrorMessage = $"Header {HeaderConstants.UploadLength} exceeds the server's max file size.";
            }
        }
Exemple #4
0
 public ConcatenateFilesHandler(ContextAdapter context, ITusConcatenationStore concatenationStore)
     : base(context, IntentType.ConcatenateFiles, LockType.NoLock)
 {
     UploadConcat        = ParseUploadConcatHeader();
     _concatenationStore = concatenationStore;
     _expirationHelper   = new ExpirationHelper(context.Configuration);
 }
Exemple #5
0
        internal static async Task <ResultType> Validate <T>(ContextAdapter context, Action <T> configure = null) where T : ValidationContext <T>, new()
        {
            var handler = GetHandler <T>(context);

            if (handler == null)
            {
                return(ResultType.ContinueExecution);
            }

            var eventContext = EventContext <T> .Create(context, configure);

            await handler.Invoke(eventContext);

            if (eventContext.HasFailed)
            {
                var includeTusResumableHeaderInResponse = true;
                // Do not leak information on the tus endpoint during authorization.
                if (eventContext is EventContext <AuthorizeContext> )
                {
                    includeTusResumableHeaderInResponse = false;
                }
                await context.Response.Error(eventContext.StatusCode, eventContext.ErrorMessage, includeTusResumableHeaderInResponse);

                return(ResultType.StopExecution);
            }

            return(ResultType.ContinueExecution);
        }
Exemple #6
0
        internal Task <bool> Validate(ContextAdapter context)
        {
            var validator = new Validator(Requires);

            validator.Validate(context);

            if (validator.StatusCode == HttpStatusCode.OK)
            {
                return(Task.FromResult(true));
            }

            if (validator.StatusCode == HttpStatusCode.NotFound)
            {
                context.Response.NotFound();
                return(Task.FromResult(false));
            }

            return(RespondWithError());

            async Task <bool> RespondWithError()
            {
                await context.Response.Error(validator.StatusCode, validator.ErrorMessage);

                return(false);
            }
        }
        internal override async Task <bool> Handle(ContextAdapter context)
        {
            var response = context.Response;

            response.SetHeader(HeaderConstants.TusResumable, HeaderConstants.TusResumableValue);
            response.SetHeader(HeaderConstants.TusVersion, HeaderConstants.TusResumableValue);

            var maximumAllowedSize = context.Configuration.GetMaxAllowedUploadSizeInBytes();

            if (maximumAllowedSize.HasValue)
            {
                response.SetHeader(HeaderConstants.TusMaxSize, maximumAllowedSize.Value.ToString());
            }

            var extensions = context.DetectExtensions();

            if (extensions.Count > 0)
            {
                response.SetHeader(HeaderConstants.TusExtension, string.Join(",", extensions));
            }

            if (context.Configuration.Store is ITusChecksumStore checksumStore)
            {
                var checksumAlgorithms = await checksumStore.GetSupportedAlgorithmsAsync(context.CancellationToken);

                response.SetHeader(HeaderConstants.TusChecksumAlgorithm, string.Join(",", checksumAlgorithms));
            }

            response.SetStatus((int)HttpStatusCode.NoContent);
            return(true);
        }
Exemple #8
0
        private async Task ValidateForPost(ContextAdapter context)
        {
            ITusConcatenationStore concatenationStore = context.Configuration.Store as ITusConcatenationStore;

            if (concatenationStore != null &&
                context.Request.Headers.ContainsKey(HeaderConstants.UploadConcat))
            {
                var uploadConcat = new Models.Concatenation.UploadConcat(
                    context.Request.GetHeader(HeaderConstants.UploadConcat),
                    context.Configuration.UrlPath);

                if (!uploadConcat.IsValid)
                {
                    await BadRequest(uploadConcat.ErrorMessage);

                    return;
                }

                if (uploadConcat.Type is FileConcatFinal finalConcat)
                {
                    await ValidateFinalFileCreation(finalConcat, context, concatenationStore);
                }
            }
            else
            {
                await Done;
            }
        }
Exemple #9
0
        private Task ValidateForPatch(ContextAdapter context)
        {
            if (!(context.Configuration.Store is ITusCreationDeferLengthStore))
            {
                return(Task.FromResult(0));
            }

            return(ValidateForPatchLocal());

            async Task ValidateForPatchLocal()
            {
                var fileId = context.GetFileId();

                var fileUploadLength = await context.Configuration.Store.GetUploadLengthAsync(fileId, context.CancellationToken);

                if (!context.Request.Headers.ContainsKey(HeaderConstants.UploadLength) && fileUploadLength == null)
                {
                    await BadRequest(
                        $"Header {HeaderConstants.UploadLength} must be specified as this file was created using Upload-Defer-Length");
                }
                else
                {
                    if (context.Request.Headers.ContainsKey(HeaderConstants.UploadLength) && fileUploadLength != null)
                    {
                        await BadRequest($"{HeaderConstants.UploadLength} cannot be updated once set");
                    }
                }
            }
        }
Exemple #10
0
        private Task ValidateForPost(ContextAdapter context, string uploadLengthHeader, string uploadDeferLengthHeader)
        {
            if (uploadLengthHeader != null && uploadDeferLengthHeader != null)
            {
                return(BadRequest(
                           $"Headers {HeaderConstants.UploadLength} and {HeaderConstants.UploadDeferLength} are mutually exclusive and cannot be used in the same request"));
            }

            var uploadConcat =
                new Models.Concatenation.UploadConcat(context.Request.GetHeader(HeaderConstants.UploadConcat),
                                                      context.Configuration.UrlPath).Type;

            if (uploadConcat is Models.Concatenation.FileConcatFinal)
            {
                return(Done);
            }

            if (uploadDeferLengthHeader == null)
            {
                VerifyRequestUploadLength(context, uploadLengthHeader);
            }
            else
            {
                VerifyDeferLength(uploadDeferLengthHeader);
            }

            return(Done);
        }
Exemple #11
0
        private static async Task <string> HandleCreationOfConcatFiles(
            ContextAdapter context,
            UploadConcat uploadConcat,
            ITusConcatenationStore tusConcatenationStore,
            long uploadLength,
            string metadata,
            CancellationToken cancellationToken)
        {
            string fileId;

            if (uploadConcat.Type is FileConcatPartial)
            {
                fileId = await tusConcatenationStore
                         .CreatePartialFileAsync(uploadLength, metadata, cancellationToken);

                await HandleOnCreateComplete(context, fileId, true, uploadConcat, metadata, uploadLength);
            }
            else
            {
                var finalConcat = (FileConcatFinal)uploadConcat.Type;
                fileId = await tusConcatenationStore.CreateFinalFileAsync(finalConcat.Files, metadata,
                                                                          cancellationToken);

                await HandleOnCreateComplete(context, fileId, true, uploadConcat, metadata, uploadLength);

                // Run callback that the final file is completed.
                await HandleOnFileComplete(context, cancellationToken, fileId);
            }

            return(fileId);
        }
Exemple #12
0
        private static Task <bool> HandleOnBeforeCreateAsync(ContextAdapter context, bool supportsUploadConcat,
                                                             UploadConcat uploadConcat, string metadata, long uploadLength)
        {
            if (context.Configuration.Events?.OnBeforeCreateAsync == null)
            {
                return(Task.FromResult(false));
            }

            return(HandleOnBeforeCreateAsyncLocal());

            async Task <bool> HandleOnBeforeCreateAsyncLocal()
            {
                var beforeCreateContext = BeforeCreateContext.Create(context, ctx =>
                {
                    ctx.FileConcatenation = supportsUploadConcat ? uploadConcat.Type : null;
                    ctx.Metadata          = Metadata.Parse(metadata);
                    ctx.UploadLength      = uploadLength;
                });

                await context.Configuration.Events.OnBeforeCreateAsync(beforeCreateContext);

                if (beforeCreateContext.HasFailed)
                {
                    return(await context.Response.Error(HttpStatusCode.BadRequest, beforeCreateContext.ErrorMessage));
                }

                return(false);
            }
        }
        private Task VerifyRequestUploadLength(ContextAdapter context, string uploadLengthHeader)
        {
            var request = context.Request;

            if (uploadLengthHeader == null)
            {
                return(BadRequest($"Missing {HeaderConstants.UploadLength} header"));
            }

            if (!long.TryParse(request.Headers[HeaderConstants.UploadLength][0], out long uploadLength))
            {
                return(BadRequest($"Could not parse {HeaderConstants.UploadLength}"));
            }

            if (uploadLength < 0)
            {
                return(BadRequest($"Header {HeaderConstants.UploadLength} must be a positive number"));
            }

            if (uploadLength > context.Configuration.GetMaxAllowedUploadSizeInBytes())
            {
                return(RequestEntityTooLarge(
                           $"Header {HeaderConstants.UploadLength} exceeds the server's max file size."));
            }

            return(TaskHelper.Completed);
        }
        private static Task RunOnUploadComplete(ContextAdapter context, long fileOffset, long bytesWritten)
        {
            if (context.Configuration.OnUploadCompleteAsync == null && context.Configuration.Events?.OnFileCompleteAsync == null)
            {
                return(Task.FromResult(0));
            }

            return(RunOnUploadCompleteLocal());

            async Task RunOnUploadCompleteLocal()
            {
                if (await IsPartialUpload(context))
                {
                    return;
                }

                var fileId           = context.GetFileId();
                var fileUploadLength = await context.Configuration.Store.GetUploadLengthAsync(fileId, context.CancellationToken);

                var fileIsComplete = fileOffset + bytesWritten == fileUploadLength;

                if (fileIsComplete)
                {
                    if (context.Configuration.OnUploadCompleteAsync != null)
                    {
                        await context.Configuration.OnUploadCompleteAsync(fileId, context.Configuration.Store, context.CancellationToken);
                    }

                    if (context.Configuration.Events?.OnFileCompleteAsync != null)
                    {
                        await context.Configuration.Events.OnFileCompleteAsync(FileCompleteContext.Create(context));
                    }
                }
            }
        }
Exemple #15
0
        private void StartReading()
        {
            using (var reader = new StreamReader(this.stream))
            {
                while (true)
                {
                    try
                    {
                        var line = reader.ReadLine();
                        if (string.IsNullOrWhiteSpace(line))
                        {
                            this.OnCompleted();
                            return;
                        }

                        var context = new ContextAdapter(this.currentChannel, line, this);
                        this.OnNext(line);
                        this.requestDelegate(context);
                    }
                    catch (Exception ex)
                    {
                        this.OnError(ex);
                        return;
                    }
                }
            }
        }
        public void Execute(IEnumerable <string> parameters)
        {
            var adapter = new ContextAdapter();
            var context = adapter.CreateObjectFromParameters(parameters);

            _contextOperations.SaveContext(context);
        }
        public override Task Validate(ContextAdapter context)
        {
            var uploadDeferLengthHeader = context.Request.GetHeader(HeaderConstants.UploadDeferLength);
            var uploadLengthHeader      = context.Request.GetHeader(HeaderConstants.UploadLength);

            return(ValidateForPost(context, uploadLengthHeader, uploadDeferLengthHeader));
        }
Exemple #18
0
        internal override async Task <bool> Handle(ContextAdapter context)
        {
            var creationDeferLengthStore = context.Configuration.Store as ITusCreationDeferLengthStore;

            var fileId            = context.GetFileId();
            var cancellationToken = context.CancellationToken;
            var response          = context.Response;

            if (creationDeferLengthStore != null && context.Request.Headers.ContainsKey(HeaderConstants.UploadLength))
            {
                var uploadLength = long.Parse(context.Request.GetHeader(HeaderConstants.UploadLength));
                await creationDeferLengthStore.SetUploadLengthAsync(fileId, uploadLength, cancellationToken);
            }

            DateTimeOffset?expires;
            long           bytesWritten;

            try
            {
                bytesWritten = await context.Configuration.Store.AppendDataAsync(fileId, context.Request.Body, cancellationToken);
            }
            catch (IOException ioException)
            {
                // Indicates that the client disconnected.
                if (!ioException.ClientDisconnected())
                {
                    throw;
                }

                // Client disconnected so no need to return a response.
                return(true);
            }
            finally
            {
                expires = await GetOrUpdateExpires(context);
            }

            if (!await MatchChecksum(context))
            {
                return(await response.Error((HttpStatusCode)460, "Header Upload-Checksum does not match the checksum of the file"));
            }

            var fileOffset = long.Parse(context.Request.GetHeader(HeaderConstants.UploadOffset));

            response.SetHeader(HeaderConstants.TusResumable, HeaderConstants.TusResumableValue);
            response.SetHeader(HeaderConstants.UploadOffset, (fileOffset + bytesWritten).ToString());

            if (expires.HasValue)
            {
                response.SetHeader(HeaderConstants.UploadExpires, expires.Value.ToString("R"));
            }

            response.SetStatus((int)HttpStatusCode.NoContent);

            // Run OnUploadComplete if it has been provided.
            await RunOnUploadComplete(context, fileOffset, bytesWritten);

            return(true);
        }
Exemple #19
0
        private static async Task <bool> InvokeAsync(ContextAdapter context, ProtocolMethodHandler methodHandler)
        {
            var response = context.Response;

            if (!(methodHandler is OptionsHandler))
            {
                var tusResumable = context.Request.Headers.ContainsKey(HeaderConstants.TusResumable)
                    ? context.Request.Headers[HeaderConstants.TusResumable].FirstOrDefault()
                    : null;

                if (tusResumable == null)
                {
                    return(false);
                }

                if (tusResumable != HeaderConstants.TusResumableValue)
                {
                    response.SetHeader(HeaderConstants.TusResumable, HeaderConstants.TusResumableValue);
                    response.SetHeader(HeaderConstants.TusVersion, HeaderConstants.TusResumableValue);
                    return(await response.Error(HttpStatusCode.PreconditionFailed,
                                                $"Tus version {tusResumable} is not supported. Supported versions: {HeaderConstants.TusResumableValue}"));
                }
            }

            InMemoryFileLock fileLock = null;

            if (methodHandler.RequiresLock)
            {
                fileLock = new InMemoryFileLock(context.GetFileId());

                var hasLock = fileLock.Lock();
                if (!hasLock)
                {
                    return(await response.Error(HttpStatusCode.Conflict,
                                                $"File {context.GetFileId()} is currently being updated. Please try again later"));
                }
            }

            try
            {
                if (!await methodHandler.Validate(context))
                {
                    return(true);
                }

                return(await methodHandler.Handle(context));
            }
            catch (TusStoreException storeException)
            {
                await context.Response.Error(HttpStatusCode.BadRequest, storeException.Message);

                return(true);
            }
            finally
            {
                fileLock?.ReleaseIfHeld();
            }
        }
Exemple #20
0
        internal override async Task <bool> Handle(ContextAdapter context)
        {
            var metadata = context.Request.GetHeader(HeaderConstants.UploadMetadata);

            string         fileId;
            DateTimeOffset?expires = null;

            var response          = context.Response;
            var request           = context.Request;
            var cancellationToken = context.CancellationToken;

            var tusConcatenationStore = context.Configuration.Store as ITusConcatenationStore;

            var uploadConcat = request.Headers.ContainsKey(HeaderConstants.UploadConcat)
                ? new UploadConcat(request.GetHeader(HeaderConstants.UploadConcat), context.Configuration.UrlPath)
                : null;

            var supportsUploadConcat = tusConcatenationStore != null && uploadConcat != null;

            var uploadLength = GetUploadLength(context.Request);

            if (await HandleOnBeforeCreateAsync(context, supportsUploadConcat, uploadConcat, metadata, uploadLength))
            {
                return(true);
            }

            if (supportsUploadConcat)
            {
                fileId = await HandleCreationOfConcatFiles(context, uploadConcat, tusConcatenationStore, uploadLength, metadata, cancellationToken);
            }
            else
            {
                var creationStore = (ITusCreationStore)context.Configuration.Store;
                fileId = await creationStore.CreateFileAsync(uploadLength, metadata, cancellationToken);
                await HandleOnCreateComplete(context, fileId, supportsUploadConcat, uploadConcat, metadata, uploadLength);
            }

            ITusExpirationStore expirationStore = context.Configuration.Store as ITusExpirationStore;

            if (expirationStore != null &&
                context.Configuration.Expiration != null &&
                !(uploadConcat?.Type is FileConcatFinal))
            {
                expires = DateTimeOffset.UtcNow.Add(context.Configuration.Expiration.Timeout);
                await expirationStore.SetExpirationAsync(fileId, expires.Value, context.CancellationToken);
            }

            response.SetHeader(HeaderConstants.TusResumable, HeaderConstants.TusResumableValue);
            response.SetHeader(HeaderConstants.Location, $"{context.Configuration.UrlPath.TrimEnd('/')}/{fileId}");

            if (expires != null)
            {
                response.SetHeader(HeaderConstants.UploadExpires, expires.Value.ToString("R"));
            }

            response.SetStatus((int)HttpStatusCode.Created);
            return(true);
        }
        private async Task ValidateFinalFileCreation(FileConcatFinal finalConcat, ContextAdapter context, ITusConcatenationStore store)
        {
            var filesExist =
                await Task.WhenAll(finalConcat.Files.Select(
                                       f => context.Configuration.Store.FileExistAsync(f, context.CancellationToken)));

            if (filesExist.Any(f => !f))
            {
                await BadRequest(
                    $"Could not find some of the files supplied for concatenation: {string.Join(", ", filesExist.Zip(finalConcat.Files, (b, s) => new {exist = b, name = s}).Where(f => !f.exist).Select(f => f.name))}");

                return;
            }

            var filesArePartial = await Task.WhenAll(
                finalConcat.Files.Select(f => store.GetUploadConcatAsync(f, context.CancellationToken)));

            if (filesArePartial.Any(f => !(f is FileConcatPartial)))
            {
                await BadRequest($"Some of the files supplied for concatenation are not marked as partial and can not be concatenated: {string.Join(", ", filesArePartial.Zip(finalConcat.Files, (s, s1) => new { partial = s is FileConcatPartial, name = s1 }).Where(f => !f.partial).Select(f => f.name))}");

                return;
            }

            var incompleteFiles = new List <string>();
            var totalSize       = 0L;

            foreach (var file in finalConcat.Files)
            {
                var length = context.Configuration.Store.GetUploadLengthAsync(file, context.CancellationToken);
                var offset = context.Configuration.Store.GetUploadOffsetAsync(file, context.CancellationToken);
                await Task.WhenAll(length, offset);

                if (length.Result != null)
                {
                    totalSize += length.Result.Value;
                }

                if (length.Result != offset.Result)
                {
                    incompleteFiles.Add(file);
                }
            }

            if (incompleteFiles.Any())
            {
                await BadRequest(
                    $"Some of the files supplied for concatenation are not finished and can not be concatenated: {string.Join(", ", incompleteFiles)}");

                return;
            }

            if (totalSize > context.Configuration.MaxAllowedUploadSizeInBytes)
            {
                await RequestEntityTooLarge("The concatenated file exceeds the server's max file size.");
            }
        }
        private Task <string> GetMetadata(ContextAdapter context)
        {
            if (context.Configuration.Store is ITusCreationStore tusCreationStore)
            {
                return(tusCreationStore.GetUploadMetadataAsync(Request.FileId, context.CancellationToken));
            }

            return(Task.FromResult <string>(null));
        }
Exemple #23
0
        public override Task Validate(ContextAdapter context)
        {
            if (context.Request.ContentType?.Equals("application/offset+octet-stream", StringComparison.OrdinalIgnoreCase) != true)
            {
                return(BadRequest($"Content-Type {context.Request.ContentType} is invalid. Must be application/offset+octet-stream"));
            }

            return(TaskHelper.Completed);
        }
Exemple #24
0
        public override async Task Validate(ContextAdapter context)
        {
            var exists = await context.Configuration.Store.FileExistAsync(context.GetFileId(), context.CancellationToken);

            if (!exists)
            {
                await NotFound();
            }
        }
Exemple #25
0
        private static IntentHandler DetermineIntentForDelete(ContextAdapter context)
        {
            if (!(context.Configuration.Store is ITusTerminationStore terminationStore))
            {
                return(IntentHandler.NotApplicable);
            }

            return(new DeleteFileHandler(context, terminationStore));
        }
        private async Task ValidateForPatch(ContextAdapter context, ITusConcatenationStore concatStore)
        {
            var uploadConcat = await concatStore.GetUploadConcatAsync(context.Request.FileId, context.CancellationToken);

            if (uploadConcat is FileConcatFinal)
            {
                await Forbidden("File with \"Upload-Concat: final\" cannot be patched");
            }
        }
Exemple #27
0
        private async Task ValidateInternal(ContextAdapter context, ITusExpirationStore expirationStore)
        {
            var expires = await expirationStore.GetExpirationAsync(context.Request.FileId, context.CancellationToken);

            if (expires?.HasPassed() == true)
            {
                await NotFound();
            }
        }
Exemple #28
0
        public override Task Validate(ContextAdapter context)
        {
            if (!(context.Configuration.Store is ITusExpirationStore expirationStore))
            {
                return(Task.FromResult(0));
            }

            return(ValidateInternal(context, expirationStore));
        }
Exemple #29
0
        private static Task HandleOnDeleteCompleteAsync(ContextAdapter context)
        {
            if (context.Configuration.Events?.OnDeleteCompleteAsync == null)
            {
                return(Task.FromResult(0));
            }

            return(context.Configuration.Events.OnDeleteCompleteAsync(DeleteCompleteContext.Create(context)));
        }
        public override Task Validate(ContextAdapter context)
        {
            if (!(context.Configuration.Store is ITusConcatenationStore concatStore))
            {
                return(TaskHelper.Completed);
            }

            return(ValidateForPatch(context, concatStore));
        }