如何使用 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 '>' 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 ();
}
}
}
我一直在寻找如何将所有消息保存到单个文件(如 .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 '>' 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 ();
}
}
}