Exemplo n.º 1
0
    public async Task FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRentedBuffer_WhenCopyingFromMemoryStreamDuringReadAsync()
    {
        var    inner = MakeStream(1024 * 1024 + 25);
        string tempFileName;
        var    arrayPool = new Mock <ArrayPool <byte> >();

        arrayPool.Setup(p => p.Rent(It.IsAny <int>()))
        .Returns((int m) => ArrayPool <byte> .Shared.Rent(m));
        arrayPool.Setup(p => p.Return(It.IsAny <byte[]>(), It.IsAny <bool>()))
        .Callback((byte[] bytes, bool clear) => ArrayPool <byte> .Shared.Return(bytes, clear));

        using (var stream = new FileBufferingReadStream(inner, 1024 * 1024 + 1, 2 * 1024 * 1024, GetCurrentDirectory(), arrayPool.Object))
        {
            arrayPool.Verify(v => v.Rent(It.IsAny <int>()), Times.Never());

            await stream.ReadAsync(new byte[1024 * 1024]);

            Assert.False(File.Exists(stream.TempFileName), "tempFile should not be created as yet");

            await stream.ReadAsync(new byte[4]);

            Assert.True(File.Exists(stream.TempFileName), "tempFile should be created");
            tempFileName = stream.TempFileName !;

            arrayPool.Verify(v => v.Rent(It.IsAny <int>()), Times.Once());
            arrayPool.Verify(v => v.Return(It.IsAny <byte[]>(), It.IsAny <bool>()), Times.Once());
        }

        Assert.False(File.Exists(tempFileName));
    }
Exemplo n.º 2
0
    private static async ValueTask <byte[]> ReadDataAsync(JsonTranscodingServerCallContext serverCallContext)
    {
        // Buffer to disk if content is larger than 30Kb.
        // Based on value in XmlSerializer and NewtonsoftJson input formatters.
        const int DefaultMemoryThreshold = 1024 * 30;

        var memoryThreshold = DefaultMemoryThreshold;
        var contentLength   = serverCallContext.HttpContext.Request.ContentLength.GetValueOrDefault();

        if (contentLength > 0 && contentLength < memoryThreshold)
        {
            // If the Content-Length is known and is smaller than the default buffer size, use it.
            memoryThreshold = (int)contentLength;
        }

        using var fs = new FileBufferingReadStream(serverCallContext.HttpContext.Request.Body, memoryThreshold);

        // Read the request body into buffer.
        // No explicit cancellation token. Request body uses underlying request aborted token.
        await fs.DrainAsync(CancellationToken.None);

        fs.Seek(0, SeekOrigin.Begin);

        var data = new byte[fs.Length];
        var read = fs.Read(data);

        Debug.Assert(read == data.Length);

        return(data);
    }
Exemplo n.º 3
0
        /// <inheritdoc />
        public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default)
        {
            EnsureArg.IsNotNull(stream, nameof(stream));

            int    bufferThreshold = DefaultBufferThreshold;
            long?  bufferLimit     = null;
            Stream seekableStream  = null;

            if (!stream.CanSeek)
            {
                try
                {
                    seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                    _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream);
                    await seekableStream.DrainAsync(cancellationToken);
                }
                catch (InvalidDataException)
                {
                    // This will result in bad request, we need to handle this differently when we make the processing serial.
                    throw new DicomFileLengthLimitExceededException(_storeConfiguration.Value.MaxAllowedDicomFileSize);
                }
                catch (IOException ex)
                {
                    throw new InvalidMultipartBodyPartException(ex);
                }
            }
            else
            {
                seekableStream = stream;
            }

            seekableStream.Seek(0, SeekOrigin.Begin);
            return(seekableStream);
        }
Exemplo n.º 4
0
    public async Task FileBufferingReadStream_AsyncReadWithOnDiskLimit_EnforcesLimit()
    {
        var    inner = MakeStream(1024 * 2);
        string tempFileName;

        using (var stream = new FileBufferingReadStream(inner, 512, 1024, GetCurrentDirectory()))
        {
            var bytes = new byte[500];
            var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(bytes.Length, read0);
            Assert.Equal(read0, stream.Length);
            Assert.Equal(read0, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(bytes.Length, read1);
            Assert.Equal(read0 + read1, stream.Length);
            Assert.Equal(read0 + read1, stream.Position);
            Assert.False(stream.InMemory);
            Assert.NotNull(stream.TempFileName);
            tempFileName = stream.TempFileName !;
            Assert.True(File.Exists(tempFileName));

            var exception = await Assert.ThrowsAsync <IOException>(() => stream.ReadAsync(bytes, 0, bytes.Length));

            Assert.Equal("Buffer limit exceeded.", exception.Message);
            Assert.False(stream.InMemory);
            Assert.NotNull(stream.TempFileName);
        }

        Assert.False(File.Exists(tempFileName));
    }
Exemplo n.º 5
0
        /// <inheritdoc />
        public override async ValueTask <T> DeserializeFromRequest <T>(RequestExecutionContext context)
        {
            var fileReader = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryThreshold);

            context.HttpContext.Response.RegisterForDispose(fileReader);

            try
            {
                await fileReader.DrainAsync(context.HttpContext.RequestAborted);

                fileReader.Seek(0L, SeekOrigin.Begin);

                using var textReader = new StreamReader(fileReader);

                using var xmlReader = new XmlTextReader(textReader);

                var serializer = GetSerializer(typeof(T));

                return((T)serializer.Deserialize(xmlReader));
            }
            finally
            {
                await fileReader.DisposeAsync();
            }
        }
        /// <summary>
        /// Resolve the <paramref name="stream"/> for the <see cref="FileUploadProvider"/> and awaits the upload.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing uploaded data.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
        /// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="null"/>, <see cref="ErrorMessageResponse"/> otherwise.</returns>
        public async Task <ErrorMessageResponse> Completion(Stream stream, CancellationToken cancellationToken)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            if (ticketExpiryCts.IsCancellationRequested)
            {
                return(new ErrorMessageResponse(ErrorCode.ResourceNotPresent));
            }

            Stream bufferedStream = null;

            if (requireSynchronousIO)
            {
                // big reads, we should buffer to disk
                bufferedStream = new FileBufferingReadStream(stream, DefaultIOManager.DefaultBufferSize);
                await bufferedStream.DrainAsync(cancellationToken).ConfigureAwait(false);
            }

            using (bufferedStream)
            {
                taskCompletionSource.TrySetResult(bufferedStream ?? stream);

                await completionTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);

                return(errorMessage);
            }
        }
Exemplo n.º 7
0
    public void FileBufferingReadStream_SyncReadUnderThreshold_DoesntCreateFile()
    {
        var inner = MakeStream(1024 * 2);

        using (var stream = new FileBufferingReadStream(inner, 1024 * 3, null, Directory.GetCurrentDirectory()))
        {
            var bytes = new byte[1000];
            var read0 = stream.Read(bytes, 0, bytes.Length);
            Assert.Equal(bytes.Length, read0);
            Assert.Equal(read0, stream.Length);
            Assert.Equal(read0, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var read1 = stream.Read(bytes, 0, bytes.Length);
            Assert.Equal(bytes.Length, read1);
            Assert.Equal(read0 + read1, stream.Length);
            Assert.Equal(read0 + read1, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var read2 = stream.Read(bytes, 0, bytes.Length);
            Assert.Equal(inner.Length - read0 - read1, read2);
            Assert.Equal(read0 + read1 + read2, stream.Length);
            Assert.Equal(read0 + read1 + read2, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var read3 = stream.Read(bytes, 0, bytes.Length);
            Assert.Equal(0, read3);
        }
    }
    public async Task FileBufferingReadStream_Async0ByteReadAfterBuffering_ReadsFromFile()
    {
        var    inner = MakeStream(1024 * 2);
        string tempFileName;

        using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
        {
            await stream.DrainAsync(default);
        private async Task <InputFormatterResult> BufferedStreamAsync(InputFormatterContext context)
        {
            using var readStream = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryBufferThreshold);
            await readStream.DrainAsync(CancellationToken.None);

            readStream.Position = 0;
            var payload = _model.Deserialize(readStream, value: null, type: context.ModelType);

            return(InputFormatterResult.Success(payload));
        }
Exemplo n.º 10
0
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var request = context.HttpContext.Request;

            var readStream = request.Body;
            FileBufferingReadStream?fileBufferingReadStream = null;

            if (readStream.CanSeek)
            {
                var position = request.Body.Position;
                await readStream.DrainAsync(CancellationToken.None);

                readStream.Position = position;
            }
            else if (!_mvcOptions.SuppressOutputFormatterBuffering)
            {
                var memoryThreshold = DefaultMemoryThreshold;
                var contentLength   = request.ContentLength.GetValueOrDefault();
                if (contentLength > 0 && contentLength < memoryThreshold)
                {
                    memoryThreshold = (int)contentLength;
                }

                readStream = fileBufferingReadStream = new FileBufferingReadStream(request.Body, memoryThreshold);
            }

            object?   model     = null;
            Exception?exception = null;

            await using (fileBufferingReadStream)
            {
                if (fileBufferingReadStream != null)
                {
                    await readStream.DrainAsync(CancellationToken.None);

                    readStream.Seek(0, SeekOrigin.Begin);
                }

                try { model = ApiContractSerializer.ProtoBuf.Deserialize(readStream, context.ModelType); }
                catch (ProtoException ex) { exception = ex; }
            }

            if (exception != null)
            {
                _logger.LogDebug(exception, "ProtoBuf input formatter threw an exception.");
                return(InputFormatterResult.Failure());
            }

            return(InputFormatterResult.Success(model));
        }
Exemplo n.º 11
0
    public void FileBufferingReadStream_Properties_ExpectedValues()
    {
        var inner = MakeStream(1024 * 2);

        using (var stream = new FileBufferingReadStream(inner, 1024, null, Directory.GetCurrentDirectory()))
        {
            Assert.True(stream.CanRead);
            Assert.True(stream.CanSeek);
            Assert.False(stream.CanWrite);
            Assert.Equal(0, stream.Length); // Nothing buffered yet
            Assert.Equal(0, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);
        }
    }
Exemplo n.º 12
0
        public static void BufferingRequestStream(this IRequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException(nameof(requestContext));
            }

            var inputStream = requestContext.Request.Body;

            if (inputStream != null && !inputStream.CanSeek)
            {
                var fileStream = new FileBufferingReadStream(inputStream, DefaultBufferThreshold, null, TempDirectory);
                requestContext.RegisterForDispose(fileStream);
                requestContext.Request.Body = fileStream;
            }
        }
Exemplo n.º 13
0
    public async Task ReadThenCopyToAsyncWorks()
    {
        var data  = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
        var inner = new MemoryStream(data);

        using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());

        var withoutBufferMs = new MemoryStream();
        var buffer          = new byte[100];
        var read            = stream.Read(buffer);
        await stream.CopyToAsync(withoutBufferMs);

        Assert.Equal(100, read);
        Assert.Equal(data.AsMemory(0, read).ToArray(), buffer);
        Assert.Equal(data.AsMemory(read).ToArray(), withoutBufferMs.ToArray());
    }
Exemplo n.º 14
0
        public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long?bufferLimit = null)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var body = request.Body;

            if (!body.CanSeek)
            {
                var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                request.Body = fileStream;
                request.HttpContext.Response.RegisterForDispose(fileStream);
            }
            return(request);
        }
Exemplo n.º 15
0
    public async Task FileBufferingReadStream_AsyncReadOverThreshold_CreatesFile()
    {
        var    inner = MakeStream(1024 * 2);
        string tempFileName;

        using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
        {
            var bytes = new byte[1000];
            var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(bytes.Length, read0);
            Assert.Equal(read0, stream.Length);
            Assert.Equal(read0, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(bytes.Length, read1);
            Assert.Equal(read0 + read1, stream.Length);
            Assert.Equal(read0 + read1, stream.Position);
            Assert.False(stream.InMemory);
            Assert.NotNull(stream.TempFileName);
            tempFileName = stream.TempFileName !;
            Assert.True(File.Exists(tempFileName));

            var read2 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(inner.Length - read0 - read1, read2);
            Assert.Equal(read0 + read1 + read2, stream.Length);
            Assert.Equal(read0 + read1 + read2, stream.Position);
            Assert.False(stream.InMemory);
            Assert.NotNull(stream.TempFileName);
            Assert.True(File.Exists(tempFileName));

            var read3 = await stream.ReadAsync(bytes, 0, bytes.Length);

            Assert.Equal(0, read3);
        }

        Assert.False(File.Exists(tempFileName));
    }
Exemplo n.º 16
0
    public async Task PartialReadAsyncThenSeekReplaysBuffer()
    {
        var data  = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
        var inner = new MemoryStream(data);

        using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());

        var withoutBufferMs = new MemoryStream();
        var buffer          = new byte[100];
        var read1           = await stream.ReadAsync(buffer);

        stream.Position = 0;
        var buffer2 = new byte[200];
        var read2   = await stream.ReadAsync(buffer2);

        Assert.Equal(100, read1);
        Assert.Equal(100, read2);
        Assert.Equal(data.AsMemory(0, read1).ToArray(), buffer);
        Assert.Equal(data.AsMemory(0, read2).ToArray(), buffer2.AsMemory(0, read2).ToArray());
    }
 public void FileBufferingReadStream_Properties_ExpectedValues()
 {
     using var inner = MakeStream(1024 * 2);
     System.IO.Stream bufferSteam;
     {
         using var stream = new FileBufferingReadStream(inner, 1024, null, Directory.GetCurrentDirectory());
         bufferSteam      = stream;
         Assert.True(stream.CanRead);
         Assert.True(stream.CanSeek);
         Assert.False(stream.CanWrite);
         Assert.Equal(0, stream.Length); // Nothing buffered yet
         Assert.Equal(0, stream.Position);
         Assert.True(stream.InMemory);
         Assert.Null(stream.TempFileName);
     }
     Assert.False(bufferSteam.CanRead);  // Buffered Stream now disposed
     Assert.False(bufferSteam.CanSeek);
     Assert.True(inner.CanRead);         // Inner Stream not disposed
     Assert.True(inner.CanSeek);
 }
Exemplo n.º 18
0
    public void FileBufferingReadStream_SyncReadWithInMemoryLimit_EnforcesLimit()
    {
        var inner = MakeStream(1024 * 2);

        using (var stream = new FileBufferingReadStream(inner, 1024, 900, Directory.GetCurrentDirectory()))
        {
            var bytes = new byte[500];
            var read0 = stream.Read(bytes, 0, bytes.Length);
            Assert.Equal(bytes.Length, read0);
            Assert.Equal(read0, stream.Length);
            Assert.Equal(read0, stream.Position);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);

            var exception = Assert.Throws <IOException>(() => stream.Read(bytes, 0, bytes.Length));
            Assert.Equal("Buffer limit exceeded.", exception.Message);
            Assert.True(stream.InMemory);
            Assert.Null(stream.TempFileName);
            Assert.False(File.Exists(stream.TempFileName));
        }
    }
Exemplo n.º 19
0
        public static MultipartSection EnableRewind(this MultipartSection section, Action <IDisposable> registerForDispose,
                                                    int bufferThreshold = DefaultBufferThreshold, long?bufferLimit = null)
        {
            if (section == null)
            {
                throw new ArgumentNullException(nameof(section));
            }
            if (registerForDispose == null)
            {
                throw new ArgumentNullException(nameof(registerForDispose));
            }

            var body = section.Body;

            if (!body.CanSeek)
            {
                var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                section.Body = fileStream;
                registerForDispose(fileStream);
            }
            return(section);
        }
Exemplo n.º 20
0
        /// <inheritdoc />
        public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default)
        {
            EnsureArg.IsNotNull(stream, nameof(stream));

            int    bufferThreshold = DefaultBufferThreshold;
            long?  bufferLimit     = null;
            Stream seekableStream  = null;

            if (!stream.CanSeek)
            {
                seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
                _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream);
                await seekableStream.DrainAsync(cancellationToken);
            }
            else
            {
                seekableStream = stream;
            }

            seekableStream.Seek(0, SeekOrigin.Begin);

            return(seekableStream);
        }
Exemplo n.º 21
0
    public async Task CopyToAsyncWorksWithFileThreshold()
    {
        // 4K is the lower bound on buffer sizes
        var bufferSize         = 4096;
        var mostExpectedWrites = 8;
        var data  = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).Reverse().ToArray();
        var inner = new MemoryStream(data);

        using var stream = new FileBufferingReadStream(inner, 100, bufferLimit: null, GetCurrentDirectory());

        var withoutBufferMs = new NumberOfWritesMemoryStream();
        await stream.CopyToAsync(withoutBufferMs);

        var withBufferMs = new NumberOfWritesMemoryStream();

        stream.Position = 0;
        await stream.CopyToAsync(withBufferMs);

        Assert.Equal(data, withoutBufferMs.ToArray());
        Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
        Assert.Equal(data, withBufferMs.ToArray());
        Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
    }
Exemplo n.º 22
0
    /// <inheritdoc />
    public override async Task <InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context,
        Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        var httpContext = context.HttpContext;
        var request     = httpContext.Request;

        var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

        var readStream        = request.Body;
        var disposeReadStream = false;

        if (readStream.CanSeek)
        {
            // The most common way of getting here is the user has request buffering on.
            // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
            // reads as part of the deserialization.
            // To avoid this, drain and reset the stream.
            var position = request.Body.Position;
            await readStream.DrainAsync(CancellationToken.None);

            readStream.Position = position;
        }
        else if (!suppressInputFormatterBuffering)
        {
            // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
            // read everything into a buffer, and then seek back to the beginning.
            var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold;
            var contentLength   = request.ContentLength.GetValueOrDefault();
            if (contentLength > 0 && contentLength < memoryThreshold)
            {
                // If the Content-Length is known and is smaller than the default buffer size, use it.
                memoryThreshold = (int)contentLength;
            }

            readStream = new FileBufferingReadStream(request.Body, memoryThreshold);
            // Ensure the file buffer stream is always disposed at the end of a request.
            httpContext.Response.RegisterForDispose(readStream);

            await readStream.DrainAsync(CancellationToken.None);

            readStream.Seek(0L, SeekOrigin.Begin);

            disposeReadStream = true;
        }

        var       successful = true;
        Exception?exception  = null;
        object?   model;

        using (var streamReader = context.ReaderFactory(readStream, encoding))
        {
            using var jsonReader  = new JsonTextReader(streamReader);
            jsonReader.ArrayPool  = _charPool;
            jsonReader.CloseInput = false;

            var type           = context.ModelType;
            var jsonSerializer = CreateJsonSerializer(context);
            jsonSerializer.Error += ErrorHandler;

            if (_jsonOptions.ReadJsonWithRequestCulture)
            {
                jsonSerializer.Culture = CultureInfo.CurrentCulture;
            }

            try
            {
                model = jsonSerializer.Deserialize(jsonReader, type);
            }
            finally
            {
                // Clean up the error handler since CreateJsonSerializer() pools instances.
                jsonSerializer.Error -= ErrorHandler;
                ReleaseJsonSerializer(jsonSerializer);

                if (disposeReadStream)
                {
                    await readStream.DisposeAsync();
                }
            }
        }

        if (successful)
        {
            if (model == null && !context.TreatEmptyInputAsDefaultValue)
            {
                // Some nonempty inputs might deserialize as null, for example whitespace,
                // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                // be notified that we don't regard this as a real input so it can register
                // a model binding error.
                return(InputFormatterResult.NoValue());
            }
            else
            {
                return(InputFormatterResult.Success(model));
            }
        }

        if (exception is not null && exception is not(JsonException or OverflowException or FormatException))
        {
            // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary.
            // We only need to rethrow an exception if we believe it needs to be handled by something further up
            // the stack.
            // JsonException, OverflowException, and FormatException are assumed to be only encountered when
            // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else
            // needs to be rethrown.

            var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
            exceptionDispatchInfo.Throw();
        }

        return(InputFormatterResult.Failure());

        void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
        {
            // Skipping error, if it's already marked as handled
            // This allows user code to implement its own error handling
            if (eventArgs.ErrorContext.Handled)
            {
                return;
            }

            successful = false;

            // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path.
            var path      = eventArgs.ErrorContext.Path;
            var member    = eventArgs.ErrorContext.Member?.ToString();
            var addMember = !string.IsNullOrEmpty(member);

            if (addMember)
            {
                // Path.Member case (path.Length < member.Length) needs no further checks.
                if (path.Length == member !.Length)
                {
                    // Add Member in Path.Memb case but not for Path.Path.
                    addMember = !string.Equals(path, member, StringComparison.Ordinal);
                }
                else if (path.Length > member.Length)
                {
                    // Finally, check whether Path already ends with Member.
                    if (member[0] == '[')
                    {
                        addMember = !path.EndsWith(member, StringComparison.Ordinal);
                    }
                    else
                    {
                        addMember = !path.EndsWith("." + member, StringComparison.Ordinal) &&
                                    !path.EndsWith("['" + member + "']", StringComparison.Ordinal) &&
                                    !path.EndsWith("[" + member + "]", StringComparison.Ordinal);
                    }
                }
            }

            if (addMember)
            {
                path = ModelNames.CreatePropertyModelName(path, member);
            }

            // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
            var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

            exception = eventArgs.ErrorContext.Error;

            var metadata            = GetPathMetadata(context.Metadata, path);
            var modelStateException = WrapExceptionForModelState(exception);

            context.ModelState.TryAddModelError(key, modelStateException, metadata);

            _logger.JsonInputException(exception);

            // Error must always be marked as handled
            // Failure to do so can cause the exception to be rethrown at every recursive level and
            // overflow the stack for x64 CLR processes
            eventArgs.ErrorContext.Handled = true;
        }
Exemplo n.º 23
0
    /// <inheritdoc />
    public override async Task <InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context,
        Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        var httpContext = context.HttpContext;
        var request     = httpContext.Request;

        var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

        var readStream        = request.Body;
        var disposeReadStream = false;

        if (readStream.CanSeek)
        {
            // The most common way of getting here is the user has request buffering on.
            // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
            // reads as part of the deserialization.
            // To avoid this, drain and reset the stream.
            var position = request.Body.Position;
            await readStream.DrainAsync(CancellationToken.None);

            readStream.Position = position;
        }
        else if (!suppressInputFormatterBuffering)
        {
            // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
            // read everything into a buffer, and then seek back to the beginning.
            var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold;
            var contentLength   = request.ContentLength.GetValueOrDefault();
            if (contentLength > 0 && contentLength < memoryThreshold)
            {
                // If the Content-Length is known and is smaller than the default buffer size, use it.
                memoryThreshold = (int)contentLength;
            }

            readStream = new FileBufferingReadStream(request.Body, memoryThreshold);
            // Ensure the file buffer stream is always disposed at the end of a request.
            httpContext.Response.RegisterForDispose(readStream);

            await readStream.DrainAsync(CancellationToken.None);

            readStream.Seek(0L, SeekOrigin.Begin);

            disposeReadStream = true;
        }

        var       successful = true;
        Exception?exception  = null;
        object?   model;

        using (var streamReader = context.ReaderFactory(readStream, encoding))
        {
            using var jsonReader  = new JsonTextReader(streamReader);
            jsonReader.ArrayPool  = _charPool;
            jsonReader.CloseInput = false;

            var type           = context.ModelType;
            var jsonSerializer = CreateJsonSerializer(context);
            jsonSerializer.Error += ErrorHandler;

            if (_jsonOptions.ReadJsonWithRequestCulture)
            {
                jsonSerializer.Culture = CultureInfo.CurrentCulture;
            }

            try
            {
                model = jsonSerializer.Deserialize(jsonReader, type);
            }
            finally
            {
                // Clean up the error handler since CreateJsonSerializer() pools instances.
                jsonSerializer.Error -= ErrorHandler;
                ReleaseJsonSerializer(jsonSerializer);

                if (disposeReadStream)
                {
                    await readStream.DisposeAsync();
                }
            }
        }

        if (successful)
        {
            if (model == null && !context.TreatEmptyInputAsDefaultValue)
            {
                // Some nonempty inputs might deserialize as null, for example whitespace,
                // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                // be notified that we don't regard this as a real input so it can register
                // a model binding error.
                return(InputFormatterResult.NoValue());
            }
            else
            {
                return(InputFormatterResult.Success(model));
            }
        }

        if (exception is not null && exception is not(JsonException or OverflowException or FormatException))
        {
            // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary.
            // We only need to rethrow an exception if we believe it needs to be handled by something further up
            // the stack.
            // JsonException, OverflowException, and FormatException are assumed to be only encountered when
            // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else
            // needs to be rethrown.

            var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
            exceptionDispatchInfo.Throw();
        }

        return(InputFormatterResult.Failure());

        void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
        {
            // Skipping error, if it's already marked as handled
            // This allows user code to implement its own error handling
            if (eventArgs.ErrorContext.Handled)
            {
                return;
            }

            successful = false;

            // The following addMember logic is intended to append the names of missing required properties to the
            // ModelStateDictionary key. Normally, just the ModelName and ErrorContext.Path is used for this key,
            // but ErrorContext.Path does not include the missing required property name like we want it to.
            // For example, given the following class and input missing the required "Name" property:
            //
            // class Person
            // {
            //     [JsonProperty(Required = Required.Always)]
            //     public string Name { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Required property 'Name' not found in JSON. Path 'Person'..."} System.Exception {Newtonsoft.Json.JsonSerializationException}
            // Member  "Name"   object {string}
            // Path    "Person" string
            //
            // So we update the path used for the ModelStateDictionary key to be "Person.Name" instead of just "Person".
            // See https://github.com/aspnet/Mvc/issues/8509
            var path   = eventArgs.ErrorContext.Path;
            var member = eventArgs.ErrorContext.Member as string;

            // There are some deserialization exceptions that include the member in the path but not at the end.
            // For example, given the following classes and invalid input like { "b": { "c": { "d": abc } } }:
            //
            // class A
            // {
            //     public B B { get; set; }
            // }
            // class B
            // {
            //     public C C { get; set; }
            // }
            // class C
            // {
            //     public string D { get; set; }
            // }
            //
            // We will see the following ErrorContext:
            //
            // Error   {"Unexpected character encountered while parsing value: b. Path 'b.c.d'..."} System.Exception {Newtonsoft.Json.JsonReaderException}
            // Member  "c"     object {string}
            // Path    "b.c.d" string
            //
            // Notice that Member "c" is in the middle of the Path "b.c.d". The error handler gets invoked for each level of nesting.
            // null, "b", "c" and "d" are each a Member in different ErrorContexts all reporting the same parsing error.
            //
            // The parsing error is reported as a JsonReaderException instead of as a JsonSerializationException like
            // for missing required properties. We use the exception type to filter out these errors and keep the path used
            // for the ModelStateDictionary key as "b.c.d" instead of "b.c.d.c"
            // See https://github.com/dotnet/aspnetcore/issues/33451
            var addMember = !string.IsNullOrEmpty(member) && eventArgs.ErrorContext.Error is JsonSerializationException;

            // There are still JsonSerilizationExceptions that set ErrorContext.Member but include it at the
            // end of ErrorContext.Path already. The following logic attempts to filter these out.
            if (addMember)
            {
                // Path.Member case (path.Length < member.Length) needs no further checks.
                if (path.Length == member !.Length)
                {
                    // Add Member in Path.Member case but not for Path.Path.
                    addMember = !string.Equals(path, member, StringComparison.Ordinal);
                }
                else if (path.Length > member.Length)
                {
                    // Finally, check whether Path already ends or starts with Member.
                    if (member[0] == '[')
                    {
                        addMember = !path.EndsWith(member, StringComparison.Ordinal);
                    }
                    else
                    {
                        addMember = !path.EndsWith($".{member}", StringComparison.Ordinal) &&
                                    !path.EndsWith($"['{member}']", StringComparison.Ordinal) &&
                                    !path.EndsWith($"[{member}]", StringComparison.Ordinal);
                    }
                }
            }

            if (addMember)
            {
                path = ModelNames.CreatePropertyModelName(path, member);
            }

            // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
            var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

            exception = eventArgs.ErrorContext.Error;

            var metadata            = GetPathMetadata(context.Metadata, path);
            var modelStateException = WrapExceptionForModelState(exception);

            context.ModelState.TryAddModelError(key, modelStateException, metadata);

            _logger.JsonInputException(exception);

            // Error must always be marked as handled
            // Failure to do so can cause the exception to be rethrown at every recursive level and
            // overflow the stack for x64 CLR processes
            eventArgs.ErrorContext.Handled = true;
        }
Exemplo n.º 24
0
        /// <inheritdoc />
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(
            InputFormatterContext context,
            Encoding encoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (encoding == null)
            {
                throw new ArgumentNullException(nameof(encoding));
            }

            var request = context.HttpContext.Request;

            var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering;

            var readStream        = request.Body;
            var disposeReadStream = false;

            if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
            {
                // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                // read everything into a buffer, and then seek back to the beginning.
                var memoryThreshold = DefaultMemoryThreshold;
                var contentLength   = request.ContentLength.GetValueOrDefault();
                if (contentLength > 0 && contentLength < memoryThreshold)
                {
                    // If the Content-Length is known and is smaller than the default buffer size, use it.
                    memoryThreshold = (int)contentLength;
                }

                readStream = new FileBufferingReadStream(request.Body, memoryThreshold);

                await readStream.DrainAsync(CancellationToken.None);

                readStream.Seek(0L, SeekOrigin.Begin);

                disposeReadStream = true;
            }

            var       successful = true;
            Exception exception  = null;
            object    model;

            using (var streamReader = context.ReaderFactory(readStream, encoding))
            {
                using var jsonReader  = new JsonTextReader(streamReader);
                jsonReader.ArrayPool  = _charPool;
                jsonReader.CloseInput = false;

                var type           = context.ModelType;
                var jsonSerializer = CreateJsonSerializer(context);
                jsonSerializer.Error += ErrorHandler;
                try
                {
                    model = jsonSerializer.Deserialize(jsonReader, type);
                }
                finally
                {
                    // Clean up the error handler since CreateJsonSerializer() pools instances.
                    jsonSerializer.Error -= ErrorHandler;
                    ReleaseJsonSerializer(jsonSerializer);

                    if (disposeReadStream)
                    {
                        await readStream.DisposeAsync();
                    }
                }
            }

            if (successful)
            {
                if (model == null && !context.TreatEmptyInputAsDefaultValue)
                {
                    // Some nonempty inputs might deserialize as null, for example whitespace,
                    // or the JSON-encoded value "null". The upstream BodyModelBinder needs to
                    // be notified that we don't regard this as a real input so it can register
                    // a model binding error.
                    return(InputFormatterResult.NoValue());
                }
                else
                {
                    return(InputFormatterResult.Success(model));
                }
            }

            if (!(exception is JsonException || exception is OverflowException))
            {
                var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
                exceptionDispatchInfo.Throw();
            }

            return(InputFormatterResult.Failure());

            void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs)
            {
                successful = false;

                // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path.
                var path      = eventArgs.ErrorContext.Path;
                var member    = eventArgs.ErrorContext.Member?.ToString();
                var addMember = !string.IsNullOrEmpty(member);

                if (addMember)
                {
                    // Path.Member case (path.Length < member.Length) needs no further checks.
                    if (path.Length == member.Length)
                    {
                        // Add Member in Path.Memb case but not for Path.Path.
                        addMember = !string.Equals(path, member, StringComparison.Ordinal);
                    }
                    else if (path.Length > member.Length)
                    {
                        // Finally, check whether Path already ends with Member.
                        if (member[0] == '[')
                        {
                            addMember = !path.EndsWith(member, StringComparison.Ordinal);
                        }
                        else
                        {
                            addMember = !path.EndsWith("." + member, StringComparison.Ordinal) &&
                                        !path.EndsWith("['" + member + "']", StringComparison.Ordinal) &&
                                        !path.EndsWith("[" + member + "]", StringComparison.Ordinal);
                        }
                    }
                }

                if (addMember)
                {
                    path = ModelNames.CreatePropertyModelName(path, member);
                }

                // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]".
                var key = ModelNames.CreatePropertyModelName(context.ModelName, path);

                exception = eventArgs.ErrorContext.Error;

                var metadata            = GetPathMetadata(context.Metadata, path);
                var modelStateException = WrapExceptionForModelState(exception);

                context.ModelState.TryAddModelError(key, modelStateException, metadata);

                _logger.JsonInputException(exception);

                // Error must always be marked as handled
                // Failure to do so can cause the exception to be rethrown at every recursive level and
                // overflow the stack for x64 CLR processes
                eventArgs.ErrorContext.Handled = true;
            }
        }
Exemplo n.º 25
0
        /// <inheritdoc />
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(
            InputFormatterContext context,
            Encoding encoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (encoding == null)
            {
                throw new ArgumentNullException(nameof(encoding));
            }

            var    request    = context.HttpContext.Request;
            Stream readStream = new NonDisposableStream(request.Body);

            if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
            {
                // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                // read everything into a buffer, and then seek back to the beginning.
                var memoryThreshold = DefaultMemoryThreshold;
                var contentLength   = request.ContentLength.GetValueOrDefault();
                if (contentLength > 0 && contentLength < memoryThreshold)
                {
                    // If the Content-Length is known and is smaller than the default buffer size, use it.
                    memoryThreshold = (int)contentLength;
                }

                readStream = new FileBufferingReadStream(request.Body, memoryThreshold);

                await readStream.DrainAsync(CancellationToken.None);

                readStream.Seek(0L, SeekOrigin.Begin);
            }

            try
            {
                using var xmlReader = CreateXmlReader(readStream, encoding);
                var type = GetSerializableType(context.ModelType);

                var serializer = GetCachedSerializer(type);

                var deserializedObject = serializer.Deserialize(xmlReader);

                // Unwrap only if the original type was wrapped.
                if (type != context.ModelType)
                {
                    if (deserializedObject is IUnwrappable unwrappable)
                    {
                        deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType);
                    }
                }

                return(InputFormatterResult.Success(deserializedObject));
            }
            // XmlSerializer wraps actual exceptions (like FormatException or XmlException) into an InvalidOperationException
            // https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs#L652
            catch (InvalidOperationException exception) when(exception.InnerException != null &&
                                                             exception.InnerException.InnerException == null &&
                                                             string.Equals("Microsoft.GeneratedCode", exception.InnerException.Source, StringComparison.InvariantCulture))
            {
                // Know this was an XML parsing error because the inner Exception was thrown in the (generated)
                // assembly the XmlSerializer uses for parsing. The problem did not arise lower in the stack i.e. it's
                // not (for example) an out-of-memory condition.
                throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException);
            }
            catch (InvalidOperationException exception) when(exception.InnerException is FormatException ||
                                                             exception.InnerException is XmlException)
            {
                throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException);
            }
            finally
            {
                if (readStream is FileBufferingReadStream fileBufferingReadStream)
                {
                    await fileBufferingReadStream.DisposeAsync();
                }
            }
        }