File: net\System\Net\mail\Message.cs
Project: ndp\fx\src\System.csproj (System)
using System.Net.Mime;
using System.Text;
 
namespace System.Net.Mail
{
    public enum MailPriority {
        Normal = 0,  
        Low = 1,
        High = 2
    }
    
    internal class Message
    {
        #region Fields
 
        MailAddress from;
        MailAddress sender;
        MailAddressCollection replyToList;
        MailAddress replyTo;
        MailAddressCollection to;
        MailAddressCollection cc;
        MailAddressCollection bcc;
        MimeBasePart content;
        HeaderCollection headers;
        HeaderCollection envelopeHeaders;
        string subject;
        Encoding subjectEncoding;
        Encoding headersEncoding;
        MailPriority priority = (MailPriority)(-1);
 
        #endregion Fields
 
        #region Constructors
 
        internal Message() {
        }
 
        internal Message(string from, string to):this() {
            if (from == null) 
                throw new ArgumentNullException("from");
 
            if (to == null)
                throw new ArgumentNullException("to");
 
            if (from == String.Empty)
                throw new ArgumentException(SR.GetString(SR.net_emptystringcall,"from"), "from");
 
            if (to == String.Empty)
                throw new ArgumentException(SR.GetString(SR.net_emptystringcall,"to"), "to");
 
            this.from = new MailAddress(from);
            MailAddressCollection collection = new MailAddressCollection();
            collection.Add(to);
            this.to = collection;
        }
 
 
        internal Message(MailAddress from, MailAddress to):this() {
            this.from = from;
            this.To.Add(to);
        }
        
        #endregion Constructors
 
        #region Properties
 
        public MailPriority Priority{
            get {
                return (((int)priority == -1)?MailPriority.Normal:priority);
            }
            set{
                priority = value;
            }
        }
 
        internal MailAddress From {
            get {
                return from;
            }
            set {
                if (value == null) {
                    throw new ArgumentNullException("value");
                }
                from = value;
            }
        }
        
        
        internal MailAddress Sender {
            get {
                return sender;
            }
            set {
                sender = value;
            }
        }
        
        
        internal MailAddress ReplyTo {
            get {
                return replyTo;
            }
            set {
                replyTo = value;
            }
        }
 
        internal MailAddressCollection ReplyToList {
            get {
                if (replyToList == null)
                    replyToList = new MailAddressCollection();
 
                return replyToList;
            }
        }
 
        internal MailAddressCollection To {
            get {
                if (to == null)
                    to = new MailAddressCollection();
 
                return to;
            }
        }
        
        internal MailAddressCollection Bcc {
            get {
                if (bcc == null)
                    bcc = new MailAddressCollection();
 
                return bcc;
            }
        }
 
        internal MailAddressCollection CC {
            get {
                if (cc == null)
                    cc = new MailAddressCollection();
 
                return cc;
            }
        }
 
 
        internal string Subject {
            get {
                return subject;
            }
            set {
                Encoding inputEncoding = null;
                try { 
                    // extract the encoding from =?encoding?BorQ?blablalba?=
                    inputEncoding = MimeBasePart.DecodeEncoding(value);
                }
                catch (ArgumentException) { };
 
                if (inputEncoding != null && value != null) {
                    try {
                        // Store the decoded value, we'll re-encode before sending
                        value = MimeBasePart.DecodeHeaderValue(value);
                        subjectEncoding = subjectEncoding ?? inputEncoding;
                    }
                    // Failed to decode, just pass it through as ascii (legacy)
                    catch (FormatException) { }
                }
 
                if (value != null && MailBnfHelper.HasCROrLF(value)) {
                    throw new ArgumentException(SR.GetString(SR.MailSubjectInvalidFormat));
                }
                subject = value;
 
                if (subject != null) {
                    subject = subject.Normalize(NormalizationForm.FormC);   
                    if (subjectEncoding == null && !MimeBasePart.IsAscii(subject, false)) {
                        subjectEncoding = Encoding.GetEncoding(MimeBasePart.defaultCharSet);
                    }
                }
            }
        }
 
        internal Encoding SubjectEncoding {
            get {
                return subjectEncoding;
            }
            set {
                subjectEncoding = value;
            }
        }
 
        internal HeaderCollection Headers {
            get {
                if (headers == null) {
                    headers = new HeaderCollection();
                    if(Logging.On)Logging.Associate(Logging.Web, this, headers);
                }
 
                return headers;
            }
        }
 
        internal Encoding HeadersEncoding {
            get {
                return headersEncoding;
            }
            set {
                headersEncoding = value;
            }
        }
 
        internal HeaderCollection EnvelopeHeaders {
            get {
                if (envelopeHeaders == null) {
                    envelopeHeaders = new HeaderCollection();
                    if(Logging.On)Logging.Associate(Logging.Web, this, envelopeHeaders);
                }
 
                return envelopeHeaders;
            }
        }
 
 
        internal virtual MimeBasePart Content {
            get {
                return content;
            }
            set {
                if (value == null) {
                    throw new ArgumentNullException("value");
                }
 
                content = value;
            }
        }
        
        #endregion Properties
 
        #region Sending
 
        internal void EmptySendCallback(IAsyncResult result)
        {
            Exception e = null;
 
            if(result.CompletedSynchronously){
                return;
            }
 
            EmptySendContext context = (EmptySendContext)result.AsyncState;
            try{
                context.writer.EndGetContentStream(result).Close();
            }
            catch(Exception ex){
                e = ex;
            }
            context.result.InvokeCallback(e);
        }
 
        internal class EmptySendContext {
            internal EmptySendContext(BaseWriter writer, LazyAsyncResult result) {
                this.writer = writer;
                this.result = result;
            }
        
            internal LazyAsyncResult result;
            internal BaseWriter writer;
        }
 
        internal virtual IAsyncResult BeginSend(BaseWriter writer, bool sendEnvelope, bool allowUnicode, 
            AsyncCallback callback, object state) {
                       
            PrepareHeaders(sendEnvelope, allowUnicode);
            writer.WriteHeaders(Headers, allowUnicode);
 
            if (Content != null) {
                return Content.BeginSend(writer, callback, allowUnicode, state);
            }
            else{
                LazyAsyncResult result = new LazyAsyncResult(this,state,callback);
                IAsyncResult newResult = writer.BeginGetContentStream(EmptySendCallback, new EmptySendContext(writer,result));
                if(newResult.CompletedSynchronously){
                    writer.EndGetContentStream(newResult).Close();
                }
                return result;
            }
        }
 
        internal virtual void EndSend(IAsyncResult asyncResult){
            if (asyncResult == null) {
                throw new ArgumentNullException("asyncResult");
            }
 
            if (Content != null) {
                Content.EndSend(asyncResult);
            }
            else{
                LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
 
                if (castedAsyncResult == null || castedAsyncResult.AsyncObject != this) {
                    throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult));
                }
 
                if (castedAsyncResult.EndCalled) {
                    throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndSend"));
                }
 
                castedAsyncResult.InternalWaitForCompletion();
                castedAsyncResult.EndCalled = true;
                if (castedAsyncResult.Result is Exception) {
                    throw (Exception)castedAsyncResult.Result;
                }
            }
        }
 
        internal virtual void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode) {
 
            if (sendEnvelope) {
                PrepareEnvelopeHeaders(sendEnvelope, allowUnicode);
                writer.WriteHeaders(EnvelopeHeaders, allowUnicode);
            }
 
            PrepareHeaders(sendEnvelope, allowUnicode);
            writer.WriteHeaders(Headers, allowUnicode);
 
            if (Content != null) {
                Content.Send(writer, allowUnicode);
            }
            else{
                writer.GetContentStream().Close();
            }
        }
 
        internal void PrepareEnvelopeHeaders(bool sendEnvelope, bool allowUnicode) {
            
            if (this.headersEncoding == null) {
                this.headersEncoding = Encoding.GetEncoding(MimeBasePart.defaultCharSet);
            }
 
            EncodeHeaders(this.EnvelopeHeaders, allowUnicode);
 
            // Dev10 #430372: only add X-Sender header if it wasn't already set by the user
            string xSenderHeader = MailHeaderInfo.GetString(MailHeaderID.XSender);
            if (!IsHeaderSet(xSenderHeader)) {
                MailAddress sender = Sender ?? From;
                EnvelopeHeaders.InternalSet(xSenderHeader, sender.Encode(xSenderHeader.Length, allowUnicode));
            }
 
            string headerName = MailHeaderInfo.GetString(MailHeaderID.XReceiver);
            EnvelopeHeaders.Remove(headerName);
 
            foreach (MailAddress address in To) {
                EnvelopeHeaders.InternalAdd(headerName, address.Encode(headerName.Length, allowUnicode));
            }
            foreach (MailAddress address in CC) {
                EnvelopeHeaders.InternalAdd(headerName, address.Encode(headerName.Length, allowUnicode));
            }
            foreach (MailAddress address in Bcc) {
                EnvelopeHeaders.InternalAdd(headerName, address.Encode(headerName.Length, allowUnicode));
            }
        }
 
        internal void PrepareHeaders(bool sendEnvelope, bool allowUnicode) {
            
            string headerName;
 
            if (this.headersEncoding == null) {
                this.headersEncoding = Encoding.GetEncoding(MimeBasePart.defaultCharSet);
            }
 
            //ContentType is written directly to the stream so remove potential user duplicate
            Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.ContentType));
 
            Headers[MailHeaderInfo.GetString(MailHeaderID.MimeVersion)] = "1.0";
            
            // add sender to headers first so that it is written first to allow the IIS smtp svc to
            // send MAIL FROM with the sender if both sender and from are present
            headerName = MailHeaderInfo.GetString(MailHeaderID.Sender);
            if(Sender != null) {
                Headers.InternalAdd(headerName, Sender.Encode(headerName.Length, allowUnicode));
            }
            else {
                Headers.Remove(headerName);
            }
 
            headerName = MailHeaderInfo.GetString(MailHeaderID.From);
            Headers.InternalAdd(headerName, From.Encode(headerName.Length, allowUnicode));
 
            headerName = MailHeaderInfo.GetString(MailHeaderID.To);
            if (To.Count > 0) {
                Headers.InternalAdd(headerName, To.Encode(headerName.Length, allowUnicode));
            }
            else {
                Headers.Remove(headerName);
            }
 
            headerName = MailHeaderInfo.GetString(MailHeaderID.Cc);
            if (CC.Count > 0) {
                Headers.InternalAdd(headerName, CC.Encode(headerName.Length, allowUnicode));
            }
            else {
                Headers.Remove(headerName);
            }
 
            headerName = MailHeaderInfo.GetString(MailHeaderID.ReplyTo);
            if (ReplyTo != null) {
                Headers.InternalAdd(headerName, ReplyTo.Encode(headerName.Length, allowUnicode));
            } 
            else if (ReplyToList.Count > 0) {
                Headers.InternalAdd(headerName, ReplyToList.Encode(headerName.Length, allowUnicode));
            } 
            else {
                Headers.Remove(headerName);
            }
 
            Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.Bcc));
 
            if (priority == MailPriority.High){
                Headers[MailHeaderInfo.GetString(MailHeaderID.XPriority)] = "1";
                Headers[MailHeaderInfo.GetString(MailHeaderID.Priority)] = "urgent";
                Headers[MailHeaderInfo.GetString(MailHeaderID.Importance)] = "high";
            }
            else if (priority == MailPriority.Low){
                Headers[MailHeaderInfo.GetString(MailHeaderID.XPriority)] = "5";
                Headers[MailHeaderInfo.GetString(MailHeaderID.Priority)] = "non-urgent";
                Headers[MailHeaderInfo.GetString(MailHeaderID.Importance)] = "low";
               }
            //if the priority was never set, allow the app to set the headers directly.
            else if (((int)priority) != -1){
                Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.XPriority));
                Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.Priority));
                Headers.Remove(MailHeaderInfo.GetString(MailHeaderID.Importance));
            }
 
            Headers.InternalAdd(MailHeaderInfo.GetString(MailHeaderID.Date), 
                MailBnfHelper.GetDateTimeString(DateTime.Now, null));
 
            headerName = MailHeaderInfo.GetString(MailHeaderID.Subject);
            if (!string.IsNullOrEmpty(subject)){
                if (allowUnicode) {
                    Headers.InternalAdd(headerName, subject);
                }
                else {
                    Headers.InternalAdd(headerName,
                        MimeBasePart.EncodeHeaderValue(subject, subjectEncoding,
                        MimeBasePart.ShouldUseBase64Encoding(subjectEncoding),
                        headerName.Length));
                }
            }
            else{
                Headers.Remove(headerName);
            }
 
            EncodeHeaders(this.headers, allowUnicode);
        }
 
        internal void EncodeHeaders(HeaderCollection headers, bool allowUnicode) {
            
            if (this.headersEncoding == null) {
                this.headersEncoding = Encoding.GetEncoding(MimeBasePart.defaultCharSet);
            }
 
            System.Diagnostics.Debug.Assert(this.headersEncoding != null);
            
            for (int i = 0; i < headers.Count; i++) {
                string headerName = headers.GetKey(i);
 
                //certain well-known values are encoded by PrepareHeaders and PrepareEnvelopeHeaders
                //so we can ignore them because either we encoded them already or there is no 
                //way for the user to have set them.  If a header is well known and user settable then
                //we should encode it here, otherwise we have already encoded it if necessary
                if (!MailHeaderInfo.IsUserSettable(headerName)) {
                    continue;
                }
 
                string[] values = headers.GetValues(headerName);
                string encodedValue = String.Empty;
                for (int j = 0; j < values.Length; j++) {
                    //encode if we need to
                    if (MimeBasePart.IsAscii(values[j], false)
                         || (allowUnicode && MailHeaderInfo.AllowsUnicode(headerName) // EAI
                            && !MailBnfHelper.HasCROrLF(values[j]))) { 
                        encodedValue = values[j];
                    } 
                    else {
                        encodedValue = MimeBasePart.EncodeHeaderValue(values[j],
                                                        this.headersEncoding,
                                                        MimeBasePart.ShouldUseBase64Encoding(this.headersEncoding),
                                                        headerName.Length);
                    }
 
                    //potentially there are multiple values per key
                    if (j == 0) {
                        //if it's the first or only value, set will overwrite all the values assigned to that key
                        //which is fine since we have them stored in values[]
                        headers.Set(headerName, encodedValue);
                    } 
                    else {
                        //this is a subsequent key, so we must Add it since the first key will have overwritten the
                        //other values
                        headers.Add(headerName, encodedValue);
                    }
                    
                }
            }
        }
 
        private bool IsHeaderSet(string headerName)
        {
            for (int i = 0; i < Headers.Count; i++)
            {
                if (string.Compare(Headers.GetKey(i), headerName, 
                    StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    return true;
                }
            }
            return false;
        }
 
        #endregion Sending
    }
}