|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Specialized;
using System.IO;
using System.Net.Mail;
using System.Runtime.ExceptionServices;
namespace System.Net.Mime
{
internal abstract class BaseWriter
{
// This is the maximum default line length that can actually be written. When encoding
// headers, the line length is more conservative to account for things like folding.
// In MailWriter, all encoding has already been done so this will only fold lines
// that are NOT encoded already, which means being less conservative is ok.
private const int DefaultLineLength = 76;
protected readonly BufferBuilder _bufferBuilder;
protected readonly Stream _stream;
private readonly EventHandler _onCloseHandler;
private readonly bool _shouldEncodeLeadingDots;
private readonly int _lineLength;
protected Stream _contentStream = null!; // set to null on dispose
protected bool _isInContent;
protected BaseWriter(Stream stream, bool shouldEncodeLeadingDots)
{
ArgumentNullException.ThrowIfNull(stream);
_stream = stream;
_shouldEncodeLeadingDots = shouldEncodeLeadingDots;
_onCloseHandler = new EventHandler(OnClose);
_bufferBuilder = new BufferBuilder();
_lineLength = DefaultLineLength;
}
#region Headers
internal abstract void WriteHeaders(NameValueCollection headers, bool allowUnicode);
internal void WriteHeader(string name, string value, bool allowUnicode)
{
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(value);
if (_isInContent)
{
throw new InvalidOperationException(SR.MailWriterIsInContent);
}
CheckBoundary();
_bufferBuilder.Append(name);
_bufferBuilder.Append(": ");
WriteAndFold(value, name.Length + 2, allowUnicode);
_bufferBuilder.Append("\r\n"u8);
}
private void WriteAndFold(string value, int charsAlreadyOnLine, bool allowUnicode)
{
int lastSpace = 0, startOfLine = 0;
for (int index = 0; index < value.Length; index++)
{
// When we find a FWS (CRLF) copy it as is.
if (MailBnfHelper.IsFWSAt(value, index)) // At the first char of "\r\n " or "\r\n\t"
{
index += 2; // Skip the FWS
_bufferBuilder.Append(value, startOfLine, index - startOfLine, allowUnicode);
// Reset for the next line
startOfLine = index;
lastSpace = index;
charsAlreadyOnLine = 0;
}
// When we pass the line length limit, and know where there was a space to fold at, fold there
else if (((index - startOfLine) > (_lineLength - charsAlreadyOnLine)) && lastSpace != startOfLine)
{
_bufferBuilder.Append(value, startOfLine, lastSpace - startOfLine, allowUnicode);
_bufferBuilder.Append("\r\n"u8);
startOfLine = lastSpace;
charsAlreadyOnLine = 0;
}
// Mark a foldable space. If we go over the line length limit, fold here.
else if (value[index] == MailBnfHelper.Space || value[index] == MailBnfHelper.Tab)
{
lastSpace = index;
}
}
// Write any remaining data to the buffer.
if (value.Length - startOfLine > 0)
{
_bufferBuilder.Append(value, startOfLine, value.Length - startOfLine, allowUnicode);
}
}
#endregion Headers
#region Content
internal Stream GetContentStream()
{
if (_isInContent)
{
throw new InvalidOperationException(SR.MailWriterIsInContent);
}
_isInContent = true;
CheckBoundary();
_bufferBuilder.Append("\r\n"u8);
Flush();
ClosableStream cs = new ClosableStream(new EightBitStream(_stream, _shouldEncodeLeadingDots), _onCloseHandler);
_contentStream = cs;
return cs;
}
#endregion Content
#region Cleanup
protected void Flush()
{
if (_bufferBuilder.Length > 0)
{
_stream.Write(_bufferBuilder.GetBuffer(), 0, _bufferBuilder.Length);
_bufferBuilder.Reset();
}
}
internal abstract void Close();
protected abstract void OnClose(object? sender, EventArgs args);
#endregion Cleanup
protected virtual void CheckBoundary() { }
}
}
|