static FormatOptions() { Default = new FormatOptions (); Default.MaxLineLength = 72; Default.WriteHeaders = true; if (Environment.NewLine.Length == 1) Default.NewLineFormat = NewLineFormat.Unix; else Default.NewLineFormat = NewLineFormat.Dos; }
/// <summary> /// Clones an instance of <see cref="MimeKit.FormatOptions"/>. /// </summary> /// <remarks> /// Clones the formatting options. /// </remarks> /// <returns>An exact copy of the <see cref="FormatOptions"/>.</returns> public FormatOptions Clone () { var options = new FormatOptions (); //options.maxLineLength = maxLineLength; options.newLineFormat = newLineFormat; options.HiddenHeaders = new HashSet<HeaderId> (HiddenHeaders); options.international = international; options.WriteHeaders = true; return options; }
/// <summary> /// Initializes a new instance of the <see cref="MailKit.Net.Imap.ImapCommand"/> class. /// </summary> /// <remarks> /// Creates a new <see cref="MailKit.Net.Imap.ImapCommand"/>. /// </remarks> /// <param name="engine">The IMAP engine that will be sending the command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="folder">The IMAP folder that the command operates on.</param> /// <param name="options">The formatting options.</param> /// <param name="format">The command format.</param> /// <param name="args">The command arguments.</param> public ImapCommand (ImapEngine engine, CancellationToken cancellationToken, ImapFolder folder, FormatOptions options, string format, params object[] args) { UntaggedHandlers = new Dictionary<string, ImapUntaggedHandler> (); RespCodes = new List<ImapResponseCode> (); CancellationToken = cancellationToken; Response = ImapCommandResponse.None; Status = ImapCommandStatus.Created; Engine = engine; Folder = folder; using (var builder = new MemoryStream ()) { var plus = (Engine.Capabilities & ImapCapabilities.LiteralPlus) != 0 ? "+" : string.Empty; int argc = 0; byte[] buf; string str; char c; for (int i = 0; i < format.Length; i++) { if (format[i] == '%') { switch (format[++i]) { case '%': // a literal % builder.WriteByte ((byte) '%'); break; case 'c': // a character c = (char) args[argc++]; builder.WriteByte ((byte) c); break; case 'd': // an integer str = ((int) args[argc++]).ToString (); buf = Encoding.ASCII.GetBytes (str); builder.Write (buf, 0, buf.Length); break; case 'u': // an unsigned integer str = ((uint) args[argc++]).ToString (); buf = Encoding.ASCII.GetBytes (str); builder.Write (buf, 0, buf.Length); break; case 'F': // an ImapFolder var utf7 = ((ImapFolder) args[argc++]).EncodedName; AppendString (options, true, builder, utf7); break; case 'L': var literal = new ImapLiteral (options, args[argc++], UpdateProgress); var length = literal.Length; totalSize += length; if (options.International) str = "UTF8 (~{" + length + plus + "}\r\n"; else str = "{" + length + plus + "}\r\n"; buf = Encoding.ASCII.GetBytes (str); builder.Write (buf, 0, buf.Length); parts.Add (new ImapCommandPart (builder.ToArray (), literal)); builder.SetLength (0); if (options.International) builder.WriteByte ((byte) ')'); break; case 'S': // a string which may need to be quoted or made into a literal AppendString (options, true, builder, (string) args[argc++]); break; case 'Q': // similar to %S but string must be quoted at a minimum AppendString (options, false, builder, (string) args[argc++]); break; case 's': // a safe atom string buf = Encoding.ASCII.GetBytes ((string) args[argc++]); builder.Write (buf, 0, buf.Length); break; default: throw new FormatException (); } } else { builder.WriteByte ((byte) format[i]); } } parts.Add (new ImapCommandPart (builder.ToArray (), null)); } }
byte[] FormatRawValue (FormatOptions format, Encoding encoding) { switch (Id) { case HeaderId.DispositionNotificationTo: case HeaderId.ResentFrom: case HeaderId.ResentBcc: case HeaderId.ResentCc: case HeaderId.ResentTo: case HeaderId.From: case HeaderId.Bcc: case HeaderId.Cc: case HeaderId.To: return EncodeAddressHeader (Options, format, encoding, Field, textValue); case HeaderId.Received: return EncodeReceivedHeader (Options, format, encoding, Field, textValue); case HeaderId.ResentMessageId: case HeaderId.MessageId: case HeaderId.ContentId: return EncodeMessageIdHeader (Options, format, encoding, Field, textValue); case HeaderId.References: return EncodeReferencesHeader (Options, format, encoding, Field, textValue); case HeaderId.ContentDisposition: return EncodeContentDisposition (Options, format, encoding, Field, textValue); case HeaderId.ContentType: return EncodeContentType (Options, format, encoding, Field, textValue); case HeaderId.DkimSignature: return EncodeDkimSignatureHeader (Options, format, encoding, Field, textValue); default: return EncodeUnstructuredHeader (Options, format, encoding, Field, textValue); } }
/// <summary> /// Writes the <see cref="MimeKit.MessagePart"/> to the output stream. /// </summary> /// <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) { base.WriteTo (options, stream, cancellationToken); if (Message != null) Message.WriteTo (options, stream, cancellationToken); }
/// <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> /// Writes the <see cref="MimeKit.HeaderList"/> to the specified output stream. /// </summary> /// <param name="options">The formatting options.</param> /// <param name="stream">The output stream.</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.IO.IOException"> /// An I/O error occurred. /// </exception> public void WriteTo(FormatOptions options, Stream stream) { WriteTo(options, stream, CancellationToken.None); }
void DkimWriteHeaders (FormatOptions options, IList<string> fields, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm, Stream stream) { var counts = new Dictionary<string, int> (); Header header; for (int i = 0; i < fields.Count; i++) { var name = fields[i].ToLowerInvariant (); int index, count, n = 0; if (!counts.TryGetValue (name, out count)) count = 0; // Note: signers choosing to sign an existing header field that occurs more // than once in the message (such as Received) MUST sign the physically last // instance of that header field in the header block. Signers wishing to sign // multiple instances of such a header field MUST include the header field // name multiple times in the list of header fields and MUST sign such header // fields in order from the bottom of the header field block to the top. index = Headers.LastIndexOf (name); // find the n'th header with this name while (n < count && --index >= 0) { if (Headers[index].Field.Equals (name, StringComparison.OrdinalIgnoreCase)) n++; } if (index < 0) continue; header = Headers[index]; switch (headerCanonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: DkimWriteHeaderRelaxed (options, stream, header); break; default: DkimWriteHeaderSimple (options, stream, header); break; } counts[name] = ++count; } }
/// <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">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 msg = 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 (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; } 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> /// Asynchronously writes the <see cref="MimeKit.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); } }
internal override void Encode(FormatOptions options, StringBuilder builder, ref int lineLength) { if (builder == null) { throw new ArgumentNullException("builder"); } if (lineLength < 0) { throw new ArgumentOutOfRangeException("lineLength"); } string route = Route.ToString(); if (!string.IsNullOrEmpty(route)) { route += ":"; } if (!string.IsNullOrEmpty(Name)) { var encoded = Rfc2047.EncodePhrase(options, Encoding, Name); var str = Encoding.ASCII.GetString(encoded); if (lineLength + str.Length > options.MaxLineLength) { if (str.Length > options.MaxLineLength) { // we need to break up the name... builder.AppendFolded(options, str, ref lineLength); } else { // the name itself is short enough to fit on a single line, // but only if we write it on a line by itself if (lineLength > 1) { builder.LineWrap(options); lineLength = 1; } lineLength += str.Length; builder.Append(str); } } else { // we can safely fit the name on this line... lineLength += str.Length; builder.Append(str); } if ((lineLength + route.Length + Address.Length + 3) > options.MaxLineLength) { builder.Append("\n\t<"); lineLength = 2; } else { builder.Append(" <"); lineLength += 2; } lineLength += route.Length; builder.Append(route); lineLength += Address.Length + 1; builder.Append(Address); builder.Append('>'); } else if (!string.IsNullOrEmpty(route)) { if ((lineLength + route.Length + Address.Length + 2) > options.MaxLineLength) { builder.Append("\n\t<"); lineLength = 2; } else { builder.Append('<'); lineLength++; } lineLength += route.Length; builder.Append(route); lineLength += Address.Length + 1; builder.Append(Address); builder.Append('>'); } else { if ((lineLength + Address.Length) > options.MaxLineLength) { builder.LineWrap(options); lineLength = 1; } lineLength += Address.Length; builder.Append(Address); } }
internal override void Encode(FormatOptions options, StringBuilder builder, ref int lineLength) { var route = Route.Encode(options); if (!string.IsNullOrEmpty(route)) { route += ":"; } string addrspec; if (options.International) { addrspec = DecodeAddrspec(address, at); } else { addrspec = EncodeAddrspec(address, at); } if (!string.IsNullOrEmpty(Name)) { string name; if (!options.International) { var encoded = Rfc2047.EncodePhrase(options, Encoding, Name); name = Encoding.ASCII.GetString(encoded, 0, encoded.Length); } else { name = EncodeInternationalizedPhrase(Name); } if (lineLength + name.Length > options.MaxLineLength) { if (name.Length > options.MaxLineLength) { // we need to break up the name... builder.AppendFolded(options, name, ref lineLength); } else { // the name itself is short enough to fit on a single line, // but only if we write it on a line by itself if (lineLength > 1) { builder.LineWrap(options); lineLength = 1; } lineLength += name.Length; builder.Append(name); } } else { // we can safely fit the name on this line... lineLength += name.Length; builder.Append(name); } if ((lineLength + route.Length + addrspec.Length + 3) > options.MaxLineLength) { builder.Append(options.NewLine); builder.Append("\t<"); lineLength = 2; } else { builder.Append(" <"); lineLength += 2; } lineLength += route.Length; builder.Append(route); lineLength += addrspec.Length + 1; builder.Append(addrspec); builder.Append('>'); } else if (!string.IsNullOrEmpty(route)) { if ((lineLength + route.Length + addrspec.Length + 2) > options.MaxLineLength) { builder.Append(options.NewLine); builder.Append("\t<"); lineLength = 2; } else { builder.Append('<'); lineLength++; } lineLength += route.Length; builder.Append(route); lineLength += addrspec.Length + 1; builder.Append(addrspec); builder.Append('>'); } else { if ((lineLength + addrspec.Length) > options.MaxLineLength) { builder.LineWrap(options); lineLength = 1; } lineLength += addrspec.Length; builder.Append(addrspec); } }
internal static string Fold(FormatOptions format, string field, string value) { var folded = new StringBuilder(value.Length); int lineLength = field.Length + 2; int lastLwsp = -1; folded.Append(' '); var words = TokenizeText(value); foreach (var word in words) { if (IsWhiteSpace(word[0])) { if (lineLength + word.Length > format.MaxLineLength) { for (int i = 0; i < word.Length; i++) { if (lineLength > format.MaxLineLength) { folded.Append(format.NewLine); lineLength = 0; } folded.Append(word[i]); lineLength++; } } else { lineLength += word.Length; folded.Append(word); } lastLwsp = folded.Length - 1; continue; } if (lastLwsp != -1 && lineLength + word.Length > format.MaxLineLength) { folded.Insert(lastLwsp, format.NewLine); lineLength = 1; lastLwsp = -1; } if (word.Length > format.MaxLineLength) { foreach (var broken in WordBreak(format, word, lineLength)) { if (lineLength + broken.Length > format.MaxLineLength) { folded.Append(format.NewLine); folded.Append(' '); lineLength = 1; } folded.Append(broken.Text, broken.StartIndex, broken.Length); lineLength += broken.Length; } } else { lineLength += word.Length; folded.Append(word); } } folded.Append(format.NewLine); return(folded.ToString()); }
/// <summary> /// Writes the message to the specified file. /// </summary> /// <remarks> /// Writes the message to the specified file using the provided formatting options. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="fileName">The file.</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="fileName"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="fileName"/> is a zero-length string, contains only white space, or /// contains one or more invalid characters as defined by /// <see cref="System.IO.Path.InvalidPathChars"/>. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.DirectoryNotFoundException"> /// <paramref name="fileName"/> is an invalid file path. /// </exception> /// <exception cref="System.IO.FileNotFoundException"> /// The specified file path could not be found. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// The user does not have access to write to the specified file. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public void WriteTo (FormatOptions options, string fileName, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (fileName == null) throw new ArgumentNullException ("fileName"); using (var stream = File.Open (fileName, FileMode.Create, FileAccess.Write)) WriteTo (options, stream, cancellationToken); }
/// <summary> /// Splits the specified message into multiple messages. /// </summary> /// <remarks> /// Splits the specified message into multiple messages, each with a /// message/partial body no larger than the max size specified. /// </remarks> /// <returns>An enumeration of partial messages.</returns> /// <param name="message">The message.</param> /// <param name="maxSize">The maximum size for each message body.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="message"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="maxSize"/> is less than <c>1</c>. /// </exception> public static IEnumerable <MimeMessage> Split(MimeMessage message, int maxSize) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (maxSize < 1) { throw new ArgumentOutOfRangeException(nameof(maxSize)); } var options = FormatOptions.CloneDefault(); foreach (HeaderId id in Enum.GetValues(typeof(HeaderId))) { switch (id) { case HeaderId.Subject: case HeaderId.MessageId: case HeaderId.Encrypted: case HeaderId.MimeVersion: case HeaderId.ContentAlternative: case HeaderId.ContentBase: case HeaderId.ContentClass: case HeaderId.ContentDescription: case HeaderId.ContentDisposition: case HeaderId.ContentDuration: case HeaderId.ContentFeatures: case HeaderId.ContentId: case HeaderId.ContentIdentifier: case HeaderId.ContentLanguage: case HeaderId.ContentLength: case HeaderId.ContentLocation: case HeaderId.ContentMd5: case HeaderId.ContentReturn: case HeaderId.ContentTransferEncoding: case HeaderId.ContentTranslationType: case HeaderId.ContentType: break; default: options.HiddenHeaders.Add(id); break; } } var memory = new MemoryStream(); message.WriteTo(options, memory); memory.Seek(0, SeekOrigin.Begin); if (memory.Length <= maxSize) { memory.Dispose(); yield return(message); yield break; } var streams = new List <Stream> (); #if !NETSTANDARD1_3 && !NETSTANDARD1_6 var buf = memory.GetBuffer(); #else var buf = memory.ToArray(); #endif long startIndex = 0; while (startIndex < memory.Length) { // Preferably, we'd split on whole-lines if we can, // but if that's not possible, split on max size long endIndex = Math.Min(memory.Length, startIndex + maxSize); if (endIndex < memory.Length) { long ebx = endIndex; while (ebx > (startIndex + 1) && buf[ebx] != (byte)'\n') { ebx--; } if (buf[ebx] == (byte)'\n') { endIndex = ebx + 1; } } streams.Add(new BoundStream(memory, startIndex, endIndex, true)); startIndex = endIndex; } var msgid = message.MessageId ?? MimeUtils.GenerateMessageId(); int number = 1; foreach (var stream in streams) { var part = new MessagePartial(msgid, number++, streams.Count) { Content = new MimeContent(stream) }; var submessage = CloneMessage(message); submessage.MessageId = MimeUtils.GenerateMessageId(); submessage.Body = part; yield return(submessage); } yield break; }
static void DkimWriteHeaderSimple (FormatOptions options, Stream stream, Header header) { var rawValue = header.GetRawValue (options); stream.Write (header.RawField, 0, header.RawField.Length); stream.Write (new [] { (byte) ':' }, 0, 1); stream.Write (rawValue, 0, rawValue.Length); }
static FormatOptions() { Default = new FormatOptions(); }
/// <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> /// 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> /// 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> /// Writes the <see cref="MimeKit.MimePart"/> to the specified output stream. /// </summary> /// <remarks> /// Writes the MIME part 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 (ContentObject == null) { return; } var cancellable = stream as ICancellableStream; if (ContentObject.Encoding != encoding) { if (encoding == ContentEncoding.UUEncode) { var begin = string.Format("begin 0644 {0}", FileName ?? "unknown"); var buffer = Encoding.UTF8.GetBytes(begin); if (cancellable != null) { cancellable.Write(buffer, 0, buffer.Length, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } else { cancellationToken.ThrowIfCancellationRequested(); stream.Write(buffer, 0, buffer.Length); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } // 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)); } ContentObject.DecodeTo(filtered, cancellationToken); filtered.Flush(cancellationToken); } if (encoding == ContentEncoding.UUEncode) { var buffer = Encoding.ASCII.GetBytes("end"); if (cancellable != null) { cancellable.Write(buffer, 0, buffer.Length, cancellationToken); cancellable.Write(options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } else { cancellationToken.ThrowIfCancellationRequested(); stream.Write(buffer, 0, buffer.Length); stream.Write(options.NewLineBytes, 0, options.NewLineBytes.Length); } } } else if (encoding != ContentEncoding.Binary) { 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)); ContentObject.WriteTo(filtered, cancellationToken); filtered.Flush(cancellationToken); } } else { ContentObject.WriteTo(stream, cancellationToken); } }
internal abstract void Encode(FormatOptions options, StringBuilder builder, ref int lineLength);
internal static string FoldPreambleOrEpilogue(FormatOptions options, string text, bool isEpilogue) { var builder = new StringBuilder(); int startIndex, wordIndex; int lineLength = 0; int index = 0; if (isEpilogue) { builder.Append(options.NewLine); } while (index < text.Length) { startIndex = index; while (index < text.Length) { if (!char.IsWhiteSpace(text[index])) { break; } if (text[index] == '\n') { builder.Append(options.NewLine); startIndex = index + 1; lineLength = 0; } index++; } wordIndex = index; while (index < text.Length && !char.IsWhiteSpace(text[index])) { index++; } int length = index - startIndex; if (lineLength > 0 && lineLength + length >= options.MaxLineLength) { builder.Append(options.NewLine); length = index - wordIndex; startIndex = wordIndex; lineLength = 0; } if (length > 0) { builder.Append(text, startIndex, length); lineLength += length; } } if (lineLength > 0) { builder.Append(options.NewLine); } return(builder.ToString()); }
static byte[] EncodeReceivedHeader(ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var tokens = new List <ReceivedTokenValue> (); var rawValue = charset.GetBytes(value); var encoded = new StringBuilder(); int lineLength = field.Length + 1; bool date = false; int index = 0; int count = 0; while (index < rawValue.Length) { ReceivedTokenValue token = null; int startIndex = index; if (!ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false) || index >= rawValue.Length) { tokens.Add(new ReceivedTokenValue(startIndex, index - startIndex)); break; } while (index < rawValue.Length && !rawValue[index].IsWhitespace()) { index++; } var atom = charset.GetString(rawValue, startIndex, index - startIndex); for (int i = 0; i < ReceivedTokens.Length; i++) { if (atom == ReceivedTokens[i].Atom) { ReceivedTokens[i].Skip(rawValue, ref index); if (ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false)) { if (index < rawValue.Length && rawValue[index] == (byte)';') { date = true; index++; } } token = new ReceivedTokenValue(startIndex, index - startIndex); break; } } if (token == null) { if (ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false)) { while (index < rawValue.Length && !rawValue[index].IsWhitespace()) { index++; } } token = new ReceivedTokenValue(startIndex, index - startIndex); } tokens.Add(token); ParseUtils.SkipWhiteSpace(rawValue, ref index, rawValue.Length); if (date && index < rawValue.Length) { // slurp up the date (the final token) tokens.Add(new ReceivedTokenValue(index, rawValue.Length - index)); break; } } foreach (var token in tokens) { var text = charset.GetString(rawValue, token.StartIndex, token.Length).TrimEnd(); if (count > 0 && lineLength + text.Length + 1 > format.MaxLineLength) { encoded.Append(format.NewLine); encoded.Append('\t'); lineLength = 1; count = 0; } else { encoded.Append(' '); lineLength++; } lineLength += text.Length; encoded.Append(text); count++; } encoded.Append(format.NewLine); return(charset.GetBytes(encoded.ToString())); }
/// <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); } }
/// <summary> /// Clones an instance of <see cref="MimeKit.FormatOptions"/>. /// </summary> public FormatOptions Clone() { var options = new FormatOptions (); options.MaxLineLength = MaxLineLength; options.NewLineFormat = NewLineFormat; options.WriteHeaders = true; return options; }
EncodeMethod GetEncodeMethod(FormatOptions options, string name, string value, out string quoted) { var method = EncodeMethod.None; EncodeMethod encode; switch (encodingMethod) { default: if (options.ParameterEncodingMethod == ParameterEncodingMethod.Rfc2231) { encode = EncodeMethod.Rfc2231; } else { encode = EncodeMethod.Rfc2047; } break; case ParameterEncodingMethod.Rfc2231: encode = EncodeMethod.Rfc2231; break; case ParameterEncodingMethod.Rfc2047: encode = EncodeMethod.Rfc2047; break; } quoted = null; if (name.Length + 1 + value.Length >= options.MaxLineLength) { return(encode); } for (int i = 0; i < value.Length; i++) { if (value[i] < 128) { var c = (byte)value[i]; if (c.IsCtrl()) { return(encode); } if (!c.IsAttr()) { method = EncodeMethod.Quote; } } else if (options.International) { method = EncodeMethod.Quote; } else { return(encode); } } if (method == EncodeMethod.Quote) { quoted = MimeUtils.Quote(value); if (name.Length + 1 + quoted.Length >= options.MaxLineLength) { return(encode); } } return(method); }
static FormatOptions () { Default = new FormatOptions (); }
static bool Rfc2231GetNextValue(FormatOptions options, string charset, Encoder encoder, HexEncoder hex, char[] chars, ref int index, ref byte[] bytes, ref byte[] encoded, int maxLength, out string value) { int length = chars.Length - index; if (length < maxLength) { switch (GetEncodeMethod(options, chars, index, length)) { case EncodeMethod.Quote: value = MimeUtils.Quote(new string (chars, index, length)); index += length; return(false); case EncodeMethod.None: value = new string (chars, index, length); index += length; return(false); } } length = Math.Min(maxLength, length); int ratio, count, n; do { count = encoder.GetByteCount(chars, index, length, true); if (count > maxLength && length > 1) { if ((ratio = (int)Math.Round((double)count / (double)length)) > 1) { length -= Math.Max((count - maxLength) / ratio, 1); } else { length--; } continue; } if (bytes.Length < count) { Array.Resize <byte> (ref bytes, count); } count = encoder.GetBytes(chars, index, length, bytes, 0, true); // Note: the first chunk needs to be encoded in order to declare the charset if (index > 0 || charset == "us-ascii") { var method = GetEncodeMethod(bytes, count); if (method == EncodeMethod.Quote) { value = MimeUtils.Quote(Encoding.ASCII.GetString(bytes, 0, count)); index += length; return(false); } if (method == EncodeMethod.None) { value = Encoding.ASCII.GetString(bytes, 0, count); index += length; return(false); } } n = hex.EstimateOutputLength(count); if (encoded.Length < n) { Array.Resize <byte> (ref encoded, n); } // only the first value gets a charset declaration int charsetLength = index == 0 ? charset.Length + 2 : 0; n = hex.Encode(bytes, 0, count, encoded); if (n > 3 && (charsetLength + n) > maxLength) { int x = 0; for (int i = n - 1; i >= 0 && charsetLength + i >= maxLength; i--) { if (encoded[i] == (byte)'%') { x--; } else { x++; } } if ((ratio = (int)Math.Round((double)count / (double)length)) > 1) { length -= Math.Max(x / ratio, 1); } else { length--; } continue; } if (index == 0) { value = charset + "''" + Encoding.ASCII.GetString(encoded, 0, n); } else { value = Encoding.ASCII.GetString(encoded, 0, n); } index += length; return(true); } while (true); }
/// <summary> /// Writes the <see cref="MimeKit.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">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)) { base.WriteTo (options, stream, contentOnly, cancellationToken); if (Message != null) Message.WriteTo (options, stream, cancellationToken); }
void EncodeRfc2231(FormatOptions options, StringBuilder builder, ref int lineLength, Encoding headerEncoding) { var bestEncoding = options.International ? CharsetUtils.UTF8 : GetBestEncoding(Value, encoding ?? headerEncoding); int maxLength = options.MaxLineLength - (Name.Length + 6); var charset = CharsetUtils.GetMimeCharset(bestEncoding); var encoder = (Encoder)bestEncoding.GetEncoder(); var bytes = new byte[Math.Max(maxLength, 6)]; var hexbuf = new byte[bytes.Length * 3 + 3]; var chars = Value.ToCharArray(); var hex = new HexEncoder(); int index = 0, i = 0; string value, id; bool encoded; int length; do { builder.Append(';'); lineLength++; encoded = Rfc2231GetNextValue(options, charset, encoder, hex, chars, ref index, ref bytes, ref hexbuf, maxLength, out value); length = Name.Length + (encoded ? 1 : 0) + 1 + value.Length; if (i == 0 && index == chars.Length) { if (lineLength + 1 + length >= options.MaxLineLength) { builder.Append(options.NewLine); builder.Append('\t'); lineLength = 1; } else { builder.Append(' '); lineLength++; } builder.Append(Name); if (encoded) { builder.Append('*'); } builder.Append('='); builder.Append(value); lineLength += length; return; } builder.Append(options.NewLine); builder.Append('\t'); lineLength = 1; id = i.ToString(); length += id.Length + 1; builder.Append(Name); builder.Append('*'); builder.Append(id); if (encoded) { builder.Append('*'); } builder.Append('='); builder.Append(value); lineLength += length; i++; } while (index < chars.Length); }
static void DkimWriteHeaderRelaxed (FormatOptions options, Stream stream, Header header) { var name = Encoding.ASCII.GetBytes (header.Field.ToLowerInvariant ()); var rawValue = header.GetRawValue (options); int index = 0; stream.Write (name, 0, name.Length); stream.WriteByte ((byte) ':'); // look for the first non-whitespace character while (index < rawValue.Length && rawValue[index].IsBlank ()) index++; while (index < rawValue.Length) { int startIndex = index; int endIndex, nextLine; // look for the first non-whitespace character while (index < rawValue.Length && rawValue[index].IsBlank ()) index++; // look for the end of the line endIndex = index; while (endIndex < rawValue.Length && rawValue[endIndex] != (byte) '\n') endIndex++; nextLine = endIndex + 1; if (endIndex > index && rawValue[endIndex - 1] == (byte) '\r') endIndex--; if (index > startIndex) stream.WriteByte ((byte) ' '); while (index < endIndex) { startIndex = index; while (index < endIndex && !rawValue[index].IsBlank ()) index++; stream.Write (rawValue, startIndex, index - startIndex); startIndex = index; while (index < endIndex && rawValue[index].IsBlank ()) index++; if (index > startIndex) stream.WriteByte ((byte) ' '); } index = nextLine; } stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length); }
/// <summary> /// Writes the <see cref="MimeKit.MimeEntity"/> to the specified output stream. /// </summary> /// <remarks> /// Writes the entity to the output stream. /// </remarks> /// <param name="stream">The output stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="stream"/> is <c>null</c>. /// </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 void WriteTo(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { WriteTo(FormatOptions.GetDefault(), stream, cancellationToken); }
byte[] DkimHashBody (FormatOptions options, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm, int maxLength) { using (var stream = new DkimHashStream (signatureAlgorithm, maxLength)) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); if (bodyCanonicalizationAlgorithm == DkimCanonicalizationAlgorithm.Relaxed) filtered.Add (new DkimRelaxedBodyFilter ()); else filtered.Add (new DkimSimpleBodyFilter ()); if (Body != null) { try { Body.Headers.Suppress = true; Body.WriteTo (options, stream, CancellationToken.None); } finally { Body.Headers.Suppress = false; } } filtered.Flush (); } return stream.GenerateHash (); } }
internal override void Encode(FormatOptions options, StringBuilder builder, ref int lineLength) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (lineLength < 0) { throw new ArgumentOutOfRangeException(nameof(lineLength)); } if (!string.IsNullOrEmpty(Name)) { string name; if (!options.International) { var encoded = Rfc2047.EncodePhrase(options, Encoding, Name); name = Encoding.ASCII.GetString(encoded, 0, encoded.Length); } else { name = EncodeInternationalizedPhrase(Name); } if (lineLength + name.Length > options.MaxLineLength) { if (name.Length > options.MaxLineLength) { // we need to break up the name... builder.AppendFolded(options, name, ref lineLength); } else { // the name itself is short enough to fit on a single line, // but only if we write it on a line by itself if (lineLength > 1) { builder.LineWrap(options); lineLength = 1; } lineLength += name.Length; builder.Append(name); } } else { // we can safely fit the name on this line... lineLength += name.Length; builder.Append(name); } } builder.Append(": "); lineLength += 2; Members.Encode(options, builder, ref lineLength); builder.Append(';'); lineLength++; }
/// <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); } }
static byte[] EncodeReferencesHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var encoded = new StringBuilder (); int lineLength = field.Length + 1; int count = 0; foreach (var reference in MimeUtils.EnumerateReferences (value)) { if (count > 0 && lineLength + reference.Length + 3 > format.MaxLineLength) { encoded.Append (format.NewLine); encoded.Append ('\t'); lineLength = 1; count = 0; } else { encoded.Append (' '); lineLength++; } encoded.Append ('<').Append (reference).Append ('>'); lineLength += reference.Length + 2; count++; } encoded.Append (format.NewLine); return charset.GetBytes (encoded.ToString ()); }
/// <summary> /// Writes the message to the specified output stream. /// </summary> /// <remarks> /// Writes the message to the output stream using the provided formatting options. /// </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 void WriteTo (FormatOptions options, Stream stream, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); if (version == null && Body != null && Body.Headers.Count > 0) MimeVersion = new Version (1, 0); if (Body != null) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); foreach (var header in MergeHeaders ()) { if (options.HiddenHeaders.Contains (header.Id)) continue; filtered.Write (header.RawField, 0, header.RawField.Length, cancellationToken); filtered.Write (new [] { (byte) ':' }, 0, 1, cancellationToken); filtered.Write (header.RawValue, 0, header.RawValue.Length, cancellationToken); } filtered.Flush (cancellationToken); } var cancellable = stream as ICancellableStream; if (cancellable != null) { cancellable.Write (options.NewLineBytes, 0, options.NewLineBytes.Length, cancellationToken); } else { cancellationToken.ThrowIfCancellationRequested (); stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length); } try { Body.Headers.Suppress = true; Body.WriteTo (options, stream, cancellationToken); } finally { Body.Headers.Suppress = false; } } else { Headers.WriteTo (options, stream, cancellationToken); } }
static IEnumerable<BrokenWord> WordBreak (FormatOptions format, string word, int lineLength) { var chars = word.ToCharArray (); int startIndex = 0; lineLength = Math.Max (lineLength, 1); while (startIndex < word.Length) { int length = Math.Min (format.MaxLineLength - lineLength, word.Length); if (char.IsSurrogatePair (word, startIndex + length - 1)) length--; yield return new BrokenWord (chars, startIndex, length); startIndex += length; lineLength = 1; } yield break; }
ImapCommand QueueAppend (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, CancellationToken cancellationToken, ITransferProgress progress) { string format = "APPEND %F"; if ((flags & SettableFlags) != 0) format += " " + ImapUtils.FormatFlagsList (flags, 0); if (date.HasValue) format += " \"" + ImapUtils.FormatInternalDate (date.Value) + "\""; format += " %L\r\n"; var ic = new ImapCommand (Engine, cancellationToken, null, options, format, this, message); ic.Progress = progress; Engine.QueueCommand (ic); return ic; }
internal static string Fold (FormatOptions format, string field, string value) { var folded = new StringBuilder (value.Length); int lineLength = field.Length + 2; int lastLwsp = -1; folded.Append (' '); var words = TokenizeText (value); foreach (var word in words) { if (IsWhiteSpace (word[0])) { if (lineLength + word.Length > format.MaxLineLength) { for (int i = 0; i < word.Length; i++) { if (lineLength > format.MaxLineLength) { folded.Append (format.NewLine); lineLength = 0; } folded.Append (word[i]); lineLength++; } } else { lineLength += word.Length; folded.Append (word); } lastLwsp = folded.Length - 1; continue; } if (lastLwsp != -1 && lineLength + word.Length > format.MaxLineLength) { folded.Insert (lastLwsp, format.NewLine); lineLength = 1; lastLwsp = -1; } if (word.Length > format.MaxLineLength) { foreach (var broken in WordBreak (format, word, lineLength)) { if (lineLength + broken.Length > format.MaxLineLength) { folded.Append (format.NewLine); folded.Append (' '); lineLength = 1; } folded.Append (broken.Text, broken.StartIndex, broken.Length); lineLength += broken.Length; } } else { lineLength += word.Length; folded.Append (word); } } folded.Append (format.NewLine); return folded.ToString (); }
ImapCommand QueueMultiAppend (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, CancellationToken cancellationToken, ITransferProgress progress) { var args = new List<object> (); string format = "APPEND %F"; args.Add (this); for (int i = 0; i < messages.Count; i++) { if ((flags[i] & SettableFlags) != 0) format += " " + ImapUtils.FormatFlagsList (flags[i], 0); if (dates != null) format += " \"" + ImapUtils.FormatInternalDate (dates[i]) + "\""; format += " %L"; args.Add (messages[i]); } format += "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, null, options, format, args.ToArray ()); ic.Progress = progress; Engine.QueueCommand (ic); return ic; }
static byte[] EncodeContentDisposition (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var disposition = ContentDisposition.Parse (options, value); var encoded = disposition.Encode (format, charset); return Encoding.UTF8.GetBytes (encoded); }
internal string Encode(FormatOptions options, Encoding charset) { int lineLength = "Content-Type: ".Length; var value = new StringBuilder (" "); value.Append (MediaType); value.Append ('/'); value.Append (MediaSubtype); Parameters.Encode (options, value, ref lineLength, charset); value.Append (options.NewLine); return value.ToString (); }
static byte[] EncodeContentType (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var contentType = ContentType.Parse (options, value); var encoded = contentType.Encode (format, charset); return Encoding.UTF8.GetBytes (encoded); }
/// <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; }
static byte[] EncodeUnstructuredHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { if (format.International) { var folded = Fold (format, field, value); return Encoding.UTF8.GetBytes (folded); } var encoded = Rfc2047.EncodeText (format, charset, value); return Rfc2047.FoldUnstructuredHeader (format, field, encoded); }
void AppendString (FormatOptions options, bool allowAtom, MemoryStream builder, string value) { byte[] buf; switch (GetStringType (value, allowAtom)) { case ImapStringType.Literal: var literal = Encoding.UTF8.GetBytes (value); var length = literal.Length.ToString (); buf = Encoding.ASCII.GetBytes (length); builder.WriteByte ((byte) '{'); builder.Write (buf, 0, buf.Length); if (Engine.IsGMail || (Engine.Capabilities & ImapCapabilities.LiteralPlus) != 0) builder.WriteByte ((byte) '+'); builder.WriteByte ((byte) '}'); builder.WriteByte ((byte) '\r'); builder.WriteByte ((byte) '\n'); if (Engine.IsGMail || (Engine.Capabilities & ImapCapabilities.LiteralPlus) != 0) { builder.Write (literal, 0, literal.Length); } else { parts.Add (new ImapCommandPart (builder.ToArray (), new ImapLiteral (options, literal))); builder.SetLength (0); } break; case ImapStringType.QString: buf = Encoding.UTF8.GetBytes (MimeUtils.Quote (value)); builder.Write (buf, 0, buf.Length); break; case ImapStringType.Atom: buf = Encoding.UTF8.GetBytes (value); builder.Write (buf, 0, buf.Length); break; } }
static byte[] EncodeMessageIdHeader(ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { return(charset.GetBytes(" " + value + format.NewLine)); }