| File: System\Net\Mime\EightBitStream.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Buffers; namespace System.Net.Mime { /// <summary> /// This stream does not encode content, but merely allows the user to declare /// that the content does not need encoding. /// /// This stream is also used to implement RFC 2821 Section 4.5.2 (pad leading /// dots on a line) on the entire message so we don't have to implement it /// on all of the individual components. /// /// History: This class used to be called SevenBitStream and was supposed to /// validate that outgoing bytes were within the acceptable range of 0 - 127 /// and throw if a value > 127 is found. /// However, the enforcement was not properly implemented and rarely executed. /// For legacy (app-compat) reasons we have chosen to remove the enforcement /// and rename the class from SevenBitStream to EightBitStream. /// </summary> internal sealed class EightBitStream : DelegatedStream, IEncodableStream { // Should we do RFC 2821 Section 4.5.2 encoding of leading dots on a line? // We make this optional because this stream may be used recursively and // the encoding should only be done once. private readonly bool _shouldEncodeLeadingDots; private bool _lastWriteEndedWithCr; private WriteStateInfoBase WriteState => field ??= new WriteStateInfoBase(); /// <summary> /// ctor. /// </summary> /// <param name="stream">Underlying stream</param> internal EightBitStream(Stream stream) : base(stream) { } internal EightBitStream(Stream stream, bool shouldEncodeLeadingDots) : this(stream) { _shouldEncodeLeadingDots = shouldEncodeLeadingDots; } public override bool CanRead => false; public override bool CanWrite => BaseStream.CanWrite; protected override int ReadInternal(Span<byte> buffer) { throw new NotImplementedException(); } protected override ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } // Implement abstract Write methods protected override void WriteInternal(ReadOnlySpan<byte> buffer) { if (_shouldEncodeLeadingDots) { EncodeLines(buffer); BaseStream.Write(WriteState.Buffer.AsSpan(0, WriteState.Length)); WriteState.BufferFlushed(); } else { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. BaseStream.Write(buffer); } } protected override ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) { if (_shouldEncodeLeadingDots) { EncodeLines(buffer.Span); ValueTask task = BaseStream.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken); WriteState.BufferFlushed(); // Reset state after initiating async write return task; } else { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. return BaseStream.WriteAsync(buffer, cancellationToken); } } // helper methods // Despite not having to encode content, we still have to implement // RFC 2821 Section 4.5.2 about leading dots on a line private void EncodeLines(ReadOnlySpan<byte> buffer) { for (int i = 0; i < buffer.Length; i++) { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. if (_lastWriteEndedWithCr) { _lastWriteEndedWithCr = false; // Detect CRLF line endings if (buffer[i] == '\n') { WriteState.AppendCRLF(false); // Resets CurrentLineLength to 0 continue; } else { // Write the delayed CR since it wasn't part of a CRLF sequence WriteState.Append((byte)'\r'); } } if ((WriteState.CurrentLineLength == 0) && (buffer[i] == '.')) { // RFC 2821 Section 4.5.2: We must pad leading dots on a line with an extra dot // This is the only 'encoding' change we make to the data in this method WriteState.Append((byte)'.'); } if (buffer[i] == '\r') { // defer writing CR until we see if it's followed by LF _lastWriteEndedWithCr = true; continue; } // Regular data byte, pass it through unchanged. WriteState.Append(buffer[i]); } } public override void Close() { if (_lastWriteEndedWithCr) { // Write the delayed CR before the underlying stream is closed. // DelegatedStream.Close() closes the underlying stream, so this // must happen before we delegate to the base implementation. _lastWriteEndedWithCr = false; BaseStream.WriteByte((byte)'\r'); } base.Close(); } public override async ValueTask DisposeAsync() { if (_lastWriteEndedWithCr) { // write the delayed CR _lastWriteEndedWithCr = false; await BaseStream.WriteAsync(new byte[] { (byte)'\r' }, CancellationToken.None).ConfigureAwait(false); } // DelegatedStream does not override DisposeAsync, so the base implementation // falls back to synchronous Dispose(), which disposes the underlying stream. await base.DisposeAsync().ConfigureAwait(false); } public int DecodeBytes(Span<byte> buffer) { throw new NotImplementedException(); } public int EncodeBytes(ReadOnlySpan<byte> buffer) { throw new NotImplementedException(); } public int EncodeString(string value, Encoding encoding) { throw new NotImplementedException(); } public string GetEncodedString() { throw new NotImplementedException(); } } }