如何使用 Mailkit / MimeKit IMAP 将所有消息保存到单个 .mbox 文件?

How to save all messages to a single .mbox file using Mailkit / MimeKit IMAP?

我一直在寻找如何将所有消息保存到单个文件(如 .mbox 文件)的示例,但一直没有成功。这是我尝试过但目前无法使用的一些代码。

var exportStream = new MemoryStream(); foreach (var uid in uids) { var message = client.Inbox.GetMessage(uid); message.WriteTo(exportStream);} exportStream.Position = 0; using (var fileStream = File.Create(@"C:\temp\results.mbox")) { exportStream.Seek(0, SeekOrigin.Begin); exportStream.CopyTo(fileStream); }

我在另一个 Whosebug 问题的评论部分对你的快速而粗暴的回答有点过于简单,但基本上你需要做的就是遍历 IMAP 文件夹中的消息,然后将它们写入文件流,由 mbox 标记分隔(通常为 "From<SPACE><SOMETHING-ELSE><NEW-LINE>")。

遗憾的是,mbox 文件格式并不是特别标准化,因此对于 "From<SPACE>" 之后的内容没有严格的规定可遵循。 通常它是帐户的用户名,后跟某种日期格式的时间戳或另一种格式的时间戳,然后是换行序列,但另一个非常常见的标记只是 "From -\n"(在UNIX) 或 "From -\r\n"(在 Windows 上)。

因此,要将 IMAP 收件箱中的所有邮件保存到一个 mbox 文件中,我们可能会这样做:

using (var mboxStream = File.Create ("Inbox.mbox")) {
    // Create our standard Mbox marker.
    var mboxMarker = Encoding.ASCII.GetBytes ("From -" + Environment.NewLine);

    // Get the full list of message UIDs in the folder
    var uids = client.Inbox.Search (SearchQuery.All);

    // Iterate over each UID, saving each message into the Mbox.
    foreach (var uid in uids) {
        var message = client.Inbox.GetMessage (uid);

        // The start of each message in an Mbox file is marked by a "From "-line.
        mboxStream.Write (mboxMarker, 0, mboxMarker.Length);

        // Since lines beginning with "From " have special meaning to mbox 
        // parsers, we need to somehow make sure that no line of the message
        // begins with "From ". To do that, we create a filtered stream that
        // will munge the From-lines for us.
        using (var filtered = new FilteredStream (mboxStream)) {
            // Add the mbox filter.
            filtered.Add (new MboxFilter ());

            // Write the message to the mbox file, passing through the filter.
            message.WriteTo (filtered);

            // Flush the filtered stream before disposing it.
            filtered.Flush ();
        }
    }
}

MboxFilter 看起来像这样(尚未包含在 MimeKit 中,但可能会添加到 v3.0 中):

//
// MboxFilter.cs
//
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
//
// Copyright (c) 2013-2021 .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

using System;
using System.Collections.Generic;

namespace MimeKit.IO.Filters {
    /// <summary>
    /// A filter that munges lines beginning with "From " by stuffing a '&gt;' into the beginning of the line.
    /// </summary>
    /// <remarks>
    /// <para>Munging Mbox-style "From "-lines is a workaround to prevent Mbox parsers from misinterpreting a
    /// line beginning with "From " as an mbox marker delineating messages. This munging is non-reversable but
    /// is necessary to properly format a message for saving to an Mbox file.</para>
    /// </remarks>
    public class MboxFilter : MimeFilterBase
    {
        const string From = "From ";
        bool midline;

        /// <summary>
        /// Initialize a new instance of the <see cref="MboxFilter"/> class.
        /// </summary>
        /// <remarks>
        /// Creates a new <see cref="MboxFilter"/>.
        /// </remarks>
        public MboxFilter ()
        {
        }

        static bool StartsWithFrom (byte[] input, int startIndex, int endIndex)
        {
            for (int i = 0, index = startIndex; i < From.Length && index < endIndex; i++, index++) {
                if (input[index] != (byte) From[i])
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Filter the specified input.
        /// </summary>
        /// <remarks>
        /// Filters the specified input buffer starting at the given index,
        /// spanning across the specified number of bytes.
        /// </remarks>
        /// <returns>The filtered output.</returns>
        /// <param name="input">The input buffer.</param>
        /// <param name="startIndex">The starting index of the input buffer.</param>
        /// <param name="length">The length of the input buffer, starting at <paramref name="startIndex"/>.</param>
        /// <param name="outputIndex">The output index.</param>
        /// <param name="outputLength">The output length.</param>
        /// <param name="flush">If set to <c>true</c>, all internally buffered data should be flushed to the output buffer.</param>
        protected override byte[] Filter (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength, bool flush)
        {
            var fromOffsets = new List<int> ();
            int endIndex = startIndex + length;
            int index = startIndex;
            int left;

            while (index < endIndex) {
                byte c = 0;

                if (midline) {
                    while (index < endIndex) {
                        c = input[index++];
                        if (c == (byte) '\n')
                            break;
                    }
                }

                if (c == (byte) '\n' || !midline) {
                    if ((left = endIndex - index) > 0) {
                        midline = true;

                        if (left < 5) {
                            if (StartsWithFrom (input, index, endIndex)) {
                                SaveRemainingInput (input, index, left);
                                endIndex = index;
                                midline = false;
                                break;
                            }
                        } else {
                            if (StartsWithFrom (input, index, endIndex)) {
                                fromOffsets.Add (index);
                                index += 5;
                            }
                        }
                    } else {
                        midline = false;
                    }
                }
            }

            if (fromOffsets.Count > 0) {
                int need = (endIndex - startIndex) + fromOffsets.Count;

                EnsureOutputSize (need, false);
                outputLength = 0;
                outputIndex = 0;

                index = startIndex;
                foreach (var offset in fromOffsets) {
                    if (index < offset) {
                        Buffer.BlockCopy (input, index, OutputBuffer, outputLength, offset - index);
                        outputLength += offset - index;
                        index = offset;
                    }

                    // munge the beginning of the "From "-line.
                    OutputBuffer[outputLength++] = (byte) '>';
                }

                Buffer.BlockCopy (input, index, OutputBuffer, outputLength, endIndex - index);
                outputLength += endIndex - index;

                return OutputBuffer;
            }

            outputLength = endIndex - startIndex;
            outputIndex = 0;
            return input;
        }

        /// <summary>
        /// Resets the filter.
        /// </summary>
        /// <remarks>
        /// Resets the filter.
        /// </remarks>
        public override void Reset ()
        {
            midline = false;
            base.Reset ();
        }
    }
}