/// <summary> /// Write the <see cref="MessagePart"/> to the output stream. /// </summary> /// <remarks> /// Writes the MIME entity and its message to the output stream. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</param> /// <param name="contentOnly"><c>true</c> if only the content should be written; otherwise, <c>false</c>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public override void WriteTo(FormatOptions options, Stream stream, bool contentOnly, CancellationToken cancellationToken = default(CancellationToken)) { base.WriteTo(options, stream, contentOnly, cancellationToken); if (Message == null) { return; } if (Message.MboxMarker != null && Message.MboxMarker.Length != 0) { var cancellable = stream as ICancellableStream; if (cancellable != null) { cancellable.Write(Message.MboxMarker, 0, Message.MboxMarker.Length, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } else { stream.Write(Message.MboxMarker, 0, Message.MboxMarker.Length); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } if (options.EnsureNewLine) { options = options.Clone(); options.EnsureNewLine = false; } Message.WriteTo(options, stream, cancellationToken); }
/// <summary> /// Writes the <see cref="MimeKit.Multipart"/> to the specified output stream. /// </summary> /// <remarks> /// Writes the multipart MIME entity and its subparts to the output stream. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public override void WriteTo(FormatOptions options, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { if (Boundary == null) { Boundary = GenerateBoundary(); } base.WriteTo(options, stream, cancellationToken); if (options.International && ContentType.Matches("multipart", "signed")) { // don't reformat the headers or content of any children of a multipart/signed options = options.Clone(); options.HiddenHeaders.Clear(); options.International = false; } var cancellable = stream as ICancellableStream; if (RawPreamble != null && RawPreamble.Length > 0) { WriteBytes(options, stream, RawPreamble, cancellationToken); } var boundary = Encoding.ASCII.GetBytes("--" + Boundary + "--"); if (cancellable != null) { for (int i = 0; i < children.Count; i++) { cancellable.Write(boundary, 0, boundary.Length - 2, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); children[i].WriteTo(options, stream, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } cancellable.Write(boundary, 0, boundary.Length, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } else { for (int i = 0; i < children.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); stream.Write(boundary, 0, boundary.Length - 2); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); children[i].WriteTo(options, stream, cancellationToken); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } cancellationToken.ThrowIfCancellationRequested(); stream.Write(boundary, 0, boundary.Length); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } if (RawEpilogue != null && RawEpilogue.Length > 0) { WriteBytes(options, stream, RawEpilogue, cancellationToken); } }
/// <summary> /// Asynchronously write the <see cref="MessagePart"/> to the output stream. /// </summary> /// <remarks> /// Asynchronously writes the MIME entity and its message to the output stream. /// </remarks> /// <returns>An awaitable task.</returns> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</param> /// <param name="contentOnly"><c>true</c> if only the content should be written; otherwise, <c>false</c>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public override async Task WriteToAsync(FormatOptions options, Stream stream, bool contentOnly, CancellationToken cancellationToken = default(CancellationToken)) { await base.WriteToAsync(options, stream, contentOnly, cancellationToken).ConfigureAwait(false); if (Message == null) { return; } if (Message.MboxMarker != null && Message.MboxMarker.Length != 0) { await stream.WriteAsync(Message.MboxMarker, 0, Message.MboxMarker.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); } if (options.EnsureNewLine) { options = options.Clone(); options.EnsureNewLine = false; } await Message.WriteToAsync(options, stream, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Initializes a new instance of the <see cref="MailKit.Net.Imap.ImapLiteral"/> class. /// </summary> /// <remarks> /// Creates a new <see cref="MailKit.Net.Imap.ImapLiteral"/>. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="literal">The literal.</param> /// <param name="action">The progress update action.</param> public ImapLiteral (FormatOptions options, object literal, Action<int> action = null) { format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; update = action; if (literal is MimeMessage) { Type = ImapLiteralType.MimeMessage; } else if (literal is Stream) { Type = ImapLiteralType.Stream; } else if (literal is string) { literal = Encoding.UTF8.GetBytes ((string) literal); Type = ImapLiteralType.String; } else if (literal is byte[]) { Type = ImapLiteralType.String; } else { throw new ArgumentException ("Unknown literal type"); } Literal = literal; }
/// <summary> /// Get the default formatting options in a thread-safe way. /// </summary> /// <remarks> /// Gets the default formatting options in a thread-safe way. /// </remarks> /// <returns>The default formatting options.</returns> internal static FormatOptions CloneDefault() { lock (Default) { return(Default.Clone()); } }
/// <summary> /// Appends the specified messages to the folder. /// </summary> /// <remarks> /// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages. /// </remarks> /// <returns>The UIDs of the appended messages, if available; otherwise an empty array.</returns> /// <param name="options">The formatting options.</param> /// <param name="messages">The list of messages to append to the folder.</param> /// <param name="flags">The message flags to use for each of the messages.</param> /// <param name="dates">The received dates to use for each of the messages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="messages"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="flags"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="dates"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>One or more of the <paramref name="messages"/> is null.</para> /// <para>-or-</para> /// <para>The number of messages, flags, and dates do not match.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.InvalidOperationException"> /// Internationalized formatting was requested but has not been enabled. /// </exception> /// <exception cref="FolderNotFoundException"> /// The <see cref="ImapFolder"/> does not exist. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.NotSupportedException"> /// Internationalized formatting was requested but is not supported by the server. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<UniqueId> Append (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (options == null) throw new ArgumentNullException ("options"); if (messages == null) throw new ArgumentNullException ("messages"); for (int i = 0; i < messages.Count; i++) { if (messages[i] == null) throw new ArgumentException ("One or more of the messages is null."); } if (flags == null) throw new ArgumentNullException ("flags"); if (dates == null) throw new ArgumentNullException ("dates"); if (messages.Count != flags.Count || messages.Count != dates.Count) throw new ArgumentException ("The number of messages, the number of flags, and the number of dates must be equal."); CheckState (false, false); if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); if (messages.Count == 0) return new UniqueId[0]; if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { var ic = QueueMultiAppend (format, messages, flags, dates, cancellationToken, progress); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("APPEND", ic); var append = ic.RespCodes.OfType<AppendUidResponseCode> ().FirstOrDefault (); if (append != null) return append.UidSet; return new UniqueId[0]; } // FIXME: use an aggregate progress reporter var uids = new List<UniqueId> (); for (int i = 0; i < messages.Count; i++) { var uid = Append (format, messages[i], flags[i], dates[i], cancellationToken); if (uids != null && uid.HasValue) uids.Add (uid.Value); else uids = null; } if (uids == null) return new UniqueId[0]; return uids; }
/// <summary> /// Appends the specified message to the folder. /// </summary> /// <remarks> /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// </remarks> /// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns> /// <param name="options">The formatting options.</param> /// <param name="message">The message.</param> /// <param name="flags">The message flags.</param> /// <param name="date">The received date of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="message"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.InvalidOperationException"> /// Internationalized formatting was requested but has not been enabled. /// </exception> /// <exception cref="FolderNotFoundException"> /// The <see cref="ImapFolder"/> does not exist. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.NotSupportedException"> /// Internationalized formatting was requested but is not supported by the server. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (options == null) throw new ArgumentNullException ("options"); if (message == null) throw new ArgumentNullException ("message"); CheckState (false, false); if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); var ic = QueueAppend (format, message, flags, date, cancellationToken, progress); Engine.Wait (ic); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("APPEND", ic); var append = ic.RespCodes.OfType<AppendUidResponseCode> ().FirstOrDefault (); if (append != null) return append.UidSet[0]; return null; }
/// <summary> /// Verify the specified DKIM-Signature header. /// </summary> /// <remarks> /// Verifies the specified DKIM-Signature header. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="dkimSignature">The DKIM-Signature header.</param> /// <param name="publicKeyLocator">The public key locator service.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="dkimSignature"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="publicKeyLocator"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="dkimSignature"/> is not a DKIM-Signature header. /// </exception> /// <exception cref="System.FormatException"> /// The DKIM-Signature header value is malformed. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> bool Verify (FormatOptions options, Header dkimSignature, IDkimPublicKeyLocator publicKeyLocator, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (dkimSignature == null) throw new ArgumentNullException ("dkimSignature"); if (dkimSignature.Id != HeaderId.DkimSignature) throw new ArgumentException ("The dkimSignature parameter MUST be a DKIM-Signature header.", "dkimSignature"); if (publicKeyLocator == null) throw new ArgumentNullException ("publicKeyLocator"); var parameters = ParseDkimSignature (dkimSignature.Value); DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm; DkimSignatureAlgorithm signatureAlgorithm; AsymmetricKeyParameter key; string d, s, q, h, bh, b; int maxLength; ValidateDkimSignatureParameters (parameters, out signatureAlgorithm, out headerAlgorithm, out bodyAlgorithm, out d, out s, out q, out h, out bh, out b, out maxLength); key = publicKeyLocator.LocatePublicKey (q, d, s, cancellationToken); options = options.Clone (); options.NewLineFormat = NewLineFormat.Dos; // first check the body hash (if that's invalid, then the entire signature is invalid) var hash = Convert.ToBase64String (DkimHashBody (options, signatureAlgorithm, bodyAlgorithm, maxLength)); if (hash != bh) return false; using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signatureAlgorithm, key))) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); DkimWriteHeaders (options, h.Split (':'), headerAlgorithm, filtered); // now include the DKIM-Signature header that we are verifying, // but only after removing the "b=" signature value. var header = GetSignedDkimSignatureHeader (dkimSignature); switch (headerAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: DkimWriteHeaderRelaxed (options, filtered, header); break; default: DkimWriteHeaderSimple (options, filtered, header); break; } filtered.Flush (); } return stream.VerifySignature (b); } }
/// <summary> /// Digitally sign the message using a DomainKeys Identified Mail (DKIM) signature. /// </summary> /// <remarks> /// Digitally signs the message using a DomainKeys Identified Mail (DKIM) signature. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="signer">The DKIM signer.</param> /// <param name="headers">The list of header fields to sign.</param> /// <param name="headerCanonicalizationAlgorithm">The header canonicalization algorithm.</param> /// <param name="bodyCanonicalizationAlgorithm">The body canonicalization algorithm.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="headers"/> does not contain the 'From' header.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> contains one or more of the following headers: Return-Path, /// Received, Comments, Keywords, Bcc, Resent-Bcc, or DKIM-Signature.</para> /// </exception> void Sign (FormatOptions options, DkimSigner signer, IList<HeaderId> headers, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple) { if (options == null) throw new ArgumentNullException ("options"); if (signer == null) throw new ArgumentNullException ("signer"); if (headers == null) throw new ArgumentNullException ("headers"); if (!headers.Contains (HeaderId.From)) throw new ArgumentException ("The list of headers to sign MUST include the 'From' header."); var fields = new string[headers.Count]; for (int i = 0; i < headers.Count; i++) { if (DkimShouldNotInclude.Contains (headers[i])) throw new ArgumentException (string.Format ("The list of headers to sign SHOULD NOT include the '{0}' header.", headers[i].ToHeaderName ())); fields[i] = headers[i].ToHeaderName ().ToLowerInvariant (); } if (version == null && Body != null && Body.Headers.Count > 0) MimeVersion = new Version (1, 0); Prepare (EncodingConstraint.SevenBit, 78); var t = DateTime.Now - DateUtils.UnixEpoch; var value = new StringBuilder ("v=1"); byte[] signature, hash; Header dkim; options = options.Clone (); options.NewLineFormat = NewLineFormat.Dos; switch (signer.SignatureAlgorithm) { case DkimSignatureAlgorithm.RsaSha256: value.Append ("; a=rsa-sha256"); break; default: value.Append ("; a=rsa-sha1"); break; } value.AppendFormat ("; d={0}; s={1}", signer.Domain, signer.Selector); value.AppendFormat ("; c={0}/{1}", headerCanonicalizationAlgorithm.ToString ().ToLowerInvariant (), bodyCanonicalizationAlgorithm.ToString ().ToLowerInvariant ()); if (!string.IsNullOrEmpty (signer.QueryMethod)) value.AppendFormat ("; q={0}", signer.QueryMethod); if (!string.IsNullOrEmpty (signer.AgentOrUserIdentifier)) value.AppendFormat ("; i={0}", signer.AgentOrUserIdentifier); value.AppendFormat ("; t={0}", (long) t.TotalSeconds); using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signer.SignatureAlgorithm, signer.PrivateKey))) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); // write the specified message headers DkimWriteHeaders (options, fields, headerCanonicalizationAlgorithm, filtered); value.AppendFormat ("; h={0}", string.Join (":", fields.ToArray ())); hash = DkimHashBody (options, signer.SignatureAlgorithm, bodyCanonicalizationAlgorithm, -1); value.AppendFormat ("; bh={0}", Convert.ToBase64String (hash)); value.Append ("; b="); dkim = new Header (HeaderId.DkimSignature, value.ToString ()); Headers.Insert (0, dkim); switch (headerCanonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: DkimWriteHeaderRelaxed (options, filtered, dkim); break; default: DkimWriteHeaderSimple (options, filtered, dkim); break; } filtered.Flush (); } signature = stream.GenerateSignature (); dkim.Value += Convert.ToBase64String (signature); } }
void Send (FormatOptions options, MimeMessage message, MailboxAddress sender, IList<MailboxAddress> recipients, CancellationToken cancellationToken, ITransferProgress progress) { CheckDisposed (); if (!IsConnected) throw new ServiceNotConnectedException ("The SmtpClient is not connected."); var format = options.Clone (); format.International = format.International || sender.IsInternational || recipients.Any (x => x.IsInternational); format.HiddenHeaders.Add (HeaderId.ContentLength); format.HiddenHeaders.Add (HeaderId.ResentBcc); format.HiddenHeaders.Add (HeaderId.Bcc); format.NewLineFormat = NewLineFormat.Dos; if (format.International && (Capabilities & SmtpCapabilities.UTF8) == 0) throw new NotSupportedException ("The SMTP server does not support the SMTPUTF8 extension."); if (format.International && (Capabilities & SmtpCapabilities.EightBitMime) == 0) throw new NotSupportedException ("The SMTP server does not support the 8BITMIME extension."); // prepare the message if ((Capabilities & SmtpCapabilities.BinaryMime) != 0) message.Prepare (EncodingConstraint.None, MaxLineLength); else if ((Capabilities & SmtpCapabilities.EightBitMime) != 0) message.Prepare (EncodingConstraint.EightBit, MaxLineLength); else message.Prepare (EncodingConstraint.SevenBit, MaxLineLength); // figure out which SMTP extensions we need to use var visitor = new ContentTransferEncodingVisitor (capabilities); visitor.Visit (message); var extensions = visitor.SmtpExtensions; if (format.International) extensions |= SmtpExtension.UTF8; try { // Note: if PIPELINING is supported, MailFrom() and RcptTo() will // queue their commands instead of sending them immediately. MailFrom (message, sender, extensions, cancellationToken); var unique = new HashSet<string> (StringComparer.OrdinalIgnoreCase); foreach (var recipient in recipients) { if (unique.Add (recipient.Address)) RcptTo (message, recipient, cancellationToken); } // Note: if PIPELINING is supported, this will flush all outstanding // MAIL FROM and RCPT TO commands to the server and then process all // of their responses. FlushCommandQueue (sender, recipients, cancellationToken); if ((extensions & SmtpExtension.BinaryMime) != 0) Bdat (format, message, cancellationToken, progress); else Data (format, message, cancellationToken, progress); } catch (ServiceNotAuthenticatedException) { // do not disconnect throw; } catch (SmtpCommandException) { Reset (cancellationToken); throw; } catch { Disconnect (); throw; } }
/// <summary> /// Asynchronously write the <see cref="Multipart"/> to the specified output stream. /// </summary> /// <remarks> /// Asynchronously writes the multipart MIME entity and its subparts to the output stream. /// </remarks> /// <returns>An awaitable task.</returns> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</param> /// <param name="contentOnly"><c>true</c> if only the content should be written; otherwise, <c>false</c>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public override async Task WriteToAsync(FormatOptions options, Stream stream, bool contentOnly, CancellationToken cancellationToken = default(CancellationToken)) { await base.WriteToAsync(options, stream, contentOnly, cancellationToken).ConfigureAwait(false); if (ContentType.IsMimeType("multipart", "signed")) { // don't hide or reformat the headers of any children of a multipart/signed if (options.International || options.HiddenHeaders.Count > 0) { options = options.Clone(); options.HiddenHeaders.Clear(); options.International = false; } } if (RawPreamble != null && RawPreamble.Length > 0) { await WriteBytesAsync(options, stream, RawPreamble, children.Count > 0 || EnsureNewLine, cancellationToken).ConfigureAwait(false); } var boundary = Encoding.ASCII.GetBytes("--" + Boundary + "--"); for (int i = 0; i < children.Count; i++) { var msg = children[i] as MessagePart; var multi = children[i] as Multipart; var part = children[i] as MimePart; await stream.WriteAsync(boundary, 0, boundary.Length - 2, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); await children[i].WriteToAsync(options, stream, false, cancellationToken).ConfigureAwait(false); if (msg != null && msg.Message != null && msg.Message.Body != null) { multi = msg.Message.Body as Multipart; part = msg.Message.Body as MimePart; } if ((part != null && part.Content == null) || (multi != null && !multi.WriteEndBoundary)) { continue; } await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); } if (!WriteEndBoundary) { return; } await stream.WriteAsync(boundary, 0, boundary.Length, cancellationToken).ConfigureAwait(false); if (RawEpilogue == null) { await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); } if (RawEpilogue != null && RawEpilogue.Length > 0) { await WriteBytesAsync(options, stream, RawEpilogue, EnsureNewLine, cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Write the <see cref="Multipart"/> to the specified output stream. /// </summary> /// <remarks> /// Writes the multipart MIME entity and its subparts to the output stream. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</param> /// <param name="contentOnly"><c>true</c> if only the content should be written; otherwise, <c>false</c>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public override void WriteTo(FormatOptions options, Stream stream, bool contentOnly, CancellationToken cancellationToken = default(CancellationToken)) { base.WriteTo(options, stream, contentOnly, cancellationToken); if (ContentType.IsMimeType("multipart", "signed")) { // don't reformat the headers or content of any children of a multipart/signed if (options.International || options.HiddenHeaders.Count > 0) { options = options.Clone(); options.HiddenHeaders.Clear(); options.International = false; } } var cancellable = stream as ICancellableStream; if (RawPreamble != null && RawPreamble.Length > 0) { WriteBytes(options, stream, RawPreamble, children.Count > 0 || EnsureNewLine, cancellationToken); } var boundary = Encoding.ASCII.GetBytes("--" + Boundary + "--"); if (cancellable != null) { for (int i = 0; i < children.Count; i++) { var msg = children[i] as MessagePart; var multi = children[i] as Multipart; var part = children[i] as MimePart; cancellable.Write(boundary, 0, boundary.Length - 2, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); children[i].WriteTo(options, stream, false, cancellationToken); if (msg != null && msg.Message != null && msg.Message.Body != null) { multi = msg.Message.Body as Multipart; part = msg.Message.Body as MimePart; } if ((part != null && part.Content == null) || (multi != null && !multi.WriteEndBoundary)) { continue; } cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } if (!WriteEndBoundary) { return; } cancellable.Write(boundary, 0, boundary.Length, cancellationToken); if (RawEpilogue == null) { cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } } else { for (int i = 0; i < children.Count; i++) { var rfc822 = children[i] as MessagePart; var multi = children[i] as Multipart; var part = children[i] as MimePart; cancellationToken.ThrowIfCancellationRequested(); stream.Write(boundary, 0, boundary.Length - 2); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); children[i].WriteTo(options, stream, false, cancellationToken); if (rfc822 != null && rfc822.Message != null && rfc822.Message.Body != null) { multi = rfc822.Message.Body as Multipart; part = rfc822.Message.Body as MimePart; } if ((part != null && part.Content == null) || (multi != null && !multi.WriteEndBoundary)) { continue; } cancellationToken.ThrowIfCancellationRequested(); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } if (!WriteEndBoundary) { return; } cancellationToken.ThrowIfCancellationRequested(); stream.Write(boundary, 0, boundary.Length); if (RawEpilogue == null) { cancellationToken.ThrowIfCancellationRequested(); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } if (RawEpilogue != null && RawEpilogue.Length > 0) { WriteBytes(options, stream, RawEpilogue, EnsureNewLine, cancellationToken); } }