MimeEntity CreateAttachment(ContentType contentType, string fileName, Stream stream) { MimeEntity attachment; if (contentType.IsMimeType("message", "rfc822")) { var message = MimeMessage.Load(stream); var rfc822 = new MessagePart { Message = message }; rfc822.ContentDisposition = new ContentDisposition(linked ? ContentDisposition.Inline : ContentDisposition.Attachment); rfc822.ContentDisposition.FileName = Path.GetFileName(fileName); rfc822.ContentType.Name = Path.GetFileName(fileName); attachment = rfc822; } else { MimePart part; if (contentType.IsMimeType("text", "*")) { part = new TextPart(contentType.MediaSubtype); foreach (var param in contentType.Parameters) { part.ContentType.Parameters.Add(param); } // TODO: should we try to auto-detect charsets if no charset parameter is specified? } else { part = new MimePart(contentType); } part.FileName = Path.GetFileName(fileName); part.IsAttachment = !linked; LoadContent(part, stream); attachment = part; } if (linked) { attachment.ContentLocation = new Uri(Path.GetFileName(fileName), UriKind.Relative); } return(attachment); }
/// <summary> /// Computes the MD5 checksum of the content. /// </summary> /// <remarks> /// Computes the MD5 checksum of the MIME content in its canonical /// format and then base64-encodes the result. /// </remarks> /// <returns>The md5sum of the content.</returns> /// <exception cref="System.InvalidOperationException"> /// The <see cref="ContentObject"/> is <c>null</c>. /// </exception> public string ComputeContentMd5() { if (ContentObject == null) { throw new InvalidOperationException("Cannot compute Md5 checksum without a ContentObject."); } using (var stream = ContentObject.Open()) { byte[] checksum; using (var filtered = new FilteredStream(stream)) { if (ContentType.IsMimeType("text", "*")) { filtered.Add(new Unix2DosFilter()); } using (var md5 = MD5.Create()) checksum = md5.ComputeHash(filtered); } var base64 = new Base64Encoder(true); var digest = new byte[base64.EstimateOutputLength(checksum.Length)]; int n = base64.Flush(checksum, 0, checksum.Length, digest); return(Encoding.ASCII.GetString(digest, 0, n)); } }
MimePart CreateAttachment(ContentType contentType, string fileName, Stream stream) { MimePart attachment; if (contentType.IsMimeType("text", "*")) { attachment = new TextPart(contentType.MediaSubtype); foreach (var param in contentType.Parameters) { attachment.ContentType.Parameters.Add(param); } // TODO: should we try to auto-detect charsets if no charset parameter is specified? } else { attachment = new MimePart(contentType); } attachment.FileName = Path.GetFileName(fileName); attachment.IsAttachment = !linked; if (linked) { attachment.ContentLocation = new Uri(Path.GetFileName(fileName), UriKind.Relative); } LoadContent(attachment, stream); return(attachment); }
async Task <MimeEntity> CreateAttachmentAsync(ContentType contentType, string path, Stream stream, CancellationToken cancellationToken) { var fileName = GetFileName(path); MimeEntity attachment; if (contentType.IsMimeType("message", "rfc822")) { var message = await MimeMessage.LoadAsync(stream, cancellationToken).ConfigureAwait(false); attachment = new MessagePart { Message = message }; } else { MimePart part; if (contentType.IsMimeType("text", "*")) { // TODO: should we try to auto-detect charsets if no charset parameter is specified? part = new TextPart(contentType); } else { part = new MimePart(contentType); } await LoadContentAsync(part, stream, cancellationToken).ConfigureAwait(false); attachment = part; } attachment.ContentDisposition = new ContentDisposition(linked ? ContentDisposition.Inline : ContentDisposition.Attachment); attachment.ContentDisposition.FileName = fileName; attachment.ContentType.Name = fileName; if (linked) { attachment.ContentLocation = new Uri(fileName, UriKind.Relative); } return(attachment); }
/// <summary> /// Calculates the most efficient content encoding given the specified constraint. /// </summary> /// <remarks> /// If no <see cref="Content"/> is set, <see cref="ContentEncoding.SevenBit"/> will be returned. /// </remarks> /// <returns>The most efficient content encoding.</returns> /// <param name="constraint">The encoding constraint.</param> /// <param name="maxLineLength">The maximum allowable length for a line (not counting the CRLF). Must be between <c>72</c> and <c>998</c> (inclusive).</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <para><paramref name="maxLineLength"/> is not between <c>72</c> and <c>998</c> (inclusive).</para> /// <para>-or-</para> /// <para><paramref name="constraint"/> is not a valid value.</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 ContentEncoding GetBestEncoding(EncodingConstraint constraint, int maxLineLength, CancellationToken cancellationToken = default(CancellationToken)) { if (ContentType.IsMimeType("text", "*") || ContentType.IsMimeType("message", "*")) { if (Content == null) { return(ContentEncoding.SevenBit); } using (var measure = new MeasuringStream()) { using (var filtered = new FilteredStream(measure)) { var filter = new BestEncodingFilter(); filtered.Add(filter); Content.DecodeTo(filtered, cancellationToken); filtered.Flush(); return(filter.GetBestEncoding(constraint, maxLineLength)); } } } return(constraint == EncodingConstraint.None ? ContentEncoding.Binary : ContentEncoding.Base64); }
/// <summary> /// Asynchronously write the <see cref="MimePart"/> to the specified output stream. /// </summary> /// <remarks> /// Asynchronously writes the MIME part 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 (Content == null) { return; } var isText = ContentType.IsMimeType("text", "*") || ContentType.IsMimeType("message", "*"); if (Content.Encoding != encoding) { if (encoding == ContentEncoding.UUEncode) { var begin = string.Format("begin 0644 {0}", FileName ?? "unknown"); var buffer = Encoding.UTF8.GetBytes(begin); await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); } // transcode the content into the desired Content-Transfer-Encoding using (var filtered = new FilteredStream(stream)) { filtered.Add(EncoderFilter.Create(encoding)); if (encoding != ContentEncoding.Binary) { filtered.Add(options.CreateNewLineFilter(EnsureNewLine)); } await Content.DecodeToAsync(filtered, cancellationToken).ConfigureAwait(false); await filtered.FlushAsync(cancellationToken).ConfigureAwait(false); } if (encoding == ContentEncoding.UUEncode) { var buffer = Encoding.ASCII.GetBytes("end"); await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); await stream.WriteAsync(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken).ConfigureAwait(false); } } else if (encoding == ContentEncoding.Binary) { // Do not alter binary content. await Content.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); } else if (options.VerifyingSignature && Content.NewLineFormat.HasValue && Content.NewLineFormat.Value == NewLineFormat.Mixed) { // Allow pass-through of the original parsed content without canonicalization when verifying signatures // if the content contains a mix of line-endings. // // See https://github.com/jstedfast/MimeKit/issues/569 for details. await Content.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); } else { using (var filtered = new FilteredStream(stream)) { // Note: if we are writing the top-level MimePart, make sure it ends with a new-line so that // MimeMessage.WriteTo() *always* ends with a new-line. filtered.Add(options.CreateNewLineFilter(EnsureNewLine)); await Content.WriteToAsync(filtered, cancellationToken).ConfigureAwait(false); await filtered.FlushAsync(cancellationToken).ConfigureAwait(false); } } }
/// <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); } }
/// <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="contentOnly"><c>true</c> if only the content should be written; otherwise, <c>false</c>.</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, bool contentOnly, CancellationToken cancellationToken = default(CancellationToken)) { if (Boundary == null) { Boundary = GenerateBoundary(); } 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, cancellationToken); } var boundary = Encoding.ASCII.GetBytes("--" + Boundary + "--"); if (cancellable != null) { for (int i = 0; i < children.Count; i++) { 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 (part == null || (part.ContentObject != null && part.ContentObject.Stream.Length != 0)) { cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } } 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 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 (part == null || (part.ContentObject != null && part.ContentObject.Stream.Length != 0)) { stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } cancellationToken.ThrowIfCancellationRequested(); stream.Write(boundary, 0, boundary.Length); if (RawEpilogue == null) { stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } if (RawEpilogue != null && RawEpilogue.Length > 0) { WriteBytes(options, stream, RawEpilogue, cancellationToken); } }