| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Diagnostics.CodeAnalysis; |
| | | 6 | | using System.IO; |
| | | 7 | | using System.Runtime.CompilerServices; |
| | | 8 | | using System.Threading; |
| | | 9 | | using System.Threading.Tasks; |
| | | 10 | | |
| | | 11 | | namespace System.Net.Http |
| | | 12 | | { |
| | | 13 | | public class StreamContent : HttpContent |
| | | 14 | | { |
| | | 15 | | private Stream _content; |
| | | 16 | | private int _bufferSize; |
| | | 17 | | private bool _contentConsumed; |
| | | 18 | | private long _start; |
| | | 19 | | |
| | 0 | 20 | | public StreamContent(Stream content) |
| | 0 | 21 | | { |
| | 0 | 22 | | ArgumentNullException.ThrowIfNull(content); |
| | | 23 | | |
| | | 24 | | // Indicate that we should use default buffer size by setting size to 0. |
| | 0 | 25 | | InitializeContent(content, 0); |
| | 0 | 26 | | } |
| | | 27 | | |
| | 0 | 28 | | public StreamContent(Stream content, int bufferSize) |
| | 0 | 29 | | { |
| | 0 | 30 | | ArgumentNullException.ThrowIfNull(content); |
| | 0 | 31 | | ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize); |
| | | 32 | | |
| | 0 | 33 | | InitializeContent(content, bufferSize); |
| | 0 | 34 | | } |
| | | 35 | | |
| | | 36 | | [MemberNotNull(nameof(_content))] |
| | | 37 | | private void InitializeContent(Stream content, int bufferSize) |
| | 0 | 38 | | { |
| | 0 | 39 | | _content = content; |
| | 0 | 40 | | _bufferSize = bufferSize; |
| | 0 | 41 | | if (content.CanSeek) |
| | 0 | 42 | | { |
| | 0 | 43 | | _start = content.Position; |
| | 0 | 44 | | } |
| | 0 | 45 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Associate(this, content); |
| | 0 | 46 | | } |
| | | 47 | | |
| | | 48 | | protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellati |
| | 0 | 49 | | { |
| | 0 | 50 | | Debug.Assert(stream != null); |
| | 0 | 51 | | PrepareContent(); |
| | | 52 | | // If the stream can't be re-read, make sure that it gets disposed once it is consumed. |
| | 0 | 53 | | StreamToStreamCopy.Copy(_content, stream, _bufferSize, !_content.CanSeek); |
| | 0 | 54 | | } |
| | | 55 | | |
| | | 56 | | protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => |
| | 0 | 57 | | SerializeToStreamAsyncCore(stream, default); |
| | | 58 | | |
| | | 59 | | protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cance |
| | | 60 | | // Only skip the original protected virtual SerializeToStreamAsync if this |
| | | 61 | | // isn't a derived type that may have overridden the behavior. |
| | 0 | 62 | | GetType() == typeof(StreamContent) ? SerializeToStreamAsyncCore(stream, cancellationToken) : |
| | 0 | 63 | | base.SerializeToStreamAsync(stream, context, cancellationToken); |
| | | 64 | | |
| | | 65 | | private Task SerializeToStreamAsyncCore(Stream stream, CancellationToken cancellationToken) |
| | 0 | 66 | | { |
| | 0 | 67 | | Debug.Assert(stream != null); |
| | 0 | 68 | | PrepareContent(); |
| | 0 | 69 | | return StreamToStreamCopy.CopyAsync( |
| | 0 | 70 | | _content, |
| | 0 | 71 | | stream, |
| | 0 | 72 | | _bufferSize, |
| | 0 | 73 | | !_content.CanSeek, // If the stream can't be re-read, make sure that it gets disposed once it is consume |
| | 0 | 74 | | cancellationToken); |
| | 0 | 75 | | } |
| | | 76 | | |
| | | 77 | | protected internal override bool TryComputeLength(out long length) |
| | 0 | 78 | | { |
| | 0 | 79 | | if (_content.CanSeek) |
| | 0 | 80 | | { |
| | 0 | 81 | | length = _content.Length - _start; |
| | 0 | 82 | | return true; |
| | | 83 | | } |
| | | 84 | | else |
| | 0 | 85 | | { |
| | 0 | 86 | | length = 0; |
| | 0 | 87 | | return false; |
| | | 88 | | } |
| | 0 | 89 | | } |
| | | 90 | | |
| | | 91 | | protected override void Dispose(bool disposing) |
| | 0 | 92 | | { |
| | 0 | 93 | | if (disposing) |
| | 0 | 94 | | { |
| | 0 | 95 | | _content.Dispose(); |
| | 0 | 96 | | } |
| | 0 | 97 | | base.Dispose(disposing); |
| | 0 | 98 | | } |
| | | 99 | | |
| | | 100 | | protected override Stream CreateContentReadStream(CancellationToken cancellationToken) |
| | 0 | 101 | | { |
| | 0 | 102 | | SeekToStartIfSeekable(); |
| | 0 | 103 | | return new ReadOnlyStream(_content); |
| | 0 | 104 | | } |
| | | 105 | | |
| | | 106 | | protected override Task<Stream> CreateContentReadStreamAsync() |
| | 0 | 107 | | { |
| | 0 | 108 | | SeekToStartIfSeekable(); |
| | | 109 | | // Wrap the stream with a read-only stream to prevent someone from writing to the stream. |
| | 0 | 110 | | return Task.FromResult<Stream>(new ReadOnlyStream(_content)); |
| | 0 | 111 | | } |
| | | 112 | | |
| | | 113 | | internal override Stream? TryCreateContentReadStream() => |
| | 0 | 114 | | GetType() == typeof(StreamContent) ? new ReadOnlyStream(_content) : // type check ensures we use possible de |
| | 0 | 115 | | null; |
| | | 116 | | |
| | 0 | 117 | | internal override bool AllowDuplex => false; |
| | | 118 | | |
| | | 119 | | private void PrepareContent() |
| | 0 | 120 | | { |
| | 0 | 121 | | if (_contentConsumed) |
| | 0 | 122 | | { |
| | | 123 | | // If the content needs to be written to a target stream a 2nd time, then the stream must support |
| | | 124 | | // seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target |
| | | 125 | | // stream (e.g. a NetworkStream). |
| | 0 | 126 | | if (_content.CanSeek) |
| | 0 | 127 | | { |
| | 0 | 128 | | _content.Position = _start; |
| | 0 | 129 | | } |
| | | 130 | | else |
| | 0 | 131 | | { |
| | 0 | 132 | | throw new InvalidOperationException(SR.net_http_content_stream_already_read); |
| | | 133 | | } |
| | 0 | 134 | | } |
| | | 135 | | |
| | 0 | 136 | | _contentConsumed = true; |
| | 0 | 137 | | } |
| | | 138 | | |
| | | 139 | | private void SeekToStartIfSeekable() |
| | 0 | 140 | | { |
| | 0 | 141 | | if (_content.CanSeek) |
| | 0 | 142 | | { |
| | 0 | 143 | | _content.Position = _start; |
| | 0 | 144 | | } |
| | 0 | 145 | | } |
| | | 146 | | |
| | | 147 | | private sealed class ReadOnlyStream : DelegatingStream |
| | | 148 | | { |
| | 0 | 149 | | public ReadOnlyStream(Stream innerStream) : base(innerStream) |
| | 0 | 150 | | { |
| | 0 | 151 | | } |
| | | 152 | | |
| | 0 | 153 | | public override bool CanWrite => false; |
| | | 154 | | |
| | 0 | 155 | | public override void Flush() { } |
| | | 156 | | |
| | 0 | 157 | | public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
| | | 158 | | |
| | | 159 | | public override void SetLength(long value) => |
| | 0 | 160 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 161 | | |
| | | 162 | | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, objec |
| | 0 | 163 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 164 | | |
| | | 165 | | public override void EndWrite(IAsyncResult asyncResult) => |
| | 0 | 166 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 167 | | |
| | | 168 | | public override void Write(byte[] buffer, int offset, int count) => |
| | 0 | 169 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 170 | | |
| | | 171 | | public override void Write(ReadOnlySpan<byte> buffer) => |
| | 0 | 172 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 173 | | |
| | | 174 | | public override void WriteByte(byte value) => |
| | 0 | 175 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 176 | | |
| | | 177 | | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) = |
| | 0 | 178 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 179 | | |
| | | 180 | | public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = defa |
| | 0 | 181 | | throw new NotSupportedException(SR.net_http_content_readonly_stream); |
| | | 182 | | |
| | | 183 | | public override int WriteTimeout |
| | | 184 | | { |
| | 0 | 185 | | get => throw new InvalidOperationException(SR.net_http_content_readonly_stream); |
| | 0 | 186 | | set => throw new InvalidOperationException(SR.net_http_content_readonly_stream); |
| | | 187 | | } |
| | | 188 | | } |
| | | 189 | | } |
| | | 190 | | } |