|
// 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.Net.Mail;
using System.Text;
namespace System.Net.Mime
{
internal class MimeBasePart
{
internal const string DefaultCharSet = "utf-8";
protected ContentType? _contentType;
protected ContentDisposition? _contentDisposition;
private HeaderCollection? _headers;
internal MimeBasePart() { }
internal static bool ShouldUseBase64Encoding(Encoding? encoding) =>
encoding == Encoding.Unicode || encoding == Encoding.UTF8 || encoding == Encoding.UTF32 || encoding == Encoding.BigEndianUnicode;
//use when the length of the header is not known or if there is no header
internal static string EncodeHeaderValue(string value, Encoding encoding, bool base64Encoding) =>
EncodeHeaderValue(value, encoding, base64Encoding, 0);
//used when the length of the header name itself is known (i.e. Subject : )
internal static string EncodeHeaderValue(string value, Encoding? encoding, bool base64Encoding, int headerLength)
{
//no need to encode if it's pure ascii
if (IsAscii(value, false))
{
return value;
}
encoding ??= Encoding.GetEncoding(DefaultCharSet);
IEncodableStream stream = EncodedStreamFactory.GetEncoderForHeader(encoding, base64Encoding, headerLength);
stream.EncodeString(value, encoding);
return stream.GetEncodedString();
}
private static readonly char[] s_headerValueSplitChars = new char[] { '\r', '\n', ' ' };
internal static string DecodeHeaderValue(string? value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
string newValue = string.Empty;
//split strings, they may be folded. If they are, decode one at a time and append the results
string[] substringsToDecode = value.Split(s_headerValueSplitChars, StringSplitOptions.RemoveEmptyEntries);
foreach (string foldedSubString in substringsToDecode)
{
//an encoded string has as specific format in that it must start and end with an
//'=' char and contains five parts, separated by '?' chars.
//the first and last part are therefore '=', the second part is the byte encoding (B or Q)
//the third is the unicode encoding type, and the fourth is encoded message itself. '?' is not valid inside of
//an encoded string other than as a separator for these five parts.
//If this check fails, the string is either not encoded or cannot be decoded by this method
string[] subStrings = foldedSubString.Split('?');
if ((subStrings.Length != 5 || subStrings[0] != "=" || subStrings[4] != "="))
{
return value;
}
string charSet = subStrings[1];
bool base64Encoding = (subStrings[2] == "B");
byte[] buffer = Encoding.ASCII.GetBytes(subStrings[3]);
int newLength;
IEncodableStream s = EncodedStreamFactory.GetEncoderForHeader(Encoding.GetEncoding(charSet), base64Encoding, 0);
newLength = s.DecodeBytes(buffer, 0, buffer.Length);
Encoding encoding = Encoding.GetEncoding(charSet);
newValue += encoding.GetString(buffer, 0, newLength);
}
return newValue;
}
// Detect the encoding: "=?encoding?BorQ?content?="
// "=?utf-8?B?RmlsZU5hbWVf55CG0Y3Qq9C60I5jw4TRicKq0YIM0Y1hSsSeTNCy0Klh?="; // 3.5
// With the addition of folding in 4.0, there may be multiple lines with encoding, only detect the first:
// "=?utf-8?B?RmlsZU5hbWVf55CG0Y3Qq9C60I5jw4TRicKq0YIM0Y1hSsSeTNCy0Klh?=\r\n =?utf-8?B??=";
internal static Encoding? DecodeEncoding(string? value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
ReadOnlySpan<char> valueSpan = value;
Span<Range> subStrings = stackalloc Range[6];
if (valueSpan.SplitAny(subStrings, "?\r\n") < 5 ||
valueSpan[subStrings[0]] is not "=" ||
valueSpan[subStrings[4]] is not "=")
{
return null;
}
return Encoding.GetEncoding(value[subStrings[1]]);
}
internal static bool IsAscii(string value, bool permitCROrLF)
{
ArgumentNullException.ThrowIfNull(value);
return Ascii.IsValid(value) && (permitCROrLF || !value.AsSpan().ContainsAny('\r', '\n'));
}
internal string? ContentID
{
get { return Headers[MailHeaderInfo.GetString(MailHeaderID.ContentID)!]; }
set
{
if (string.IsNullOrEmpty(value))
{
Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.ContentID));
}
else
{
Headers[MailHeaderInfo.GetString(MailHeaderID.ContentID)] = value;
}
}
}
internal string? ContentLocation
{
get { return Headers[MailHeaderInfo.GetString(MailHeaderID.ContentLocation)!]; }
set
{
if (string.IsNullOrEmpty(value))
{
Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.ContentLocation));
}
else
{
Headers[MailHeaderInfo.GetString(MailHeaderID.ContentLocation)] = value;
}
}
}
internal NameValueCollection Headers
{
get
{
//persist existing info before returning
_headers ??= new HeaderCollection();
_contentType ??= new ContentType();
_contentType.PersistIfNeeded(_headers, false);
_contentDisposition?.PersistIfNeeded(_headers, false);
return _headers;
}
}
internal ContentType ContentType
{
get { return _contentType ??= new ContentType(); }
set
{
ArgumentNullException.ThrowIfNull(value);
_contentType = value;
_contentType.PersistIfNeeded((HeaderCollection)Headers, true);
}
}
internal void PrepareHeaders(bool allowUnicode)
{
_contentType!.PersistIfNeeded((HeaderCollection)Headers, false);
_headers!.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentType)!, _contentType.Encode(allowUnicode));
if (_contentDisposition != null)
{
_contentDisposition.PersistIfNeeded((HeaderCollection)Headers, false);
_headers.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentDisposition)!, _contentDisposition.Encode(allowUnicode));
}
}
internal virtual void Send(BaseWriter writer, bool allowUnicode)
{
throw new NotImplementedException();
}
internal virtual IAsyncResult BeginSend(BaseWriter writer, AsyncCallback? callback,
bool allowUnicode, object? state)
{
throw new NotImplementedException();
}
internal void EndSend(IAsyncResult asyncResult)
{
ArgumentNullException.ThrowIfNull(asyncResult);
LazyAsyncResult? castedAsyncResult = asyncResult as MimePartAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this)
{
throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndSend)));
}
castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
if (castedAsyncResult.Result is Exception)
{
throw (Exception)castedAsyncResult.Result;
}
}
internal sealed class MimePartAsyncResult : LazyAsyncResult
{
internal MimePartAsyncResult(MimeBasePart part, object? state, AsyncCallback? callback) : base(part, state, callback)
{
}
}
}
}
|