| File: System\Net\Http\WinHttpRequestStream.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Http.WinHttpHandler\src\System.Net.Http.WinHttpHandler.csproj (System.Net.Http.WinHttpHandler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using SafeWinHttpHandle = Interop.WinHttp.SafeWinHttpHandle; #pragma warning disable CA1844 // lack of WriteAsync(ReadOnlyMemory) override in .NET Standard 2.1 build namespace System.Net.Http { internal sealed class WinHttpRequestStream : Stream { private static readonly byte[] s_crLfTerminator = "\r\n"u8.ToArray(); private static readonly byte[] s_endChunk = "0\r\n\r\n"u8.ToArray(); private volatile bool _disposed; private readonly WinHttpRequestState _state; private readonly SafeWinHttpHandle _requestHandle; private readonly WinHttpChunkMode _chunkedMode; internal WinHttpRequestStream(WinHttpRequestState state, WinHttpChunkMode chunkedMode) { _state = state; _chunkedMode = chunkedMode; // Take copy of handle from state. // The state's request handle will be set to null once the response stream starts. Debug.Assert(_state.RequestHandle != null); _requestHandle = _state.RequestHandle; } public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return !_disposed; } } public override long Length { get { CheckDisposed(); throw new NotSupportedException(); } } public override long Position { get { CheckDisposed(); throw new NotSupportedException(); } set { CheckDisposed(); throw new NotSupportedException(); } } public override void Flush() { // Nothing to do. } public override Task FlushAsync(CancellationToken cancellationToken) { return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask; } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) { ArgumentNullException.ThrowIfNull(buffer); if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } if (count > buffer.Length - offset) { throw new ArgumentException(SR.net_http_buffer_insufficient_length, nameof(buffer)); } if (token.IsCancellationRequested) { var tcs = new TaskCompletionSource<int>(); tcs.TrySetCanceled(token); return tcs.Task; } CheckDisposed(); if (_state.TcsInternalWriteDataToRequestStream != null && !_state.TcsInternalWriteDataToRequestStream.Task.IsCompleted) { throw new InvalidOperationException(SR.net_http_no_concurrent_io_allowed); } return InternalWriteAsync(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, object? asyncState) => TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), asyncCallback, asyncState); public override void EndWrite(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult); public override long Seek(long offset, SeekOrigin origin) { CheckDisposed(); throw new NotSupportedException(); } public override void SetLength(long value) { CheckDisposed(); throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) { CheckDisposed(); throw new NotSupportedException(); } internal async Task EndUploadAsync() { switch (_chunkedMode) { case WinHttpChunkMode.Manual: await InternalWriteDataAsync(s_endChunk, 0, s_endChunk.Length).ConfigureAwait(false); break; case WinHttpChunkMode.Automatic: // Send empty DATA frame with END_STREAM flag. await InternalWriteEndDataAsync().ConfigureAwait(false); break; } } protected override void Dispose(bool disposing) { _disposed = true; base.Dispose(disposing); } private void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(this.GetType().FullName); } } private Task InternalWriteAsync(byte[] buffer, int offset, int count) { if (count == 0) { return Task.CompletedTask; } return _chunkedMode == WinHttpChunkMode.Manual ? InternalWriteChunkedModeAsync(buffer, offset, count) : InternalWriteDataAsync(buffer, offset, count); } private async Task InternalWriteChunkedModeAsync(byte[] buffer, int offset, int count) { // WinHTTP does not fully support chunked uploads. It simply allows one to omit the 'Content-Length' header // and instead use the 'Transfer-Encoding: chunked' header. The caller is still required to encode the // request body according to chunking rules. Debug.Assert(_chunkedMode == WinHttpChunkMode.Manual); Debug.Assert(count > 0); byte[] chunkSize = Encoding.UTF8.GetBytes($"{count:x}\r\n"); await InternalWriteDataAsync(chunkSize, 0, chunkSize.Length).ConfigureAwait(false); await InternalWriteDataAsync(buffer, offset, count).ConfigureAwait(false); await InternalWriteDataAsync(s_crLfTerminator, 0, s_crLfTerminator.Length).ConfigureAwait(false); } private Task<bool> InternalWriteDataAsync(byte[] buffer, int offset, int count) { Debug.Assert(count > 0); _state.PinSendBuffer(buffer); _state.TcsInternalWriteDataToRequestStream = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); lock (_state.Lock) { if (!Interop.WinHttp.WinHttpWriteData( _requestHandle, Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset), (uint)count, IntPtr.Zero)) { _state.TcsInternalWriteDataToRequestStream.TrySetException( new IOException(SR.net_http_io_write, WinHttpException.CreateExceptionUsingLastError(nameof(Interop.WinHttp.WinHttpWriteData)))); } } return _state.TcsInternalWriteDataToRequestStream.Task; } private Task<bool> InternalWriteEndDataAsync() { _state.TcsInternalWriteDataToRequestStream = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); lock (_state.Lock) { if (!Interop.WinHttp.WinHttpWriteData( _requestHandle, IntPtr.Zero, 0, IntPtr.Zero)) { _state.TcsInternalWriteDataToRequestStream.TrySetException( new IOException(SR.net_http_io_write, WinHttpException.CreateExceptionUsingLastError(nameof(Interop.WinHttp.WinHttpWriteData)))); } } return _state.TcsInternalWriteDataToRequestStream.Task; } } }