File: System\Net\Mime\MimeMultiPart.cs
Web Access
Project: src\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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading;
 
namespace System.Net.Mime
{
    internal sealed class MimeMultiPart : MimeBasePart
    {
        private Collection<MimeBasePart>? _parts;
        private static int s_boundary;
        private AsyncCallback? _mimePartSentCallback;
        private bool _allowUnicode;
 
        internal MimeMultiPart(MimeMultiPartType type)
        {
            MimeMultiPartType = type;
        }
 
        internal MimeMultiPartType MimeMultiPartType
        {
            set
            {
                if (value > MimeMultiPartType.Related || value < MimeMultiPartType.Mixed)
                {
                    throw new NotSupportedException(value.ToString());
                }
                SetType(value);
            }
        }
 
        private void SetType(MimeMultiPartType type)
        {
            ContentType.MediaType = "multipart/" + type.ToString().ToLowerInvariant();
            ContentType.Boundary = GetNextBoundary();
        }
 
        internal Collection<MimeBasePart> Parts => _parts ??= new Collection<MimeBasePart>();
 
        internal static void Complete(IAsyncResult result, Exception? e)
        {
            //if we already completed and we got called again,
            //it mean's that there was an exception in the callback and we
            //should just rethrow it.
 
            MimePartContext context = (MimePartContext)result.AsyncState!;
 
            if (context._completed)
            {
                ExceptionDispatchInfo.Throw(e!);
            }
 
            try
            {
                context._outputStream!.Close();
            }
            catch (Exception ex)
            {
                e ??= ex;
            }
            context._completed = true;
            context._result.InvokeCallback(e);
        }
 
        internal void MimeWriterCloseCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
 
            ((MimePartContext)result.AsyncState!)._completedSynchronously = false;
 
            try
            {
                MimeWriterCloseCallbackHandler(result);
            }
            catch (Exception e)
            {
                Complete(result, e);
            }
        }
 
        private static void MimeWriterCloseCallbackHandler(IAsyncResult result)
        {
            MimePartContext context = (MimePartContext)result.AsyncState!;
            ((MimeWriter)context._writer).EndClose(result);
            Complete(result, null);
        }
 
        internal void MimePartSentCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
 
            ((MimePartContext)result.AsyncState!)._completedSynchronously = false;
 
            try
            {
                MimePartSentCallbackHandler(result);
            }
            catch (Exception e)
            {
                Complete(result, e);
            }
        }
 
        private void MimePartSentCallbackHandler(IAsyncResult result)
        {
            MimePartContext context = (MimePartContext)result.AsyncState!;
            MimeBasePart part = (MimeBasePart)context._partsEnumerator.Current;
            part.EndSend(result);
 
            if (context._partsEnumerator.MoveNext())
            {
                part = (MimeBasePart)context._partsEnumerator.Current;
                IAsyncResult sendResult = part.BeginSend(context._writer, _mimePartSentCallback!, _allowUnicode, context);
                if (sendResult.CompletedSynchronously)
                {
                    MimePartSentCallbackHandler(sendResult);
                }
                return;
            }
            else
            {
                IAsyncResult closeResult = ((MimeWriter)context._writer).BeginClose(new AsyncCallback(MimeWriterCloseCallback), context);
                if (closeResult.CompletedSynchronously)
                {
                    MimeWriterCloseCallbackHandler(closeResult);
                }
            }
        }
 
        internal void ContentStreamCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
 
            ((MimePartContext)result.AsyncState!)._completedSynchronously = false;
 
            try
            {
                ContentStreamCallbackHandler(result);
            }
            catch (Exception e)
            {
                Complete(result, e);
            }
        }
 
        private void ContentStreamCallbackHandler(IAsyncResult result)
        {
            MimePartContext context = (MimePartContext)result.AsyncState!;
            context._outputStream = BaseWriter.EndGetContentStream(result);
            context._writer = new MimeWriter(context._outputStream!, ContentType.Boundary!);
            if (context._partsEnumerator.MoveNext())
            {
                MimeBasePart part = (MimeBasePart)context._partsEnumerator.Current;
 
                _mimePartSentCallback = new AsyncCallback(MimePartSentCallback);
                IAsyncResult sendResult = part.BeginSend(context._writer, _mimePartSentCallback, _allowUnicode, context);
                if (sendResult.CompletedSynchronously)
                {
                    MimePartSentCallbackHandler(sendResult);
                }
                return;
            }
            else
            {
                IAsyncResult closeResult = ((MimeWriter)context._writer).BeginClose(new AsyncCallback(MimeWriterCloseCallback), context);
                if (closeResult.CompletedSynchronously)
                {
                    MimeWriterCloseCallbackHandler(closeResult);
                }
            }
        }
 
        internal override IAsyncResult BeginSend(BaseWriter writer, AsyncCallback? callback, bool allowUnicode,
            object? state)
        {
            _allowUnicode = allowUnicode;
            PrepareHeaders(allowUnicode);
            writer.WriteHeaders(Headers, allowUnicode);
            MimePartAsyncResult result = new MimePartAsyncResult(this, state, callback);
            MimePartContext context = new MimePartContext(writer, result, Parts.GetEnumerator());
            IAsyncResult contentResult = writer.BeginGetContentStream(new AsyncCallback(ContentStreamCallback), context);
            if (contentResult.CompletedSynchronously)
            {
                ContentStreamCallbackHandler(contentResult);
            }
            return result;
        }
 
        internal sealed class MimePartContext
        {
            internal MimePartContext(BaseWriter writer, LazyAsyncResult result, IEnumerator<MimeBasePart> partsEnumerator)
            {
                _writer = writer;
                _result = result;
                _partsEnumerator = partsEnumerator;
            }
 
            internal IEnumerator<MimeBasePart> _partsEnumerator;
            internal Stream? _outputStream;
            internal LazyAsyncResult _result;
            internal BaseWriter _writer;
            internal bool _completed;
            internal bool _completedSynchronously = true;
        }
 
        internal override void Send(BaseWriter writer, bool allowUnicode)
        {
            PrepareHeaders(allowUnicode);
            writer.WriteHeaders(Headers, allowUnicode);
            Stream outputStream = writer.GetContentStream();
            MimeWriter mimeWriter = new MimeWriter(outputStream, ContentType.Boundary!);
 
            foreach (MimeBasePart part in Parts)
            {
                part.Send(mimeWriter, allowUnicode);
            }
 
            mimeWriter.Close();
            outputStream.Close();
        }
 
        internal static string GetNextBoundary()
        {
            int b = Interlocked.Increment(ref s_boundary) - 1;
            return $"--boundary_{(uint)b}_{Guid.NewGuid()}";
        }
    }
}