Кодировка пути к папке IMAP (IMAP UTF-7) для.NET?
Спецификация IMAP ( RFC 2060, 5.1.3. Международное соглашение по присвоению имен почтовых ящиков) описывает, как обрабатывать символы не ASCII в именах папок. Он определяет модифицированную кодировку UTF-7:
По соглашению, имена международных почтовых ящиков указываются с использованием модифицированной версии кодировки UTF-7, описанной в [UTF-7]. Целью этих модификаций является исправление следующих проблем с UTF-7:
UTF-7 использует символ "+" для переключения; это противоречит общему использованию "+" в именах почтовых ящиков, в частности в именах групп новостей USENET.
Кодировка UTF-7 - BASE64, в которой используется символ "/"; это противоречит использованию "/" в качестве популярного разделителя иерархии.
UTF-7 запрещает незашифрованное использование "\"; это противоречит использованию "\" в качестве популярного разделителя иерархии.
UTF-7 запрещает незашифрованное использование "~"; это противоречит использованию "~" на некоторых серверах в качестве индикатора домашнего каталога.
UTF-7 позволяет нескольким альтернативным формам представлять одну и ту же строку; в частности, печатные символы US-ASCII могут быть представлены в закодированном виде.
В модифицированном UTF-7 печатаемые символы US-ASCII, за исключением символа "&", представляют себя; то есть символы со значениями октетов 0x20-0x25 и 0x27-0x7e. Символ "&" (0x26) представлен двухоктетной последовательностью "&-".
Все другие символы (значения октетов 0x00-0x1f, 0x7f-0xff и все 16-битные октеты Unicode) представлены в модифицированном BASE64 с дальнейшей модификацией из [UTF-7], в которой вместо "/" используется ",".
Модифицированный BASE64 НЕ ДОЛЖЕН использоваться для представления любого печатного символа US-ASCII, который может представлять себя."&" используется для перехода к модифицированному BASE64, а "-" для возврата к US-ASCII. Все имена начинаются с US-ASCII и ДОЛЖНЫ заканчиваться на US-ASCII (то есть имя, заканчивающееся 16-битным октетом Unicode, ДОЛЖНО заканчиваться на "-").
Прежде чем я начну реализовывать это, мой вопрос: есть ли какой-нибудь код / библиотека.NET (или даже в рамках), который выполняет эту работу? Я не смог найти.NET ресурсы (только реализации для других языков / фреймворков).
Спасибо!
2 ответа
//
// ImapEncoding.cs
//
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
//
// Copyright (c) 2013-2019 Microsoft Corp. (www.microsoft.com)
//
// 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.Text;
namespace MailKit.Net.Imap {
static class ImapEncoding
{
const string utf7_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
static readonly byte[] utf7_rank = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62, 63,255,255,255,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
};
public static string Decode (string text)
{
var decoded = new StringBuilder ();
bool shifted = false;
int bits = 0, v = 0;
int index = 0;
char c;
while (index < text.Length) {
c = text[index++];
if (shifted) {
if (c == '-') {
// shifted back out of modified UTF-7
shifted = false;
bits = v = 0;
} else if (c > 127) {
// invalid UTF-7
return text;
} else {
byte rank = utf7_rank[(byte) c];
if (rank == 0xff) {
// invalid UTF-7
return text;
}
v = (v << 6) | rank;
bits += 6;
if (bits >= 16) {
char u = (char) ((v >> (bits - 16)) & 0xffff);
decoded.Append (u);
bits -= 16;
}
}
} else if (c == '&' && index < text.Length) {
if (text[index] == '-') {
decoded.Append ('&');
index++;
} else {
// shifted into modified UTF-7
shifted = true;
}
} else {
decoded.Append (c);
}
}
return decoded.ToString ();
}
static void Utf7ShiftOut (StringBuilder output, int u, int bits)
{
if (bits > 0) {
int x = (u << (6 - bits)) & 0x3f;
output.Append (utf7_alphabet[x]);
}
output.Append ('-');
}
public static string Encode (string text)
{
var encoded = new StringBuilder ();
bool shifted = false;
int bits = 0, u = 0;
for (int index = 0; index < text.Length; index++) {
char c = text[index];
if (c >= 0x20 && c < 0x7f) {
// characters with octet values 0x20-0x25 and 0x27-0x7e
// represent themselves while 0x26 ("&") is represented
// by the two-octet sequence "&-"
if (shifted) {
Utf7ShiftOut (encoded, u, bits);
shifted = false;
bits = 0;
}
if (c == 0x26)
encoded.Append ("&-");
else
encoded.Append (c);
} else {
// base64 encode
if (!shifted) {
encoded.Append ('&');
shifted = true;
}
u = (u << 16) | (c & 0xffff);
bits += 16;
while (bits >= 6) {
int x = (u >> (bits - 6)) & 0x3f;
encoded.Append (utf7_alphabet[x]);
bits -= 6;
}
}
}
if (shifted)
Utf7ShiftOut (encoded, u, bits);
return encoded.ToString ();
}
}
}
Это слишком специализировано, чтобы присутствовать в структуре. В кодекплексе может быть что-то, хотя многие незавершенные "реализации", которые я видел, вообще не беспокоятся о преобразовании и с радостью передадут все не-ascii символы на сервер IMAP.
Однако я реализовал это в прошлом, и это всего лишь 30 строк кода. Вы просматриваете все символы в строке и выводите их, если они попадают в диапазон между 0x20 и 0x7e (не забудьте добавить "-" после "&"), в противном случае соберите все не-us-ascii и конвертируйте их, используя UTF7 (или UTF8 + base64, я здесь не совсем уверен), заменив "/" на ",". Кроме того, вам необходимо поддерживать "сдвинутое состояние", например, кодируете ли вы в настоящее время не-us-ascii или выводите us-ascii и добавляете токены перехода "&" и "-" при изменении состояния.
Не проверено, но этот MIT-лицензированный код выглядит хорошо, если применено исправление Алексея:
/// <summary>
/// Takes a UTF-16 encoded string and encodes it as modified UTF-7.
/// </summary>
/// <param name="s">The string to encode.</param>
/// <returns>A UTF-7 encoded string</returns>
/// <remarks>IMAP uses a modified version of UTF-7 for encoding international mailbox names. For
/// details, refer to RFC 3501 section 5.1.3 (Mailbox International Naming Convention).</remarks>
internal static string UTF7Encode(string s) {
StringReader reader = new StringReader(s);
StringBuilder builder = new StringBuilder();
while (reader.Peek() != -1) {
char c = (char)reader.Read();
int codepoint = Convert.ToInt32(c);
// It's a printable ASCII character.
if (codepoint > 0x1F && codepoint < 0x7F) {
builder.Append(c == '&' ? "&-" : c.ToString());
} else {
// The character sequence needs to be encoded.
StringBuilder sequence = new StringBuilder(c.ToString());
while (reader.Peek() != -1) {
codepoint = Convert.ToInt32((char)reader.Peek());
if (codepoint > 0x1F && codepoint < 0x7F)
break;
sequence.Append((char)reader.Read());
}
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(
sequence.ToString());
string encoded = Convert.ToBase64String(buffer).Replace('/', ',').
TrimEnd('=');
builder.Append("&" + encoded + "-");
}
}
return builder.ToString();
}
/// <summary>
/// Takes a modified UTF-7 encoded string and decodes it.
/// </summary>
/// <param name="s">The UTF-7 encoded string to decode.</param>
/// <returns>A UTF-16 encoded "standard" C# string</returns>
/// <exception cref="FormatException">The input string is not a properly UTF-7 encoded
/// string.</exception>
/// <remarks>IMAP uses a modified version of UTF-7 for encoding international mailbox names. For
/// details, refer to RFC 3501 section 5.1.3 (Mailbox International Naming Convention).</remarks>
internal static string UTF7Decode(string s) {
StringReader reader = new StringReader(s);
StringBuilder builder = new StringBuilder();
while (reader.Peek() != -1) {
char c = (char)reader.Read();
if (c == '&' && reader.Peek() != '-') {
// The character sequence needs to be decoded.
StringBuilder sequence = new StringBuilder();
while (reader.Peek() != -1) {
if ((c = (char)reader.Read()) == '-')
break;
sequence.Append(c);
}
string encoded = sequence.ToString().Replace(',', '/');
int pad = encoded.Length % 4;
if (pad > 0)
encoded = encoded.PadRight(encoded.Length + (4 - pad), '=');
try {
byte[] buffer = Convert.FromBase64String(encoded);
builder.Append(Encoding.BigEndianUnicode.GetString(buffer));
} catch (Exception e) {
throw new FormatException(
"The input string is not in the correct Format.", e);
}
} else {
if (c == '&' && reader.Peek() == '-')
reader.Read();
builder.Append(c);
}
}
return builder.ToString();
}
Не используйте этот код в его текущем состоянии, он содержит [...] UTF7.GetBytes([...]) [...] .Replace('+', '&')
- он использует существующую процедуру кодирования.Net UTF-7 и (среди прочего) заменяет +
с &
в результате. Это неправильно, потому что это не только изменить "символ сдвига" от +
в &
(что задумано и правильно) но и все +
символы внутри кодированных base64 областей (которые не должны быть изменены на &
).