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."; } }
public ConcatenateFilesHandler(ContextAdapter context, ITusConcatenationStore concatenationStore) : base(context, IntentType.ConcatenateFiles, LockType.NoLock) { UploadConcat = ParseUploadConcatHeader(); _concatenationStore = concatenationStore; _expirationHelper = new ExpirationHelper(context.Configuration); }
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); }
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); }
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; } }
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"); } } } }
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); }
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); }
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)); } } } }
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)); }
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); }
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(); } }
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)); }
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); }
public override async Task Validate(ContextAdapter context) { var exists = await context.Configuration.Store.FileExistAsync(context.GetFileId(), context.CancellationToken); if (!exists) { await NotFound(); } }
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"); } }
private async Task ValidateInternal(ContextAdapter context, ITusExpirationStore expirationStore) { var expires = await expirationStore.GetExpirationAsync(context.Request.FileId, context.CancellationToken); if (expires?.HasPassed() == true) { await NotFound(); } }
public override Task Validate(ContextAdapter context) { if (!(context.Configuration.Store is ITusExpirationStore expirationStore)) { return(Task.FromResult(0)); } return(ValidateInternal(context, expirationStore)); }
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)); }