using System; using System.IO; using System.Runtime.InteropServices; using System.Text; namespace EmbedIO.Net.Internal { internal class ResponseStream : Stream { private static readonly byte[] CrLf = { 13, 10 }; private readonly object _headersSyncRoot = new (); private readonly Stream _stream; private readonly HttpListenerResponse _response; private readonly bool _ignoreErrors; private bool _disposed; private bool _trailerSent; internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignoreErrors) { _response = response; _ignoreErrors = ignoreErrors; _stream = stream; } /// public override bool CanRead => false; /// public override bool CanSeek => false; /// public override bool CanWrite => true; /// public override long Length => throw new NotSupportedException(); /// public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } /// public override void Flush() { } /// public override void Write(byte[] buffer, int offset, int count) { if (_disposed) { throw new ObjectDisposedException(nameof(ResponseStream)); } byte[] bytes; var ms = GetHeaders(false); var chunked = _response.SendChunked; if (ms != null) { var start = ms.Position; // After the possible preamble for the encoding ms.Position = ms.Length; if (chunked) { bytes = GetChunkSizeBytes(count, false); ms.Write(bytes, 0, bytes.Length); } var newCount = Math.Min(count, 16384 - (int)ms.Position + (int)start); ms.Write(buffer, offset, newCount); count -= newCount; offset += newCount; InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start)); ms.SetLength(0); ms.Capacity = 0; // 'dispose' the buffer in ms. } else if (chunked) { bytes = GetChunkSizeBytes(count, false); InternalWrite(bytes, 0, bytes.Length); } if (count > 0) { InternalWrite(buffer, offset, count); } if (chunked) { InternalWrite(CrLf, 0, 2); } } /// public override int Read([In, Out] byte[] buffer, int offset, int count) => throw new NotSupportedException(); /// public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); /// public override void SetLength(long value) => throw new NotSupportedException(); internal void InternalWrite(byte[] buffer, int offset, int count) { if (_ignoreErrors) { try { _stream.Write(buffer, offset, count); } catch { // ignored } } else { _stream.Write(buffer, offset, count); } } protected override void Dispose(bool disposing) { if (_disposed) { return; } _disposed = true; if (!disposing) { return; } using var ms = GetHeaders(true); var chunked = _response.SendChunked; if (_stream.CanWrite) { try { byte[] bytes; if (ms != null) { var start = ms.Position; if (chunked && !_trailerSent) { bytes = GetChunkSizeBytes(0, true); ms.Position = ms.Length; ms.Write(bytes, 0, bytes.Length); } InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start)); _trailerSent = true; } else if (chunked && !_trailerSent) { bytes = GetChunkSizeBytes(0, true); InternalWrite(bytes, 0, bytes.Length); _trailerSent = true; } } catch (ObjectDisposedException) { // Ignored } catch (IOException) { // Ignore error due to connection reset by peer } } _response.Close(); } private static byte[] GetChunkSizeBytes(int size, bool final) => WebServer.DefaultEncoding.GetBytes($"{size:x}\r\n{(final ? "\r\n" : string.Empty)}"); private MemoryStream? GetHeaders(bool closing) { lock (_headersSyncRoot) { return _response.HeadersSent ? null : _response.SendHeaders(closing); } } } }