puhh
This commit is contained in:
parent
e0cc259bb2
commit
07fabd1413
58
MailServer/IMAP/IMAP_ACL_Flags.cs
Normal file
58
MailServer/IMAP/IMAP_ACL_Flags.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IMAP ACL(access control list) rights.
|
||||||
|
/// </summary>
|
||||||
|
public enum IMAP_ACL_Flags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No permissions at all.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Lookup (mailbox is visible to LIST/LSUB commands).
|
||||||
|
/// </summary>
|
||||||
|
l = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL,SEARCH, COPY from mailbox).
|
||||||
|
/// </summary>
|
||||||
|
r = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Keep seen/unseen information across sessions (STORE SEEN flag).
|
||||||
|
/// </summary>
|
||||||
|
s = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// Write (STORE flags other than SEEN and DELETED).
|
||||||
|
/// </summary>
|
||||||
|
w = 8,
|
||||||
|
/// <summary>
|
||||||
|
/// Insert (perform APPEND, COPY into mailbox).
|
||||||
|
/// </summary>
|
||||||
|
i = 16,
|
||||||
|
/// <summary>
|
||||||
|
/// Post (send mail to submission address for mailbox,not enforced by IMAP4 itself).
|
||||||
|
/// </summary>
|
||||||
|
p = 32,
|
||||||
|
/// <summary>
|
||||||
|
/// Create (CREATE new sub-mailboxes in any implementation-defined hierarchy).
|
||||||
|
/// </summary>
|
||||||
|
c = 64,
|
||||||
|
/// <summary>
|
||||||
|
/// Delete (STORE DELETED flag, perform EXPUNGE).
|
||||||
|
/// </summary>
|
||||||
|
d = 128,
|
||||||
|
/// <summary>
|
||||||
|
/// Administer (perform SETACL).
|
||||||
|
/// </summary>
|
||||||
|
a = 256,
|
||||||
|
/// <summary>
|
||||||
|
/// All permissions
|
||||||
|
/// </summary>
|
||||||
|
All = 0xFFFF,
|
||||||
|
}
|
||||||
|
}
|
610
MailServer/IMAP/IMAP_Utils.cs
Normal file
610
MailServer/IMAP/IMAP_Utils.cs
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using MailServer.IMAP.Server;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides utility methods for IMAP.
|
||||||
|
/// </summary>
|
||||||
|
public class IMAP_Utils
|
||||||
|
{
|
||||||
|
#region method ParseMessageFlags
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses message flags from string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flagsString">Message flags string.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IMAP_MessageFlags ParseMessageFlags(string flagsString)
|
||||||
|
{
|
||||||
|
IMAP_MessageFlags mFlags = 0;
|
||||||
|
|
||||||
|
flagsString = flagsString.ToUpper();
|
||||||
|
|
||||||
|
if (flagsString.IndexOf("ANSWERED") > -1)
|
||||||
|
{
|
||||||
|
mFlags |= IMAP_MessageFlags.Answered;
|
||||||
|
}
|
||||||
|
if (flagsString.IndexOf("FLAGGED") > -1)
|
||||||
|
{
|
||||||
|
mFlags |= IMAP_MessageFlags.Flagged;
|
||||||
|
}
|
||||||
|
if (flagsString.IndexOf("DELETED") > -1)
|
||||||
|
{
|
||||||
|
mFlags |= IMAP_MessageFlags.Deleted;
|
||||||
|
}
|
||||||
|
if (flagsString.IndexOf("SEEN") > -1)
|
||||||
|
{
|
||||||
|
mFlags |= IMAP_MessageFlags.Seen;
|
||||||
|
}
|
||||||
|
if (flagsString.IndexOf("DRAFT") > -1)
|
||||||
|
{
|
||||||
|
mFlags |= IMAP_MessageFlags.Draft;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method MessageFlagsToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts message flags to string. Eg. \SEEN \DELETED .
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string MessageFlagsToString(IMAP_MessageFlags msgFlags)
|
||||||
|
{
|
||||||
|
string retVal = "";
|
||||||
|
if (((int)IMAP_MessageFlags.Answered & (int)msgFlags) != 0)
|
||||||
|
{
|
||||||
|
retVal += " \\ANSWERED";
|
||||||
|
}
|
||||||
|
if (((int)IMAP_MessageFlags.Flagged & (int)msgFlags) != 0)
|
||||||
|
{
|
||||||
|
retVal += " \\FLAGGED";
|
||||||
|
}
|
||||||
|
if (((int)IMAP_MessageFlags.Deleted & (int)msgFlags) != 0)
|
||||||
|
{
|
||||||
|
retVal += " \\DELETED";
|
||||||
|
}
|
||||||
|
if (((int)IMAP_MessageFlags.Seen & (int)msgFlags) != 0)
|
||||||
|
{
|
||||||
|
retVal += " \\SEEN";
|
||||||
|
}
|
||||||
|
if (((int)IMAP_MessageFlags.Draft & (int)msgFlags) != 0)
|
||||||
|
{
|
||||||
|
retVal += " \\DRAFT";
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ACL_to_String
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts IMAP_ACL_Flags to string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">Flags to convert.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ACL_to_String(IMAP_ACL_Flags flags)
|
||||||
|
{
|
||||||
|
string retVal = "";
|
||||||
|
if ((flags & IMAP_ACL_Flags.l) != 0)
|
||||||
|
{
|
||||||
|
retVal += "l";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.r) != 0)
|
||||||
|
{
|
||||||
|
retVal += "r";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.s) != 0)
|
||||||
|
{
|
||||||
|
retVal += "s";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.w) != 0)
|
||||||
|
{
|
||||||
|
retVal += "w";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.i) != 0)
|
||||||
|
{
|
||||||
|
retVal += "i";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.p) != 0)
|
||||||
|
{
|
||||||
|
retVal += "p";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.c) != 0)
|
||||||
|
{
|
||||||
|
retVal += "c";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.d) != 0)
|
||||||
|
{
|
||||||
|
retVal += "d";
|
||||||
|
}
|
||||||
|
if ((flags & IMAP_ACL_Flags.a) != 0)
|
||||||
|
{
|
||||||
|
retVal += "a";
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ACL_From_String
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses IMAP_ACL_Flags from string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aclString">String from where to convert</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IMAP_ACL_Flags ACL_From_String(string aclString)
|
||||||
|
{
|
||||||
|
IMAP_ACL_Flags retVal = IMAP_ACL_Flags.None;
|
||||||
|
aclString = aclString.ToLower();
|
||||||
|
if (aclString.IndexOf('l') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.l;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('r') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.r;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('s') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.s;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('w') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.w;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('i') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.i;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('p') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.p;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('c') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.c;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('d') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.d;
|
||||||
|
}
|
||||||
|
if (aclString.IndexOf('a') > -1)
|
||||||
|
{
|
||||||
|
retVal |= IMAP_ACL_Flags.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ParseDate
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses IMAP date time from string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">DateTime string.</param>
|
||||||
|
/// <returns>Returns parsed date-time value.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>date</b> is null reference.</exception>
|
||||||
|
public static DateTime ParseDate(string date)
|
||||||
|
{
|
||||||
|
if (date == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("date");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 3501. IMAP date format.
|
||||||
|
date-time = DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
|
||||||
|
date = day-month-year
|
||||||
|
time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
|
||||||
|
*/
|
||||||
|
if (date.IndexOf('-') > -1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return DateTime.ParseExact(date.Trim(), new string[] { "d-MMM-yyyy", "d-MMM-yyyy HH:mm:ss zzz" }, System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.None);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Argument 'date' value '" + date + "' is not valid IMAP date.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MailServer.Misc.MIME.MIME_Utils.ParseRfc2822DateTime(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static DateTimeToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts date time to IMAP date time string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">DateTime to convert.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string DateTimeToString(DateTime date)
|
||||||
|
{
|
||||||
|
string retVal = "";
|
||||||
|
retVal += date.ToString("dd-MMM-yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
retVal += " " + date.ToString("zzz", System.Globalization.CultureInfo.InvariantCulture).Replace(":", "");
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Encode_IMAP_UTF7_String
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes specified data with IMAP modified UTF7 encoding. Defined in RFC 3501 5.1.3. Mailbox International Naming Convention.
|
||||||
|
/// Example: öö is encoded to &APYA9g-.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to encode.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Encode_IMAP_UTF7_String(string text)
|
||||||
|
{
|
||||||
|
/* RFC 3501 5.1.3. Mailbox International Naming Convention
|
||||||
|
In modified UTF-7, printable US-ASCII characters, except for "&",
|
||||||
|
represent themselves; that is, characters with octet values 0x20-0x25
|
||||||
|
and 0x27-0x7e. The character "&" (0x26) is represented by the
|
||||||
|
two-octet sequence "&-".
|
||||||
|
|
||||||
|
All other characters (octet values 0x00-0x1f and 0x7f-0xff) are
|
||||||
|
represented in modified BASE64, with a further modification from
|
||||||
|
[UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be
|
||||||
|
used to represent any printing US-ASCII character which can represent
|
||||||
|
itself.
|
||||||
|
|
||||||
|
"&" is used to shift to modified BASE64 and "-" to shift back to
|
||||||
|
US-ASCII. There is no implicit shift from BASE64 to US-ASCII, and
|
||||||
|
null shifts ("-&" while in BASE64; note that "&-" while in US-ASCII
|
||||||
|
means "&") are not permitted. However, all names start in US-ASCII,
|
||||||
|
and MUST end in US-ASCII; that is, a name that ends with a non-ASCII
|
||||||
|
ISO-10646 character MUST end with a "-").
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Base64 chars, except '/' is replaced with ','
|
||||||
|
char[] base64Chars = new char[]{
|
||||||
|
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||||
|
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||||
|
'0','1','2','3','4','5','6','7','8','9','+',','
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryStream retVal = new MemoryStream();
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
char c = text[i];
|
||||||
|
|
||||||
|
// The character "&" (0x26) is represented by the two-octet sequence "&-".
|
||||||
|
if (c == '&')
|
||||||
|
{
|
||||||
|
retVal.Write(new byte[] { (byte)'&', (byte)'-' }, 0, 2);
|
||||||
|
}
|
||||||
|
// It is allowed char, don't need to encode
|
||||||
|
else if (c >= 0x20 && c <= 0x25 || c >= 0x27 && c <= 0x7E)
|
||||||
|
{
|
||||||
|
retVal.WriteByte((byte)c);
|
||||||
|
}
|
||||||
|
// Not allowed char, encode it
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Superfluous shifts are not allowed.
|
||||||
|
// For example: öö may not encoded as &APY-&APY-, but must be &APYA9g-.
|
||||||
|
|
||||||
|
// Get all continuous chars that need encoding and encode them as one block
|
||||||
|
MemoryStream encodeBlock = new MemoryStream();
|
||||||
|
for (int ic = i; ic < text.Length; ic++)
|
||||||
|
{
|
||||||
|
char cC = text[ic];
|
||||||
|
|
||||||
|
// Allowed char
|
||||||
|
if (cC >= 0x20 && cC <= 0x25 || cC >= 0x27 && cC <= 0x7E)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encodeBlock.WriteByte((byte)((cC & 0xFF00) >> 8));
|
||||||
|
encodeBlock.WriteByte((byte)(cC & 0xFF));
|
||||||
|
i = ic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ecode block
|
||||||
|
byte[] encodedData = Core.Base64EncodeEx(encodeBlock.ToArray(), base64Chars, false);
|
||||||
|
retVal.WriteByte((byte)'&');
|
||||||
|
retVal.Write(encodedData, 0, encodedData.Length);
|
||||||
|
retVal.WriteByte((byte)'-');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.Text.Encoding.Default.GetString(retVal.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method Decode_IMAP_UTF7_String
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes IMAP modified UTF7 encoded data. Defined in RFC 3501 5.1.3. Mailbox International Naming Convention.
|
||||||
|
/// Example: &APYA9g- is decoded to öö.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to encode.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Decode_IMAP_UTF7_String(string text)
|
||||||
|
{
|
||||||
|
/* RFC 3501 5.1.3. Mailbox International Naming Convention
|
||||||
|
In modified UTF-7, printable US-ASCII characters, except for "&",
|
||||||
|
represent themselves; that is, characters with octet values 0x20-0x25
|
||||||
|
and 0x27-0x7e. The character "&" (0x26) is represented by the
|
||||||
|
two-octet sequence "&-".
|
||||||
|
|
||||||
|
All other characters (octet values 0x00-0x1f and 0x7f-0xff) are
|
||||||
|
represented in modified BASE64, with a further modification from
|
||||||
|
[UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be
|
||||||
|
used to represent any printing US-ASCII character which can represent
|
||||||
|
itself.
|
||||||
|
|
||||||
|
"&" is used to shift to modified BASE64 and "-" to shift back to
|
||||||
|
US-ASCII. There is no implicit shift from BASE64 to US-ASCII, and
|
||||||
|
null shifts ("-&" while in BASE64; note that "&-" while in US-ASCII
|
||||||
|
means "&") are not permitted. However, all names start in US-ASCII,
|
||||||
|
and MUST end in US-ASCII; that is, a name that ends with a non-ASCII
|
||||||
|
ISO-10646 character MUST end with a "-").
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Base64 chars, except '/' is replaced with ','
|
||||||
|
char[] base64Chars = new char[]{
|
||||||
|
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||||
|
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||||
|
'0','1','2','3','4','5','6','7','8','9','+',','
|
||||||
|
};
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
char c = text[i];
|
||||||
|
|
||||||
|
// Encoded block or escaped &
|
||||||
|
if (c == '&')
|
||||||
|
{
|
||||||
|
int endingPos = -1;
|
||||||
|
// Read encoded block
|
||||||
|
for (int b = i + 1; b < text.Length; b++)
|
||||||
|
{
|
||||||
|
// - marks block end
|
||||||
|
if (text[b] == '-')
|
||||||
|
{
|
||||||
|
endingPos = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Invalid & sequence, just treat it as '&' char and not like shift.
|
||||||
|
// &....&, but must be &....-
|
||||||
|
else if (text[b] == '&')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no ending -, invalid encoded block. Treat it like it is
|
||||||
|
if (endingPos == -1)
|
||||||
|
{
|
||||||
|
// Just let main for to handle other chars after &
|
||||||
|
retVal.Append(c);
|
||||||
|
}
|
||||||
|
// If empty block, then escaped &
|
||||||
|
else if (endingPos - i == 1)
|
||||||
|
{
|
||||||
|
retVal.Append(c);
|
||||||
|
// Move i over '-'
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// Decode block
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get encoded block
|
||||||
|
byte[] encodedBlock = System.Text.Encoding.Default.GetBytes(text.Substring(i + 1, endingPos - i - 1));
|
||||||
|
|
||||||
|
// Convert to UTF-16 char
|
||||||
|
byte[] decodedData = Core.Base64DecodeEx(encodedBlock, base64Chars);
|
||||||
|
char[] decodedChars = new char[decodedData.Length / 2];
|
||||||
|
for (int iC = 0; iC < decodedChars.Length; iC++)
|
||||||
|
{
|
||||||
|
decodedChars[iC] = (char)(decodedData[iC * 2] << 8 | decodedData[(iC * 2) + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode data
|
||||||
|
retVal.Append(decodedChars);
|
||||||
|
|
||||||
|
// Move i over '-'
|
||||||
|
i += encodedBlock.Length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normal byte
|
||||||
|
else
|
||||||
|
{
|
||||||
|
retVal.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method NormalizeFolder
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes folder path. Example: /Inbox/SubFolder/ will be Inbox/SubFolder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">Folder path to normalize.</param>
|
||||||
|
/// <returns>Returns normalized folder path.</returns>
|
||||||
|
public static string NormalizeFolder(string folder)
|
||||||
|
{
|
||||||
|
folder = folder.Replace("\\", "/");
|
||||||
|
if (folder.StartsWith("/"))
|
||||||
|
{
|
||||||
|
folder = folder.Substring(1);
|
||||||
|
}
|
||||||
|
if (folder.EndsWith("/"))
|
||||||
|
{
|
||||||
|
folder = folder.Substring(0, folder.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsValidFolderName
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified folder name is valid folder name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">Folder name.</param>
|
||||||
|
/// <returns>Returns true if specified folde name is valid.</returns>
|
||||||
|
public static bool IsValidFolderName(string folder)
|
||||||
|
{
|
||||||
|
// TODO: Path ?
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ParseQuotedParam
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses [quoted] parameter from args text. Parameter may be not quoted, then parameter is
|
||||||
|
/// terminated by SP. Example: argsText="string gdkga agkgs";argsText=stringValue 10.
|
||||||
|
///
|
||||||
|
/// This method also removes parsed parameter from argsText.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argsText">Arguments line from where to parse param.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ParseQuotedParam(ref string argsText)
|
||||||
|
{
|
||||||
|
string paramValue = "";
|
||||||
|
|
||||||
|
// Get value, it is between ""
|
||||||
|
if (argsText.StartsWith("\""))
|
||||||
|
{
|
||||||
|
// Find next " not escaped "
|
||||||
|
char lastChar = ' ';
|
||||||
|
int qIndex = -1;
|
||||||
|
for (int i = 1; i < argsText.Length; i++)
|
||||||
|
{
|
||||||
|
if (argsText[i] == '\"' && lastChar != '\\')
|
||||||
|
{
|
||||||
|
qIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastChar = argsText[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qIndex == -1)
|
||||||
|
{
|
||||||
|
throw new Exception("qouted-string doesn't have enclosing quote(\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
paramValue = argsText.Substring(1, qIndex - 1).Replace("\\\"", "\"");
|
||||||
|
|
||||||
|
// Remove <string> value from argsText
|
||||||
|
argsText = argsText.Substring(qIndex + 1).Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paramValue = argsText.Split(' ')[0];
|
||||||
|
|
||||||
|
// Remove <string> value from argsText
|
||||||
|
argsText = argsText.Substring(paramValue.Length).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ParseBracketParam
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses bracket parameter from args text. Parameter may be not between (), then
|
||||||
|
/// then args text is considered as value. Example: (test test);test test.
|
||||||
|
///
|
||||||
|
/// This method also removes parsed parameter from argsText.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argsText"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ParseBracketParam(ref string argsText)
|
||||||
|
{
|
||||||
|
string paramValue = "";
|
||||||
|
if (argsText.StartsWith("("))
|
||||||
|
{
|
||||||
|
// Find matching )
|
||||||
|
char lastChar = ' ';
|
||||||
|
int bIndex = -1;
|
||||||
|
int nestedBracketCount = 0;
|
||||||
|
for (int i = 1; i < argsText.Length; i++)
|
||||||
|
{
|
||||||
|
// There is nested ()
|
||||||
|
if (argsText[i] == '(')
|
||||||
|
{
|
||||||
|
nestedBracketCount++;
|
||||||
|
}
|
||||||
|
else if (argsText[i] == ')')
|
||||||
|
{
|
||||||
|
if (nestedBracketCount == 0)
|
||||||
|
{
|
||||||
|
bIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// This was nested bracket )
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nestedBracketCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastChar = argsText[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bIndex == -1)
|
||||||
|
{
|
||||||
|
throw new Exception("bracket doesn't have enclosing bracket ')'");
|
||||||
|
}
|
||||||
|
|
||||||
|
paramValue = argsText.Substring(1, bIndex - 1);
|
||||||
|
|
||||||
|
// Remove <string> value from argsText
|
||||||
|
argsText = argsText.Substring(bIndex + 1).Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paramValue = argsText;
|
||||||
|
|
||||||
|
argsText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
using MailServer.Settings;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class AuthUser_EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt die IMAP Session zurück
|
|
||||||
/// </summary>
|
|
||||||
public IMAP_Session Session
|
|
||||||
{
|
|
||||||
get { return m_pSession; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt den Benutzernamen zurück
|
|
||||||
/// </summary>
|
|
||||||
public string UserName
|
|
||||||
{
|
|
||||||
get { return m_UserName; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt das Passwort zurück
|
|
||||||
/// </summary>
|
|
||||||
public string PasswData
|
|
||||||
{
|
|
||||||
get { return m_PasswData; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt die AuthData zurück
|
|
||||||
/// </summary>
|
|
||||||
public string AuthData
|
|
||||||
{
|
|
||||||
get { return m_Data; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt den Authtyp zurück
|
|
||||||
/// </summary>
|
|
||||||
public AuthType AuthType
|
|
||||||
{
|
|
||||||
get { return m_AuthType; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// ist der Benutzer erlaubt
|
|
||||||
/// </summary>
|
|
||||||
public bool Validated
|
|
||||||
{
|
|
||||||
get { return m_Validated; }
|
|
||||||
set { m_Validated = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
using MailServer.Settings;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Stellt daten für das AuthUser Event zur verfügung
|
|
||||||
/// </summary>
|
|
||||||
public class AuthUser_EventArgs
|
|
||||||
{
|
|
||||||
private IMAP_Session m_pSession = null;
|
|
||||||
private string m_UserName = "";
|
|
||||||
private string m_PasswData = "";
|
|
||||||
private string m_Data = "";
|
|
||||||
private AuthType m_AuthType;
|
|
||||||
private bool m_Validated = false;
|
|
||||||
/// <summary>
|
|
||||||
/// AuthUser_EventArgs Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="userName">Benutzername</param>
|
|
||||||
/// <param name="passwData">Passwort</param>
|
|
||||||
/// <param name="data">Hängt vom Authtyp ab</param>
|
|
||||||
/// <param name="authType">Authtyp</param>
|
|
||||||
public AuthUser_EventArgs(IMAP_Session session, string userName, string passwData, string data, AuthType authType)
|
|
||||||
{
|
|
||||||
m_pSession = session;
|
|
||||||
m_UserName = userName;
|
|
||||||
m_PasswData = passwData;
|
|
||||||
m_Data = data;
|
|
||||||
m_AuthType = authType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
111
MailServer/IMAP/Server/AuthUser_EventArgs/AuthUser_EventArgs.cs
Normal file
111
MailServer/IMAP/Server/AuthUser_EventArgs/AuthUser_EventArgs.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data for the AuthUser event for IMAP_Server.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthUser_EventArgs
|
||||||
|
{
|
||||||
|
private IMAP_Session m_pSession = null;
|
||||||
|
private string m_UserName = "";
|
||||||
|
private string m_PasswData = "";
|
||||||
|
private string m_Data = "";
|
||||||
|
private AuthType m_AuthType;
|
||||||
|
private bool m_Validated = false;
|
||||||
|
private string m_ReturnData = "";
|
||||||
|
private string m_ErrorText = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="userName">Username.</param>
|
||||||
|
/// <param name="passwData">Password data.</param>
|
||||||
|
/// <param name="data">Authentication specific data(as tag).</param>
|
||||||
|
/// <param name="authType">Authentication type.</param>
|
||||||
|
public AuthUser_EventArgs(IMAP_Session session, string userName, string passwData, string data, AuthType authType)
|
||||||
|
{
|
||||||
|
m_pSession = session;
|
||||||
|
m_UserName = userName;
|
||||||
|
m_PasswData = passwData;
|
||||||
|
m_Data = data;
|
||||||
|
m_AuthType = authType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets reference to smtp session.
|
||||||
|
/// </summary>
|
||||||
|
public IMAP_Session Session
|
||||||
|
{
|
||||||
|
get { return m_pSession; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User name.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName
|
||||||
|
{
|
||||||
|
get { return m_UserName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Password data. eg. for AUTH=PLAIN it's password and for AUTH=APOP it's md5HexHash.
|
||||||
|
/// </summary>
|
||||||
|
public string PasswData
|
||||||
|
{
|
||||||
|
get { return m_PasswData; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authentication specific data(as tag).
|
||||||
|
/// </summary>
|
||||||
|
public string AuthData
|
||||||
|
{
|
||||||
|
get { return m_Data; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authentication type.
|
||||||
|
/// </summary>
|
||||||
|
public AuthType AuthType
|
||||||
|
{
|
||||||
|
get { return m_AuthType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if user is valid.
|
||||||
|
/// </summary>
|
||||||
|
public bool Validated
|
||||||
|
{
|
||||||
|
get { return m_Validated; }
|
||||||
|
|
||||||
|
set { m_Validated = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets authentication data what must be returned for connected client.
|
||||||
|
/// </summary>
|
||||||
|
public string ReturnData
|
||||||
|
{
|
||||||
|
get { return m_ReturnData; }
|
||||||
|
|
||||||
|
set { m_ReturnData = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets error text returned to connected client.
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorText
|
||||||
|
{
|
||||||
|
get { return m_ErrorText; }
|
||||||
|
|
||||||
|
set { m_ErrorText = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
115
MailServer/IMAP/Server/IMAP_Message/IMAP_Message.cs
Normal file
115
MailServer/IMAP/Server/IMAP_Message/IMAP_Message.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IMAP message info.
|
||||||
|
/// </summary>
|
||||||
|
public class IMAP_Message
|
||||||
|
{
|
||||||
|
private IMAP_MessageCollection m_pOwner = null;
|
||||||
|
private string m_ID = "";
|
||||||
|
private long m_UID = 0;
|
||||||
|
private DateTime m_InternalDate = DateTime.Now;
|
||||||
|
private long m_Size = 0;
|
||||||
|
private IMAP_MessageFlags m_Flags = IMAP_MessageFlags.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="onwer">Owner collection.</param>
|
||||||
|
/// <param name="id">Message ID.</param>
|
||||||
|
/// <param name="uid">Message IMAP UID value.</param>
|
||||||
|
/// <param name="internalDate">Message store date.</param>
|
||||||
|
/// <param name="size">Message size in bytes.</param>
|
||||||
|
/// <param name="flags">Message flags.</param>
|
||||||
|
internal IMAP_Message(IMAP_MessageCollection onwer, string id, long uid, DateTime internalDate, long size, IMAP_MessageFlags flags)
|
||||||
|
{
|
||||||
|
m_pOwner = onwer;
|
||||||
|
m_ID = id;
|
||||||
|
m_UID = uid;
|
||||||
|
m_InternalDate = internalDate;
|
||||||
|
m_Size = size;
|
||||||
|
m_Flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method SetFlags
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets message flags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">Message flags.</param>
|
||||||
|
internal void SetFlags(IMAP_MessageFlags flags)
|
||||||
|
{
|
||||||
|
m_Flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message 1 based sequence number in the collection. This property is slow, use with care, never use in big for loops !
|
||||||
|
/// </summary>
|
||||||
|
public int SequenceNo
|
||||||
|
{
|
||||||
|
get { return m_pOwner.IndexOf(this) + 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message ID.
|
||||||
|
/// </summary>
|
||||||
|
public string ID
|
||||||
|
{
|
||||||
|
get { return m_ID; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message IMAP UID value.
|
||||||
|
/// </summary>
|
||||||
|
public long UID
|
||||||
|
{
|
||||||
|
get { return m_UID; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message store date.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime InternalDate
|
||||||
|
{
|
||||||
|
get { return m_InternalDate; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get { return m_Size; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message flags.
|
||||||
|
/// </summary>
|
||||||
|
public IMAP_MessageFlags Flags
|
||||||
|
{
|
||||||
|
get { return m_Flags; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets message flags string. For example: "\DELETES \SEEN".
|
||||||
|
/// </summary>
|
||||||
|
public string FlagsString
|
||||||
|
{
|
||||||
|
get { return IMAP_Utils.MessageFlagsToString(m_Flags); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,162 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IMAP messages info collection.
|
||||||
|
/// </summary>
|
||||||
|
public class IMAP_MessageCollection : IEnumerable
|
||||||
|
{
|
||||||
|
private SortedList<long, IMAP_Message> m_pMessages = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public IMAP_MessageCollection()
|
||||||
|
{
|
||||||
|
m_pMessages = new SortedList<long, IMAP_Message>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Add
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds new message info to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Message ID.</param>
|
||||||
|
/// <param name="uid">Message IMAP UID value.</param>
|
||||||
|
/// <param name="internalDate">Message store date.</param>
|
||||||
|
/// <param name="size">Message size in bytes.</param>
|
||||||
|
/// <param name="flags">Message flags.</param>
|
||||||
|
/// <returns>Returns added IMAp message info.</returns>
|
||||||
|
public IMAP_Message Add(string id, long uid, DateTime internalDate, long size, IMAP_MessageFlags flags)
|
||||||
|
{
|
||||||
|
if (uid < 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Message UID value must be > 0 !");
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAP_Message message = new IMAP_Message(this, id, uid, internalDate, size, flags);
|
||||||
|
m_pMessages.Add(uid, message);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Remove
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified IMAP message from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">IMAP message to remove.</param>
|
||||||
|
public void Remove(IMAP_Message message)
|
||||||
|
{
|
||||||
|
m_pMessages.Remove(message.UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ContainsUID
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets collection contains specified message with specified UID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid">Message UID.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool ContainsUID(long uid)
|
||||||
|
{
|
||||||
|
return m_pMessages.ContainsKey(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method IndexOf
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets index of specified message in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message indesx to get.</param>
|
||||||
|
/// <returns>Returns index of specified message in the collection or -1 if message doesn't belong to this collection.</returns>
|
||||||
|
public int IndexOf(IMAP_Message message)
|
||||||
|
{
|
||||||
|
return m_pMessages.IndexOfKey(message.UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Clear
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all messages from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_pMessages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method GetWithFlags
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets messages which has specified flags set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">Flags to match.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IMAP_Message[] GetWithFlags(IMAP_MessageFlags flags)
|
||||||
|
{
|
||||||
|
List<IMAP_Message> retVal = new List<IMAP_Message>();
|
||||||
|
foreach (IMAP_Message message in m_pMessages.Values)
|
||||||
|
{
|
||||||
|
if ((message.Flags & flags) != 0)
|
||||||
|
{
|
||||||
|
retVal.Add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retVal.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region Interface IEnumerator
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return m_pMessages.Values.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of messages in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get { return m_pMessages.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a IMAP_Message object in the collection by index number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">An Int32 value that specifies the position of the IMAP_Message object in the IMAP_MessageCollection collection.</param>
|
||||||
|
public IMAP_Message this[int index]
|
||||||
|
{
|
||||||
|
get { return m_pMessages.Values[index]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
48
MailServer/IMAP/Server/IMAP_MessageFlags.cs
Normal file
48
MailServer/IMAP/Server/IMAP_MessageFlags.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IMAP message flags.
|
||||||
|
/// </summary>
|
||||||
|
public enum IMAP_MessageFlags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No flags defined.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message has been read.
|
||||||
|
/// </summary>
|
||||||
|
Seen = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message has been answered.
|
||||||
|
/// </summary>
|
||||||
|
Answered = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message is "flagged" for urgent/special attention.
|
||||||
|
/// </summary>
|
||||||
|
Flagged = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message is "deleted" for removal by later EXPUNGE.
|
||||||
|
/// </summary>
|
||||||
|
Deleted = 16,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message has not completed composition.
|
||||||
|
/// </summary>
|
||||||
|
Draft = 32,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message is "recently" arrived in this mailbox.
|
||||||
|
/// </summary>
|
||||||
|
Recent = 64,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds IMAP selected folder info.
|
||||||
|
/// </summary>
|
||||||
|
public class IMAP_SelectedFolder
|
||||||
|
{
|
||||||
|
private string m_Folder = "";
|
||||||
|
private long m_FolderUID = 0;
|
||||||
|
private bool m_ReadOnly = false;
|
||||||
|
private IMAP_MessageCollection m_pMessages = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder">Folder name.</param>
|
||||||
|
internal IMAP_SelectedFolder(string folder)
|
||||||
|
{
|
||||||
|
m_Folder = folder;
|
||||||
|
m_pMessages = new IMAP_MessageCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Update
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates current folder messages info with new messages info.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folder"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string Update(IMAP_SelectedFolder folder)
|
||||||
|
{
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
long maxUID = this.MessageUidNext - 1;
|
||||||
|
|
||||||
|
long countExists = this.Messages.Count;
|
||||||
|
long countRecent = this.RecentCount;
|
||||||
|
|
||||||
|
// Add new messages
|
||||||
|
for (int i = folder.Messages.Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
IMAP_Message message = folder.Messages[i];
|
||||||
|
// New message
|
||||||
|
if (message.UID > maxUID)
|
||||||
|
{
|
||||||
|
m_pMessages.Add(
|
||||||
|
message.ID,
|
||||||
|
message.UID,
|
||||||
|
message.InternalDate,
|
||||||
|
message.Size,
|
||||||
|
message.Flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// New messages ended
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deleted messages
|
||||||
|
for (int i = 0; i < m_pMessages.Count - 1; i++)
|
||||||
|
{
|
||||||
|
IMAP_Message message = m_pMessages[i];
|
||||||
|
|
||||||
|
if (!folder.m_pMessages.ContainsUID(message.UID))
|
||||||
|
{
|
||||||
|
retVal.Append("* " + message.SequenceNo + " EXPUNGE\r\n");
|
||||||
|
m_pMessages.Remove(message);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countExists != this.Messages.Count)
|
||||||
|
{
|
||||||
|
retVal.Append("* " + this.Messages.Count + " EXISTS\r\n");
|
||||||
|
}
|
||||||
|
if (countRecent != this.RecentCount)
|
||||||
|
{
|
||||||
|
retVal.Append("* " + this.RecentCount + " RECENT\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets selected folder name.
|
||||||
|
/// </summary>
|
||||||
|
public string Folder
|
||||||
|
{
|
||||||
|
get { return m_Folder; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets folder UID(UIDVADILITY) value.
|
||||||
|
/// </summary>
|
||||||
|
public long FolderUID
|
||||||
|
{
|
||||||
|
get { return m_FolderUID; }
|
||||||
|
|
||||||
|
set { m_FolderUID = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if folder is read only.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReadOnly
|
||||||
|
{
|
||||||
|
get { return m_ReadOnly; }
|
||||||
|
|
||||||
|
set { m_ReadOnly = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets selected folder messages info.
|
||||||
|
/// </summary>
|
||||||
|
public IMAP_MessageCollection Messages
|
||||||
|
{
|
||||||
|
get { return m_pMessages; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of messages with \UNSEEN flags in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int UnSeenCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (IMAP_Message message in m_pMessages)
|
||||||
|
{
|
||||||
|
if ((message.Flags & IMAP_MessageFlags.Seen) == 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of messages with \RECENT flags in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int RecentCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (IMAP_Message message in m_pMessages)
|
||||||
|
{
|
||||||
|
if ((message.Flags & IMAP_MessageFlags.Recent) != 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of messages with \DELETED flags in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int DeletedCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (IMAP_Message message in m_pMessages)
|
||||||
|
{
|
||||||
|
if ((message.Flags & IMAP_MessageFlags.Deleted) != 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets first message index in the collection which has not \SEEN flag set.
|
||||||
|
/// </summary>
|
||||||
|
public int FirstUnseen
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int index = 1;
|
||||||
|
foreach (IMAP_Message message in m_pMessages)
|
||||||
|
{
|
||||||
|
if ((message.Flags & IMAP_MessageFlags.Seen) == 0)
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets next new message predicted UID.
|
||||||
|
/// </summary>
|
||||||
|
public long MessageUidNext
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_pMessages.Count > 0)
|
||||||
|
{
|
||||||
|
return m_pMessages[m_pMessages.Count - 1].UID + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
4846
MailServer/IMAP/Server/IMAP_Session/IMAP_Session.cs
Normal file
4846
MailServer/IMAP/Server/IMAP_Session/IMAP_Session.cs
Normal file
File diff suppressed because it is too large
Load Diff
38
MailServer/IMAP/Server/Server.Designer.cs
generated
38
MailServer/IMAP/Server/Server.Designer.cs
generated
@ -1,38 +0,0 @@
|
|||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Erforderliche Designervariable.
|
|
||||||
/// </summary>
|
|
||||||
private System.ComponentModel.IContainer components = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verwendete Ressourcen bereinigen.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing && (components != null))
|
|
||||||
{
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Vom Komponenten-Designer generierter Code
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Erforderliche Methode für die Designerunterstützung.
|
|
||||||
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
components = new System.ComponentModel.Container();
|
|
||||||
this.ServiceName = "IMAP Server";
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Representiert die Methode die das AuthUser Event vom SMTP_Server handelt
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">Die Quelle des Events</param>
|
|
||||||
/// <param name="e">Ein AuthUser_EventArgs welches das event beinhaltet</param>
|
|
||||||
public delegate void AuthUserEventHandler(object sender, AuthUser_EventArgs e);
|
|
||||||
public delegate void FolderEventHandler(object sender, Mailbox_EventArgs e);
|
|
||||||
public delegate void FoldersEventHandler(object sender, IMAP_Folders e);
|
|
||||||
public delegate void MessagesEventHandler(object sender, IMAP_Messages e);
|
|
||||||
public delegate void MessageEventHandler(object sender, Message_EventArgs e);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,267 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event ValidateIpAdress
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="enpoint">Verbundener Host endpoint</param>
|
|
||||||
/// <returns>Gibt true zurück wenn die Verbindung erlaubt ist</returns>
|
|
||||||
internal bool OnValidate_IpAddress(EndPoint enpoint)
|
|
||||||
{
|
|
||||||
ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(enpoint);
|
|
||||||
if (this.ValidateIPAddress != null)
|
|
||||||
{
|
|
||||||
this.ValidateIPAddress(this, oArg);
|
|
||||||
}
|
|
||||||
return oArg.Validated;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event AuthUser
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">referenz zur Aktuellen IMAP Session</param>
|
|
||||||
/// <param name="userName">Benutzername</param>
|
|
||||||
/// <param name="passwordData">Passwort</param>
|
|
||||||
/// <param name="data">Hängt vom AuthTyp ab</param>
|
|
||||||
/// <param name="authType">Authtyp</param>
|
|
||||||
/// <returns>Gibt True zurück, wenn user erlaubt</returns>
|
|
||||||
internal bool OnAuthUser(IMAP_Session session, string userName, string passwordData, string data, AuthType authType)
|
|
||||||
{
|
|
||||||
AuthUser_EventArgs oArgs = new AuthUser_EventArgs(session, userName, passwordData, data, authType);
|
|
||||||
if (this.AuthUser != null)
|
|
||||||
{
|
|
||||||
this.AuthUser(this, oArgs);
|
|
||||||
}
|
|
||||||
return oArgs.Validated;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event SuscribeMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Name der Mailbox welche aboniert werden soll</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnSubscribeMailbox(IMAP_Session session, string mailbox)
|
|
||||||
{
|
|
||||||
if (this.SubscribeFolder != null)
|
|
||||||
{
|
|
||||||
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
|
||||||
this.SubscribeFolder(session, eArgs);
|
|
||||||
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event UnSuscribeMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Name der Mailbox welche abbestellt werden soll</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnUnSubscribeMailbox(IMAP_Session session, string mailbox)
|
|
||||||
{
|
|
||||||
if (this.UnSubscribeFolder != null)
|
|
||||||
{
|
|
||||||
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
|
||||||
this.UnSubscribeFolder(session, eArgs);
|
|
||||||
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event GetSuscribedMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="referenceName">Mailbox referenz</param>
|
|
||||||
/// <param name="mailBox">Mailbox name oder pattern</param>
|
|
||||||
/// <returns>gibt die Mailbox zurück</returns>
|
|
||||||
internal IMAP_Folders OnGetSubscribedMailboxes(IMAP_Session session, string referenceName, string mailBox)
|
|
||||||
{
|
|
||||||
IMAP_Folders retVal = new IMAP_Folders(referenceName, mailBox);
|
|
||||||
if (this.GetSubscribedFolders != null)
|
|
||||||
{
|
|
||||||
this.GetSubscribedFolders(session, retVal);
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event GetMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="referenceName">Mailbox referenz</param>
|
|
||||||
/// <param name="mailBox">Mailbox name oder pattern</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal IMAP_Folders OnGetMailboxes(IMAP_Session session, string referenceName, string mailBox)
|
|
||||||
{
|
|
||||||
IMAP_Folders retVal = new IMAP_Folders(referenceName, mailBox);
|
|
||||||
if (this.GetFolders != null)
|
|
||||||
{
|
|
||||||
this.GetFolders(session, retVal);
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event CreateMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Mailbox name</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnCreateMailbox(IMAP_Session session, string mailbox)
|
|
||||||
{
|
|
||||||
if (this.CreateFolder != null)
|
|
||||||
{
|
|
||||||
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
|
||||||
this.CreateFolder(session, eArgs);
|
|
||||||
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event DeleteMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referent zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Mailbox name</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnDeleteMailbox(IMAP_Session session, string mailbox)
|
|
||||||
{
|
|
||||||
if (this.DeleteFolder != null)
|
|
||||||
{
|
|
||||||
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
|
||||||
this.DeleteFolder(session, eArgs);
|
|
||||||
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt Event RenameMailbox
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Mailbox name</param>
|
|
||||||
/// <param name="newMailboxName">neuer Mailbox name</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnRenameMailbox(IMAP_Session session, string mailbox, string newMailboxName)
|
|
||||||
{
|
|
||||||
if (this.RenameFolder != null)
|
|
||||||
{
|
|
||||||
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox, newMailboxName);
|
|
||||||
this.RenameFolder(session, eArgs);
|
|
||||||
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event MailboxInfo
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="mailbox">Mailbox name</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal IMAP_Messages OnGetMessagesInfo(IMAP_Session session, string mailbox)
|
|
||||||
{
|
|
||||||
IMAP_Messages messages = new IMAP_Messages(mailbox);
|
|
||||||
if (this.GetMessagesInfo != null)
|
|
||||||
{
|
|
||||||
this.GetMessagesInfo(session, messages);
|
|
||||||
}
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeugt event GetMessage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="msg">Nachricht</param>
|
|
||||||
/// <param name="headersOnly">nur Kopfzeilen</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal Message_EventArgs OnGetMessage(IMAP_Session session, IMAP_Message msg, bool headersOnly)
|
|
||||||
{
|
|
||||||
Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg, headersOnly);
|
|
||||||
if (this.GetMessage != null)
|
|
||||||
{
|
|
||||||
this.GetMessage(session, eArgs);
|
|
||||||
}
|
|
||||||
return eArgs;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeuge Event DeleteMessage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="message">Nachricht</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnDeleteMessage(IMAP_Session session, IMAP_Message message)
|
|
||||||
{
|
|
||||||
Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, message);
|
|
||||||
if (this.DeleteMessage != null)
|
|
||||||
{
|
|
||||||
this.DeleteMessage(session, eArgs);
|
|
||||||
}
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeuge Event CopyMesage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="msg">Nachricht</param>
|
|
||||||
/// <param name="location">Neue Position</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnCopyMessage(IMAP_Session session, IMAP_Message msg, string location)
|
|
||||||
{
|
|
||||||
Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg, location);
|
|
||||||
if (this.CopyMessage != null)
|
|
||||||
{
|
|
||||||
this.CopyMessage(session, eArgs);
|
|
||||||
}
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeuge Event StoreMessage
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="folder">Ordner</param>
|
|
||||||
/// <param name="msg">Nachricht</param>
|
|
||||||
/// <param name="messageData">Nachrichteninhalt</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnStoreMessage(IMAP_Session session, string folder, IMAP_Message msg, byte[] messageData)
|
|
||||||
{
|
|
||||||
Message_EventArgs eArgs = new Message_EventArgs(folder, msg);
|
|
||||||
eArgs.MessageData = messageData;
|
|
||||||
if (this.StoreMessage != null)
|
|
||||||
{
|
|
||||||
this.StoreMessage(session, eArgs);
|
|
||||||
}
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeuge Event StoreMessageFlags
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">Referenz zur IMAP Session</param>
|
|
||||||
/// <param name="msg">Nachricht</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal string OnStoreMessageFlags(IMAP_Session session, IMAP_Message msg)
|
|
||||||
{
|
|
||||||
Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg);
|
|
||||||
if (this.StoreMessageFlags != null)
|
|
||||||
{
|
|
||||||
this.StoreMessageFlags(session, eArgs);
|
|
||||||
}
|
|
||||||
return eArgs.ErrorText;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Erzeuge Event SysError
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="x">Ausnahme</param>
|
|
||||||
/// <param name="stackTrace">Stacktrace</param>
|
|
||||||
internal void OnSysError(Exception x, StackTrace stackTrace)
|
|
||||||
{
|
|
||||||
if (this.SysError != null)
|
|
||||||
{
|
|
||||||
this.SysError(this, new Error_EventArgs(x, stackTrace));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn ein Computer zum IMAP Server verbindet
|
|
||||||
/// </summary>
|
|
||||||
public event ValidateIPHandler ValidateIPAddress = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn ein User sich zu authentifizieren versucht
|
|
||||||
/// </summary>
|
|
||||||
public event AuthUserEventHandler AuthUser = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht einen Ordner zu beziehen
|
|
||||||
/// </summary>
|
|
||||||
public event FolderEventHandler SubscribeFolder = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht einen Ordner abzubestellen
|
|
||||||
/// </summary>
|
|
||||||
public event FolderEventHandler UnSubscribeFolder = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht alle Ordner zu lesen
|
|
||||||
/// </summary>
|
|
||||||
public event FoldersEventHandler GetFolders = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht die bestellen Ordner zu lesen
|
|
||||||
/// </summary>
|
|
||||||
public event FoldersEventHandler GetSubscribedFolders = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht einen Ordner zu erstellen
|
|
||||||
/// </summary>
|
|
||||||
public event FolderEventHandler CreateFolder = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht einen Ordner zu löschen
|
|
||||||
/// </summary>
|
|
||||||
public event FolderEventHandler DeleteFolder = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht einen Ordner umzubenennen
|
|
||||||
/// </summary>
|
|
||||||
public event FolderEventHandler RenameFolder = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server Nachrichten Infos zu Ordner zu lesen
|
|
||||||
/// </summary>
|
|
||||||
public event MessagesEventHandler GetMessagesInfo = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht eine Nachricht zu löschen
|
|
||||||
/// </summary>
|
|
||||||
public event MessageEventHandler DeleteMessage = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht eine Nachricht zu speichern
|
|
||||||
/// </summary>
|
|
||||||
public event MessageEventHandler StoreMessage = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht Nachrichten Flags zu Setzen
|
|
||||||
/// </summary>
|
|
||||||
public event MessageEventHandler StoreMessageFlags = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht eine Nachricht zu kopieren
|
|
||||||
/// </summary>
|
|
||||||
public event MessageEventHandler CopyMessage = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server versucht eine Nachricht zu lesen
|
|
||||||
/// </summary>
|
|
||||||
public event MessageEventHandler GetMessage = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein wenn der Server einen Fehler hat
|
|
||||||
/// </summary>
|
|
||||||
public event ErrorEventHandler SysError = null;
|
|
||||||
/// <summary>
|
|
||||||
/// Tritt ein Wenn die Sitzung zu ende ist und der Server einen Log hat
|
|
||||||
/// </summary>
|
|
||||||
public event LogEventHandler SessionLog = null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// IP Adresse an der der Server hört
|
|
||||||
/// </summary>
|
|
||||||
[Description("IP Address to Listen IMAP requests"), DefaultValue("ALL")]
|
|
||||||
public string IpAddress
|
|
||||||
{
|
|
||||||
get { return m_IPAddress; }
|
|
||||||
set { m_IPAddress = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Port an dem der Server hört
|
|
||||||
/// </summary>
|
|
||||||
[Description("Port to use for IMAP"),DefaultValue(143)]
|
|
||||||
public int Port
|
|
||||||
{
|
|
||||||
get { return m_port; }
|
|
||||||
set { m_port = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Maximale Sessions
|
|
||||||
/// </summary>
|
|
||||||
[Description("Maximum Allowed threads"),DefaultValue(50)]
|
|
||||||
public int Threads
|
|
||||||
{
|
|
||||||
get { return m_MaxThreads; }
|
|
||||||
set { m_MaxThreads = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Run and Stops the Server
|
|
||||||
/// </summary>
|
|
||||||
[Description("Use this property to run and stop SMTP Server"),DefaultValue(false)]
|
|
||||||
public bool Enabled
|
|
||||||
{
|
|
||||||
get { return m_enabled; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value != m_enabled)
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
m_enabled = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Logging
|
|
||||||
/// </summary>
|
|
||||||
public bool LogCommands
|
|
||||||
{
|
|
||||||
get { return m_LogCmds; }
|
|
||||||
set { m_LogCmds = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Session ilde timeout [ms]
|
|
||||||
/// </summary>
|
|
||||||
public int SessionIdleTimeOut
|
|
||||||
{
|
|
||||||
get { return m_SessionIdleTimeOut; }
|
|
||||||
set { m_SessionIdleTimeOut = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Command ilde timeout [ms]
|
|
||||||
/// </summary>
|
|
||||||
public int CommandIdleTimeOut
|
|
||||||
{
|
|
||||||
get { return m_CommandIdleTimeOut; }
|
|
||||||
set { m_CommandIdleTimeOut = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Max message Size [b]
|
|
||||||
/// </summary>
|
|
||||||
public int MaxMessageSize
|
|
||||||
{
|
|
||||||
get { return m_MaxMessageSize; }
|
|
||||||
set { m_MaxMessageSize = value; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Max bad commands bevore kick
|
|
||||||
/// </summary>
|
|
||||||
public int MaxBadCommands
|
|
||||||
{
|
|
||||||
get { return m_MaxBadCommands; }
|
|
||||||
set { m_MaxBadCommands = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fügt eine Session hinzu
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sessionID">Session ID</param>
|
|
||||||
/// <param name="session">Session Object</param>
|
|
||||||
/// <param name="logWriter">Logwriter</param>
|
|
||||||
internal void AddSession(string sessionID, IMAP_Session session, _LogWriter logWriter)
|
|
||||||
{
|
|
||||||
lock (m_SessionTable)
|
|
||||||
{
|
|
||||||
m_SessionTable.Add(sessionID, session);
|
|
||||||
|
|
||||||
if (m_LogCmds)
|
|
||||||
{
|
|
||||||
logWriter.AddEntry("//----- Sys: 'Session:'" + sessionID + " added " + DateTime.Now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Löscht eine Session
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sessionID">Session ID</param>
|
|
||||||
/// <param name="logWriter">Logwriter</param>
|
|
||||||
internal void RemoveSession(string sessionID, _LogWriter logWriter)
|
|
||||||
{
|
|
||||||
lock (m_SessionTable)
|
|
||||||
{
|
|
||||||
if (!m_SessionTable.Contains(sessionID))
|
|
||||||
{
|
|
||||||
OnSysError(new Exception("Session '" + sessionID + "' doesn't exist."), new System.Diagnostics.StackTrace());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_SessionTable.Remove(sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_LogCmds)
|
|
||||||
{
|
|
||||||
logWriter.AddEntry("//----- Sys: 'Session:'" + sessionID + " removed " + DateTime.Now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Threading;
|
|
||||||
using System;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
partial class Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Startet den IMAP Server
|
|
||||||
/// </summary>
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// if(!m_enabled && !this.DesignMode){
|
|
||||||
m_SessionTable = new Hashtable();
|
|
||||||
|
|
||||||
Thread startServer = new Thread(new ThreadStart(Run));
|
|
||||||
startServer.Start();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
OnSysError(x, new System.Diagnostics.StackTrace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Stoppt den IMAP Server
|
|
||||||
/// </summary>
|
|
||||||
private void Stop()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (IMAP_Listener != null)
|
|
||||||
{
|
|
||||||
IMAP_Listener.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
OnSysError(x, new System.Diagnostics.StackTrace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Server Hauptschleife
|
|
||||||
/// </summary>
|
|
||||||
private void Run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// check which ip's to listen (all or assigned)
|
|
||||||
if (m_IPAddress.ToLower().IndexOf("all") > -1)
|
|
||||||
{
|
|
||||||
IMAP_Listener = new TcpListener(IPAddress.Any, m_port);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IMAP_Listener = new TcpListener(IPAddress.Parse(m_IPAddress), m_port);
|
|
||||||
}
|
|
||||||
// Start listening
|
|
||||||
IMAP_Listener.Start();
|
|
||||||
|
|
||||||
|
|
||||||
//-------- Main Server message loop --------------------------------//
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Check if maximum allowed thread count isn't exceeded
|
|
||||||
if (m_SessionTable.Count <= m_MaxThreads)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Thread is sleeping, until a client connects
|
|
||||||
Socket clientSocket = IMAP_Listener.AcceptSocket();
|
|
||||||
|
|
||||||
string sessionID = clientSocket.GetHashCode().ToString();
|
|
||||||
|
|
||||||
//****
|
|
||||||
_LogWriter logWriter = new _LogWriter(this.SessionLog);
|
|
||||||
IMAP_Session session = new IMAP_Session(clientSocket, this, sessionID, logWriter);
|
|
||||||
|
|
||||||
Thread clientThread = new Thread(new ThreadStart(session.StartProcessing));
|
|
||||||
|
|
||||||
// Add session to session list
|
|
||||||
AddSession(sessionID, session, logWriter);
|
|
||||||
|
|
||||||
// Start proccessing
|
|
||||||
clientThread.Start();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ThreadInterruptedException e)
|
|
||||||
{
|
|
||||||
string dummy = e.Message; // Neede for to remove compile warning
|
|
||||||
Thread.CurrentThread.Abort();
|
|
||||||
}
|
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
if (x.Message != "A blocking operation was interrupted by a call to WSACancelBlockingCall")
|
|
||||||
{
|
|
||||||
OnSysError(x, new System.Diagnostics.StackTrace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace MailServer.IMAP.Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// IMAP Server Komponente
|
|
||||||
/// </summary>
|
|
||||||
public partial class Server : ServiceBase
|
|
||||||
{
|
|
||||||
private TcpListener IMAP_Listener = null;
|
|
||||||
private Hashtable m_SessionTable = null;
|
|
||||||
|
|
||||||
private string m_IPAddress = "ALL"; // Holds IP Address, which to listen incoming calls.
|
|
||||||
private int m_port = 143; // Holds port number, which to listen incoming calls.
|
|
||||||
private int m_MaxThreads = 20; // Holds maximum allowed Worker Threads.
|
|
||||||
private bool m_enabled = false; // If true listens incoming calls.
|
|
||||||
private bool m_LogCmds = false; // If true, writes commands to log file.
|
|
||||||
private int m_SessionIdleTimeOut = 80000; // Holds session idle timeout.
|
|
||||||
private int m_CommandIdleTimeOut = 6000000; // Holds command ilde timeout.
|
|
||||||
private int m_MaxMessageSize = 1000000; // Hold maximum message size.
|
|
||||||
private int m_MaxBadCommands = 8; // Holds maximum bad commands allowed to session.
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Server laden und anhängen
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="container">anhängen an</param>
|
|
||||||
public Server(System.ComponentModel.IContainer container)
|
|
||||||
{
|
|
||||||
container.Add(this);
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Server laden
|
|
||||||
/// </summary>
|
|
||||||
public Server()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Starte Server über Dienst
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
protected override void OnStart(string[] args)
|
|
||||||
{
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Stoppe Server über Dienst
|
|
||||||
/// </summary>
|
|
||||||
protected override void OnStop()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
62
MailServer/IMAP/Server/Server/Server.Delegates.cs
Normal file
62
MailServer/IMAP/Server/Server/Server.Delegates.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the method that will handle the AuthUser event for SMTP_Server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The source of the event. </param>
|
||||||
|
/// <param name="e">A AuthUser_EventArgs that contains the event data.</param>
|
||||||
|
public delegate void AuthUserEventHandler(object sender, AuthUser_EventArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void FolderEventHandler(object sender, Mailbox_EventArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void FoldersEventHandler(object sender, IMAP_Folders e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void MessagesEventHandler(object sender, IMAP_eArgs_GetMessagesInfo e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void MessagesItemsEventHandler(object sender, IMAP_eArgs_MessageItems e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void MessageEventHandler(object sender, Message_EventArgs e);
|
||||||
|
/*/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void SearchEventHandler(object sender,IMAP_eArgs_Search e);*/
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void SharedRootFoldersEventHandler(object sender, SharedRootFolders_EventArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void GetFolderACLEventHandler(object sender, IMAP_GETACL_eArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void DeleteFolderACLEventHandler(object sender, IMAP_DELETEACL_eArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void SetFolderACLEventHandler(object sender, IMAP_SETACL_eArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void GetUserACLEventHandler(object sender, IMAP_GetUserACL_eArgs e);
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void GetUserQuotaHandler(object sender, IMAP_eArgs_GetQuota e);
|
||||||
|
}
|
341
MailServer/IMAP/Server/Server/Server.Events.cs
Normal file
341
MailServer/IMAP/Server/Server/Server.Events.cs
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event ValidateIP event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localEndPoint">Server IP.</param>
|
||||||
|
/// <param name="remoteEndPoint">Connected client IP.</param>
|
||||||
|
/// <returns>Returns true if connection allowed.</returns>
|
||||||
|
internal bool OnValidate_IpAddress(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
|
||||||
|
{
|
||||||
|
ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(localEndPoint, remoteEndPoint);
|
||||||
|
if (this.ValidateIPAddress != null)
|
||||||
|
{
|
||||||
|
this.ValidateIPAddress(this, oArg);
|
||||||
|
}
|
||||||
|
return oArg.Validated;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event AuthUser.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to current IMAP session.</param>
|
||||||
|
/// <param name="userName">User name.</param>
|
||||||
|
/// <param name="passwordData">Password compare data,it depends of authentication type.</param>
|
||||||
|
/// <param name="data">For md5 eg. md5 calculation hash.It depends of authentication type.</param>
|
||||||
|
/// <param name="authType">Authentication type.</param>
|
||||||
|
/// <returns>Returns true if user is authenticated ok.</returns>
|
||||||
|
internal AuthUser_EventArgs OnAuthUser(IMAP_Session session, string userName, string passwordData, string data, AuthType authType)
|
||||||
|
{
|
||||||
|
AuthUser_EventArgs oArgs = new AuthUser_EventArgs(session, userName, passwordData, data, authType);
|
||||||
|
if (this.AuthUser != null)
|
||||||
|
{
|
||||||
|
this.AuthUser(this, oArgs);
|
||||||
|
}
|
||||||
|
return oArgs;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'SubscribeMailbox'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="mailbox">Mailbox which to subscribe.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnSubscribeMailbox(IMAP_Session session, string mailbox)
|
||||||
|
{
|
||||||
|
if (this.SubscribeFolder != null)
|
||||||
|
{
|
||||||
|
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
||||||
|
this.SubscribeFolder(session, eArgs);
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'UnSubscribeMailbox'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="mailbox">Mailbox which to unsubscribe.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnUnSubscribeMailbox(IMAP_Session session, string mailbox)
|
||||||
|
{
|
||||||
|
if (this.UnSubscribeFolder != null)
|
||||||
|
{
|
||||||
|
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
||||||
|
this.UnSubscribeFolder(session, eArgs);
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'GetSubscribedMailboxes'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="referenceName">Mailbox reference.</param>
|
||||||
|
/// <param name="mailBox">Mailbox search pattern or mailbox.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal IMAP_Folders OnGetSubscribedMailboxes(IMAP_Session session, string referenceName, string mailBox)
|
||||||
|
{
|
||||||
|
IMAP_Folders retVal = new IMAP_Folders(session, referenceName, mailBox);
|
||||||
|
if (this.GetSubscribedFolders != null)
|
||||||
|
{
|
||||||
|
this.GetSubscribedFolders(session, retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'GetMailboxes'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="referenceName">Mailbox reference.</param>
|
||||||
|
/// <param name="mailBox">Mailbox search pattern or mailbox.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal IMAP_Folders OnGetMailboxes(IMAP_Session session, string referenceName, string mailBox)
|
||||||
|
{
|
||||||
|
IMAP_Folders retVal = new IMAP_Folders(session, referenceName, mailBox);
|
||||||
|
if (this.GetFolders != null)
|
||||||
|
{
|
||||||
|
this.GetFolders(session, retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'CreateMailbox'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="mailbox">Mailbox to create.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnCreateMailbox(IMAP_Session session, string mailbox)
|
||||||
|
{
|
||||||
|
if (this.CreateFolder != null)
|
||||||
|
{
|
||||||
|
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
||||||
|
this.CreateFolder(session, eArgs);
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'DeleteMailbox'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="mailbox">Mailbox which to delete.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnDeleteMailbox(IMAP_Session session, string mailbox)
|
||||||
|
{
|
||||||
|
if (this.DeleteFolder != null)
|
||||||
|
{
|
||||||
|
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
|
||||||
|
this.DeleteFolder(session, eArgs);
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'RenameMailbox'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="mailbox">Mailbox which to rename.</param>
|
||||||
|
/// <param name="newMailboxName">New mailbox name.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnRenameMailbox(IMAP_Session session, string mailbox, string newMailboxName)
|
||||||
|
{
|
||||||
|
if (this.RenameFolder != null)
|
||||||
|
{
|
||||||
|
Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox, newMailboxName);
|
||||||
|
this.RenameFolder(session, eArgs);
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'GetMessagesInfo'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="folder">Folder which messages info to get.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal IMAP_eArgs_GetMessagesInfo OnGetMessagesInfo(IMAP_Session session, IMAP_SelectedFolder folder)
|
||||||
|
{
|
||||||
|
IMAP_eArgs_GetMessagesInfo eArgs = new IMAP_eArgs_GetMessagesInfo(session, folder);
|
||||||
|
if (this.GetMessagesInfo != null)
|
||||||
|
{
|
||||||
|
this.GetMessagesInfo(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event GetMessageItems.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="messageInfo">Message info what message items to get.</param>
|
||||||
|
/// <param name="messageItems">Specifies message items what must be filled.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal protected IMAP_eArgs_MessageItems OnGetMessageItems(IMAP_Session session, IMAP_Message messageInfo, IMAP_MessageItems_enum messageItems)
|
||||||
|
{
|
||||||
|
IMAP_eArgs_MessageItems eArgs = new IMAP_eArgs_MessageItems(session, messageInfo, messageItems);
|
||||||
|
if (this.GetMessageItems != null)
|
||||||
|
{
|
||||||
|
this.GetMessageItems(session, eArgs);
|
||||||
|
}
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'DeleteMessage'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="message">Message which to delete.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnDeleteMessage(IMAP_Session session, IMAP_Message message)
|
||||||
|
{
|
||||||
|
Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), message);
|
||||||
|
if (this.DeleteMessage != null)
|
||||||
|
{
|
||||||
|
this.DeleteMessage(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'CopyMessage'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="msg">Message which to copy.</param>
|
||||||
|
/// <param name="location">New message location.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnCopyMessage(IMAP_Session session, IMAP_Message msg, string location)
|
||||||
|
{
|
||||||
|
Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), msg, location);
|
||||||
|
if (this.CopyMessage != null)
|
||||||
|
{
|
||||||
|
this.CopyMessage(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'StoreMessage'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="folder">Folder where to store.</param>
|
||||||
|
/// <param name="msg">Message which to store.</param>
|
||||||
|
/// <param name="messageData">Message data which to store.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnStoreMessage(IMAP_Session session, string folder, IMAP_Message msg, byte[] messageData)
|
||||||
|
{
|
||||||
|
Message_EventArgs eArgs = new Message_EventArgs(folder, msg);
|
||||||
|
eArgs.MessageData = messageData;
|
||||||
|
if (this.StoreMessage != null)
|
||||||
|
{
|
||||||
|
this.StoreMessage(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
/*/// <summary>
|
||||||
|
/// Raises event 'Search'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">IMAP session what calls this search.</param>
|
||||||
|
/// <param name="folder">Folder what messages to search.</param>
|
||||||
|
/// <param name="matcher">Matcher what must be used to check if message matches searching criterial.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal IMAP_eArgs_Search OnSearch(IMAP_Session session,string folder,IMAP_SearchMatcher matcher)
|
||||||
|
{
|
||||||
|
IMAP_eArgs_Search eArgs = new IMAP_eArgs_Search(session,folder,matcher);
|
||||||
|
if(this.Search != null){
|
||||||
|
this.Search(session,eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}*/
|
||||||
|
/// <summary>
|
||||||
|
/// Raises event 'StoreMessageFlags'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Reference to IMAP session.</param>
|
||||||
|
/// <param name="msg">Message which flags to store.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal string OnStoreMessageFlags(IMAP_Session session, IMAP_Message msg)
|
||||||
|
{
|
||||||
|
Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), msg);
|
||||||
|
if (this.StoreMessageFlags != null)
|
||||||
|
{
|
||||||
|
this.StoreMessageFlags(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs.ErrorText;
|
||||||
|
}
|
||||||
|
internal SharedRootFolders_EventArgs OnGetSharedRootFolders(IMAP_Session session)
|
||||||
|
{
|
||||||
|
SharedRootFolders_EventArgs eArgs = new SharedRootFolders_EventArgs(session);
|
||||||
|
if (this.GetSharedRootFolders != null)
|
||||||
|
{
|
||||||
|
this.GetSharedRootFolders(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
internal IMAP_GETACL_eArgs OnGetFolderACL(IMAP_Session session, string folderName)
|
||||||
|
{
|
||||||
|
IMAP_GETACL_eArgs eArgs = new IMAP_GETACL_eArgs(session, folderName);
|
||||||
|
if (this.GetFolderACL != null)
|
||||||
|
{
|
||||||
|
this.GetFolderACL(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
internal IMAP_SETACL_eArgs OnSetFolderACL(IMAP_Session session, string folderName, string userName, IMAP_Flags_SetType flagsSetType, IMAP_ACL_Flags aclFlags)
|
||||||
|
{
|
||||||
|
IMAP_SETACL_eArgs eArgs = new IMAP_SETACL_eArgs(session, folderName, userName, flagsSetType, aclFlags);
|
||||||
|
if (this.SetFolderACL != null)
|
||||||
|
{
|
||||||
|
this.SetFolderACL(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
internal IMAP_DELETEACL_eArgs OnDeleteFolderACL(IMAP_Session session, string folderName, string userName)
|
||||||
|
{
|
||||||
|
IMAP_DELETEACL_eArgs eArgs = new IMAP_DELETEACL_eArgs(session, folderName, userName);
|
||||||
|
if (this.DeleteFolderACL != null)
|
||||||
|
{
|
||||||
|
this.DeleteFolderACL(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
internal IMAP_GetUserACL_eArgs OnGetUserACL(IMAP_Session session, string folderName, string userName)
|
||||||
|
{
|
||||||
|
IMAP_GetUserACL_eArgs eArgs = new IMAP_GetUserACL_eArgs(session, folderName, userName);
|
||||||
|
if (this.GetUserACL != null)
|
||||||
|
{
|
||||||
|
this.GetUserACL(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
internal IMAP_eArgs_GetQuota OnGetUserQuota(IMAP_Session session)
|
||||||
|
{
|
||||||
|
IMAP_eArgs_GetQuota eArgs = new IMAP_eArgs_GetQuota(session);
|
||||||
|
if (this.GetUserQuota != null)
|
||||||
|
{
|
||||||
|
this.GetUserQuota(session, eArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
MailServer/IMAP/Server/Server/Server.EventsDeclarations.cs
Normal file
103
MailServer/IMAP/Server/Server/Server.EventsDeclarations.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when new computer connected to IMAP server.
|
||||||
|
/// </summary>
|
||||||
|
public event ValidateIPHandler ValidateIPAddress = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when connected user tryes to authenticate.
|
||||||
|
/// </summary>
|
||||||
|
public event AuthUserEventHandler AuthUser = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to subscribe folder.
|
||||||
|
/// </summary>
|
||||||
|
public event FolderEventHandler SubscribeFolder = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to unsubscribe folder.
|
||||||
|
/// </summary>
|
||||||
|
public event FolderEventHandler UnSubscribeFolder = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests all available folders.
|
||||||
|
/// </summary>
|
||||||
|
public event FoldersEventHandler GetFolders = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests subscribed folders.
|
||||||
|
/// </summary>
|
||||||
|
public event FoldersEventHandler GetSubscribedFolders = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to create folder.
|
||||||
|
/// </summary>
|
||||||
|
public event FolderEventHandler CreateFolder = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to delete folder.
|
||||||
|
/// </summary>
|
||||||
|
public event FolderEventHandler DeleteFolder = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to rename folder.
|
||||||
|
/// </summary>
|
||||||
|
public event FolderEventHandler RenameFolder = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to folder messages info.
|
||||||
|
/// </summary>
|
||||||
|
public event MessagesEventHandler GetMessagesInfo = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to delete message.
|
||||||
|
/// </summary>
|
||||||
|
public event MessageEventHandler DeleteMessage = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to store message.
|
||||||
|
/// </summary>
|
||||||
|
public event MessageEventHandler StoreMessage = null;
|
||||||
|
/*/// <summary>
|
||||||
|
/// Occurs when server requests to search specified folder messages.
|
||||||
|
/// </summary>
|
||||||
|
public event SearchEventHandler Search = null;*/
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to store message flags.
|
||||||
|
/// </summary>
|
||||||
|
public event MessageEventHandler StoreMessageFlags = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to copy message to new location.
|
||||||
|
/// </summary>
|
||||||
|
public event MessageEventHandler CopyMessage = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server requests to get message items.
|
||||||
|
/// </summary>
|
||||||
|
public event MessagesItemsEventHandler GetMessageItems = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP session has finished and session log is available.
|
||||||
|
/// </summary>
|
||||||
|
public event LogEventHandler SessionLog = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests shared root folders info.
|
||||||
|
/// </summary>
|
||||||
|
public event SharedRootFoldersEventHandler GetSharedRootFolders = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests folder ACL.
|
||||||
|
/// </summary>
|
||||||
|
public event GetFolderACLEventHandler GetFolderACL = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests to delete folder ACL.
|
||||||
|
/// </summary>
|
||||||
|
public event DeleteFolderACLEventHandler DeleteFolderACL = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests to set folder ACL.
|
||||||
|
/// </summary>
|
||||||
|
public event SetFolderACLEventHandler SetFolderACL = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests to get user ACL for specified folder.
|
||||||
|
/// </summary>
|
||||||
|
public event GetUserACLEventHandler GetUserACL = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when IMAP server requests to get user quota.
|
||||||
|
/// </summary>
|
||||||
|
public event GetUserQuotaHandler GetUserQuota = null;
|
||||||
|
}
|
||||||
|
}
|
57
MailServer/IMAP/Server/Server/Server.Properties.cs
Normal file
57
MailServer/IMAP/Server/Server/Server.Properties.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets server supported authentication types.
|
||||||
|
/// </summary>
|
||||||
|
public SaslAuthTypes SupportedAuthentications
|
||||||
|
{
|
||||||
|
get { return m_SupportedAuth; }
|
||||||
|
set { m_SupportedAuth = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets server greeting text.
|
||||||
|
/// </summary>
|
||||||
|
public string GreetingText
|
||||||
|
{
|
||||||
|
get { return m_GreetingText; }
|
||||||
|
set { m_GreetingText = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets maximum allowed conncurent connections from 1 IP address. Value 0 means unlimited connections.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxConnectionsPerIP
|
||||||
|
{
|
||||||
|
get { return m_MaxConnectionsPerIP; }
|
||||||
|
set { m_MaxConnectionsPerIP = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum message size.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxMessageSize
|
||||||
|
{
|
||||||
|
get { return m_MaxMessageSize; }
|
||||||
|
set { m_MaxMessageSize = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets active sessions.
|
||||||
|
/// </summary>
|
||||||
|
public new IMAP_Session[] Sessions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
SocketServerSession[] sessions = base.Sessions;
|
||||||
|
IMAP_Session[] imapSessions = new IMAP_Session[sessions.Length];
|
||||||
|
sessions.CopyTo(imapSessions, 0);
|
||||||
|
|
||||||
|
return imapSessions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
MailServer/IMAP/Server/Server/Server.Session.cs
Normal file
57
MailServer/IMAP/Server/Server/Server.Session.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
public partial class Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize and start new session here. Session isn't added to session list automatically,
|
||||||
|
/// session must add itself to server session list by calling AddSession().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket">Connected client socket.</param>
|
||||||
|
/// <param name="bindInfo">BindInfo what accepted socket.</param>
|
||||||
|
protected override void InitNewSession(Socket socket, IPBindInfo bindInfo)
|
||||||
|
{
|
||||||
|
// Check maximum conncurent connections from 1 IP.
|
||||||
|
if (m_MaxConnectionsPerIP > 0)
|
||||||
|
{
|
||||||
|
lock (this.Sessions)
|
||||||
|
{
|
||||||
|
int nSessions = 0;
|
||||||
|
foreach (SocketServerSession s in this.Sessions)
|
||||||
|
{
|
||||||
|
IPEndPoint ipEndpoint = s.RemoteEndPoint;
|
||||||
|
if (ipEndpoint != null)
|
||||||
|
{
|
||||||
|
if (ipEndpoint.Address.Equals(((IPEndPoint)socket.RemoteEndPoint).Address))
|
||||||
|
{
|
||||||
|
nSessions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maimum allowed exceeded
|
||||||
|
if (nSessions >= m_MaxConnectionsPerIP)
|
||||||
|
{
|
||||||
|
socket.Send(System.Text.Encoding.ASCII.GetBytes("* NO Maximum connections from your IP address is exceeded, try again later !\r\n"));
|
||||||
|
socket.Shutdown(SocketShutdown.Both);
|
||||||
|
socket.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string sessionID = Guid.NewGuid().ToString();
|
||||||
|
SocketEx socketEx = new SocketEx(socket);
|
||||||
|
if (LogCommands)
|
||||||
|
{
|
||||||
|
socketEx.Logger = new SocketLogger(socket, this.SessionLog);
|
||||||
|
socketEx.Logger.SessionID = sessionID;
|
||||||
|
}
|
||||||
|
IMAP_Session session = new IMAP_Session(sessionID, socketEx, bindInfo, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
MailServer/IMAP/Server/Server/Server.cs
Normal file
24
MailServer/IMAP/Server/Server/Server.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using MailServer.Misc.SocketServer;
|
||||||
|
using MailServer.Misc;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MailServer.IMAP.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IMAP server componet.
|
||||||
|
/// </summary>
|
||||||
|
public partial class Server : SocketServer
|
||||||
|
{
|
||||||
|
private int m_MaxConnectionsPerIP = 0;
|
||||||
|
private SaslAuthTypes m_SupportedAuth = SaslAuthTypes.All;
|
||||||
|
private string m_GreetingText = "";
|
||||||
|
private int m_MaxMessageSize = 1000000;
|
||||||
|
/// <summary>
|
||||||
|
/// Defalut constructor.
|
||||||
|
/// </summary>
|
||||||
|
public Server() : base()
|
||||||
|
{
|
||||||
|
this.BindInfo = new IPBindInfo[]{new IPBindInfo("",IPAddress.Any,143,SslMode.None,null)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,40 +44,80 @@
|
|||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="IMAP\Server\IMAP_Session.cs" />
|
<Compile Include="IMAP\IMAP_ACL_Flags.cs" />
|
||||||
<Compile Include="Settings\Enum.cs" />
|
<Compile Include="IMAP\IMAP_Utils.cs" />
|
||||||
<Compile Include="IMAP\Server\AuthUser_EventArgs.cs" />
|
<Compile Include="IMAP\Server\AuthUser_EventArgs\AuthUser_EventArgs.cs" />
|
||||||
<Compile Include="IMAP\Server\AuthUser_EventArgs.Properties.cs">
|
<Compile Include="IMAP\Server\IMAP_MessageCollection\IMAP_MessageCollection.cs" />
|
||||||
<DependentUpon>AuthUser_EventArgs.cs</DependentUpon>
|
<Compile Include="IMAP\Server\IMAP_MessageFlags.cs" />
|
||||||
</Compile>
|
<Compile Include="IMAP\Server\IMAP_Message\IMAP_Message.cs" />
|
||||||
<Compile Include="IMAP\Server\Server.cs">
|
<Compile Include="IMAP\Server\IMAP_SelectedFolder\IMAP_SelectedFolder.cs" />
|
||||||
|
<Compile Include="IMAP\Server\IMAP_Session\IMAP_Session.cs" />
|
||||||
|
<Compile Include="IMAP\Server\Server\Server.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.Designer.cs">
|
<Compile Include="IMAP\Server\Server\Server.Delegates.cs" />
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
<Compile Include="IMAP\Server\Server\Server.Events.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.EventDelegates.cs">
|
<Compile Include="IMAP\Server\Server\Server.EventsDeclarations.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="IMAP\Server\Server\Server.Properties.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="IMAP\Server\Server\Server.Session.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Misc\Base64\Base64.cs" />
|
||||||
|
<Compile Include="Misc\BindInfoProtocol.cs" />
|
||||||
|
<Compile Include="Misc\CircleCollection\CircleCollection.cs" />
|
||||||
|
<Compile Include="Misc\commonDelegate.cs" />
|
||||||
|
<Compile Include="Misc\Error_EventArgs\Error_EventArgs.cs" />
|
||||||
|
<Compile Include="Misc\IPBindInfo\IPBindInfo.cs" />
|
||||||
|
<Compile Include="Misc\MIME\MIME_Reader.cs" />
|
||||||
|
<Compile Include="Misc\MIME\MIME_Utils.cs" />
|
||||||
|
<Compile Include="Misc\Net_Utils\Net_Utils.cs" />
|
||||||
|
<Compile Include="Misc\SaslAuthTypes.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\Log_EventArgs\Log_EventArgs.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\ReadExecption\ReadExecption.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketCallBackResult.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Delegates.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Network.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Read.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Ssl.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Write.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketLogEntryType.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketLogEntry\SocketLogEntry.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketLogger\SocketLogger.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketLogger\SocketLogger.Entry.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketLogger\SocketLogger.Properties.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketServerSession\SocketServerSession.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketServerSession\SocketServerSession.Events.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketServerSession\SocketServerSession.Properties.cs" />
|
||||||
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.Events.cs">
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.EventDeclarations.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.EventsDeclarations.cs">
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.Events.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.Properties.cs">
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.Properties.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.Session.cs">
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.Session.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="IMAP\Server\Server.StartStopRun.cs">
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.StartStop.cs">
|
||||||
<DependentUpon>Server.cs</DependentUpon>
|
<SubType>Component</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Misc\SocketServer\SocketServer\SocketServer.Structs.cs">
|
||||||
|
<SubType>Component</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Misc\SSLMode.cs" />
|
||||||
|
<Compile Include="Misc\TimerEx\TimerEx.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Service1.cs">
|
<Compile Include="Service1.cs">
|
||||||
@ -89,6 +129,10 @@
|
|||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Misc\SocketServer\SocketEx\SocketEx.Properties.cs" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
6
MailServer/MailServer.csproj.user
Normal file
6
MailServer/MailServer.csproj.user
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectView>ShowAllFiles</ProjectView>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
280
MailServer/Misc/Base64/Base64.cs
Normal file
280
MailServer/Misc/Base64/Base64.cs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class implements base64 encoder/decoder. Defined in RFC 4648.
|
||||||
|
/// </summary>
|
||||||
|
public class Base64
|
||||||
|
{
|
||||||
|
#region BASE64_ENCODE_TABLE
|
||||||
|
|
||||||
|
private readonly static byte[] BASE64_ENCODE_TABLE = new byte[]{
|
||||||
|
(byte)'A',(byte)'B',(byte)'C',(byte)'D',(byte)'E',(byte)'F',(byte)'G',(byte)'H',(byte)'I',(byte)'J',
|
||||||
|
(byte)'K',(byte)'L',(byte)'M',(byte)'N',(byte)'O',(byte)'P',(byte)'Q',(byte)'R',(byte)'S',(byte)'T',
|
||||||
|
(byte)'U',(byte)'V',(byte)'W',(byte)'X',(byte)'Y',(byte)'Z',(byte)'a',(byte)'b',(byte)'c',(byte)'d',
|
||||||
|
(byte)'e',(byte)'f',(byte)'g',(byte)'h',(byte)'i',(byte)'j',(byte)'k',(byte)'l',(byte)'m',(byte)'n',
|
||||||
|
(byte)'o',(byte)'p',(byte)'q',(byte)'r',(byte)'s',(byte)'t',(byte)'u',(byte)'v',(byte)'w',(byte)'x',
|
||||||
|
(byte)'y',(byte)'z',(byte)'0',(byte)'1',(byte)'2',(byte)'3',(byte)'4',(byte)'5',(byte)'6',(byte)'7',
|
||||||
|
(byte)'8',(byte)'9',(byte)'+',(byte)'/'
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region BASE64_DECODE_TABLE
|
||||||
|
|
||||||
|
private readonly static short[] BASE64_DECODE_TABLE = new short[]{
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0 - 9
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //10 - 19
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //20 - 29
|
||||||
|
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, //30 - 39
|
||||||
|
-1,-1,-1,62,-1,-1,-1,63,52,53, //40 - 49
|
||||||
|
54,55,56,57,58,59,60,61,-1,-1, //50 - 59
|
||||||
|
-1,-1,-1,-1,-1, 0, 1, 2, 3, 4, //60 - 69
|
||||||
|
5, 6, 7, 8, 9,10,11,12,13,14, //70 - 79
|
||||||
|
15,16,17,18,19,20,21,22,23,24, //80 - 89
|
||||||
|
25,-1,-1,-1,-1,-1,-1,26,27,28, //90 - 99
|
||||||
|
29,30,31,32,33,34,35,36,37,38, //100 - 109
|
||||||
|
39,40,41,42,43,44,45,46,47,48, //110 - 119
|
||||||
|
49,50,51,-1,-1,-1,-1,-1 //120 - 127
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public Base64()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Encode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Data buffer.</param>
|
||||||
|
/// <param name="offset">Offset in the buffer.</param>
|
||||||
|
/// <param name="count">Number of bytes available in the buffer.</param>
|
||||||
|
/// <param name="last">Last data block.</param>
|
||||||
|
/// <returns>Returns encoded data.</returns>
|
||||||
|
public byte[] Encode(byte[] buffer, int offset, int count, bool last)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Decode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes specified base64 string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Base64 string.</param>
|
||||||
|
/// <param name="ignoreNonBase64Chars">If true all invalid base64 chars ignored. If false, FormatException is raised.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="FormatException">Is raised when <b>value</b> contains invalid base64 data.</exception>
|
||||||
|
public byte[] Decode(string value, bool ignoreNonBase64Chars)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encBuffer = Encoding.ASCII.GetBytes(value);
|
||||||
|
byte[] buffer = new byte[encBuffer.Length];
|
||||||
|
|
||||||
|
int decodedCount = Decode(encBuffer, 0, encBuffer.Length, buffer, 0, ignoreNonBase64Chars);
|
||||||
|
byte[] retVal = new byte[decodedCount];
|
||||||
|
Array.Copy(buffer, retVal, decodedCount);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes specified base64 data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Base64 encoded data buffer.</param>
|
||||||
|
/// <param name="offset">Offset in the buffer.</param>
|
||||||
|
/// <param name="count">Number of bytes available in the buffer.</param>
|
||||||
|
/// <param name="ignoreNonBase64Chars">If true all invalid base64 chars ignored. If false, FormatException is raised.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
|
||||||
|
/// <exception cref="FormatException">Is raised when <b>value</b> contains invalid base64 data.</exception>
|
||||||
|
public byte[] Decode(byte[] data, int offset, int count, bool ignoreNonBase64Chars)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[data.Length];
|
||||||
|
|
||||||
|
int decodedCount = Decode(data, offset, count, buffer, 0, ignoreNonBase64Chars);
|
||||||
|
byte[] retVal = new byte[decodedCount];
|
||||||
|
Array.Copy(buffer, retVal, decodedCount);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes base64 encoded bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encBuffer">Base64 encoded data buffer.</param>
|
||||||
|
/// <param name="encOffset">Offset in the encBuffer.</param>
|
||||||
|
/// <param name="encCount">Number of bytes available in the encBuffer.</param>
|
||||||
|
/// <param name="buffer">Buffer where to decode data.</param>
|
||||||
|
/// <param name="offset">Offset int the buffer.</param>
|
||||||
|
/// <param name="ignoreNonBase64Chars">If true all invalid base64 chars ignored. If false, FormatException is raised.</param>
|
||||||
|
/// <returns>Returns number of bytes decoded.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>encBuffer</b> or <b>encBuffer</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Is raised when any of the arguments has out of valid range.</exception>
|
||||||
|
/// <exception cref="FormatException">Is raised when <b>encBuffer</b> contains invalid base64 data.</exception>
|
||||||
|
public int Decode(byte[] encBuffer, int encOffset, int encCount, byte[] buffer, int offset, bool ignoreNonBase64Chars)
|
||||||
|
{
|
||||||
|
if (encBuffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("encBuffer");
|
||||||
|
}
|
||||||
|
if (encOffset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("encOffset", "Argument 'encOffset' value must be >= 0.");
|
||||||
|
}
|
||||||
|
if (encCount < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("encCount", "Argument 'encCount' value must be >= 0.");
|
||||||
|
}
|
||||||
|
if (encOffset + encCount > encBuffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("encCount", "Argument 'count' is bigger than than argument 'encBuffer'.");
|
||||||
|
}
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("buffer");
|
||||||
|
}
|
||||||
|
if (offset < 0 || offset >= buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("offset");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 4648.
|
||||||
|
|
||||||
|
Base64 is processed from left to right by 4 6-bit byte block, 4 6-bit byte block
|
||||||
|
are converted to 3 8-bit bytes.
|
||||||
|
If base64 4 byte block doesn't have 3 8-bit bytes, missing bytes are marked with =.
|
||||||
|
|
||||||
|
Value Encoding Value Encoding Value Encoding Value Encoding
|
||||||
|
0 A 17 R 34 i 51 z
|
||||||
|
1 B 18 S 35 j 52 0
|
||||||
|
2 C 19 T 36 k 53 1
|
||||||
|
3 D 20 U 37 l 54 2
|
||||||
|
4 E 21 V 38 m 55 3
|
||||||
|
5 F 22 W 39 n 56 4
|
||||||
|
6 G 23 X 40 o 57 5
|
||||||
|
7 H 24 Y 41 p 58 6
|
||||||
|
8 I 25 Z 42 q 59 7
|
||||||
|
9 J 26 a 43 r 60 8
|
||||||
|
10 K 27 b 44 s 61 9
|
||||||
|
11 L 28 c 45 t 62 +
|
||||||
|
12 M 29 d 46 u 63 /
|
||||||
|
13 N 30 e 47 v
|
||||||
|
14 O 31 f 48 w (pad) =
|
||||||
|
15 P 32 g 49 x
|
||||||
|
16 Q 33 h 50 y
|
||||||
|
|
||||||
|
NOTE: 4 base64 6-bit bytes = 3 8-bit bytes
|
||||||
|
// | 6-bit | 6-bit | 6-bit | 6-bit |
|
||||||
|
// | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 |
|
||||||
|
// | 8-bit | 8-bit | 8-bit |
|
||||||
|
*/
|
||||||
|
|
||||||
|
int decodeOffset = encOffset;
|
||||||
|
int decodedOffset = 0;
|
||||||
|
byte[] base64Block = new byte[4];
|
||||||
|
|
||||||
|
// Decode while we have data.
|
||||||
|
while ((decodeOffset - encOffset) < encCount)
|
||||||
|
{
|
||||||
|
// Read 4-byte base64 block.
|
||||||
|
int offsetInBlock = 0;
|
||||||
|
while (offsetInBlock < 4)
|
||||||
|
{
|
||||||
|
// Check that we won't exceed buffer data.
|
||||||
|
if ((decodeOffset - encOffset) >= encCount)
|
||||||
|
{
|
||||||
|
if (offsetInBlock == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Incomplete 4-byte base64 data block.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FormatException("Invalid incomplete base64 4-char block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read byte.
|
||||||
|
short b = encBuffer[decodeOffset++];
|
||||||
|
|
||||||
|
// Pad char.
|
||||||
|
if (b == '=')
|
||||||
|
{
|
||||||
|
// Padding may appear only in last two chars of 4-char block.
|
||||||
|
// ab==
|
||||||
|
// abc=
|
||||||
|
if (offsetInBlock < 2)
|
||||||
|
{
|
||||||
|
throw new FormatException("Invalid base64 padding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip next padding char.
|
||||||
|
if (offsetInBlock == 2)
|
||||||
|
{
|
||||||
|
decodeOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Non-base64 char.
|
||||||
|
else if (b > 127 || BASE64_DECODE_TABLE[b] == -1)
|
||||||
|
{
|
||||||
|
if (!ignoreNonBase64Chars)
|
||||||
|
{
|
||||||
|
throw new FormatException("Invalid base64 char '" + b + "'.");
|
||||||
|
}
|
||||||
|
// Igonre that char.
|
||||||
|
//else{
|
||||||
|
}
|
||||||
|
// Base64 char.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base64Block[offsetInBlock++] = (byte)BASE64_DECODE_TABLE[b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 block.
|
||||||
|
if (offsetInBlock > 1)
|
||||||
|
{
|
||||||
|
buffer[decodedOffset++] = (byte)((base64Block[0] << 2) | (base64Block[1] >> 4));
|
||||||
|
}
|
||||||
|
if (offsetInBlock > 2)
|
||||||
|
{
|
||||||
|
buffer[decodedOffset++] = (byte)(((base64Block[1] & 0xF) << 4) | (base64Block[2] >> 2));
|
||||||
|
}
|
||||||
|
if (offsetInBlock > 3)
|
||||||
|
{
|
||||||
|
buffer[decodedOffset++] = (byte)(((base64Block[2] & 0x3) << 6) | base64Block[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
18
MailServer/Misc/BindInfoProtocol.cs
Normal file
18
MailServer/Misc/BindInfoProtocol.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies BindInfo protocol.
|
||||||
|
/// </summary>
|
||||||
|
public enum BindInfoProtocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TCP protocol.
|
||||||
|
/// </summary>
|
||||||
|
TCP,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UDP protocol.
|
||||||
|
/// </summary>
|
||||||
|
UDP
|
||||||
|
}
|
||||||
|
}
|
215
MailServer/Misc/CircleCollection/CircleCollection.cs
Normal file
215
MailServer/Misc/CircleCollection/CircleCollection.cs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Circle collection. Elements will be circled clockwise.
|
||||||
|
/// </summary>
|
||||||
|
public class CircleCollection<T>
|
||||||
|
{
|
||||||
|
private List<T> m_pItems = null;
|
||||||
|
private int m_Index = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public CircleCollection()
|
||||||
|
{
|
||||||
|
m_pItems = new List<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region methd Add
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds specified items to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">Items to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>items</b> is null.</exception>
|
||||||
|
public void Add(T[] items)
|
||||||
|
{
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("items");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (T item in items)
|
||||||
|
{
|
||||||
|
Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds specified item to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>item</b> is null.</exception>
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("item");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pItems.Add(item);
|
||||||
|
|
||||||
|
// Reset loop index.
|
||||||
|
m_Index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Remove
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified item from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to remove.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>item</b> is null.</exception>
|
||||||
|
public void Remove(T item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("item");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pItems.Remove(item);
|
||||||
|
|
||||||
|
// Reset loop index.
|
||||||
|
m_Index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Clear
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all items from collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_pItems.Clear();
|
||||||
|
|
||||||
|
// Reset loop index.
|
||||||
|
m_Index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Contains
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the collection contain the specified item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to check.</param>
|
||||||
|
/// <returns>Returns true if the collection contain the specified item, otherwise false.</returns>
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return m_pItems.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method Next
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets next item from the collection. This method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when thre is no items in the collection.</exception>
|
||||||
|
public T Next()
|
||||||
|
{
|
||||||
|
if (m_pItems.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("There is no items in the collection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (m_pItems)
|
||||||
|
{
|
||||||
|
T item = m_pItems[m_Index];
|
||||||
|
|
||||||
|
m_Index++;
|
||||||
|
if (m_Index >= m_pItems.Count)
|
||||||
|
{
|
||||||
|
m_Index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies all elements to new array, all elements will be in order they added. This method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns elements in a new array.</returns>
|
||||||
|
public T[] ToArray()
|
||||||
|
{
|
||||||
|
lock (m_pItems)
|
||||||
|
{
|
||||||
|
return m_pItems.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToCurrentOrderArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies all elements to new array, all elements will be in current circle order. This method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns elements in a new array.</returns>
|
||||||
|
public T[] ToCurrentOrderArray()
|
||||||
|
{
|
||||||
|
lock (m_pItems)
|
||||||
|
{
|
||||||
|
int index = m_Index;
|
||||||
|
T[] retVal = new T[m_pItems.Count];
|
||||||
|
for (int i = 0; i < m_pItems.Count; i++)
|
||||||
|
{
|
||||||
|
retVal[i] = m_pItems[index];
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (index >= m_pItems.Count)
|
||||||
|
{
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of items in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get { return m_pItems.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets item at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Item zero based index.</param>
|
||||||
|
/// <returns>Returns item at the specified index.</returns>
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get { return m_pItems[index]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
55
MailServer/Misc/Error_EventArgs/Error_EventArgs.cs
Normal file
55
MailServer/Misc/Error_EventArgs/Error_EventArgs.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data for the SysError event for servers.
|
||||||
|
/// </summary>
|
||||||
|
public class Error_EventArgs
|
||||||
|
{
|
||||||
|
private Exception m_pException = null;
|
||||||
|
private StackTrace m_pStackTrace = null;
|
||||||
|
private string m_Text = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x"></param>
|
||||||
|
/// <param name="stackTrace"></param>
|
||||||
|
public Error_EventArgs(Exception x, StackTrace stackTrace)
|
||||||
|
{
|
||||||
|
m_pException = x;
|
||||||
|
m_pStackTrace = stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementaion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occured error's exception.
|
||||||
|
/// </summary>
|
||||||
|
public Exception Exception
|
||||||
|
{
|
||||||
|
get { return m_pException; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occured error's stacktrace.
|
||||||
|
/// </summary>
|
||||||
|
public StackTrace StackTrace
|
||||||
|
{
|
||||||
|
get { return m_pStackTrace; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets comment text.
|
||||||
|
/// </summary>
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get { return m_Text; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
224
MailServer/Misc/IPBindInfo/IPBindInfo.cs
Normal file
224
MailServer/Misc/IPBindInfo/IPBindInfo.cs
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds IP bind info.
|
||||||
|
/// </summary>
|
||||||
|
public class IPBindInfo
|
||||||
|
{
|
||||||
|
private string m_HostName = "";
|
||||||
|
private BindInfoProtocol m_Protocol = BindInfoProtocol.TCP;
|
||||||
|
private IPEndPoint m_pEndPoint = null;
|
||||||
|
private SslMode m_SslMode = SslMode.None;
|
||||||
|
private X509Certificate2 m_pCertificate = null;
|
||||||
|
private object m_Tag = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostName">Host name.</param>
|
||||||
|
/// <param name="protocol">Bind protocol.</param>
|
||||||
|
/// <param name="ip">IP address to listen.</param>
|
||||||
|
/// <param name="port">Port to listen.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null.</exception>
|
||||||
|
public IPBindInfo(string hostName, BindInfoProtocol protocol, IPAddress ip, int port)
|
||||||
|
{
|
||||||
|
if (ip == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_HostName = hostName;
|
||||||
|
m_Protocol = protocol;
|
||||||
|
m_pEndPoint = new IPEndPoint(ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostName">Host name.</param>
|
||||||
|
/// <param name="ip">IP address to listen.</param>
|
||||||
|
/// <param name="port">Port to listen.</param>
|
||||||
|
/// <param name="sslMode">Specifies SSL mode.</param>
|
||||||
|
/// <param name="sslCertificate">Certificate to use for SSL connections.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null.</exception>
|
||||||
|
public IPBindInfo(string hostName, IPAddress ip, int port, SslMode sslMode, X509Certificate2 sslCertificate)
|
||||||
|
: this(hostName, BindInfoProtocol.TCP, ip, port, sslMode, sslCertificate)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostName">Host name.</param>
|
||||||
|
/// <param name="protocol">Bind protocol.</param>
|
||||||
|
/// <param name="ip">IP address to listen.</param>
|
||||||
|
/// <param name="port">Port to listen.</param>
|
||||||
|
/// <param name="sslMode">Specifies SSL mode.</param>
|
||||||
|
/// <param name="sslCertificate">Certificate to use for SSL connections.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public IPBindInfo(string hostName, BindInfoProtocol protocol, IPAddress ip, int port, SslMode sslMode, X509Certificate2 sslCertificate)
|
||||||
|
{
|
||||||
|
if (ip == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_HostName = hostName;
|
||||||
|
m_Protocol = protocol;
|
||||||
|
m_pEndPoint = new IPEndPoint(ip, port);
|
||||||
|
m_SslMode = sslMode;
|
||||||
|
m_pCertificate = sslCertificate;
|
||||||
|
if ((sslMode == SslMode.SSL || sslMode == SslMode.TLS) && sslCertificate == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("SSL requested, but argument 'sslCertificate' is not provided.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region override method Equals
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the current instance with another object of the same type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">An object to compare with this instance.</param>
|
||||||
|
/// <returns>Returns true if two objects are equal.</returns>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj is IPBindInfo))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPBindInfo bInfo = (IPBindInfo)obj;
|
||||||
|
if (bInfo.HostName != m_HostName)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bInfo.Protocol != m_Protocol)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!bInfo.EndPoint.Equals(m_pEndPoint))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bInfo.SslMode != m_SslMode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!X509Certificate.Equals(bInfo.Certificate, m_pCertificate))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method GetHashCode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the hash code.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the hash code.</returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets host name.
|
||||||
|
/// </summary>
|
||||||
|
public string HostName
|
||||||
|
{
|
||||||
|
get { return m_HostName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets protocol.
|
||||||
|
/// </summary>
|
||||||
|
public BindInfoProtocol Protocol
|
||||||
|
{
|
||||||
|
get { return m_Protocol; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets IP end point.
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint EndPoint
|
||||||
|
{
|
||||||
|
get { return m_pEndPoint; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets IP address.
|
||||||
|
/// </summary>
|
||||||
|
public IPAddress IP
|
||||||
|
{
|
||||||
|
get { return m_pEndPoint.Address; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets port.
|
||||||
|
/// </summary>
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return m_pEndPoint.Port; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets SSL mode.
|
||||||
|
/// </summary>
|
||||||
|
public SslMode SslMode
|
||||||
|
{
|
||||||
|
get { return m_SslMode; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets SSL certificate.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use property Certificate instead.")]
|
||||||
|
public X509Certificate2 SSL_Certificate
|
||||||
|
{
|
||||||
|
get { return m_pCertificate; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets SSL certificate.
|
||||||
|
/// </summary>
|
||||||
|
public X509Certificate2 Certificate
|
||||||
|
{
|
||||||
|
get { return m_pCertificate; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets user data. This is used internally don't use it !!!.
|
||||||
|
/// </summary>
|
||||||
|
public object Tag
|
||||||
|
{
|
||||||
|
get { return m_Tag; }
|
||||||
|
|
||||||
|
set { m_Tag = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
24
MailServer/Misc/MIME/MIME_DispositionTypes .cs
Normal file
24
MailServer/Misc/MIME/MIME_DispositionTypes .cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds MIME content disposition types. Defined in RFC 2183.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_DispositionTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A bodypart should be marked `inline' if it is intended to be displayed automatically upon display of the message.
|
||||||
|
/// Inline bodyparts should be presented in the order in which they occur, subject to the normal semantics of multipart messages.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Inline = "inline";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bodyparts can be designated `attachment' to indicate that they are separate from the main body of the mail message,
|
||||||
|
/// and that their display should not be automatic, but contingent upon some further action of the user.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Attachment = "attachment";
|
||||||
|
}
|
||||||
|
}
|
22
MailServer/Misc/MIME/MIME_EncodedWordEncoding.cs
Normal file
22
MailServer/Misc/MIME/MIME_EncodedWordEncoding.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This enum specifies MIME RFC 2047 'encoded-word' encoding method.
|
||||||
|
/// </summary>
|
||||||
|
public enum MIME_EncodedWordEncoding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The "B" encoding. Defined in RFC 2047 (section 4.1).
|
||||||
|
/// </summary>
|
||||||
|
Q,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "Q" encoding. Defined in RFC 2047 (section 4.2).
|
||||||
|
/// </summary>
|
||||||
|
B
|
||||||
|
}
|
||||||
|
}
|
262
MailServer/Misc/MIME/MIME_Encoding_EncodedWord.cs
Normal file
262
MailServer/Misc/MIME/MIME_Encoding_EncodedWord.cs
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements 'encoded-word' encoding. Defined in RFC 2047.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_Encoding_EncodedWord
|
||||||
|
{
|
||||||
|
private MIME_EncodedWordEncoding m_Encoding;
|
||||||
|
private Encoding m_pCharset = null;
|
||||||
|
private bool m_Split = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoding">Encoding to use to encode text.</param>
|
||||||
|
/// <param name="charset">Charset to use for encoding. If not sure UTF-8 is strongly recommended.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>charset</b> is null reference.</exception>
|
||||||
|
public MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding encoding,Encoding charset)
|
||||||
|
{
|
||||||
|
if(charset == null){
|
||||||
|
throw new ArgumentNullException("charset");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Encoding = encoding;
|
||||||
|
m_pCharset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Encode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes specified text if it contains 8-bit chars, otherwise text won't be encoded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to encode.</param>
|
||||||
|
/// <returns>Returns encoded text.</returns>
|
||||||
|
public string Encode(string text)
|
||||||
|
{
|
||||||
|
if(MustEncode(text)){
|
||||||
|
return EncodeS(m_Encoding,m_pCharset,m_Split,text);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Decode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes specified encoded-word.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Encoded-word value.</param>
|
||||||
|
/// <returns>Returns decoded text.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
|
||||||
|
public string Decode(string text)
|
||||||
|
{
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeS(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method MustEncode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if specified text must be encoded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to encode.</param>
|
||||||
|
/// <returns>Returns true if specified text must be encoded, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
|
||||||
|
public static bool MustEncode(string text)
|
||||||
|
{
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding is needed only for non-ASCII chars.
|
||||||
|
|
||||||
|
foreach(char c in text){
|
||||||
|
if(c > 127){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method EncodeS
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes specified text if it contains 8-bit chars, otherwise text won't be encoded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoding">Encoding to use to encode text.</param>
|
||||||
|
/// <param name="charset">Charset to use for encoding. If not sure UTF-8 is strongly recommended.</param>
|
||||||
|
/// <param name="split">If true, words are splitted after 75 chars.</param>
|
||||||
|
/// <param name="text">Text to encode.</param>
|
||||||
|
/// <returns>Returns encoded text.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>charset</b> or <b>text</b> is null reference.</exception>
|
||||||
|
public static string EncodeS(MIME_EncodedWordEncoding encoding,Encoding charset,bool split,string text)
|
||||||
|
{
|
||||||
|
if(charset == null){
|
||||||
|
throw new ArgumentNullException("charset");
|
||||||
|
}
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2047 2.
|
||||||
|
encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
|
||||||
|
|
||||||
|
An 'encoded-word' may not be more than 75 characters long, including
|
||||||
|
'charset', 'encoding', 'encoded-text', and delimiters. If it is
|
||||||
|
desirable to encode more text than will fit in an 'encoded-word' of
|
||||||
|
75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may
|
||||||
|
be used.
|
||||||
|
|
||||||
|
RFC 2231 (updates syntax)
|
||||||
|
encoded-word := "=?" charset ["*" language] "?" encoded-text "?="
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(MustEncode(text)){
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
byte[] data = charset.GetBytes(text);
|
||||||
|
int maxEncodedTextSize = int.MaxValue;
|
||||||
|
if(split){
|
||||||
|
maxEncodedTextSize = 75 - ((string)("=?" + charset.WebName + "?" + encoding.ToString() + "?" + "?=")).Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region B encode
|
||||||
|
|
||||||
|
if(encoding == MIME_EncodedWordEncoding.B){
|
||||||
|
retVal.Append("=?" + charset.WebName + "?B?");
|
||||||
|
int stored = 0;
|
||||||
|
string base64 = Convert.ToBase64String(data);
|
||||||
|
for(int i=0;i<base64.Length;i+=4){
|
||||||
|
// Encoding buffer full, create new encoded-word.
|
||||||
|
if(stored + 4 > maxEncodedTextSize){
|
||||||
|
retVal.Append("?=\r\n =?" + charset.WebName + "?B?");
|
||||||
|
stored = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.Append(base64,i,4);
|
||||||
|
stored += 4;
|
||||||
|
}
|
||||||
|
retVal.Append("?=");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Q encode
|
||||||
|
|
||||||
|
else{
|
||||||
|
retVal.Append("=?" + charset.WebName + "?Q?");
|
||||||
|
int stored = 0;
|
||||||
|
foreach(byte b in data){
|
||||||
|
string val = null;
|
||||||
|
// We need to encode byte. Defined in RFC 2047 4.2.
|
||||||
|
if(b > 127 || b == '=' || b == '?' || b == '_' || b == ' '){
|
||||||
|
val = "=" + b.ToString("X2");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
val = ((char)b).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding buffer full, create new encoded-word.
|
||||||
|
if(stored + val.Length > maxEncodedTextSize){
|
||||||
|
retVal.Append("?=\r\n =?" + charset.WebName + "?Q?");
|
||||||
|
stored = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.Append(val);
|
||||||
|
stored += val.Length;
|
||||||
|
}
|
||||||
|
retVal.Append("?=");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method DecodeS
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes non-ascii word with MIME <b>encoded-word</b> method. Defined in RFC 2047 2.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="word">MIME encoded-word value.</param>
|
||||||
|
/// <returns>Returns decoded word.</returns>
|
||||||
|
/// <remarks>If <b>word</b> is not encoded-word or has invalid syntax, <b>word</b> is leaved as is.</remarks>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>word</b> is null reference.</exception>
|
||||||
|
public static string DecodeS(string word)
|
||||||
|
{
|
||||||
|
if(word == null){
|
||||||
|
throw new ArgumentNullException("word");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2047 2.
|
||||||
|
encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
|
||||||
|
|
||||||
|
RFC 2231.
|
||||||
|
encoded-word := "=?" charset ["*" language] "?" encoded-text "?="
|
||||||
|
*/
|
||||||
|
|
||||||
|
try{
|
||||||
|
string[] parts = word.Split('?');
|
||||||
|
// Not encoded-word.
|
||||||
|
if(parts.Length != 5){
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
else if(parts[2].ToUpper() == "Q"){
|
||||||
|
return MIME_Utils.QDecode(Encoding.GetEncoding(parts[1].Split('*')[0]),parts[3]);
|
||||||
|
}
|
||||||
|
else if(parts[2].ToUpper() == "B"){
|
||||||
|
return Encoding.GetEncoding(parts[1].Split('*')[0]).GetString(Net_Utils.FromBase64(Encoding.Default.GetBytes(parts[3])));
|
||||||
|
}
|
||||||
|
// Unknown encoding.
|
||||||
|
else{
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
// Failed to parse encoded-word, leave it as is. RFC 2047 6.3.
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if long words(over 75 char) are splitted.
|
||||||
|
/// </summary>
|
||||||
|
public bool Split
|
||||||
|
{
|
||||||
|
get{ return m_Split; }
|
||||||
|
|
||||||
|
set{ m_Split = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
824
MailServer/Misc/MIME/MIME_Entity.cs
Normal file
824
MailServer/Misc/MIME/MIME_Entity.cs
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
using LumiSoft.Net.MIME;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a MIME entity. Defined in RFC 2045 2.4.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_Entity : IDisposable
|
||||||
|
{
|
||||||
|
private bool m_IsDisposed = false;
|
||||||
|
private MIME_Entity m_pParent = null;
|
||||||
|
private MIME_h_Collection m_pHeader = null;
|
||||||
|
private MIME_b m_pBody = null;
|
||||||
|
private MIME_b_Provider m_pBodyProvider = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_Entity()
|
||||||
|
{
|
||||||
|
m_pHeader = new MIME_h_Collection(new MIME_h_Provider());
|
||||||
|
m_pBodyProvider = new MIME_b_Provider();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region method Dispose
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans up any resources being used. This method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock(this){
|
||||||
|
if(m_IsDisposed){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_IsDisposed = true;
|
||||||
|
|
||||||
|
m_pHeader = null;
|
||||||
|
m_pParent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToFile
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity to the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">File name with path where to store MIME entity.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>file</b> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public void ToFile(string file,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(file == null){
|
||||||
|
throw new ArgumentNullException("file");
|
||||||
|
}
|
||||||
|
if(file == ""){
|
||||||
|
throw new ArgumentException("Argument 'file' value must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using(FileStream fs = File.Create(file)){
|
||||||
|
ToStream(fs,headerWordEncoder,headerParmetersCharset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Store MIME enity to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store MIME entity. Storing starts form stream current position.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception>
|
||||||
|
public void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pHeader.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
stream.Write(new byte[]{(int)'\r',(int)'\n'},0,2);
|
||||||
|
m_pBody.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MIME entity as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns MIME entity as string.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MIME entity as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns MIME entity as string.</returns>
|
||||||
|
public string ToString(MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
using(MemoryStream ms = new MemoryStream()){
|
||||||
|
ToStream(ms,headerWordEncoder,headerParmetersCharset);
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToByte
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MIME entity as byte[].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns MIME entity as byte[].</returns>
|
||||||
|
public byte[] ToByte(MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
using(MemoryStream ms = new MemoryStream()){
|
||||||
|
ToStream(ms,headerWordEncoder,headerParmetersCharset);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME entiry from the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Source stream.</param>
|
||||||
|
/// <param name="defaultContentType">Default content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>defaultContentType</b> is null reference.</exception>
|
||||||
|
internal void Parse(SmartStream stream,MIME_h_ContentType defaultContentType)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pHeader.Parse(stream);
|
||||||
|
|
||||||
|
m_pBody = m_pBodyProvider.Parse(this,stream,defaultContentType);
|
||||||
|
m_pBody.SetParent(this,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method SetParent
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets MIME entity parent entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">Parent entity.</param>
|
||||||
|
internal void SetParent(MIME_Entity parent)
|
||||||
|
{
|
||||||
|
m_pParent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
// Permanent headerds list: http://www.rfc-editor.org/rfc/rfc4021.txt
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this object is disposed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed
|
||||||
|
{
|
||||||
|
get{ return m_IsDisposed; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this entity is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pHeader.IsModified || m_pBody.IsModified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent entity of this entity, returns null if this is the root entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
public MIME_Entity Parent
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets MIME entity header field collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
public MIME_h_Collection Header
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets MIME version number. Value null means that header field does not exist. Normally this value is 1.0. Defined in RFC 2045 section 4.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>An indicator that this message is formatted according to the MIME
|
||||||
|
/// standard, and an indication of which version of MIME is used.</remarks>
|
||||||
|
public string MimeVersion
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("MIME-Version");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("MIME-Version");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("MIME-Version");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("MIME-Version",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets content body part ID. Value null means that header field does not exist. Defined in RFC 2045 7.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Specifies a Unique ID for one MIME body part of the content of a message.</remarks>
|
||||||
|
public string ContentID
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-ID");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-ID");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-ID");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-ID",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets description of message body part. Value null means that header field does not exist. Defined in RFC 2045 8.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Description of a particular body part of a message; for example, a caption for an image body part.</remarks>
|
||||||
|
public string ContentDescription
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Description");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Description",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets content transfer encoding. Value null means that header field does not exist.
|
||||||
|
/// RFC defined values are in <see cref="MIME_TransferEncodings">MIME_TransferEncodings</see>. Defined in RFC 2045 6.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Coding method used in a MIME message body part.</remarks>
|
||||||
|
public string ContentTransferEncoding
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Transfer-Encoding");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Transfer-Encoding");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Transfer-Encoding");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Transfer-Encoding",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets MIME content type. Value null means that header field does not exist. Defined in RFC 2045 5.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public MIME_h_ContentType ContentType
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Type");
|
||||||
|
if(h != null){
|
||||||
|
if(!(h is MIME_h_ContentType)){
|
||||||
|
throw new ParseException("Header field 'ContentType' parsing failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MIME_h_ContentType)h;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Type");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Type");
|
||||||
|
if(h == null){
|
||||||
|
m_pHeader.Add(value);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_pHeader.ReplaceFirst(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets base to be used for resolving relative URIs within this content part. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Base to be used for resolving relative URIs within this content part. See also Content-Location.</remarks>
|
||||||
|
public string ContentBase
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Base");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Base");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Base",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets URI for retrieving a body part. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>URI using which the content of this body-part part was retrieved,
|
||||||
|
/// might be retrievable, or which otherwise gives a globally unique identification of the content.</remarks>
|
||||||
|
public string ContentLocation
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Location");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Location");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Location",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets content features of a MIME body part. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>The 'Content-features:' header can be used to annotate a MIME body part with a media feature expression,
|
||||||
|
/// to indicate features of the body part content. See also RFC 2533, RFC 2506, and RFC 2045.</remarks>
|
||||||
|
public string Contentfeatures
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-features");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-features");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-features",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets content disposition. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Indicates whether a MIME body part is to be shown inline or is an attachment; can also indicate a
|
||||||
|
/// suggested filename for use when saving an attachment to a file.</remarks>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public MIME_h_ContentDisposition ContentDisposition
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Disposition");
|
||||||
|
if(h != null){
|
||||||
|
if(!(h is MIME_h_ContentDisposition)){
|
||||||
|
throw new ParseException("Header field 'ContentDisposition' parsing failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MIME_h_ContentDisposition)h;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Disposition");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Disposition");
|
||||||
|
if(h == null){
|
||||||
|
m_pHeader.Add(value);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_pHeader.ReplaceFirst(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets language of message content. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Can include a code for the natural language used in a message; e.g., 'en' for English.
|
||||||
|
/// Can also contain a list of languages for a message containing more than one language.</remarks>
|
||||||
|
public string ContentLanguage
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Language");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Language");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Language",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets message alternative content. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Information about the media features of alternative content formats available for the current message.</remarks>
|
||||||
|
public string ContentAlternative
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Alternative");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Alternative");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Alternative",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets content MD5 checksum. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Checksum of content to ensure that it has not been modified.</remarks>
|
||||||
|
public string ContentMD5
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-MD5");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-MD5");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-MD5",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets time duration of content. Value null means that header field does not exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
|
||||||
|
/// <remarks>Time duration of body part content, in seconds (e.g., for audio message).</remarks>
|
||||||
|
public string ContentDuration
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Description");
|
||||||
|
if(h != null){
|
||||||
|
return ((MIME_h_Unstructured)h).Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value == null){
|
||||||
|
m_pHeader.RemoveAll("Content-Duration");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
MIME_h h = m_pHeader.GetFirst("Content-Duration");
|
||||||
|
if(h == null){
|
||||||
|
h = new MIME_h_Unstructured("Content-Duration",value);
|
||||||
|
m_pHeader.Add(h);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
((MIME_h_Unstructured)h).Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets MIME entity body.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when null reference passed.</exception>
|
||||||
|
public MIME_b Body
|
||||||
|
{
|
||||||
|
get{ return m_pBody; }
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("Body");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pBody = value;
|
||||||
|
m_pBody.SetParent(this,true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
198
MailServer/Misc/MIME/MIME_EntityCollection.cs
Normal file
198
MailServer/Misc/MIME/MIME_EntityCollection.cs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents MIME child entity collection in multipart/xxx entity.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_EntityCollection : IEnumerable
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private List<MIME_Entity> m_pCollection = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
internal MIME_EntityCollection()
|
||||||
|
{
|
||||||
|
m_pCollection = new List<MIME_Entity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Add
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds specified MIME enity to the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">MIME entity.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>entity</b> is null reference.</exception>
|
||||||
|
public void Add(MIME_Entity entity)
|
||||||
|
{
|
||||||
|
if(entity == null){
|
||||||
|
throw new ArgumentNullException("entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pCollection.Add(entity);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Insert
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new MIME entity into the collection at the specified location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The location in the collection where you want to add the MIME entity.</param>
|
||||||
|
/// <param name="entity">MIME entity.</param>
|
||||||
|
/// <exception cref="IndexOutOfRangeException">Is raised when <b>index</b> is out of range.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>entity</b> is null reference.</exception>
|
||||||
|
public void Insert(int index,MIME_Entity entity)
|
||||||
|
{
|
||||||
|
if(entity == null){
|
||||||
|
throw new ArgumentNullException("entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pCollection.Insert(index,entity);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Remove
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified MIME entity from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">MIME entity.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
public void Remove(MIME_Entity entity)
|
||||||
|
{
|
||||||
|
if(entity == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pCollection.Remove(entity);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes MIME entity at the specified index from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the MIME entity to remove.</param>
|
||||||
|
/// <exception cref="IndexOutOfRangeException">Is raised when <b>index</b> is out of range.</exception>
|
||||||
|
public void Remove(int index)
|
||||||
|
{
|
||||||
|
m_pCollection.RemoveAt(index);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Clear
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all items from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_pCollection.Clear();
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region mehtod Contains
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the collection contains specified MIME entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">MIME entity.</param>
|
||||||
|
/// <returns>Returns true if the specified MIME entity exists in the collection, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>entity</b> is null.</exception>
|
||||||
|
public bool Contains(MIME_Entity entity)
|
||||||
|
{
|
||||||
|
if(entity == null){
|
||||||
|
throw new ArgumentNullException("entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pCollection.Contains(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method SetModified
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets IsModified property value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isModified">Modified flag.</param>
|
||||||
|
internal void SetModified(bool isModified)
|
||||||
|
{
|
||||||
|
m_IsModified = isModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region interface IEnumerator
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns IEnumerator interface.</returns>
|
||||||
|
public IEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return m_pCollection.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if enity collection has modified.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_Entity entity in m_pCollection){
|
||||||
|
if(entity.IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of items in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get{ return m_pCollection.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets MIME entity at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">MIME entity zero-based index.</param>
|
||||||
|
/// <returns>Returns MIME entity.</returns>
|
||||||
|
public MIME_Entity this[int index]
|
||||||
|
{
|
||||||
|
get{ return m_pCollection[index]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
208
MailServer/Misc/MIME/MIME_MediaTypes.cs
Normal file
208
MailServer/Misc/MIME/MIME_MediaTypes.cs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well known Content-Type header field media types. For example: text/plain, application/octet-stream.
|
||||||
|
/// Full IANA registered list can be found from: http://www.iana.org/assignments/media-types.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_MediaTypes
|
||||||
|
{
|
||||||
|
#region application
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well-known application/xxx media types.
|
||||||
|
/// </summary>
|
||||||
|
public class Application
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "application/octet-stream". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string octet_stream = "application/octet-stream";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "application/pdf". Defined in RFC 3778.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string pdf = "application/pdf";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "application/sdp". Defined in RFC 4566.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string sdp = "application/sdp";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "application/xml". Defined RFC 3023.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string xml = "application/xml";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "application/zip". Defined in RFC 4566.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string zip = "application/zip";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "application/x-pkcs7-signature". Defined in RFC 2311,2633.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string x_pkcs7_signature = "application/x-pkcs7-signature";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region image
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well-known image/xxx media types.
|
||||||
|
/// </summary>
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "image/gif".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string gif = "image/gif";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "image/jpeg".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string jpeg = "image/jpeg";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "image/tiff".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string tiff = "image/tiff";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region text
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well-known text/xxx media types.
|
||||||
|
/// </summary>
|
||||||
|
public class Text
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "text/calendar". Defined in RFC 2445.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string calendar = "text/calendar";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/css". Defined in RFC 2854
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string css = "text/css";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/html". Defined in RFC 2854.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string html = "text/html";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/plain". Defined in RFC 2646,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string plain = "text/plain";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/rfc822-headers". Defined in RFC 1892.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string rfc822_headers = "text/rfc822-headers";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/richtext". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string richtext = "text/richtext";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "text/xml". Defined in RFC 3023.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string xml = "text/xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region multipart
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well-known multipart/xxx media types.
|
||||||
|
/// </summary>
|
||||||
|
public class Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/alternative". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string alternative = "multipart/alternative";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/digest". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string digest = "multipart/digest";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/digest". Defined in RFC 1847.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string encrypted = "multipart/digest";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/form-data". Defined in RFC 2388.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string form_data = "multipart/form-data";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/mixed". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string mixed = "multipart/mixed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/parallel". Defined in RFC 2045,2046.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string parallel = "multipart/parallel";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/related". Defined in RFC 2387.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string related = "multipart/related";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/report". Defined in RFC 1892.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string report = "multipart/report";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/signed". Defined in RFC 1847.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string signed = "multipart/signed";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "multipart/voice-message". Defined in RFC 2421,2423.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string voice_message = "multipart/voice-message";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region message
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds well-known message/xxx media types.
|
||||||
|
/// </summary>
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// "message/rfc822".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string rfc822 = "message/rfc822";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "message/disposition-notification".
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string disposition_notification = "message/disposition-notification";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "message/delivery-status". Defined in RFC 3464.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string delivery_status = "message/delivery-status";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
216
MailServer/Misc/MIME/MIME_Message.cs
Normal file
216
MailServer/Misc/MIME/MIME_Message.cs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a MIME message. Defined in RFC 2045 2.3.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_Message : MIME_Entity
|
||||||
|
{
|
||||||
|
private bool m_IsDisposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_Message()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method ParseFromFile
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME message from the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">File name with path from where to parse MIME message.</param>
|
||||||
|
/// <returns>Returns parsed MIME message.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>file</b> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public static MIME_Message ParseFromFile(string file)
|
||||||
|
{
|
||||||
|
if(file == null){
|
||||||
|
throw new ArgumentNullException("file");
|
||||||
|
}
|
||||||
|
if(file == ""){
|
||||||
|
throw new ArgumentException("Argument 'file' value must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using(FileStream fs = File.OpenRead(file)){
|
||||||
|
return ParseFromStream(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ParseFromStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME message from the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream from where to parse MIME message. Parsing starts from current stream position.</param>
|
||||||
|
/// <returns>Returns parsed MIME message.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception>
|
||||||
|
public static MIME_Message ParseFromStream(Stream stream)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_Message retVal = new MIME_Message();
|
||||||
|
retVal.Parse(new SmartStream(stream,false),new MIME_h_ContentType("text/plain"));
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method CreateAttachment
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates attachment entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">File name with optional path.</param>
|
||||||
|
/// <returns>Returns created attachment entity.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>file</b> is null reference.</exception>
|
||||||
|
public static MIME_Entity CreateAttachment(string file)
|
||||||
|
{
|
||||||
|
if(file == null){
|
||||||
|
throw new ArgumentNullException("file");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_Entity retVal = new MIME_Entity();
|
||||||
|
MIME_b_Application body = new MIME_b_Application(MIME_MediaTypes.Application.octet_stream);
|
||||||
|
retVal.Body = body;
|
||||||
|
body.SetDataFromFile(file,MIME_TransferEncodings.Base64);
|
||||||
|
retVal.ContentType.Param_Name = Path.GetFileName(file);
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(file);
|
||||||
|
MIME_h_ContentDisposition disposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Attachment);
|
||||||
|
disposition.Param_FileName = Path.GetFileName(file);
|
||||||
|
disposition.Param_Size = fileInfo.Length;
|
||||||
|
disposition.Param_CreationDate = fileInfo.CreationTime;
|
||||||
|
disposition.Param_ModificationDate = fileInfo.LastWriteTime;
|
||||||
|
disposition.Param_ReadDate = fileInfo.LastAccessTime;
|
||||||
|
retVal.ContentDisposition = disposition;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates attachment entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Attachment data stream.</param>
|
||||||
|
/// <param name="fileName">File name.</param>
|
||||||
|
/// <returns>Returns created attachment entity.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>fileName</b> is null reference.</exception>
|
||||||
|
public static MIME_Entity CreateAttachment(Stream stream,string fileName)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(fileName == null){
|
||||||
|
throw new ArgumentNullException("fileName");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_Entity retVal = new MIME_Entity();
|
||||||
|
MIME_b_Application body = new MIME_b_Application(MIME_MediaTypes.Application.octet_stream);
|
||||||
|
retVal.Body = body;
|
||||||
|
body.SetData(stream,MIME_TransferEncodings.Base64);
|
||||||
|
retVal.ContentType.Param_Name = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
MIME_h_ContentDisposition disposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Attachment);
|
||||||
|
disposition.Param_FileName = Path.GetFileName(fileName);
|
||||||
|
disposition.Param_Size = stream.CanSeek ? (stream.Length - stream.Position) : -1;
|
||||||
|
//disposition.Param_CreationDate = fileInfo.CreationTime;
|
||||||
|
//disposition.Param_ModificationDate = fileInfo.LastWriteTime;
|
||||||
|
//disposition.Param_ReadDate = fileInfo.LastAccessTime;
|
||||||
|
retVal.ContentDisposition = disposition;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method GetEntityByCID
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets MIME entity with the specified Content-ID. Returns null if no such entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cid">Content ID.</param>
|
||||||
|
/// <returns>Returns MIME entity with the specified Content-ID or null if no such entity.</returns>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this class is disposed and this method is accessed.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>cid</b> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_Entity GetEntityByCID(string cid)
|
||||||
|
{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
if(cid == null){
|
||||||
|
throw new ArgumentNullException("cid");
|
||||||
|
}
|
||||||
|
if(cid == ""){
|
||||||
|
throw new ArgumentException("Argument 'cid' value must be specified.","cid");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_Entity entity in this.AllEntities){
|
||||||
|
if(entity.ContentID == cid){
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
//public MIME_Entity GetEntityByPartsSpecifier(string partsSpecifier)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all MIME entities as list.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is raised when this class is disposed and this property is accessed.</exception>
|
||||||
|
public MIME_Entity[] AllEntities
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsDisposed){
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MIME_Entity> retVal = new List<MIME_Entity>();
|
||||||
|
Queue<MIME_Entity> entitiesQueue = new Queue<MIME_Entity>();
|
||||||
|
entitiesQueue.Enqueue(this);
|
||||||
|
|
||||||
|
while(entitiesQueue.Count > 0){
|
||||||
|
MIME_Entity currentEntity = entitiesQueue.Dequeue();
|
||||||
|
|
||||||
|
// Current entity is multipart entity, add it's body-parts for processing.
|
||||||
|
if(this.Body != null && currentEntity.Body.GetType().IsSubclassOf(typeof(MIME_b_Multipart))){
|
||||||
|
foreach(MIME_Entity bodypart in ((MIME_b_Multipart)currentEntity.Body).BodyParts){
|
||||||
|
|
||||||
|
entitiesQueue.Enqueue(bodypart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.Add(currentEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
898
MailServer/Misc/MIME/MIME_Reader.cs
Normal file
898
MailServer/Misc/MIME/MIME_Reader.cs
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// MIME lexical tokens parser.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_Reader
|
||||||
|
{
|
||||||
|
private string m_Source = "";
|
||||||
|
private int m_Offset = 0;
|
||||||
|
|
||||||
|
#region constants
|
||||||
|
|
||||||
|
private static readonly char[] atextChars = new char[]{'!','#','$','%','&','\'','*','+','-','/','=','?','^','_','`','{','|','}','~'};
|
||||||
|
|
||||||
|
private static readonly char[] specials = new char[]{'(',')','<','>','[',']',':',';','@','\\',',','.','"'};
|
||||||
|
|
||||||
|
private static readonly char[] tspecials = new char[]{'(',')','<','>','@',',',';',':','\\','"','/','[',']','?','='};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to read.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null.</exception>
|
||||||
|
public MIME_Reader(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Source = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Atom
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2822 'atom' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2822 'atom' or null if end of stream reached.</returns>
|
||||||
|
public string Atom()
|
||||||
|
{
|
||||||
|
/* RFC 2822 3.2.4.
|
||||||
|
* atom = [CFWS] 1*atext [CFWS]
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
while(true){
|
||||||
|
int peekChar = Peek(false);
|
||||||
|
// We reached end of string.
|
||||||
|
if(peekChar == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
char c = (char)peekChar;
|
||||||
|
if(IsAText(c)){
|
||||||
|
retVal.Append((char)Char(false));
|
||||||
|
}
|
||||||
|
// Char is not part of 'atom', break.
|
||||||
|
else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(retVal.Length > 0){
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method DotAtom
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2822 'dot-atom' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2822 'dot-atom' or null if end of stream reached.</returns>
|
||||||
|
public string DotAtom()
|
||||||
|
{
|
||||||
|
/* RFC 2822 3.2.4.
|
||||||
|
* dot-atom = [CFWS] dot-atom-text [CFWS]
|
||||||
|
* dot-atom-text = 1*atext *("." 1*atext)
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
while(true){
|
||||||
|
string atom = Atom();
|
||||||
|
// We reached end of string.
|
||||||
|
if(atom == null){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal.Append(atom);
|
||||||
|
|
||||||
|
// dot-atom-text continues.
|
||||||
|
if(Peek(false) == '.'){
|
||||||
|
retVal.Append((char)Char(false));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(retVal.Length > 0){
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Token
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2045 (section 5) 'token' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2045 (section 5) 'token' or null if end of stream reached.</returns>
|
||||||
|
public string Token()
|
||||||
|
{
|
||||||
|
/* RFC 2045 5.
|
||||||
|
* token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials>
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
while(true){
|
||||||
|
int peekChar = Peek(false);
|
||||||
|
// We reached end of string.
|
||||||
|
if(peekChar == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
char c = (char)peekChar;
|
||||||
|
if(IsToken(c)){
|
||||||
|
retVal.Append((char)Char(false));
|
||||||
|
}
|
||||||
|
// Char is not part of 'token', break.
|
||||||
|
else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(retVal.Length > 0){
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Comment
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 822 'comment' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 822 'comment' or null if end of stream reached.</returns>
|
||||||
|
public string Comment()
|
||||||
|
{
|
||||||
|
/* RFC 822 3.3.
|
||||||
|
* comment = "(" *(ctext / quoted-pair / comment) ")"
|
||||||
|
* ctext = <any CHAR excluding "(", ")", "\" & CR, & including linear-white-space>
|
||||||
|
* quoted-pair = "\" CHAR
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
if(Peek(false) != '('){
|
||||||
|
throw new InvalidOperationException("No 'comment' value available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
|
||||||
|
// Remove '('.
|
||||||
|
Char(false);
|
||||||
|
|
||||||
|
int nestedParenthesis = 0;
|
||||||
|
while(true){
|
||||||
|
int intC = Char(false);
|
||||||
|
// End of stream reached, invalid 'comment' value.
|
||||||
|
if(intC == -1){
|
||||||
|
throw new ArgumentException("Invalid 'comment' value, no closing ')'.");
|
||||||
|
}
|
||||||
|
else if(intC == '('){
|
||||||
|
nestedParenthesis++;
|
||||||
|
}
|
||||||
|
else if(intC == ')'){
|
||||||
|
// We readed whole 'comment' ok.
|
||||||
|
if(nestedParenthesis == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
nestedParenthesis--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal.Append((char)intC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Word
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2822 (section 3.2.6) 'word' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2822 (section 3.2.6) 'word' or null if end of stream reached.</returns>
|
||||||
|
public string Word()
|
||||||
|
{
|
||||||
|
/* RFC 2822 3.2.6.
|
||||||
|
word = atom / quoted-string
|
||||||
|
|
||||||
|
Consider dot-atom as word too.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(Peek(true) == '"'){
|
||||||
|
return QuotedString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return DotAtom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method EncodedWord
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2047 'encoded-word' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2047 'encoded-word' or null if end of stream reached.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when source stream has no encoded-word at current position.</exception>
|
||||||
|
public string EncodedWord()
|
||||||
|
{
|
||||||
|
/* RFC 2047 2.
|
||||||
|
encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
|
||||||
|
|
||||||
|
encoded-text = 1*<Any printable ASCII character other than "?" or SPACE>
|
||||||
|
; (but see "Use of encoded-words in message
|
||||||
|
; headers", section 5)
|
||||||
|
|
||||||
|
An 'encoded-word' may not be more than 75 characters long, including
|
||||||
|
'charset', 'encoding', 'encoded-text', and delimiters. If it is
|
||||||
|
desirable to encode more text than will fit in an 'encoded-word' of
|
||||||
|
75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may
|
||||||
|
be used.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
if(Peek(false) != '='){
|
||||||
|
throw new InvalidOperationException("No encoded-word available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
while(true){
|
||||||
|
int index = m_Source.IndexOf("?=",m_Offset);
|
||||||
|
// Invalid or not enoded-word.
|
||||||
|
if(index == -1){
|
||||||
|
retVal.Append(ToEnd());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
string encodedWord = m_Source.Substring(m_Offset,index - m_Offset + 2);
|
||||||
|
|
||||||
|
// Move index over encoded-word.
|
||||||
|
m_Offset += encodedWord.Length;
|
||||||
|
|
||||||
|
try{
|
||||||
|
string[] encodedWordParts = encodedWord.Split('?');
|
||||||
|
if(encodedWordParts[2].ToUpper() == "Q"){
|
||||||
|
retVal.Append(MIME_Utils.QDecode(Encoding.GetEncoding(encodedWordParts[1]),encodedWordParts[3]));
|
||||||
|
}
|
||||||
|
else if(encodedWordParts[2].ToUpper() == "B"){
|
||||||
|
retVal.Append(Encoding.GetEncoding(encodedWordParts[1]).GetString(Net_Utils.FromBase64(Encoding.Default.GetBytes(encodedWordParts[3]))));
|
||||||
|
}
|
||||||
|
// Failed to parse encoded-word, leave it as is. RFC 2047 6.3.
|
||||||
|
else{
|
||||||
|
retVal.Append(encodedWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
// Failed to parse encoded-word, leave it as is. RFC 2047 6.3.
|
||||||
|
retVal.Append(encodedWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
// encoded-word does not continue.
|
||||||
|
if(Peek(false) != '='){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method QuotedString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 822 'quoted-string' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 822 'quoted-string' or null if end of stream reached.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when source stream has no quoted-string at current position.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when not valid 'quoted-string'.</exception>
|
||||||
|
public string QuotedString()
|
||||||
|
{
|
||||||
|
/* RFC 2822 3.2.5.
|
||||||
|
* qtext = NO-WS-CTL / ; Non white space controls
|
||||||
|
%d33 / ; The rest of the US-ASCII
|
||||||
|
%d35-91 / ; characters not including "\"
|
||||||
|
%d93-126 ; or the quote character
|
||||||
|
qcontent = qtext / quoted-pair
|
||||||
|
quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
|
||||||
|
*/
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
if(Peek(false) != '"'){
|
||||||
|
throw new InvalidOperationException("No quoted-string available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read start DQUOTE.
|
||||||
|
Char(false);
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
bool escape = false;
|
||||||
|
while(true){
|
||||||
|
int intC = Char(false);
|
||||||
|
// We reached end of stream, invalid quoted string, end quote is missing.
|
||||||
|
if(intC == -1){
|
||||||
|
throw new ArgumentException("Invalid quoted-string, end quote is missing.");
|
||||||
|
}
|
||||||
|
// This char is escaped.
|
||||||
|
else if(escape){
|
||||||
|
escape = false;
|
||||||
|
|
||||||
|
retVal.Append((char)intC);
|
||||||
|
}
|
||||||
|
// Closing DQUOTE.
|
||||||
|
else if(intC == '"'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Next char is escaped.
|
||||||
|
else if(intC == '\\'){
|
||||||
|
escape = true;
|
||||||
|
}
|
||||||
|
// Skip folding chars.
|
||||||
|
else if(intC == '\r' || intC == '\n'){
|
||||||
|
}
|
||||||
|
// Normal char in quoted-string.
|
||||||
|
else{
|
||||||
|
retVal.Append((char)intC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Value
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2045 (section 5) 'token' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns 2045 (section 5) 'token' or null if end of stream reached.</returns>
|
||||||
|
public string Value()
|
||||||
|
{
|
||||||
|
// value := token / quoted-string
|
||||||
|
|
||||||
|
if(Peek(true) == '"'){
|
||||||
|
return QuotedString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Phrase
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 2047 (section 5) 'phrase' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 2047 (section 5) 'phrase' or null if end of stream reached.</returns>
|
||||||
|
public string Phrase()
|
||||||
|
{
|
||||||
|
/* RFC 2047 5.
|
||||||
|
* phrase = 1*( encoded-word / word )
|
||||||
|
* word = atom / quoted-string
|
||||||
|
*/
|
||||||
|
|
||||||
|
int peek = Peek(true);
|
||||||
|
if(peek == '"'){
|
||||||
|
return QuotedString();
|
||||||
|
}
|
||||||
|
else if(peek == '='){
|
||||||
|
return EncodedWord();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Atom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Text
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads RFC 822 '*text' from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns RFC 822 '*text' or null if end of stream reached.</returns>
|
||||||
|
public string Text()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToFirstChar
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all white-space chars + CR and LF.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns readed chars.</returns>
|
||||||
|
public string ToFirstChar()
|
||||||
|
{
|
||||||
|
// NOTE: Never call Peek or Char method here or stack overflow !
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
while(true){
|
||||||
|
int peekChar = -1;
|
||||||
|
if(m_Offset > m_Source.Length - 1){
|
||||||
|
peekChar = -1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
peekChar = m_Source[m_Offset];
|
||||||
|
}
|
||||||
|
// We reached end of string.
|
||||||
|
if(peekChar == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(peekChar == ' ' || peekChar == '\t' || peekChar == '\r' || peekChar == '\n'){
|
||||||
|
retVal.Append(m_Source[m_Offset++]);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Char
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads 1 char from source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="readToFirstChar">Specifies if postion is moved to char(skips white spaces).</param>
|
||||||
|
/// <returns>Returns readed char or -1 if end of stream reached.</returns>
|
||||||
|
public int Char(bool readToFirstChar)
|
||||||
|
{
|
||||||
|
if(readToFirstChar){
|
||||||
|
ToFirstChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_Offset > m_Source.Length - 1){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return m_Source[m_Offset++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Peek
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows next char in source stream, this method won't consume that char.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="readToFirstChar">Specifies if postion is moved to char(skips white spaces).</param>
|
||||||
|
/// <returns>Returns next char in source stream, returns -1 if end of stream.</returns>
|
||||||
|
public int Peek(bool readToFirstChar)
|
||||||
|
{
|
||||||
|
if(readToFirstChar){
|
||||||
|
ToFirstChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_Offset > m_Source.Length - 1){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return m_Source[m_Offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method StartsWith
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if source stream valu starts with the specified value. Compare is case-insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to check.</param>
|
||||||
|
/// <returns>Returns true if source steam satrs with specified string.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null.</exception>
|
||||||
|
public bool StartsWith(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_Source.Substring(m_Offset).StartsWith(value,StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToEnd
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all data from current postion to the end.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Retruns readed data. Returns null if end of string is reached.</returns>
|
||||||
|
public string ToEnd()
|
||||||
|
{
|
||||||
|
if(m_Offset >= m_Source.Length){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string retVal = m_Source.Substring(m_Offset);
|
||||||
|
m_Offset = m_Source.Length;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method IsAlpha
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified char is RFC 822 'ALPHA'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Char to check.</param>
|
||||||
|
/// <returns>Returns true if specified char is RFC 822 'ALPHA'.</returns>
|
||||||
|
public static bool IsAlpha(char c)
|
||||||
|
{
|
||||||
|
/* RFC 822 3.3.
|
||||||
|
ALPHA = <any ASCII alphabetic character>; (65.- 90.); (97.-122.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if((c >= 65 && c <= 90) || (c >= 97 && c <= 122)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsAText
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified char is RFC 2822 'atext'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Char to check.</param>
|
||||||
|
/// <returns>Returns true if specified char is RFC 2822 'atext'.</returns>
|
||||||
|
public static bool IsAText(char c)
|
||||||
|
{
|
||||||
|
/* RFC 2822 3.2.4.
|
||||||
|
* atext = ALPHA / DIGIT /
|
||||||
|
* "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" /
|
||||||
|
* "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" /
|
||||||
|
* "|" / "}" / "~"
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(IsAlpha(c) || char.IsDigit(c)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foreach(char aC in atextChars){
|
||||||
|
if(c == aC){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsDotAtom
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified value can be represented as "dot-atom".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to check.</param>
|
||||||
|
/// <returns>Returns true if the specified value can be represented as "dot-atom".</returns>
|
||||||
|
public static bool IsDotAtom(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2822 3.2.4.
|
||||||
|
* dot-atom = [CFWS] dot-atom-text [CFWS]
|
||||||
|
* dot-atom-text = 1*atext *("." 1*atext)
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach(char c in value){
|
||||||
|
if(c != '.' && !IsAText(c)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsToken
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if specified valu is RFC 2045 (section 5) 'token'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to check.</param>
|
||||||
|
/// <returns>Returns true if specified char is RFC 2045 (section 5) 'token'.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null.</exception>
|
||||||
|
public static bool IsToken(string text)
|
||||||
|
{
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(text == ""){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(char c in text){
|
||||||
|
if(!IsToken(c)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified char is RFC 2045 (section 5) 'token'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Char to check.</param>
|
||||||
|
/// <returns>Returns true if specified char is RFC 2045 (section 5) 'token'.</returns>
|
||||||
|
public static bool IsToken(char c)
|
||||||
|
{
|
||||||
|
/* RFC 2045 5.
|
||||||
|
* token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials>
|
||||||
|
*
|
||||||
|
* RFC 822 3.3.
|
||||||
|
* CTL = <any ASCII control; (0.- 31.); (127.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(c <= 31 || c == 127){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(c == ' '){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foreach(char tsC in tspecials){
|
||||||
|
if(tsC == c){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsAttributeChar
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified char is RFC 2231 (section 7) 'attribute-char'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Char to check.</param>
|
||||||
|
/// <returns>Returns true if specified char is RFC 2231 (section 7) 'attribute-char'.</returns>
|
||||||
|
public static bool IsAttributeChar(char c)
|
||||||
|
{
|
||||||
|
/* RFC 2231 7.
|
||||||
|
* attribute-char := <any (US-ASCII) CHAR except SPACE, CTLs, "*", "'", "%", or tspecials>
|
||||||
|
*
|
||||||
|
* RFC 822 3.3.
|
||||||
|
* CTL = <any ASCII control; (0.- 31.); (127.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(c <= 31 || c > 127){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(c == ' ' || c == '*' || c == '\'' || c == '%'){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foreach(char cS in tspecials){
|
||||||
|
if(c == cS){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ReadParenthesized
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads parenthesized value. Supports {},(),[],<> parenthesis.
|
||||||
|
/// Throws exception if there isn't parenthesized value or closing parenthesize is missing.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns value between parenthesized.</returns>
|
||||||
|
public string ReadParenthesized()
|
||||||
|
{
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
char startingChar = ' ';
|
||||||
|
char closingChar = ' ';
|
||||||
|
|
||||||
|
if(m_Source[m_Offset] == '{'){
|
||||||
|
startingChar = '{';
|
||||||
|
closingChar = '}';
|
||||||
|
}
|
||||||
|
else if(m_Source[m_Offset] == '('){
|
||||||
|
startingChar = '(';
|
||||||
|
closingChar = ')';
|
||||||
|
}
|
||||||
|
else if(m_Source[m_Offset] == '['){
|
||||||
|
startingChar = '[';
|
||||||
|
closingChar = ']';
|
||||||
|
}
|
||||||
|
else if(m_Source[m_Offset] == '<'){
|
||||||
|
startingChar = '<';
|
||||||
|
closingChar = '>';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new Exception("No parenthesized value '" + m_Source.Substring(m_Offset) + "' !");
|
||||||
|
}
|
||||||
|
m_Offset++;
|
||||||
|
|
||||||
|
bool inQuotedString = false; // Holds flag if position is quoted string or not
|
||||||
|
char lastChar = (char)0;
|
||||||
|
int nestedStartingCharCounter = 0;
|
||||||
|
for(int i=m_Offset;i<m_Source.Length;i++){
|
||||||
|
// Skip escaped(\) "
|
||||||
|
if(lastChar != '\\' && m_Source[i] == '\"'){
|
||||||
|
// Start/end quoted string area
|
||||||
|
inQuotedString = !inQuotedString;
|
||||||
|
}
|
||||||
|
// We need to skip parenthesis in quoted string
|
||||||
|
else if(!inQuotedString){
|
||||||
|
// There is nested parenthesis
|
||||||
|
if(m_Source[i] == startingChar){
|
||||||
|
nestedStartingCharCounter++;
|
||||||
|
}
|
||||||
|
// Closing char
|
||||||
|
else if(m_Source[i] == closingChar){
|
||||||
|
// There isn't nested parenthesis closing chars left, this is closing char what we want.
|
||||||
|
if(nestedStartingCharCounter == 0){
|
||||||
|
string retVal = m_Source.Substring(m_Offset,i - m_Offset);
|
||||||
|
m_Offset = i + 1;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
// This is nested parenthesis closing char
|
||||||
|
else{
|
||||||
|
nestedStartingCharCounter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastChar = m_Source[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("There is no closing parenthesize for '" + m_Source.Substring(m_Offset) + "' !");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method QuotedReadToDelimiter
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads string to specified delimiter or to end of underlying string. Notes: Delimiters in quoted string is skipped.
|
||||||
|
/// For example: delimiter = ',', text = '"aaaa,eee",qqqq' - then result is '"aaaa,eee"'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delimiters">Data delimiters.</param>
|
||||||
|
/// <returns>Returns readed string or null if end of string reached.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>delimiters</b> is null reference.</exception>
|
||||||
|
public string QuotedReadToDelimiter(char[] delimiters)
|
||||||
|
{
|
||||||
|
if(delimiters == null){
|
||||||
|
throw new ArgumentNullException("delimiters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.Available == 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToFirstChar();
|
||||||
|
|
||||||
|
StringBuilder currentSplitBuffer = new StringBuilder(); // Holds active
|
||||||
|
bool inQuotedString = false; // Holds flag if position is quoted string or not
|
||||||
|
char lastChar = (char)0;
|
||||||
|
|
||||||
|
for(int i=m_Offset;i<m_Source.Length;i++){
|
||||||
|
char c = (char)Peek(false);
|
||||||
|
|
||||||
|
// Skip escaped(\) "
|
||||||
|
if(lastChar != '\\' && c == '\"'){
|
||||||
|
// Start/end quoted string area
|
||||||
|
inQuotedString = !inQuotedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if char is delimiter
|
||||||
|
bool isDelimiter = false;
|
||||||
|
foreach(char delimiter in delimiters){
|
||||||
|
if(c == delimiter){
|
||||||
|
isDelimiter = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current char is split char and it isn't in quoted string, do split
|
||||||
|
if(!inQuotedString && isDelimiter){
|
||||||
|
return currentSplitBuffer.ToString();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
currentSplitBuffer.Append(c);
|
||||||
|
m_Offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastChar = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached so far then we are end of string, return it.
|
||||||
|
return currentSplitBuffer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of chars has left for processing.
|
||||||
|
/// </summary>
|
||||||
|
public int Available
|
||||||
|
{
|
||||||
|
get{ return m_Source.Length - m_Offset; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
45
MailServer/Misc/MIME/MIME_TransferEncodings.cs
Normal file
45
MailServer/Misc/MIME/MIME_TransferEncodings.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds MIME content transfer encodings. Defined in RFC 2045 6.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_TransferEncodings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Up to 998 octets per line of the code range 1..127 with CR and LF (codes 13 and 10 respectively) only allowed to
|
||||||
|
/// appear as part of a CRLF line ending. This is the default value.
|
||||||
|
/// Defined in RFC 2045 6.2.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string SevenBit = "7bit";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Up to 998 octets per line with CR and LF (codes 13 and 10 respectively) only allowed to appear as part of a CRLF line ending.
|
||||||
|
/// Defined in RFC 2045 6.2.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string EightBit = "8bit";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to encode arbitrary octet sequences into a form that satisfies the rules of 7bit.
|
||||||
|
/// Designed to be efficient and mostly human readable when used for text data consisting primarily of US-ASCII characters
|
||||||
|
/// but also containing a small proportion of bytes with values outside that range.
|
||||||
|
/// Defined in RFC 2045 6.7.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string QuotedPrintable = "quoted-printable";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to encode arbitrary octet sequences into a form that satisfies the rules of 7bit. Has a fixed overhead and is
|
||||||
|
/// intended for non text data and text that is not ASCII heavy.
|
||||||
|
/// Defined in RFC 2045 6.8.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Base64 = "base64";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any sequence of octets. This type is not widely used. Defined in RFC 3030.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Binary = "binary";
|
||||||
|
}
|
||||||
|
}
|
691
MailServer/Misc/MIME/MIME_Utils.cs
Normal file
691
MailServer/Misc/MIME/MIME_Utils.cs
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides MIME related utility methods.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_Utils
|
||||||
|
{
|
||||||
|
#region static method DateTimeToRfc2822
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts date to RFC 2822 date time string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTime">Date time value to convert..</param>
|
||||||
|
/// <returns>Returns RFC 2822 date time string.</returns>
|
||||||
|
public static string DateTimeToRfc2822(DateTime dateTime)
|
||||||
|
{
|
||||||
|
return dateTime.ToString("ddd, dd MMM yyyy HH':'mm':'ss ",System.Globalization.DateTimeFormatInfo.InvariantInfo) + dateTime.ToString("zzz").Replace(":","");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ParseRfc2822DateTime
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses RFC 2822 date-time from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">RFC 2822 date-time string value.</param>
|
||||||
|
/// <returns>Returns parsed datetime value.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public static DateTime ParseRfc2822DateTime(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2822 3.
|
||||||
|
* date-time = [ day-of-week "," ] date FWS time [CFWS]
|
||||||
|
* day-of-week = ([FWS] day-name) / obs-day-of-week
|
||||||
|
* day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
|
||||||
|
* date = day month year
|
||||||
|
* year = 4*DIGIT / obs-year
|
||||||
|
* month = (FWS month-name FWS) / obs-month
|
||||||
|
* month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
|
||||||
|
* day = ([FWS] 1*2DIGIT) / obs-day
|
||||||
|
* time = time-of-day FWS zone
|
||||||
|
* time-of-day = hour ":" minute [ ":" second ]
|
||||||
|
* hour = 2DIGIT / obs-hour
|
||||||
|
* minute = 2DIGIT / obs-minute
|
||||||
|
* second = 2DIGIT / obs-second
|
||||||
|
* zone = (( "+" / "-" ) 4DIGIT) / obs-zone
|
||||||
|
*
|
||||||
|
* The date and time-of-day SHOULD express local time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
try{
|
||||||
|
MIME_Reader r = new MIME_Reader(value);
|
||||||
|
string v = r.Atom();
|
||||||
|
// Skip optional [ day-of-week "," ] and read "day".
|
||||||
|
if(v.Length == 3){
|
||||||
|
r.Char(true);
|
||||||
|
v = r.Atom();
|
||||||
|
}
|
||||||
|
int day = Convert.ToInt32(v);
|
||||||
|
v = r.Atom().ToLower();
|
||||||
|
int month = 1;
|
||||||
|
if(v == "jan"){
|
||||||
|
month = 1;
|
||||||
|
}
|
||||||
|
else if(v == "feb"){
|
||||||
|
month = 2;
|
||||||
|
}
|
||||||
|
else if(v == "mar"){
|
||||||
|
month = 3;
|
||||||
|
}
|
||||||
|
else if(v == "apr"){
|
||||||
|
month = 4;
|
||||||
|
}
|
||||||
|
else if(v == "may"){
|
||||||
|
month = 5;
|
||||||
|
}
|
||||||
|
else if(v == "jun"){
|
||||||
|
month = 6;
|
||||||
|
}
|
||||||
|
else if(v == "jul"){
|
||||||
|
month = 7;
|
||||||
|
}
|
||||||
|
else if(v == "aug"){
|
||||||
|
month = 8;
|
||||||
|
}
|
||||||
|
else if(v == "sep"){
|
||||||
|
month = 9;
|
||||||
|
}
|
||||||
|
else if(v == "oct"){
|
||||||
|
month = 10;
|
||||||
|
}
|
||||||
|
else if(v == "nov"){
|
||||||
|
month = 11;
|
||||||
|
}
|
||||||
|
else if(v == "dec"){
|
||||||
|
month = 12;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("Invalid month-name value '" + value + "'.");
|
||||||
|
}
|
||||||
|
int year = Convert.ToInt32(r.Atom());
|
||||||
|
int hour = Convert.ToInt32(r.Atom());
|
||||||
|
r.Char(true);
|
||||||
|
int minute = Convert.ToInt32(r.Atom());
|
||||||
|
int second = 0;
|
||||||
|
// We have optional "second".
|
||||||
|
if(r.Peek(true) == ':'){
|
||||||
|
r.Char(true);
|
||||||
|
second = Convert.ToInt32(r.Atom());
|
||||||
|
}
|
||||||
|
int timeZoneMinutes = 0;
|
||||||
|
v = r.Atom();
|
||||||
|
// We have RFC 2822 date. For example: +2000.
|
||||||
|
if(v[0] == '+' || v[0] == '-'){
|
||||||
|
if(v[0] == '+'){
|
||||||
|
timeZoneMinutes = (Convert.ToInt32(v.Substring(1,2)) * 60 + Convert.ToInt32(v.Substring(3,2)));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
timeZoneMinutes = -(Convert.ToInt32(v.Substring(1,2)) * 60 + Convert.ToInt32(v.Substring(3,2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have RFC 822 date with abbrevated time zone name. For example: GMT.
|
||||||
|
else{
|
||||||
|
v = v.ToUpper();
|
||||||
|
|
||||||
|
#region time zones
|
||||||
|
|
||||||
|
// Alpha Time Zone (military).
|
||||||
|
if(v == "A"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Australian Central Daylight Time.
|
||||||
|
else if(v == "ACDT"){
|
||||||
|
timeZoneMinutes = ((10 * 60) + 30);
|
||||||
|
}
|
||||||
|
// Australian Central Standard Time.
|
||||||
|
else if(v == "ACST"){
|
||||||
|
timeZoneMinutes = ((09 * 60) + 30);
|
||||||
|
}
|
||||||
|
// Atlantic Daylight Time.
|
||||||
|
else if(v == "ADT"){
|
||||||
|
timeZoneMinutes = -((03 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Australian Eastern Daylight Time.
|
||||||
|
else if(v == "AEDT"){
|
||||||
|
timeZoneMinutes = ((11 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Australian Eastern Standard Time.
|
||||||
|
else if(v == "AEST"){
|
||||||
|
timeZoneMinutes = ((10 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Alaska Daylight Time.
|
||||||
|
else if(v == "AKDT"){
|
||||||
|
timeZoneMinutes = -((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Alaska Standard Time.
|
||||||
|
else if(v == "AKST"){
|
||||||
|
timeZoneMinutes = -((09 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Atlantic Standard Time.
|
||||||
|
else if(v == "AST"){
|
||||||
|
timeZoneMinutes = -((04 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Australian Western Daylight Time.
|
||||||
|
else if(v == "AWDT"){
|
||||||
|
timeZoneMinutes = ((09 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Australian Western Standard Time.
|
||||||
|
else if(v == "AWST"){
|
||||||
|
timeZoneMinutes = ((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Bravo Time Zone (millitary).
|
||||||
|
else if(v == "B"){
|
||||||
|
timeZoneMinutes = ((02 * 60) + 00);
|
||||||
|
}
|
||||||
|
// British Summer Time.
|
||||||
|
else if(v == "BST"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Charlie Time Zone (millitary).
|
||||||
|
else if(v == "C"){
|
||||||
|
timeZoneMinutes = ((03 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Central Daylight Time.
|
||||||
|
else if(v == "CDT"){
|
||||||
|
timeZoneMinutes = -((05 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Central European Daylight Time.
|
||||||
|
else if(v == "CEDT"){
|
||||||
|
timeZoneMinutes = ((02 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Central European Summer Time.
|
||||||
|
else if(v == "CEST"){
|
||||||
|
timeZoneMinutes = ((02 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Central European Time.
|
||||||
|
else if(v == "CET"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Central Standard Time.
|
||||||
|
else if(v == "CST"){
|
||||||
|
timeZoneMinutes = -((06 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Christmas Island Time.
|
||||||
|
else if(v == "CXT"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Delta Time Zone (military).
|
||||||
|
else if(v == "D"){
|
||||||
|
timeZoneMinutes = ((04 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Echo Time Zone (military).
|
||||||
|
else if(v == "E"){
|
||||||
|
timeZoneMinutes = ((05 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Eastern Daylight Time.
|
||||||
|
else if(v == "EDT"){
|
||||||
|
timeZoneMinutes = -((04 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Eastern European Daylight Time.
|
||||||
|
else if(v == "EEDT"){
|
||||||
|
timeZoneMinutes = ((03 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Eastern European Summer Time.
|
||||||
|
else if(v == "EEST"){
|
||||||
|
timeZoneMinutes = ((03 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Eastern European Time.
|
||||||
|
else if(v == "EET"){
|
||||||
|
timeZoneMinutes = ((02 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Eastern Standard Time.
|
||||||
|
else if(v == "EST"){
|
||||||
|
timeZoneMinutes = -((05 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Foxtrot Time Zone (military).
|
||||||
|
else if(v == "F"){
|
||||||
|
timeZoneMinutes = (06 * 60 + 00);
|
||||||
|
}
|
||||||
|
// Golf Time Zone (military).
|
||||||
|
else if(v == "G"){
|
||||||
|
timeZoneMinutes = ((07 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Greenwich Mean Time.
|
||||||
|
else if(v == "GMT"){
|
||||||
|
timeZoneMinutes = 0000;
|
||||||
|
}
|
||||||
|
// Hotel Time Zone (military).
|
||||||
|
else if(v == "H"){
|
||||||
|
timeZoneMinutes = ((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// India Time Zone (military).
|
||||||
|
else if(v == "I"){
|
||||||
|
timeZoneMinutes = ((09 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Irish Summer Time.
|
||||||
|
else if(v == "IST"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Kilo Time Zone (millitary).
|
||||||
|
else if(v == "K"){
|
||||||
|
timeZoneMinutes = ((10 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Lima Time Zone (millitary).
|
||||||
|
else if(v == "L"){
|
||||||
|
timeZoneMinutes = ((11 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Mike Time Zone (millitary).
|
||||||
|
else if(v == "M"){
|
||||||
|
timeZoneMinutes = ((12 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Mountain Daylight Time.
|
||||||
|
else if(v == "MDT"){
|
||||||
|
timeZoneMinutes = -((06 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Mountain Standard Time.
|
||||||
|
else if(v == "MST"){
|
||||||
|
timeZoneMinutes = -((07 * 60) + 00);
|
||||||
|
}
|
||||||
|
// November Time Zone (military).
|
||||||
|
else if(v == "N"){
|
||||||
|
timeZoneMinutes = -((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Newfoundland Daylight Time.
|
||||||
|
else if(v == "NDT"){
|
||||||
|
timeZoneMinutes = -((02 * 60) + 30);
|
||||||
|
}
|
||||||
|
// Norfolk (Island) Time.
|
||||||
|
else if(v == "NFT"){
|
||||||
|
timeZoneMinutes = ((11 * 60) + 30);
|
||||||
|
}
|
||||||
|
// Newfoundland Standard Time.
|
||||||
|
else if(v == "NST"){
|
||||||
|
timeZoneMinutes = -((03 * 60) + 30);
|
||||||
|
}
|
||||||
|
// Oscar Time Zone (military).
|
||||||
|
else if(v == "O"){
|
||||||
|
timeZoneMinutes = -((02 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Papa Time Zone (military).
|
||||||
|
else if(v == "P"){
|
||||||
|
timeZoneMinutes = -((03 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Pacific Daylight Time.
|
||||||
|
else if(v == "PDT"){
|
||||||
|
timeZoneMinutes = -((07 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Pacific Standard Time.
|
||||||
|
else if(v == "PST"){
|
||||||
|
timeZoneMinutes = -((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Quebec Time Zone (military).
|
||||||
|
else if(v == "Q"){
|
||||||
|
timeZoneMinutes = -((04 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Romeo Time Zone (military).
|
||||||
|
else if(v == "R"){
|
||||||
|
timeZoneMinutes = -((05 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Sierra Time Zone (military).
|
||||||
|
else if(v == "S"){
|
||||||
|
timeZoneMinutes = -((06 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Tango Time Zone (military).
|
||||||
|
else if(v == "T"){
|
||||||
|
timeZoneMinutes = -((07 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Uniform Time Zone (military).
|
||||||
|
else if(v == ""){
|
||||||
|
timeZoneMinutes = -((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Coordinated Universal Time.
|
||||||
|
else if(v == "UTC"){
|
||||||
|
timeZoneMinutes = 0000;
|
||||||
|
}
|
||||||
|
// Victor Time Zone (militray).
|
||||||
|
else if(v == "V"){
|
||||||
|
timeZoneMinutes = -((09 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Whiskey Time Zone (military).
|
||||||
|
else if(v == "W"){
|
||||||
|
timeZoneMinutes = -((10 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Western European Daylight Time.
|
||||||
|
else if(v == "WEDT"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Western European Summer Time.
|
||||||
|
else if(v == "WEST"){
|
||||||
|
timeZoneMinutes = ((01 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Western European Time.
|
||||||
|
else if(v == "WET"){
|
||||||
|
timeZoneMinutes = 0000;
|
||||||
|
}
|
||||||
|
// Western Standard Time.
|
||||||
|
else if(v == "WST"){
|
||||||
|
timeZoneMinutes = ((08 * 60) + 00);
|
||||||
|
}
|
||||||
|
// X-ray Time Zone (military).
|
||||||
|
else if(v == "X"){
|
||||||
|
timeZoneMinutes = -((11 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Yankee Time Zone (military).
|
||||||
|
else if(v == "Y"){
|
||||||
|
timeZoneMinutes = -((12 * 60) + 00);
|
||||||
|
}
|
||||||
|
// Zulu Time Zone (military).
|
||||||
|
else if(v == "Z"){
|
||||||
|
timeZoneMinutes = 0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert time to UTC and then back to local.
|
||||||
|
DateTime timeUTC = new DateTime(year,month,day,hour,minute,second).AddMinutes(-(timeZoneMinutes));
|
||||||
|
return new DateTime(timeUTC.Year,timeUTC.Month,timeUTC.Day,timeUTC.Hour,timeUTC.Minute,timeUTC.Second,DateTimeKind.Utc).ToLocalTime();
|
||||||
|
}
|
||||||
|
catch(Exception x){
|
||||||
|
string dymmy = x.Message;
|
||||||
|
throw new ArgumentException("Argumnet 'value' value '" + value + "' is not valid RFC 822/2822 date-time string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method UnfoldHeader
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unfolds folded header field.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field.</param>
|
||||||
|
/// <returns>Returns unfolded header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public static string UnfoldHeader(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2822 2.2.3 Long Header Fields.
|
||||||
|
The process of moving from this folded multiple-line representation
|
||||||
|
of a header field to its single line representation is called
|
||||||
|
"unfolding". Unfolding is accomplished by simply removing any CRLF
|
||||||
|
that is immediately followed by WSP.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return value.Replace("\r\n","");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method CreateMessageID
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates Rfc 2822 3.6.4 message-id. Syntax: '<' id-left '@' id-right '>'.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string CreateMessageID()
|
||||||
|
{
|
||||||
|
return "<" + Guid.NewGuid().ToString().Replace("-","").Substring(16) + "@" + Guid.NewGuid().ToString().Replace("-","").Substring(16) + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method ParseHeaders
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses headers from message or mime entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entryStrm">Stream from where to read headers.</param>
|
||||||
|
/// <returns>Returns header lines.</returns>
|
||||||
|
internal static string ParseHeaders(Stream entryStrm)
|
||||||
|
{
|
||||||
|
/* Rfc 2822 3.1. GENERAL DESCRIPTION
|
||||||
|
A message consists of header fields and, optionally, a body.
|
||||||
|
The body is simply a sequence of lines containing ASCII charac-
|
||||||
|
ters. It is separated from the headers by a null line (i.e., a
|
||||||
|
line with nothing preceding the CRLF).
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte[] crlf = new byte[]{(byte)'\r',(byte)'\n'};
|
||||||
|
MemoryStream msHeaders = new MemoryStream();
|
||||||
|
StreamLineReader r = new StreamLineReader(entryStrm);
|
||||||
|
byte[] lineData = r.ReadLine();
|
||||||
|
while(lineData != null){
|
||||||
|
if(lineData.Length == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
msHeaders.Write(lineData,0,lineData.Length);
|
||||||
|
msHeaders.Write(crlf,0,crlf.Length);
|
||||||
|
lineData = r.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.Text.Encoding.Default.GetString(msHeaders.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ParseHeaderField
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse header specified header field value.
|
||||||
|
///
|
||||||
|
/// Use this method only if you need to get only one header field, otherwise use
|
||||||
|
/// MimeParser.ParseHeaderField(string fieldName,string headers).
|
||||||
|
/// This avoid parsing headers multiple times.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">Header field which to parse. Eg. Subject: .</param>
|
||||||
|
/// <param name="entryStrm">Stream from where to read headers.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string ParseHeaderField(string fieldName,Stream entryStrm)
|
||||||
|
{
|
||||||
|
return ParseHeaderField(fieldName,ParseHeaders(entryStrm));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse header specified header field value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fieldName">Header field which to parse. Eg. Subject: .</param>
|
||||||
|
/// <param name="headers">Full headers string. Use MimeParser.ParseHeaders() to get this value.</param>
|
||||||
|
public static string ParseHeaderField(string fieldName,string headers)
|
||||||
|
{
|
||||||
|
/* Rfc 2822 2.2 Header Fields
|
||||||
|
Header fields are lines composed of a field name, followed by a colon
|
||||||
|
(":"), followed by a field body, and terminated by CRLF. A field
|
||||||
|
name MUST be composed of printable US-ASCII characters (i.e.,
|
||||||
|
characters that have values between 33 and 126, inclusive), except
|
||||||
|
colon. A field body may be composed of any US-ASCII characters,
|
||||||
|
except for CR and LF. However, a field body may contain CRLF when
|
||||||
|
used in header "folding" and "unfolding" as described in section
|
||||||
|
2.2.3. All field bodies MUST conform to the syntax described in
|
||||||
|
sections 3 and 4 of this standard.
|
||||||
|
|
||||||
|
Rfc 2822 2.2.3 (Multiline header fields)
|
||||||
|
The process of moving from this folded multiple-line representation
|
||||||
|
of a header field to its single line representation is called
|
||||||
|
"unfolding". Unfolding is accomplished by simply removing any CRLF
|
||||||
|
that is immediately followed by WSP. Each header field should be
|
||||||
|
treated in its unfolded form for further syntactic and semantic
|
||||||
|
evaluation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Subject: aaaaa<CRLF>
|
||||||
|
<TAB or SP>aaaaa<CRLF>
|
||||||
|
*/
|
||||||
|
|
||||||
|
using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.Default.GetBytes(headers)))){
|
||||||
|
string line = r.ReadLine();
|
||||||
|
while(line != null){
|
||||||
|
// Find line where field begins
|
||||||
|
if(line.ToUpper().StartsWith(fieldName.ToUpper())){
|
||||||
|
// Remove field name and start reading value
|
||||||
|
string fieldValue = line.Substring(fieldName.Length).Trim();
|
||||||
|
|
||||||
|
// see if multi line value. See commnt above.
|
||||||
|
line = r.ReadLine();
|
||||||
|
while(line != null && (line.StartsWith("\t") || line.StartsWith(" "))){
|
||||||
|
fieldValue += line;
|
||||||
|
line = r.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = r.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method QDecode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Q" decoder. This is same as quoted-printable, except '_' is converted to ' '.
|
||||||
|
/// Defined in RFC 2047 4.2.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoding">Input string encoding.</param>
|
||||||
|
/// <param name="data">String which to encode.</param>
|
||||||
|
/// <returns>Returns decoded string.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>encoding</b> or <b>data</b> is null reference.</exception>
|
||||||
|
public static string QDecode(Encoding encoding,string data)
|
||||||
|
{
|
||||||
|
if(encoding == null){
|
||||||
|
throw new ArgumentNullException("encoding");
|
||||||
|
}
|
||||||
|
if(data == null){
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoding.GetString(QuotedPrintableDecode(Encoding.ASCII.GetBytes(data.Replace("_"," "))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method QuotedPrintableDecode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// quoted-printable decoder. Defined in RFC 2045 6.7.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data which to encode.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
|
||||||
|
public static byte[] QuotedPrintableDecode(byte[] data)
|
||||||
|
{
|
||||||
|
if(data == null){
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2045 6.7. Quoted-Printable Content-Transfer-Encoding
|
||||||
|
|
||||||
|
(1) (General 8bit representation) Any octet, except a CR or
|
||||||
|
LF that is part of a CRLF line break of the canonical
|
||||||
|
(standard) form of the data being encoded, may be
|
||||||
|
represented by an "=" followed by a two digit
|
||||||
|
hexadecimal representation of the octet's value. The
|
||||||
|
digits of the hexadecimal alphabet, for this purpose,
|
||||||
|
are "0123456789ABCDEF". Uppercase letters must be
|
||||||
|
used; lowercase letters are not allowed.
|
||||||
|
|
||||||
|
(2) (Literal representation) Octets with decimal values of
|
||||||
|
33 through 60 inclusive, and 62 through 126, inclusive,
|
||||||
|
MAY be represented as the US-ASCII characters which
|
||||||
|
correspond to those octets (EXCLAMATION POINT through
|
||||||
|
LESS THAN, and GREATER THAN through TILDE, respectively).
|
||||||
|
|
||||||
|
(3) (White Space) Octets with values of 9 and 32 MAY be
|
||||||
|
represented as US-ASCII TAB (HT) and SPACE characters,
|
||||||
|
respectively, but MUST NOT be so represented at the end
|
||||||
|
of an encoded line. Any TAB (HT) or SPACE characters
|
||||||
|
on an encoded line MUST thus be followed on that line
|
||||||
|
by a printable character. In particular, an "=" at the
|
||||||
|
end of an encoded line, indicating a soft line break
|
||||||
|
(see rule #5) may follow one or more TAB (HT) or SPACE
|
||||||
|
characters. It follows that an octet with decimal
|
||||||
|
value 9 or 32 appearing at the end of an encoded line
|
||||||
|
must be represented according to Rule #1. This rule is
|
||||||
|
necessary because some MTAs (Message Transport Agents,
|
||||||
|
programs which transport messages from one user to
|
||||||
|
another, or perform a portion of such transfers) are
|
||||||
|
known to pad lines of text with SPACEs, and others are
|
||||||
|
known to remove "white space" characters from the end
|
||||||
|
of a line. Therefore, when decoding a Quoted-Printable
|
||||||
|
body, any trailing white space on a line must be
|
||||||
|
deleted, as it will necessarily have been added by
|
||||||
|
intermediate transport agents.
|
||||||
|
|
||||||
|
(4) (Line Breaks) A line break in a text body, represented
|
||||||
|
as a CRLF sequence in the text canonical form, must be
|
||||||
|
represented by a (RFC 822) line break, which is also a
|
||||||
|
CRLF sequence, in the Quoted-Printable encoding. Since
|
||||||
|
the canonical representation of media types other than
|
||||||
|
text do not generally include the representation of
|
||||||
|
line breaks as CRLF sequences, no hard line breaks
|
||||||
|
(i.e. line breaks that are intended to be meaningful
|
||||||
|
and to be displayed to the user) can occur in the
|
||||||
|
quoted-printable encoding of such types. Sequences
|
||||||
|
like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
|
||||||
|
appear in non-text data represented in quoted-
|
||||||
|
printable, of course.
|
||||||
|
|
||||||
|
(5) (Soft Line Breaks) The Quoted-Printable encoding
|
||||||
|
REQUIRES that encoded lines be no more than 76
|
||||||
|
characters long. If longer lines are to be encoded
|
||||||
|
with the Quoted-Printable encoding, "soft" line breaks
|
||||||
|
*/
|
||||||
|
|
||||||
|
MemoryStream msRetVal = new MemoryStream();
|
||||||
|
MemoryStream msSourceStream = new MemoryStream(data);
|
||||||
|
|
||||||
|
int b = msSourceStream.ReadByte();
|
||||||
|
while(b > -1){
|
||||||
|
// Encoded 8-bit byte(=XX) or soft line break(=CRLF)
|
||||||
|
if(b == '='){
|
||||||
|
byte[] buffer = new byte[2];
|
||||||
|
int nCount = msSourceStream.Read(buffer,0,2);
|
||||||
|
if(nCount == 2){
|
||||||
|
// Soft line break, line splitted, just skip CRLF
|
||||||
|
if(buffer[0] == '\r' && buffer[1] == '\n'){
|
||||||
|
}
|
||||||
|
// This must be encoded 8-bit byte
|
||||||
|
else{
|
||||||
|
try{
|
||||||
|
msRetVal.Write(Net_Utils.FromHex(buffer),0,1);
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
// Illegal value after =, just leave it as is
|
||||||
|
msRetVal.WriteByte((byte)'=');
|
||||||
|
msRetVal.Write(buffer,0,2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Illegal =, just leave as it is
|
||||||
|
else{
|
||||||
|
msRetVal.Write(buffer,0,nCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Just write back all other bytes
|
||||||
|
else{
|
||||||
|
msRetVal.WriteByte((byte)b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
b = msSourceStream.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
return msRetVal.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
124
MailServer/Misc/MIME/MIME_b.cs
Normal file
124
MailServer/Misc/MIME/MIME_b.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is base class for MIME entity bodies.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MIME_b
|
||||||
|
{
|
||||||
|
private MIME_Entity m_pEntity = null;
|
||||||
|
private MIME_h_ContentType m_pContentType = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b(MIME_h_ContentType contentType)
|
||||||
|
{
|
||||||
|
if(contentType == null){
|
||||||
|
throw new ArgumentNullException("contentType");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pContentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotImplementedException("Body provider class does not implement required Parse method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method SetParent
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body parent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">Owner entity.</param>
|
||||||
|
/// <param name="setContentType">If true sets entity.ContentType header value.</param>
|
||||||
|
internal virtual void SetParent(MIME_Entity entity,bool setContentType)
|
||||||
|
{
|
||||||
|
m_pEntity = entity;
|
||||||
|
|
||||||
|
// Owner entity has no content-type or has different content-type, just add/overwrite it.
|
||||||
|
if(setContentType &&(entity.ContentType == null || !string.Equals(entity.ContentType.TypeWithSubype,this.MediaType,StringComparison.InvariantCultureIgnoreCase))){
|
||||||
|
entity.ContentType = m_pContentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity body to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store body data.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
internal protected abstract void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if body has modified.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool IsModified
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body owner entity. Returns null if body not bounded to any entity yet.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_Entity Entity
|
||||||
|
{
|
||||||
|
get{ return m_pEntity; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body media type. For example: 'text/plain'.
|
||||||
|
/// </summary>
|
||||||
|
public string MediaType
|
||||||
|
{
|
||||||
|
get{ return m_pContentType.TypeWithSubype; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
72
MailServer/Misc/MIME/MIME_b_Application.cs
Normal file
72
MailServer/Misc/MIME/MIME_b_Application.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME application/xxx bodies. Defined in RFC 2046 4.2.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "application" media type is to be used for discrete data which do
|
||||||
|
/// not fit in any of the other categories, and particularly for data to
|
||||||
|
/// be processed by some type of application program.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_Application : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Application(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>strean</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Application retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Application(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Application(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
67
MailServer/Misc/MIME/MIME_b_Audio.cs
Normal file
67
MailServer/Misc/MIME/MIME_b_Audio.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME audio/xxx bodies. Defined in RFC 2046 4.3.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>A media type of "audio" indicates that the body contains audio data.</remarks>
|
||||||
|
public class MIME_b_Audio : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Audio(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Audio retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Audio(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Audio(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
70
MailServer/Misc/MIME/MIME_b_Image.cs
Normal file
70
MailServer/Misc/MIME/MIME_b_Image.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME image/xxx bodies. Defined in RFC 2046 4.2.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A media type of "image" indicates that the body contains an image.
|
||||||
|
/// The subtype names the specific image format.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_Image : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Image(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Image retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Image(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Image(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
67
MailServer/Misc/MIME/MIME_b_Message.cs
Normal file
67
MailServer/Misc/MIME/MIME_b_Message.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME message/xxx bodies. Defined in RFC 2046 5.2.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_Message : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Message(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Message retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Message(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Message(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
165
MailServer/Misc/MIME/MIME_b_MessageDeliveryStatus.cs
Normal file
165
MailServer/Misc/MIME/MIME_b_MessageDeliveryStatus.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
using LumiSoft.Net.Mail;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME <b>message/delivery-status</b> body. Defined in RFC 3464.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <code>
|
||||||
|
/// delivery-status-content = per-message-fields 1*( CRLF per-recipient-fields )
|
||||||
|
///
|
||||||
|
/// per-message-fields =
|
||||||
|
/// [ original-envelope-id-field CRLF ]
|
||||||
|
/// reporting-mta-field CRLF
|
||||||
|
/// [ dsn-gateway-field CRLF ]
|
||||||
|
/// [ received-from-mta-field CRLF ]
|
||||||
|
/// [ arrival-date-field CRLF ]
|
||||||
|
/// *( extension-field CRLF )
|
||||||
|
///
|
||||||
|
/// per-recipient-fields =
|
||||||
|
/// [ original-recipient-field CRLF ]
|
||||||
|
/// final-recipient-field CRLF
|
||||||
|
/// action-field CRLF
|
||||||
|
/// status-field CRLF
|
||||||
|
/// [ remote-mta-field CRLF ]
|
||||||
|
/// [ diagnostic-code-field CRLF ]
|
||||||
|
/// [ last-attempt-date-field CRLF ]
|
||||||
|
/// [ final-log-id-field CRLF ]
|
||||||
|
/// [ will-retry-until-field CRLF ]
|
||||||
|
/// *( extension-field CRLF )
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MessageDeliveryStatus : MIME_b
|
||||||
|
{
|
||||||
|
private MIME_h_Collection m_pMessageFields = null;
|
||||||
|
private List<MIME_h_Collection> m_pRecipientBlocks = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_b_MessageDeliveryStatus() : base(new MIME_h_ContentType("message/delivery-status"))
|
||||||
|
{
|
||||||
|
m_pMessageFields = new MIME_h_Collection(new MIME_h_Provider());
|
||||||
|
m_pRecipientBlocks = new List<MIME_h_Collection>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MessageDeliveryStatus retVal = new MIME_b_MessageDeliveryStatus();
|
||||||
|
//Pare per-message fields.
|
||||||
|
retVal.m_pMessageFields.Parse(stream);
|
||||||
|
// Parse per-recipient fields.
|
||||||
|
while(true){
|
||||||
|
MIME_h_Collection recipientFields = new MIME_h_Collection(new MIME_h_Provider());
|
||||||
|
recipientFields.Parse(stream);
|
||||||
|
if(recipientFields.Count == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal.m_pRecipientBlocks.Add(recipientFields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity body to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store body data.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
internal protected override void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pMessageFields.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
stream.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);
|
||||||
|
foreach(MIME_h_Collection recipientBlock in m_pRecipientBlocks){
|
||||||
|
recipientBlock.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
stream.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if body has modified.
|
||||||
|
/// </summary>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_pMessageFields.IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach(MIME_h_Collection recipientBlock in m_pRecipientBlocks){
|
||||||
|
if(recipientBlock.IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets per-message fields collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public MIME_h_Collection MessageFields
|
||||||
|
{
|
||||||
|
get{ return m_pMessageFields; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets reciepent report blocks collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Each block contains per-recipient-fields.</remarks>
|
||||||
|
public List<MIME_h_Collection> RecipientBlocks
|
||||||
|
{
|
||||||
|
get{ return m_pRecipientBlocks; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
118
MailServer/Misc/MIME/MIME_b_MessageRfc822.cs
Normal file
118
MailServer/Misc/MIME/MIME_b_MessageRfc822.cs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
using LumiSoft.Net.Mail;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME message/rfc822 body. Defined in RFC 2046 5.2.1.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_MessageRfc822 : MIME_b
|
||||||
|
{
|
||||||
|
private Mail_Message m_pMessage = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_b_MessageRfc822() : base(new MIME_h_ContentType("message/rfc822"))
|
||||||
|
{
|
||||||
|
m_pMessage = new Mail_Message();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MessageRfc822 retVal = new MIME_b_MessageRfc822();
|
||||||
|
retVal.m_pMessage = Mail_Message.ParseFromStream(stream);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity body to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store body data.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
internal protected override void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pMessage.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if body has modified.
|
||||||
|
/// </summary>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_pMessage.IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets embbed mail message.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when null reference passed.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public Mail_Message Message
|
||||||
|
{
|
||||||
|
get{ return m_pMessage; }
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
if(this.Entity == null){
|
||||||
|
throw new InvalidOperationException("Body must be bounded to some entity first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner entity has no content-type or has different content-type, just add/overwrite it.
|
||||||
|
if(this.Entity.ContentType == null || !string.Equals(this.Entity.ContentType.TypeWithSubype,this.MediaType,StringComparison.InvariantCultureIgnoreCase)){
|
||||||
|
this.Entity.ContentType = new MIME_h_ContentType(this.MediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pMessage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
767
MailServer/Misc/MIME/MIME_b_Multipart.cs
Normal file
767
MailServer/Misc/MIME/MIME_b_Multipart.cs
Normal file
@ -0,0 +1,767 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME application/xxx bodies. Defined in RFC 2046 5.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "multipart" represents single MIME body containing multiple child MIME entities.
|
||||||
|
/// The "multipart" body must contain at least 1 MIME entity.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_Multipart : MIME_b
|
||||||
|
{
|
||||||
|
#region class _MultipartReader
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements multipart "body parts" reader.
|
||||||
|
/// </summary>
|
||||||
|
public class _MultipartReader : Stream
|
||||||
|
{
|
||||||
|
#region enum State
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This enum specified multipart reader sate.
|
||||||
|
/// </summary>
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// First boundary must be seeked.
|
||||||
|
/// </summary>
|
||||||
|
SeekFirst = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read next boundary. (Method Next must be called to continue next boundary reading)
|
||||||
|
/// </summary>
|
||||||
|
ReadNext = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Active boundary reading pending.
|
||||||
|
/// </summary>
|
||||||
|
InBoundary = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All boundraies readed.
|
||||||
|
/// </summary>
|
||||||
|
Done = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region class _DataLine
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class holds readed data line info.
|
||||||
|
/// </summary>
|
||||||
|
private class _DataLine
|
||||||
|
{
|
||||||
|
private byte[] m_pLineBuffer = null;
|
||||||
|
private int m_BytesInBuffer = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public _DataLine()
|
||||||
|
{
|
||||||
|
m_pLineBuffer = new byte[32000];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method AssignFrom
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assigns data line info from rea line operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="op">Read line operation.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception>
|
||||||
|
public void AssignFrom(SmartStream.ReadLineAsyncOP op)
|
||||||
|
{
|
||||||
|
if(op == null){
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_BytesInBuffer = op.BytesInBuffer;
|
||||||
|
Array.Copy(op.Buffer,m_pLineBuffer,op.BytesInBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets line data buffer.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] LineBuffer
|
||||||
|
{
|
||||||
|
get{ return m_pLineBuffer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of bytes used in <b>LineBuffer</b>.
|
||||||
|
/// </summary>
|
||||||
|
public int BytesInBuffer
|
||||||
|
{
|
||||||
|
get{ return m_BytesInBuffer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private State m_State = State.SeekFirst;
|
||||||
|
private SmartStream m_pStream = null;
|
||||||
|
private string m_Boundary = "";
|
||||||
|
private _DataLine m_pPreviousLine = null;
|
||||||
|
private SmartStream.ReadLineAsyncOP m_pReadLineOP = null;
|
||||||
|
private StringBuilder m_pTextPreamble = null;
|
||||||
|
private StringBuilder m_pTextEpilogue = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream from where to read body part.</param>
|
||||||
|
/// <param name="boundary">Boundry ID what separates body parts.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>boundary</b> is null reference.</exception>
|
||||||
|
public _MultipartReader(SmartStream stream,string boundary)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(boundary == null){
|
||||||
|
throw new ArgumentNullException("boundary");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pStream = stream;
|
||||||
|
m_Boundary = boundary;
|
||||||
|
|
||||||
|
m_pReadLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.ThrowException);
|
||||||
|
m_pTextPreamble = new StringBuilder();
|
||||||
|
m_pTextEpilogue = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Next
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves to next "body part". Returns true if moved to next "body part" or false if there are no more parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if moved to next "body part" or false if there are no more body parts.</returns>
|
||||||
|
public bool Next()
|
||||||
|
{
|
||||||
|
/* RFC 2046 5.1.1.
|
||||||
|
NOTE: The CRLF preceding the boundary delimiter line is conceptually
|
||||||
|
attached to the boundary so that it is possible to have a part that
|
||||||
|
does not end with a CRLF (line break).
|
||||||
|
*/
|
||||||
|
|
||||||
|
m_pPreviousLine = null;
|
||||||
|
|
||||||
|
if(m_State == State.Done){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(m_State == State.SeekFirst){
|
||||||
|
while(true){
|
||||||
|
m_pStream.ReadLine(m_pReadLineOP,false);
|
||||||
|
if(m_pReadLineOP.Error != null){
|
||||||
|
throw m_pReadLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream. Bad boundary: boundary end tag missing.
|
||||||
|
else if(m_pReadLineOP.BytesInBuffer == 0){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Check if we have boundary start/end.
|
||||||
|
if(m_pReadLineOP.Buffer[0] == '-'){
|
||||||
|
string boundary = m_pReadLineOP.LineUtf8;
|
||||||
|
// We have readed all MIME entity body parts.
|
||||||
|
if("--" + m_Boundary + "--" == boundary){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
// Last CRLF is no part of preamble, but is part of boundary-tag.
|
||||||
|
m_pTextPreamble.Remove(m_pTextPreamble.Length - 2,2);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We have next boundary.
|
||||||
|
else if("--" + m_Boundary == boundary){
|
||||||
|
m_State = State.InBoundary;
|
||||||
|
|
||||||
|
// Last CRLF is no part of preamble, but is part of boundary-tag.
|
||||||
|
if(m_pTextPreamble.Length >= 2){
|
||||||
|
m_pTextPreamble.Remove(m_pTextPreamble.Length - 2,2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Not boundary or not boundary we want.
|
||||||
|
//else{
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pTextPreamble.Append(m_pReadLineOP.LineUtf8 + "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(m_State == State.ReadNext){
|
||||||
|
m_State = State.InBoundary;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override method Flush
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
|
||||||
|
/// </summary>
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method Seek
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the position within the current stream. This method is not supported and always throws a NotSupportedException.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">A byte offset relative to the <b>origin</b> parameter.</param>
|
||||||
|
/// <param name="origin">A value of type SeekOrigin indicating the reference point used to obtain the new position.</param>
|
||||||
|
/// <returns>The new position within the current stream.</returns>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when this method is accessed.</exception>
|
||||||
|
public override long Seek(long offset,SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method SetLength
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the length of the current stream. This method is not supported and always throws a NotSupportedException.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The desired length of the current stream in bytes.</param>
|
||||||
|
/// <exception cref="Seek">Is raised when this method is accessed.</exception>
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method Read
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||||
|
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
|
||||||
|
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||||
|
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>buffer</b> is null reference.</exception>
|
||||||
|
public override int Read(byte[] buffer,int offset,int count)
|
||||||
|
{
|
||||||
|
if(buffer == null){
|
||||||
|
throw new ArgumentNullException("buffer");
|
||||||
|
}
|
||||||
|
if(m_State == State.SeekFirst){
|
||||||
|
throw new InvalidOperationException("Read method is not valid in '" + m_State + "' state.");
|
||||||
|
}
|
||||||
|
if(m_State == State.ReadNext || m_State == State.Done){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2046 5.1.1.
|
||||||
|
NOTE: The CRLF preceding the boundary delimiter line is conceptually
|
||||||
|
attached to the boundary so that it is possible to have a part that
|
||||||
|
does not end with a CRLF (line break).
|
||||||
|
|
||||||
|
NOTE: We just need read 1 line ahead, oterwise we can't remove boundary preceeding CRLF.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Read line ahead, if none available. This is done for the boundary first line only.
|
||||||
|
if(m_pPreviousLine == null){
|
||||||
|
m_pPreviousLine = new _DataLine();
|
||||||
|
|
||||||
|
m_pStream.ReadLine(m_pReadLineOP,false);
|
||||||
|
if(m_pReadLineOP.Error != null){
|
||||||
|
throw m_pReadLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream. Bad boundary: boundary end tag missing.
|
||||||
|
else if(m_pReadLineOP.BytesInBuffer == 0){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// We have readed all MIME entity body parts.(boundary end tag reached)
|
||||||
|
else if(m_pReadLineOP.Buffer[0] == '-' && string.Equals("--" + m_Boundary + "--",m_pReadLineOP.LineUtf8)){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
// Read "epilogoue",if has any.
|
||||||
|
while(true){
|
||||||
|
m_pStream.ReadLine(m_pReadLineOP,false);
|
||||||
|
|
||||||
|
if(m_pReadLineOP.Error != null){
|
||||||
|
throw m_pReadLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream. Epilogue reading completed.
|
||||||
|
else if(m_pReadLineOP.BytesInBuffer == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_pTextEpilogue.Append(m_pReadLineOP.LineUtf8 + "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// We have readed all active boundary data, next boundary start tag.
|
||||||
|
else if(m_pReadLineOP.Buffer[0] == '-' && string.Equals("--" + m_Boundary,m_pReadLineOP.LineUtf8)){
|
||||||
|
m_State = State.ReadNext;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Store first read-ahed line.
|
||||||
|
else{
|
||||||
|
m_pPreviousLine.AssignFrom(m_pReadLineOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pStream.ReadLine(m_pReadLineOP,false);
|
||||||
|
if(m_pReadLineOP.Error != null){
|
||||||
|
throw m_pReadLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream. Bad boundary: boundary end tag missing.
|
||||||
|
else if(m_pReadLineOP.BytesInBuffer == 0){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
if(count < m_pPreviousLine.BytesInBuffer){
|
||||||
|
throw new ArgumentException("Argument 'buffer' is to small. This should never happen.");
|
||||||
|
}
|
||||||
|
if(m_pPreviousLine.BytesInBuffer > 0){
|
||||||
|
Array.Copy(m_pPreviousLine.LineBuffer,0,buffer,offset,m_pPreviousLine.BytesInBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pPreviousLine.BytesInBuffer;
|
||||||
|
}
|
||||||
|
// We have readed all MIME entity body parts.(boundary end tag reached)
|
||||||
|
else if(m_pReadLineOP.Buffer[0] == '-' && string.Equals("--" + m_Boundary + "--",m_pReadLineOP.LineUtf8)){
|
||||||
|
m_State = State.Done;
|
||||||
|
|
||||||
|
// Read "epilogoue",if has any.
|
||||||
|
while(true){
|
||||||
|
m_pStream.ReadLine(m_pReadLineOP,false);
|
||||||
|
|
||||||
|
if(m_pReadLineOP.Error != null){
|
||||||
|
throw m_pReadLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream. Epilogue reading completed.
|
||||||
|
else if(m_pReadLineOP.BytesInBuffer == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_pTextEpilogue.Append(m_pReadLineOP.LineUtf8 + "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return previous line data - CRLF, because CRLF if part of boundary tag.
|
||||||
|
if(count < m_pPreviousLine.BytesInBuffer){
|
||||||
|
throw new ArgumentException("Argument 'buffer' is to small. This should never happen.");
|
||||||
|
}
|
||||||
|
if(m_pPreviousLine.BytesInBuffer > 2){
|
||||||
|
Array.Copy(m_pPreviousLine.LineBuffer,0,buffer,offset,m_pPreviousLine.BytesInBuffer - 2);
|
||||||
|
|
||||||
|
return m_pPreviousLine.BytesInBuffer - 2;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have readed all active boundary data, next boundary start tag.
|
||||||
|
else if(m_pReadLineOP.Buffer[0] == '-' && string.Equals("--" + m_Boundary,m_pReadLineOP.LineUtf8)){
|
||||||
|
m_State = State.ReadNext;
|
||||||
|
|
||||||
|
// Return previous line data - CRLF, because CRLF if part of boundary tag.
|
||||||
|
if(count < m_pPreviousLine.BytesInBuffer){
|
||||||
|
throw new ArgumentException("Argument 'buffer' is to small. This should never happen.");
|
||||||
|
}
|
||||||
|
if(m_pPreviousLine.BytesInBuffer > 2){
|
||||||
|
Array.Copy(m_pPreviousLine.LineBuffer,0,buffer,offset,m_pPreviousLine.BytesInBuffer - 2);
|
||||||
|
|
||||||
|
return m_pPreviousLine.BytesInBuffer - 2;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have boundary data-line.
|
||||||
|
else{
|
||||||
|
// Here we actually process previous line and store current.
|
||||||
|
|
||||||
|
if(count < m_pPreviousLine.BytesInBuffer){
|
||||||
|
throw new ArgumentException("Argument 'buffer' is to small. This should never happen.");
|
||||||
|
}
|
||||||
|
Array.Copy(m_pPreviousLine.LineBuffer,0,buffer,offset,m_pPreviousLine.BytesInBuffer);
|
||||||
|
|
||||||
|
int countCopied = m_pPreviousLine.BytesInBuffer;
|
||||||
|
|
||||||
|
// Store current line as previous.
|
||||||
|
m_pPreviousLine.AssignFrom(m_pReadLineOP);
|
||||||
|
|
||||||
|
return countCopied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method Write
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
|
||||||
|
/// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
|
||||||
|
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when this method is accessed.</exception>
|
||||||
|
public override void Write(byte[] buffer,int offset,int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports reading.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports seeking.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the current stream supports writing.
|
||||||
|
/// </summary>
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length in bytes of the stream. This method is not supported and always throws a NotSupportedException.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when this property is accessed.</exception>
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the position within the current stream. This method is not supported and always throws a NotSupportedException.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when this property is accessed.</exception>
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets "preamble" text. Defined in RFC 2046 5.1.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Preamble text is text between MIME entiy headers and first boundary.</remarks>
|
||||||
|
public string TextPreamble
|
||||||
|
{
|
||||||
|
get{ return m_pTextPreamble.ToString(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets "epilogue" text. Defined in RFC 2046 5.1.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Epilogue text is text after last boundary end.</remarks>
|
||||||
|
public string TextEpilogue
|
||||||
|
{
|
||||||
|
get{ return m_pTextEpilogue.ToString(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private MIME_h_ContentType m_pContentType = null;
|
||||||
|
private MIME_EntityCollection m_pBodyParts = null;
|
||||||
|
private string m_TextPreamble = "";
|
||||||
|
private string m_TextEpilogue = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_Multipart(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(contentType == null){
|
||||||
|
throw new ArgumentNullException("contentType");
|
||||||
|
}
|
||||||
|
if(string.IsNullOrEmpty(contentType.Param_Boundary)){
|
||||||
|
throw new ArgumentException("Argument 'contentType' doesn't contain required boundary parameter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pContentType = contentType;
|
||||||
|
|
||||||
|
m_pBodyParts = new MIME_EntityCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Multipart retVal = new MIME_b_Multipart(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ParseInternal
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal body parsing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="mediaType">MIME media type. For example: text/plain.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <param name="body">Multipart body instance.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>mediaType</b>, <b>stream</b> or <b>body</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static void ParseInternal(MIME_Entity owner,string mediaType,SmartStream stream,MIME_b_Multipart body)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(mediaType == null){
|
||||||
|
throw new ArgumentNullException("mediaType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' parameter.");
|
||||||
|
}
|
||||||
|
if(body == null){
|
||||||
|
throw new ArgumentNullException("body");
|
||||||
|
}
|
||||||
|
|
||||||
|
_MultipartReader multipartReader = new _MultipartReader(stream,owner.ContentType.Param_Boundary);
|
||||||
|
while(multipartReader.Next()){
|
||||||
|
MIME_Entity entity = new MIME_Entity();
|
||||||
|
entity.Parse(new SmartStream(multipartReader,false),body.DefaultBodyPartContentType);
|
||||||
|
body.m_pBodyParts.Add(entity);
|
||||||
|
entity.SetParent(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.m_TextPreamble = multipartReader.TextPreamble;
|
||||||
|
body.m_TextEpilogue = multipartReader.TextEpilogue;
|
||||||
|
|
||||||
|
body.BodyParts.SetModified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override SetParent
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body parent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">Owner entity.</param>
|
||||||
|
/// <param name="setContentType">If true sets entity.ContentType header value.</param>
|
||||||
|
internal override void SetParent(MIME_Entity entity,bool setContentType)
|
||||||
|
{
|
||||||
|
base.SetParent(entity,setContentType);
|
||||||
|
|
||||||
|
// Owner entity has no content-type or has different content-type, just add/overwrite it.
|
||||||
|
if(setContentType && (this.Entity.ContentType == null || !string.Equals(this.Entity.ContentType.TypeWithSubype,this.MediaType,StringComparison.InvariantCultureIgnoreCase))){
|
||||||
|
this.Entity.ContentType = m_pContentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity body to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store body data.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
internal protected override void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2046 5.1.1.
|
||||||
|
NOTE: The CRLF preceding the boundary delimiter line is conceptually
|
||||||
|
attached to the boundary so that it is possible to have a part that
|
||||||
|
does not end with a CRLF (line break).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Set "preamble" text if any.
|
||||||
|
if(!string.IsNullOrEmpty(m_TextPreamble)){
|
||||||
|
byte[] preableBytes = Encoding.UTF8.GetBytes(m_TextPreamble);
|
||||||
|
stream.Write(preableBytes,0,preableBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<m_pBodyParts.Count;i++){
|
||||||
|
MIME_Entity bodyPart = m_pBodyParts[i];
|
||||||
|
// Start new body part.
|
||||||
|
byte[] bStart = Encoding.UTF8.GetBytes("\r\n--" + this.Entity.ContentType.Param_Boundary + "\r\n");
|
||||||
|
stream.Write(bStart,0,bStart.Length);
|
||||||
|
|
||||||
|
bodyPart.ToStream(stream,headerWordEncoder,headerParmetersCharset);
|
||||||
|
|
||||||
|
// Last body part, close boundary.
|
||||||
|
if(i == (m_pBodyParts.Count - 1)){
|
||||||
|
byte[] bEnd = Encoding.UTF8.GetBytes("\r\n--" + this.Entity.ContentType.Param_Boundary + "--\r\n");
|
||||||
|
stream.Write(bEnd,0,bEnd.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set "epilogoue" text if any.
|
||||||
|
if(!string.IsNullOrEmpty(m_TextEpilogue)){
|
||||||
|
byte[] epilogoueBytes = Encoding.UTF8.GetBytes(m_TextEpilogue);
|
||||||
|
stream.Write(epilogoueBytes,0,epilogoueBytes.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if body has modified.
|
||||||
|
/// </summary>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_pBodyParts.IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets default body part Content-Type. For more info see RFC 2046 5.1.
|
||||||
|
/// </summary>
|
||||||
|
public virtual MIME_h_ContentType DefaultBodyPartContentType
|
||||||
|
{
|
||||||
|
/* RFC 2026 5.1.
|
||||||
|
The absence of a Content-Type header usually indicates that the corresponding body has
|
||||||
|
a content-type of "text/plain; charset=US-ASCII".
|
||||||
|
*/
|
||||||
|
|
||||||
|
get{
|
||||||
|
MIME_h_ContentType retVal = new MIME_h_ContentType("text/plain");
|
||||||
|
retVal.Param_Charset = "US-ASCII";
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets multipart body body-parts collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Multipart entity child entities are called "body parts" in RFC 2045.</remarks>
|
||||||
|
public MIME_EntityCollection BodyParts
|
||||||
|
{
|
||||||
|
get{ return m_pBodyParts; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets "preamble" text. Defined in RFC 2046 5.1.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Preamble text is text between MIME entiy headers and first boundary.</remarks>
|
||||||
|
public string TextPreamble
|
||||||
|
{
|
||||||
|
get{ return m_TextPreamble; }
|
||||||
|
|
||||||
|
set{ m_TextPreamble = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets "epilogue" text. Defined in RFC 2046 5.1.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Epilogue text is text after last boundary end.</remarks>
|
||||||
|
public string TextEpilogue
|
||||||
|
{
|
||||||
|
get{ return m_TextEpilogue; }
|
||||||
|
|
||||||
|
set{ m_TextEpilogue = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
73
MailServer/Misc/MIME/MIME_b_MultipartAlternative.cs
Normal file
73
MailServer/Misc/MIME/MIME_b_MultipartAlternative.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/alternative body. Defined in RFC 2046 5.1.4.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "multipart/alternative" is intended for use when each of the body parts is an "alternative" version of the same information.
|
||||||
|
/// In general, user agents that compose "multipart/alternative" entities
|
||||||
|
/// must place the body parts in increasing order of preference, that is,
|
||||||
|
/// with the preferred format last.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartAlternative : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartAlternative(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/alternative",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/alternative'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartAlternative retVal = new MIME_b_MultipartAlternative(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
86
MailServer/Misc/MIME/MIME_b_MultipartDigest.cs
Normal file
86
MailServer/Misc/MIME/MIME_b_MultipartDigest.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/digest body. Defined in RFC 2046 5.1.5.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "multipart/digest" Content-Type is intended to be used to send collections of messages.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartDigest : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartDigest(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/digest",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/digest'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartDigest retVal = new MIME_b_MultipartDigest(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets default body part Content-Type. For more info see RFC 2046 5.1.
|
||||||
|
/// </summary>
|
||||||
|
public override MIME_h_ContentType DefaultBodyPartContentType
|
||||||
|
{
|
||||||
|
/* RFC 2046 5.1.6.
|
||||||
|
The absence of a Content-Type header usually indicates that the corresponding body has
|
||||||
|
a content-type of "message/rfc822".
|
||||||
|
*/
|
||||||
|
|
||||||
|
get{
|
||||||
|
MIME_h_ContentType retVal = new MIME_h_ContentType("message/rfc822");
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
66
MailServer/Misc/MIME/MIME_b_MultipartEncrypted.cs
Normal file
66
MailServer/Misc/MIME/MIME_b_MultipartEncrypted.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/encrypted body. Defined in rfc 1847.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_MultipartEncrypted : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartEncrypted(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/encrypted",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/encrypted'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartEncrypted retVal = new MIME_b_MultipartEncrypted(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
66
MailServer/Misc/MIME/MIME_b_MultipartFormData.cs
Normal file
66
MailServer/Misc/MIME/MIME_b_MultipartFormData.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/from-data body. Defined in RFC 2046.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_MultipartFormData : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartFormData(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/from-data",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/from-data'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartFormData retVal = new MIME_b_MultipartFormData(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
71
MailServer/Misc/MIME/MIME_b_MultipartMixed.cs
Normal file
71
MailServer/Misc/MIME/MIME_b_MultipartMixed.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/mixed body. Defined in RFC 2046 5.1.3.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "mixed" subtype of "multipart" is intended for use when the body
|
||||||
|
/// parts are independent and need to be bundled in a particular order.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartMixed : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartMixed(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/mixed",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/mixed'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartMixed retVal = new MIME_b_MultipartMixed(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
70
MailServer/Misc/MIME/MIME_b_MultipartParallel.cs
Normal file
70
MailServer/Misc/MIME/MIME_b_MultipartParallel.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME message/parallel bodies. Defined in RFC 2046 5.1.6.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "parallel" subtype of "multipart" is intended for use when the body
|
||||||
|
/// parts are independent and their order is not important. Parts can be processed parallel.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartParallel : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartParallel(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/parallel",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/parallel'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartParallel retVal = new MIME_b_MultipartParallel(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
70
MailServer/Misc/MIME/MIME_b_MultipartRelated.cs
Normal file
70
MailServer/Misc/MIME/MIME_b_MultipartRelated.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/related body. Defined in RFC 2387.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The Multipart/Related content-type provides a common mechanism for
|
||||||
|
/// representing objects that are aggregates of related MIME body parts.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartRelated : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartRelated(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/related",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/related'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartRelated retVal = new MIME_b_MultipartRelated(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
73
MailServer/Misc/MIME/MIME_b_MultipartReport.cs
Normal file
73
MailServer/Misc/MIME/MIME_b_MultipartReport.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/report body. Defined in RFC 3462.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The Multipart/Report Multipurpose Internet Mail Extensions (MIME) content-type is a general "family" or
|
||||||
|
/// "container" type for electronic mail reports of any kind. The most used type is <b>delivery-status</b>.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_MultipartReport : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartReport(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/report",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/report'.");
|
||||||
|
}
|
||||||
|
//if(contentType.Parameters["report-type"] == null){
|
||||||
|
// throw new ArgumentException("Argument 'contentType' does not contain required 'report-type' paramter.");
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartReport retVal = new MIME_b_MultipartReport(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
82
MailServer/Misc/MIME/MIME_b_MultipartSigned.cs
Normal file
82
MailServer/Misc/MIME/MIME_b_MultipartSigned.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME multipart/signed body. Defined in rfc 1847.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_MultipartSigned : MIME_b_Multipart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_MultipartSigned(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(!string.Equals(contentType.TypeWithSubype,"multipart/signed",StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
throw new ArgumentException("Argument 'contentType.TypeWithSubype' value must be 'multipart/signed'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>mediaTypedefaultContentTypeb></b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(owner.ContentType == null || owner.ContentType.Param_Boundary == null){
|
||||||
|
throw new ParseException("Multipart entity has not required 'boundary' paramter.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_MultipartSigned retVal = new MIME_b_MultipartSigned(owner.ContentType);
|
||||||
|
ParseInternal(owner,owner.ContentType.TypeWithSubype,stream,retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// <summary>
|
||||||
|
/// Signs entiy data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cert"></param>
|
||||||
|
public void Sign(X509Certificate2 cert)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that body content has not changed after it was siiged.
|
||||||
|
/// </summary>
|
||||||
|
public void Verify()
|
||||||
|
{
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
107
MailServer/Misc/MIME/MIME_b_Provider.cs
Normal file
107
MailServer/Misc/MIME/MIME_b_Provider.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represent MIME entity body provider.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_Provider
|
||||||
|
{
|
||||||
|
private Dictionary<string,Type> m_pBodyTypes = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_b_Provider()
|
||||||
|
{
|
||||||
|
m_pBodyTypes = new Dictionary<string,Type>(StringComparer.CurrentCultureIgnoreCase);
|
||||||
|
m_pBodyTypes.Add("message/rfc822",typeof(MIME_b_MessageRfc822));
|
||||||
|
m_pBodyTypes.Add("message/delivery-status",typeof(MIME_b_MessageDeliveryStatus));
|
||||||
|
m_pBodyTypes.Add("multipart/alternative",typeof(MIME_b_MultipartAlternative));
|
||||||
|
m_pBodyTypes.Add("multipart/digest",typeof(MIME_b_MultipartDigest));
|
||||||
|
m_pBodyTypes.Add("multipart/encrypted",typeof(MIME_b_MultipartEncrypted));
|
||||||
|
m_pBodyTypes.Add("multipart/form-data",typeof(MIME_b_MultipartFormData));
|
||||||
|
m_pBodyTypes.Add("multipart/mixed",typeof(MIME_b_MultipartMixed));
|
||||||
|
m_pBodyTypes.Add("multipart/parallel",typeof(MIME_b_MultipartParallel));
|
||||||
|
m_pBodyTypes.Add("multipart/related",typeof(MIME_b_MultipartRelated));
|
||||||
|
m_pBodyTypes.Add("multipart/report",typeof(MIME_b_MultipartReport));
|
||||||
|
m_pBodyTypes.Add("multipart/signed",typeof(MIME_b_MultipartSigned));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME entity body from specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="stream">Stream from where to parse entity body.</param>
|
||||||
|
/// <param name="defaultContentType">Default content type.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>owner</b>, <b>strean</b> or <b>defaultContentType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public MIME_b Parse(MIME_Entity owner,SmartStream stream,MIME_h_ContentType defaultContentType)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
|
||||||
|
string mediaType = defaultContentType.TypeWithSubype;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
mediaType = owner.ContentType.TypeWithSubype;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type bodyType = null;
|
||||||
|
|
||||||
|
// We have exact body provider for specified mediaType.
|
||||||
|
if(m_pBodyTypes.ContainsKey(mediaType)){
|
||||||
|
bodyType = m_pBodyTypes[mediaType];
|
||||||
|
}
|
||||||
|
// Use default mediaType.
|
||||||
|
else{
|
||||||
|
// Registered list of mediaTypes are available: http://www.iana.org/assignments/media-types/.
|
||||||
|
|
||||||
|
string mediaRootType = mediaType.Split('/')[0].ToLowerInvariant();
|
||||||
|
if(mediaRootType == "application"){
|
||||||
|
bodyType = typeof(MIME_b_Application);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "audio"){
|
||||||
|
bodyType = typeof(MIME_b_Audio);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "image"){
|
||||||
|
bodyType = typeof(MIME_b_Image);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "message"){
|
||||||
|
bodyType = typeof(MIME_b_Message);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "multipart"){
|
||||||
|
bodyType = typeof(MIME_b_Multipart);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "text"){
|
||||||
|
bodyType = typeof(MIME_b_Text);
|
||||||
|
}
|
||||||
|
else if(mediaRootType == "video"){
|
||||||
|
bodyType = typeof(MIME_b_Video);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
bodyType = typeof(MIME_b_Unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MIME_b)bodyType.GetMethod("Parse",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy).Invoke(null,new object[]{owner,defaultContentType,stream});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
327
MailServer/Misc/MIME/MIME_b_SinglepartBase.cs
Normal file
327
MailServer/Misc/MIME/MIME_b_SinglepartBase.cs
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is base class for singlepart media bodies like: text,video,audio,image.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MIME_b_SinglepartBase : MIME_b
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private FileStream m_pEncodedDataStream = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentType</b> is null reference.</exception>
|
||||||
|
public MIME_b_SinglepartBase(MIME_h_ContentType contentType) : base(contentType)
|
||||||
|
{
|
||||||
|
if(contentType == null){
|
||||||
|
throw new ArgumentNullException("contentType");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEncodedDataStream = new FileStream(Path.GetTempFileName(),FileMode.Create,FileAccess.ReadWrite,FileShare.None,32000,FileOptions.DeleteOnClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region override SetParent
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body parent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">Owner entity.</param>
|
||||||
|
/// <param name="setContentType">If true sets entity.ContentType header value.</param>
|
||||||
|
internal override void SetParent(MIME_Entity entity,bool setContentType)
|
||||||
|
{
|
||||||
|
base.SetParent(entity,setContentType);
|
||||||
|
|
||||||
|
// Owner entity has no content-type or has different content-type, just add/overwrite it.
|
||||||
|
if(setContentType && (this.Entity.ContentType == null || !string.Equals(this.Entity.ContentType.TypeWithSubype,this.MediaType,StringComparison.InvariantCultureIgnoreCase))){
|
||||||
|
this.Entity.ContentType = new MIME_h_ContentType(MediaType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores MIME entity body to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store body data.</param>
|
||||||
|
/// <param name="headerWordEncoder">Header 8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="headerParmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
internal protected override void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(GetEncodedDataStream(),stream,32000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method SetModified
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets IsModified property value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isModified">Modified flag.</param>
|
||||||
|
protected void SetModified(bool isModified)
|
||||||
|
{
|
||||||
|
m_IsModified = isModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method GetEncodedDataStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body encoded data stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns body encoded data stream.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public Stream GetEncodedDataStream()
|
||||||
|
{
|
||||||
|
if(this.Entity == null){
|
||||||
|
throw new InvalidOperationException("Body must be bounded to some entity first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEncodedDataStream.Position = 0;
|
||||||
|
|
||||||
|
return m_pEncodedDataStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method SetEncodedData
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body encoded data from specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTransferEncoding">Content-Transfer-Encoding in what encoding <b>stream</b> data is.</param>
|
||||||
|
/// <param name="stream">Stream data to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>contentTransferEncoding</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the argumennts has invalid value.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public void SetEncodedData(string contentTransferEncoding,Stream stream)
|
||||||
|
{
|
||||||
|
if(contentTransferEncoding == null){
|
||||||
|
throw new ArgumentNullException("contentTransferEncoding");
|
||||||
|
}
|
||||||
|
if(contentTransferEncoding == string.Empty){
|
||||||
|
throw new ArgumentException("Argument 'contentTransferEncoding' value must be specified.");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(this.Entity == null){
|
||||||
|
throw new InvalidOperationException("Body must be bounded to some entity first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner entity has no content-type or has different content-type, just add/overwrite it.
|
||||||
|
if(this.Entity.ContentType == null || !string.Equals(this.Entity.ContentType.TypeWithSubype,this.MediaType,StringComparison.InvariantCultureIgnoreCase)){
|
||||||
|
this.Entity.ContentType = new MIME_h_ContentType(this.MediaType);
|
||||||
|
}
|
||||||
|
this.Entity.ContentTransferEncoding = contentTransferEncoding;
|
||||||
|
|
||||||
|
m_pEncodedDataStream.SetLength(0);
|
||||||
|
Net_Utils.StreamCopy(stream,m_pEncodedDataStream,32000);
|
||||||
|
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method GetDataStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body decoded data stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns body decoded data stream.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when body contains not supported Content-Transfer-Encoding.</exception>
|
||||||
|
/// <remarks>The returned stream should be closed/disposed as soon as it's not needed any more.</remarks>
|
||||||
|
public Stream GetDataStream()
|
||||||
|
{
|
||||||
|
if(this.Entity == null){
|
||||||
|
throw new InvalidOperationException("Body must be bounded to some entity first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2045 6.1.
|
||||||
|
This is the default value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the
|
||||||
|
Content-Transfer-Encoding header field is not present.
|
||||||
|
*/
|
||||||
|
string transferEncoding = MIME_TransferEncodings.SevenBit;
|
||||||
|
if(this.Entity.ContentTransferEncoding != null){
|
||||||
|
transferEncoding = this.Entity.ContentTransferEncoding.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEncodedDataStream.Position = 0;
|
||||||
|
if(transferEncoding == MIME_TransferEncodings.QuotedPrintable){
|
||||||
|
return new QuotedPrintableStream(new SmartStream(m_pEncodedDataStream,false),FileAccess.Read);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.Base64){
|
||||||
|
return new Base64Stream(m_pEncodedDataStream,false,true,FileAccess.Read);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.Binary){
|
||||||
|
return new ReadWriteControlledStream(m_pEncodedDataStream,FileAccess.Read);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.EightBit){
|
||||||
|
return new ReadWriteControlledStream(m_pEncodedDataStream,FileAccess.Read);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.SevenBit){
|
||||||
|
return new ReadWriteControlledStream(m_pEncodedDataStream,FileAccess.Read);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new NotSupportedException("Not supported Content-Transfer-Encoding '" + this.Entity.ContentTransferEncoding + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method SetData
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body data from the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Source stream.</param>
|
||||||
|
/// <param name="transferEncoding">Specifies content-transfer-encoding to use to encode data.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> or <b>transferEncoding</b> is null reference.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public void SetData(Stream stream,string transferEncoding)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
if(transferEncoding == null){
|
||||||
|
throw new ArgumentNullException("transferEncoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(transferEncoding == MIME_TransferEncodings.QuotedPrintable){
|
||||||
|
using(FileStream fs = File.Create(Path.GetTempFileName(),32000,FileOptions.DeleteOnClose)){
|
||||||
|
QuotedPrintableStream encoder = new QuotedPrintableStream(new SmartStream(fs,false),FileAccess.ReadWrite);
|
||||||
|
Net_Utils.StreamCopy(stream,encoder,32000);
|
||||||
|
encoder.Flush();
|
||||||
|
fs.Position = 0;
|
||||||
|
SetEncodedData(transferEncoding,fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.Base64){
|
||||||
|
using(FileStream fs = File.Create(Path.GetTempFileName(),32000,FileOptions.DeleteOnClose)){
|
||||||
|
Base64Stream encoder = new Base64Stream(fs,false,true,FileAccess.ReadWrite);
|
||||||
|
Net_Utils.StreamCopy(stream,encoder,32000);
|
||||||
|
encoder.Finish();
|
||||||
|
fs.Position = 0;
|
||||||
|
SetEncodedData(transferEncoding,fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.Binary){
|
||||||
|
SetEncodedData(transferEncoding,stream);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.EightBit){
|
||||||
|
SetEncodedData(transferEncoding,stream);
|
||||||
|
}
|
||||||
|
else if(transferEncoding == MIME_TransferEncodings.SevenBit){
|
||||||
|
SetEncodedData(transferEncoding,stream);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new NotSupportedException("Not supported Content-Transfer-Encoding '" + transferEncoding + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method SetDataFromFile
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets body data from the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">File name with optional path.</param>
|
||||||
|
/// <param name="transferEncoding">Specifies content-transfer-encoding to use to encode data.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>file</b> is null reference.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
public void SetDataFromFile(string file,string transferEncoding)
|
||||||
|
{
|
||||||
|
if(file == null){
|
||||||
|
throw new ArgumentNullException("file");
|
||||||
|
}
|
||||||
|
|
||||||
|
using(FileStream fs = File.OpenRead(file)){
|
||||||
|
SetData(fs,transferEncoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if body has modified.
|
||||||
|
/// </summary>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets encoded body data size in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int EncodedDataSize
|
||||||
|
{
|
||||||
|
get{ return (int)m_pEncodedDataStream.Length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body encoded data.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>NOTE: Use this property with care, because body data may be very big and you may run out of memory.
|
||||||
|
/// For bigger data use <see cref="GetEncodedDataStream"/> method instead.</remarks>
|
||||||
|
public byte[] EncodedData
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
Net_Utils.StreamCopy(this.GetEncodedDataStream(),ms,32000);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body decoded data.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>NOTE: Use this property with care, because body data may be very big and you may run out of memory.
|
||||||
|
/// For bigger data use <see cref="GetDataStream"/> method instead.</remarks>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when body contains not supported Content-Transfer-Encoding.</exception>
|
||||||
|
public byte[] Data
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
Net_Utils.StreamCopy(this.GetDataStream(),ms,32000);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets encoded data stream.
|
||||||
|
/// </summary>
|
||||||
|
protected Stream EncodedStream
|
||||||
|
{
|
||||||
|
get{ return m_pEncodedDataStream; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
137
MailServer/Misc/MIME/MIME_b_Text.cs
Normal file
137
MailServer/Misc/MIME/MIME_b_Text.cs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME text/xxx bodies. Defined in RFC 2045.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The "text" media type is intended for sending material which is principally textual in form.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_Text : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaSubType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_b_Text(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>mediaType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Text retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Text(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Text(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
retVal.SetModified(false);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method SetText
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transferEncoding">Content transfer encoding.</param>
|
||||||
|
/// <param name="charset">Charset to use to encode text. If not sure, utf-8 is recommended.</param>
|
||||||
|
/// <param name="text">Text.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>transferEncoding</b>, <b>charset</b> or <b>text</b> is null reference.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Is raised when this method is accessed and this body is not bounded to any entity.</exception>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when body contains not supported Content-Transfer-Encoding.</exception>
|
||||||
|
public void SetText(string transferEncoding,Encoding charset,string text)
|
||||||
|
{
|
||||||
|
if(transferEncoding == null){
|
||||||
|
throw new ArgumentNullException("transferEncoding");
|
||||||
|
}
|
||||||
|
if(charset == null){
|
||||||
|
throw new ArgumentNullException("charset");
|
||||||
|
}
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
if(this.Entity == null){
|
||||||
|
throw new InvalidOperationException("Body must be bounded to some entity first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEncodedData(transferEncoding,new MemoryStream(charset.GetBytes(text)));
|
||||||
|
this.Entity.ContentType.Param_Charset = charset.WebName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method GetCharset
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets charset from Content-Type. If char set isn't specified, "ascii" is defined as default and it will be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns content charset.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when Content-Type has not supported charset parameter value.</exception>
|
||||||
|
private Encoding GetCharset()
|
||||||
|
{
|
||||||
|
// RFC 2046 4.1.2. The default character set, US-ASCII.
|
||||||
|
|
||||||
|
if(this.Entity.ContentType == null || string.IsNullOrEmpty(this.Entity.ContentType.Param_Charset)){
|
||||||
|
return Encoding.ASCII;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Encoding.GetEncoding(this.Entity.ContentType.Param_Charset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets body decoded text.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when not supported content-type charset or not supported content-transfer-encoding value.</exception>
|
||||||
|
/// <exception cref="NotSupportedException">Is raised when body contains not supported Content-Transfer-Encoding.</exception>
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get{ return GetCharset().GetString(this.Data); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
62
MailServer/Misc/MIME/MIME_b_Unknown.cs
Normal file
62
MailServer/Misc/MIME/MIME_b_Unknown.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME unknown bodies.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_b_Unknown : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Unknown(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>defaultContentType</b> or <b>strean</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Unknown retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Unknown(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Unknown(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
71
MailServer/Misc/MIME/MIME_b_Video.cs
Normal file
71
MailServer/Misc/MIME/MIME_b_Video.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME video/xxx bodies. Defined in RFC 2046 4.4.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A media type of "video" indicates that the body contains a time-
|
||||||
|
/// varying-picture image, possibly with color and coordinated sound.
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_b_Video : MIME_b_SinglepartBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">MIME media type.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_b_Video(string mediaType) : base(new MIME_h_ContentType(mediaType))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body from the specified stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME entity.</param>
|
||||||
|
/// <param name="defaultContentType">Default content-type for this body.</param>
|
||||||
|
/// <param name="stream">Stream from where to read body.</param>
|
||||||
|
/// <returns>Returns parsed body.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b>, <b>mediaType</b> or <b>stream</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when any parsing errors.</exception>
|
||||||
|
protected static new MIME_b Parse(MIME_Entity owner,MIME_h_ContentType defaultContentType,SmartStream stream)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
if(defaultContentType == null){
|
||||||
|
throw new ArgumentNullException("defaultContentType");
|
||||||
|
}
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_b_Video retVal = null;
|
||||||
|
if(owner.ContentType != null){
|
||||||
|
retVal = new MIME_b_Video(owner.ContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal = new MIME_b_Video(defaultContentType.TypeWithSubype);
|
||||||
|
}
|
||||||
|
|
||||||
|
Net_Utils.StreamCopy(stream,retVal.EncodedStream,32000);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
91
MailServer/Misc/MIME/MIME_h.cs
Normal file
91
MailServer/Misc/MIME/MIME_h.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is base class for MIME header fields. Defined in RFC 2045 3.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MIME_h
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.
|
||||||
|
/// If encoding needed, UTF-8 is strongly reccomended if not sure.</param>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public abstract string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ValueToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field value as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns header field value as string.</returns>
|
||||||
|
public string ValueToString()
|
||||||
|
{
|
||||||
|
return ValueToString(null,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field value as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.
|
||||||
|
/// If encoding needed, UTF-8 is strongly reccomended if not sure.</param>
|
||||||
|
/// <returns>Returns header field value as string.</returns>
|
||||||
|
public string ValueToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
return ToString(wordEncoder,parmetersCharset).Split(new char[]{':'},2)[1].TrimStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public abstract bool IsModified
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header field name. For example "Content-Type".
|
||||||
|
/// </summary>
|
||||||
|
public abstract string Name
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
529
MailServer/Misc/MIME/MIME_h_Collection.cs
Normal file
529
MailServer/Misc/MIME/MIME_h_Collection.cs
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using LumiSoft.Net.IO;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME header fields collection. Defined in RFC 2045.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_Collection : IEnumerable
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private MIME_h_Provider m_pProvider = null;
|
||||||
|
private List<MIME_h> m_pFields = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">Header fields provider.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>provider</b> is null reference.</exception>
|
||||||
|
public MIME_h_Collection(MIME_h_Provider provider)
|
||||||
|
{
|
||||||
|
if(provider == null){
|
||||||
|
throw new ArgumentNullException("provider");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pProvider = provider;
|
||||||
|
|
||||||
|
m_pFields = new List<MIME_h>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Insert
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new header field into the collection at the specified location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The location in the collection where you want to add the item.</param>
|
||||||
|
/// <param name="field">Header field to insert.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Is raised when <b>index</b> is out of range.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
public void Insert(int index,MIME_h field)
|
||||||
|
{
|
||||||
|
if(index < 0 || index > m_pFields.Count){
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
}
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pFields.Insert(index,field);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Add
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses and adds specified header field to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Header field string (Name: value).</param>
|
||||||
|
/// <returns>Retunrs added header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
public MIME_h Add(string field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h h = m_pProvider.Parse(field);
|
||||||
|
m_pFields.Add(h);
|
||||||
|
m_IsModified = true;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds specified header field to the end of the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Header field to add.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference value.</exception>
|
||||||
|
public void Add(MIME_h field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pFields.Add(field);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Remove
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified header field from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Header field to remove.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference value.</exception>
|
||||||
|
public void Remove(MIME_h field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pFields.Remove(field);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method RemoveAll
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all header fields with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public void RemoveAll(string name)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
if(name == string.Empty){
|
||||||
|
throw new ArgumentException("Argument 'name' value must be specified.","name");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_h field in m_pFields.ToArray()){
|
||||||
|
if(string.Compare(name,field.Name,true) == 0){
|
||||||
|
m_pFields.Remove(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Clear
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all items from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_pFields.Clear();
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Contains
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if collection has item with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <returns>Returns true if specified item exists in the collection, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public bool Contains(string name)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
if(name == string.Empty){
|
||||||
|
throw new ArgumentException("Argument 'name' value must be specified.","name");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_h field in m_pFields.ToArray()){
|
||||||
|
if(string.Compare(name,field.Name,true) == 0){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if collection contains the specified item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Header field.</param>
|
||||||
|
/// <returns>Returns true if specified item exists in the collection, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
public bool Contains(MIME_h field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pFields.Contains(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method GetFirst
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets first header field with the specified name. returns null if specified header field doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <returns>Returns first header field with the specified name. returns null if specified header field doesn't exist.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
public MIME_h GetFirst(string name)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_h field in m_pFields.ToArray()){
|
||||||
|
if(string.Equals(name,field.Name,StringComparison.InvariantCultureIgnoreCase)){
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ReplaceFirst
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces first header field with specified name with specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Hedaer field.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
public void ReplaceFirst(MIME_h field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<m_pFields.Count;i++){
|
||||||
|
if(string.Equals(field.Name,m_pFields[i].Name,StringComparison.CurrentCultureIgnoreCase)){
|
||||||
|
m_pFields.RemoveAt(i);
|
||||||
|
m_pFields.Insert(i,field);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies header fields to new array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns header fields array.</returns>
|
||||||
|
public MIME_h[] ToArray()
|
||||||
|
{
|
||||||
|
return m_pFields.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToFile
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores header to the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">File name with optional path.</param>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>fileName</b> is null reference.</exception>
|
||||||
|
public void ToFile(string fileName,MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
if(fileName == null){
|
||||||
|
throw new ArgumentNullException("fileName");
|
||||||
|
}
|
||||||
|
|
||||||
|
using(FileStream fs = File.Create(fileName)){
|
||||||
|
ToStream(fs,wordEncoder,parmetersCharset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToByte
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header as byte[] data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header as byte[] data.</returns>
|
||||||
|
public byte[] ToByte(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
using(MemoryStream ms = new MemoryStream()){
|
||||||
|
ToStream(ms,wordEncoder,parmetersCharset);
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToStream
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores header to the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store header.</param>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null reference.</exception>
|
||||||
|
public void ToStream(Stream stream,MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] header = Encoding.UTF8.GetBytes(ToString(wordEncoder,parmetersCharset));
|
||||||
|
stream.Write(header,0,header.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region override method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MIME header as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns MIME header as string.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns MIME header as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit header parameters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns MIME header as string.</returns>
|
||||||
|
public string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
foreach(MIME_h field in m_pFields){
|
||||||
|
retVal.Append(field.ToString(wordEncoder,parmetersCharset));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME header from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">MIME header string.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public void Parse(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
Parse(new SmartStream(new MemoryStream(Encoding.UTF8.GetBytes(value)),true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses MIME header from the specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">MIME header stream.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception>
|
||||||
|
public void Parse(SmartStream stream)
|
||||||
|
{
|
||||||
|
if(stream == null){
|
||||||
|
throw new ArgumentNullException("stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder currentHeader = new StringBuilder();
|
||||||
|
SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.ThrowException);
|
||||||
|
while(true){
|
||||||
|
stream.ReadLine(readLineOP,false);
|
||||||
|
if(readLineOP.Error != null){
|
||||||
|
throw readLineOP.Error;
|
||||||
|
}
|
||||||
|
// We reached end of stream.
|
||||||
|
else if(readLineOP.BytesInBuffer == 0){
|
||||||
|
if(currentHeader.Length > 0){
|
||||||
|
Add(currentHeader.ToString());
|
||||||
|
}
|
||||||
|
m_IsModified = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We got blank header terminator line.
|
||||||
|
else if(readLineOP.LineBytesInBuffer == 0){
|
||||||
|
if(currentHeader.Length > 0){
|
||||||
|
Add(currentHeader.ToString());
|
||||||
|
}
|
||||||
|
m_IsModified = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
string line = Encoding.UTF8.GetString(readLineOP.Buffer,0,readLineOP.BytesInBuffer);
|
||||||
|
|
||||||
|
// New header field starts.
|
||||||
|
if(currentHeader.Length == 0){
|
||||||
|
currentHeader.Append(line);
|
||||||
|
}
|
||||||
|
// Header field continues.
|
||||||
|
else if(char.IsWhiteSpace(line[0])){
|
||||||
|
currentHeader.Append(line);
|
||||||
|
}
|
||||||
|
// Current header field closed, new starts.
|
||||||
|
else{
|
||||||
|
Add(currentHeader.ToString());
|
||||||
|
|
||||||
|
currentHeader = new StringBuilder();
|
||||||
|
currentHeader.Append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region interface IEnumerator
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return m_pFields.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if header has modified since it was loaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(MIME_h field in m_pFields){
|
||||||
|
if(field.IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of items in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get{ return m_pFields.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The zero-based index of the element to get.</param>
|
||||||
|
/// <returns>Returns the element at the specified index.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Is raised when <b>index</b> is out of range.</exception>
|
||||||
|
public MIME_h this[int index]
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(index < 0 || index >= m_pFields.Count){
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pFields[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header fields with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <returns>Returns header fields with the specified name.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
public MIME_h[] this[string name]
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MIME_h> retVal = new List<MIME_h>();
|
||||||
|
foreach(MIME_h field in m_pFields.ToArray()){
|
||||||
|
if(string.Compare(name,field.Name,true) == 0){
|
||||||
|
retVal.Add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header fields provider.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h_Provider FieldsProvider
|
||||||
|
{
|
||||||
|
get{ return m_pProvider; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
288
MailServer/Misc/MIME/MIME_h_ContentDisposition.cs
Normal file
288
MailServer/Misc/MIME/MIME_h_ContentDisposition.cs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents "Content-Disposition:" header. Defined in RFC 2183.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// RFC 2183.
|
||||||
|
/// In the extended BNF notation of [RFC 822], the Content-Disposition
|
||||||
|
/// header field is defined as follows:
|
||||||
|
///
|
||||||
|
/// disposition := "Content-Disposition" ":" disposition-type *(";" disposition-parm)
|
||||||
|
///
|
||||||
|
/// disposition-type := "inline" / "attachment" / extension-token
|
||||||
|
/// ; values are not case-sensitive
|
||||||
|
///
|
||||||
|
/// disposition-parm := filename-parm
|
||||||
|
/// / creation-date-parm
|
||||||
|
/// / modification-date-parm
|
||||||
|
/// / read-date-parm
|
||||||
|
/// / size-parm
|
||||||
|
/// / parameter
|
||||||
|
///
|
||||||
|
/// filename-parm := "filename" "=" value
|
||||||
|
///
|
||||||
|
/// creation-date-parm := "creation-date" "=" quoted-date-time
|
||||||
|
///
|
||||||
|
/// modification-date-parm := "modification-date" "=" quoted-date-time
|
||||||
|
///
|
||||||
|
/// read-date-parm := "read-date" "=" quoted-date-time
|
||||||
|
///
|
||||||
|
/// size-parm := "size" "=" 1*DIGIT
|
||||||
|
///
|
||||||
|
/// quoted-date-time := quoted-string
|
||||||
|
/// ; contents MUST be an RFC 822 `date-time'
|
||||||
|
/// ; numeric timezones (+HHMM or -HHMM) MUST be used
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public class MIME_h_ContentDisposition : MIME_h
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private string m_ParseValue = null;
|
||||||
|
private string m_DispositionType = "";
|
||||||
|
private MIME_h_ParameterCollection m_pParameters = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dispositionType">The disposition-type. Known values are in <see cref="MIME_DispositionTypes">MIME_DispositionTypes</see>.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>dispositionType</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public MIME_h_ContentDisposition(string dispositionType)
|
||||||
|
{
|
||||||
|
if(dispositionType == null){
|
||||||
|
throw new ArgumentNullException("dispositionType");
|
||||||
|
}
|
||||||
|
if(dispositionType == string.Empty){
|
||||||
|
throw new ArgumentException("Argument 'dispositionType' value must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_DispositionType = dispositionType;
|
||||||
|
|
||||||
|
m_pParameters = new MIME_h_ParameterCollection(this);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal parser constructor.
|
||||||
|
/// </summary>
|
||||||
|
private MIME_h_ContentDisposition()
|
||||||
|
{
|
||||||
|
m_pParameters = new MIME_h_ParameterCollection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses header field from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field value. Header field name must be included. For example: 'Content-Type: text/plain'.</param>
|
||||||
|
/// <returns>Returns parsed header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public static MIME_h_ContentDisposition Parse(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h_ContentDisposition retVal = new MIME_h_ContentDisposition();
|
||||||
|
|
||||||
|
string[] name_value = value.Split(new char[]{':'},2);
|
||||||
|
if(name_value.Length != 2){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_Reader r = new MIME_Reader(name_value[1]);
|
||||||
|
string type = r.Token();
|
||||||
|
if(type == null){
|
||||||
|
throw new ParseException("Invalid Content-Disposition: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
retVal.m_DispositionType = type;
|
||||||
|
|
||||||
|
retVal.m_pParameters.Parse(r);
|
||||||
|
|
||||||
|
retVal.m_ParseValue = value;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public override string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
if(this.IsModified){
|
||||||
|
return "Content-Disposition: " + m_DispositionType + m_pParameters.ToString(parmetersCharset) + "\r\n";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return m_ParseValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_IsModified || m_pParameters.IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns always "Content-Disposition".
|
||||||
|
/// </summary>
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return "Content-Disposition"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the disposition-type. Known values are in <see cref="MIME_DispositionTypes">MIME_DispositionTypes</see>.
|
||||||
|
/// </summary>
|
||||||
|
public string DispositionType
|
||||||
|
{
|
||||||
|
get{ return m_DispositionType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets Content-Type parameters collection.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h_ParameterCollection Parameters
|
||||||
|
{
|
||||||
|
get{ return m_pParameters; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the suggested file name. Value DateTime.MinValue means not specified. Defined in RFC 2183 2.3.
|
||||||
|
/// </summary>
|
||||||
|
public string Param_FileName
|
||||||
|
{
|
||||||
|
get{ return this.Parameters["filename"]; }
|
||||||
|
|
||||||
|
set{ m_pParameters["filename"] = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the creation date for a file. Value DateTime.MinValue means not specified. Defined in RFC 2183 2.4.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Param_CreationDate
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
string value = this.Parameters["creation-date"];
|
||||||
|
if(value == null){
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return MIME_Utils.ParseRfc2822DateTime(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == DateTime.MinValue){
|
||||||
|
this.Parameters.Remove("creation-date");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.Parameters["creation-date"] = MIME_Utils.DateTimeToRfc2822(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the modification date of a file. Value DateTime.MinValue means not specified. Defined in RFC 2183 2.5.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Param_ModificationDate
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
string value = this.Parameters["modification-date"];
|
||||||
|
if(value == null){
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return MIME_Utils.ParseRfc2822DateTime(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == DateTime.MinValue){
|
||||||
|
this.Parameters.Remove("modification-date");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.Parameters["modification-date"] = MIME_Utils.DateTimeToRfc2822(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the last read date of a file. Value DateTime.MinValue means not specified. Defined in RFC 2183 2.6.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Param_ReadDate
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
string value = this.Parameters["read-date"];
|
||||||
|
if(value == null){
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return MIME_Utils.ParseRfc2822DateTime(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == DateTime.MinValue){
|
||||||
|
this.Parameters.Remove("read-date");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.Parameters["read-date"] = MIME_Utils.DateTimeToRfc2822(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the size of a file. Value -1 means not specified. Defined in RFC 2183 2.7.
|
||||||
|
/// </summary>
|
||||||
|
public long Param_Size
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
string value = this.Parameters["size"];
|
||||||
|
if(value == null){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Convert.ToInt64(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value < 0){
|
||||||
|
this.Parameters.Remove("size");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.Parameters["size"] = value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
264
MailServer/Misc/MIME/MIME_h_ContentType.cs
Normal file
264
MailServer/Misc/MIME/MIME_h_ContentType.cs
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents "Content-Type:" header. Defined in RFC 2045 5.1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <code>
|
||||||
|
/// RFC 2045 5.1.
|
||||||
|
/// In the Augmented BNF notation of RFC 822, a Content-Type header field
|
||||||
|
/// value is defined as follows:
|
||||||
|
///
|
||||||
|
/// content := "Content-Type" ":" type "/" subtype
|
||||||
|
/// *(";" parameter)
|
||||||
|
/// ; Matching of media type and subtype
|
||||||
|
/// ; is ALWAYS case-insensitive.
|
||||||
|
///
|
||||||
|
/// type := discrete-type / composite-type
|
||||||
|
///
|
||||||
|
/// discrete-type := "text" / "image" / "audio" / "video" / "application" / extension-token
|
||||||
|
///
|
||||||
|
/// composite-type := "message" / "multipart" / extension-token
|
||||||
|
///
|
||||||
|
/// extension-token := ietf-token / x-token
|
||||||
|
///
|
||||||
|
/// ietf-token := (An extension token defined by a standards-track RFC and registered with IANA.)
|
||||||
|
///
|
||||||
|
/// x-token := (The two characters "X-" or "x-" followed, with no intervening white space, by any token)
|
||||||
|
///
|
||||||
|
/// subtype := extension-token / iana-token
|
||||||
|
///
|
||||||
|
/// iana-token := (A publicly-defined extension token. Tokens of this form must be registered with IANA as specified in RFC 2048.)
|
||||||
|
///
|
||||||
|
/// parameter := attribute "=" value
|
||||||
|
///
|
||||||
|
/// attribute := token
|
||||||
|
/// ; Matching of attributes
|
||||||
|
/// ; is ALWAYS case-insensitive.
|
||||||
|
///
|
||||||
|
/// value := token / quoted-string
|
||||||
|
///
|
||||||
|
/// token := 1*(any (US-ASCII) CHAR except SPACE, CTLs,or tspecials)
|
||||||
|
///
|
||||||
|
/// tspecials := "(" / ")" / "<" / ">" / "@" /
|
||||||
|
/// "," / ";" / ":" / "\" / "
|
||||||
|
/// "/" / "[" / "]" / "?" / "="
|
||||||
|
/// ; Must be in quoted-string,
|
||||||
|
/// ; to use within parameter values
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
public class MIME_h_ContentType : MIME_h
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private string m_ParseValue = null;
|
||||||
|
private string m_Type = "";
|
||||||
|
private string m_SubType = "";
|
||||||
|
private MIME_h_ParameterCollection m_pParameters = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaType">Media type with subtype. For example <b>text/plain</b>.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>mediaType</b> is null reference.</exception>
|
||||||
|
public MIME_h_ContentType(string mediaType)
|
||||||
|
{
|
||||||
|
if(mediaType == null){
|
||||||
|
throw new ArgumentNullException(mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] type_subtype = mediaType.Split(new char[]{'/',},2);
|
||||||
|
if(type_subtype.Length == 2){
|
||||||
|
if(type_subtype[0] == "" || !MIME_Reader.IsToken(type_subtype[0])){
|
||||||
|
throw new ArgumentException("Invalid argument 'mediaType' value '" + mediaType + "', value must be token.");
|
||||||
|
}
|
||||||
|
if(type_subtype[1] == "" || !MIME_Reader.IsToken(type_subtype[1])){
|
||||||
|
throw new ArgumentException("Invalid argument 'mediaType' value '" + mediaType + "', value must be token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Type = type_subtype[0];
|
||||||
|
m_SubType = type_subtype[1];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("Invalid argument 'mediaType' value '" + mediaType + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pParameters = new MIME_h_ParameterCollection(this);
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal parser constructor.
|
||||||
|
/// </summary>
|
||||||
|
private MIME_h_ContentType()
|
||||||
|
{
|
||||||
|
m_pParameters = new MIME_h_ParameterCollection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses header field from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field value. Header field name must be included. For example: 'Content-Type: text/plain'.</param>
|
||||||
|
/// <returns>Returns parsed header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public static MIME_h_ContentType Parse(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h_ContentType retVal = new MIME_h_ContentType();
|
||||||
|
|
||||||
|
string[] name_value = value.Split(new char[]{':'},2);
|
||||||
|
if(name_value.Length != 2){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_Reader r = new MIME_Reader(name_value[1]);
|
||||||
|
string type = r.Token();
|
||||||
|
if(type == null){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
retVal.m_Type = type;
|
||||||
|
|
||||||
|
if(r.Char(false) != '/'){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string subtype = r.Token();
|
||||||
|
if(subtype == null){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
retVal.m_SubType = subtype;
|
||||||
|
|
||||||
|
retVal.m_pParameters.Parse(r);
|
||||||
|
|
||||||
|
retVal.m_ParseValue = value;
|
||||||
|
retVal.m_IsModified = false;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public override string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
if(!this.IsModified){
|
||||||
|
return m_ParseValue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
retVal.Append("Content-Type: " + m_Type + "/" + m_SubType);
|
||||||
|
retVal.Append(m_pParameters.ToString(parmetersCharset));
|
||||||
|
retVal.Append("\r\n");
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_IsModified || m_pParameters.IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns always "Content-Type".
|
||||||
|
/// </summary>
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return "Content-Type"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets media type. For example: application,image,text, ... .
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The official list of reggistered types are http://www.iana.org/assignments/media-types .</remarks>
|
||||||
|
public string Type
|
||||||
|
{
|
||||||
|
get{ return m_Type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets media sub-type. For example for text/plain, sub-type is 'plain'.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The official list of reggistered types are http://www.iana.org/assignments/media-types .</remarks>
|
||||||
|
public string SubType
|
||||||
|
{
|
||||||
|
get{ return m_SubType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets media type with subtype as Type/SubType. Well known value are in <see cref="MIME_MediaTypes">MIME_MediaTypes</see>. For example: text/plain.
|
||||||
|
/// </summary>
|
||||||
|
public string TypeWithSubype
|
||||||
|
{
|
||||||
|
get{ return m_Type + "/" + m_SubType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets Content-Type parameters collection.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h_ParameterCollection Parameters
|
||||||
|
{
|
||||||
|
get{ return m_pParameters; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Content-Type <b>name</b> parameter value. Value null means not specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Param_Name
|
||||||
|
{
|
||||||
|
get{ return m_pParameters["name"]; }
|
||||||
|
|
||||||
|
set{ m_pParameters["name"] = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Content-Type <b>charset</b> parameter value. Value null means not specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Param_Charset
|
||||||
|
{
|
||||||
|
get{ return m_pParameters["charset"]; }
|
||||||
|
|
||||||
|
set{ m_pParameters["charset"] = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Content-Type <b>boundary</b> parameter value. Value null means not specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Param_Boundary
|
||||||
|
{
|
||||||
|
get{ return m_pParameters["boundary"]; }
|
||||||
|
|
||||||
|
set{ m_pParameters["boundary"] = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
68
MailServer/Misc/MIME/MIME_h_Parameter.cs
Normal file
68
MailServer/Misc/MIME/MIME_h_Parameter.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents MIME header field parameter.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_Parameter
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private string m_Name = "";
|
||||||
|
private string m_Value = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Parameter name.</param>
|
||||||
|
/// <param name="value">Parameter value. Value null means not specified.</param>
|
||||||
|
public MIME_h_Parameter(string name,string value)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Name = name;
|
||||||
|
m_Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field parameter is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields parameters has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_IsModified; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets parameter name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get{ return m_Name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets parameter value. Value null means not specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get{ return m_Value; }
|
||||||
|
|
||||||
|
set{
|
||||||
|
m_Value = value;
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
421
MailServer/Misc/MIME/MIME_h_ParameterCollection.cs
Normal file
421
MailServer/Misc/MIME/MIME_h_ParameterCollection.cs
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents MIME header field parameters collection.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_ParameterCollection : IEnumerable
|
||||||
|
{
|
||||||
|
private bool m_IsModified = false;
|
||||||
|
private MIME_h m_pOwner = null;
|
||||||
|
private Dictionary<string,MIME_h_Parameter> m_pParameters = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="owner">Owner MIME header field.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>owner</b> is null reference.</exception>
|
||||||
|
public MIME_h_ParameterCollection(MIME_h owner)
|
||||||
|
{
|
||||||
|
if(owner == null){
|
||||||
|
throw new ArgumentNullException("owner");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pOwner = owner;
|
||||||
|
|
||||||
|
m_pParameters = new Dictionary<string,MIME_h_Parameter>(StringComparer.CurrentCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Remove
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified parametr from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Parameter name.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
public void Remove(string name)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_pParameters.Remove(name)){
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Clear
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all items from the collection.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_pParameters.Clear();
|
||||||
|
m_IsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method ToArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies header fields parameters to new array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns header fields parameters array.</returns>
|
||||||
|
public MIME_h_Parameter[] ToArray()
|
||||||
|
{
|
||||||
|
MIME_h_Parameter[] retVal = new MIME_h_Parameter[m_pParameters.Count];
|
||||||
|
m_pParameters.Values.CopyTo(retVal,0);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field parameters as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns header field parameters as string.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field parameters as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="charset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header field parameters as string.</returns>
|
||||||
|
public string ToString(Encoding charset)
|
||||||
|
{
|
||||||
|
/* RFC 2231.
|
||||||
|
* If parameter conatins 8-bit byte, we need to encode parameter value
|
||||||
|
* If parameter value length bigger than MIME maximum allowed line length,
|
||||||
|
* we need split value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(charset == null){
|
||||||
|
charset = Encoding.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder retVal = new StringBuilder();
|
||||||
|
foreach(MIME_h_Parameter parameter in this.ToArray()){
|
||||||
|
if(string.IsNullOrEmpty(parameter.Value)){
|
||||||
|
retVal.Append(";\r\n\t" + parameter.Name);
|
||||||
|
}
|
||||||
|
// We don't need to encode or split value.
|
||||||
|
else if((charset == null || Net_Utils.IsAscii(parameter.Value)) && parameter.Value.Length < 76){
|
||||||
|
retVal.Append(";\r\n\t" + parameter.Name + "=" + TextUtils.QuoteString(parameter.Value));
|
||||||
|
}
|
||||||
|
// We need to encode/split value.
|
||||||
|
else{
|
||||||
|
byte[] byteValue = charset.GetBytes(parameter.Value);
|
||||||
|
|
||||||
|
List<string> values = new List<string>();
|
||||||
|
// Do encoding/splitting.
|
||||||
|
int offset = 0;
|
||||||
|
char[] valueBuff = new char[50];
|
||||||
|
foreach(byte b in byteValue){
|
||||||
|
// We need split value as RFC 2231 says.
|
||||||
|
if(offset >= (50 - 3)){
|
||||||
|
values.Add(new string(valueBuff,0,offset));
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal char, we don't need to encode.
|
||||||
|
if(MIME_Reader.IsAttributeChar((char)b)){
|
||||||
|
valueBuff[offset++] = (char)b;
|
||||||
|
}
|
||||||
|
// We need to encode byte as %X2.
|
||||||
|
else{
|
||||||
|
valueBuff[offset++] = '%';
|
||||||
|
valueBuff[offset++] = (b >> 4).ToString("X")[0];
|
||||||
|
valueBuff[offset++] = (b & 0xF).ToString("X")[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add pending buffer value.
|
||||||
|
if(offset > 0){
|
||||||
|
values.Add(new string(valueBuff,0,offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<values.Count;i++){
|
||||||
|
// Only fist value entry has charset and language info.
|
||||||
|
if(charset != null && i == 0){
|
||||||
|
retVal.Append(";\r\n\t" + parameter.Name + "*" + i.ToString() + "*=" + charset.WebName + "''" + values[i]);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
retVal.Append(";\r\n\t" + parameter.Name + "*" + i.ToString() + "*=" + values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses parameters from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field parameters string.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public void Parse(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
Parse(new MIME_Reader(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses parameters from the specified reader.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">MIME reader.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>reader</b> is null reference.</exception>
|
||||||
|
public void Parse(MIME_Reader reader)
|
||||||
|
{
|
||||||
|
if(reader == null){
|
||||||
|
throw new ArgumentNullException("reader");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 2231.
|
||||||
|
*/
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
// End os stream reached.
|
||||||
|
if(reader.Peek(true) == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Next parameter start, just eat that char.
|
||||||
|
else if(reader.Peek(true) == ';'){
|
||||||
|
reader.Char(false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
string name = reader.Token();
|
||||||
|
|
||||||
|
string value = "";
|
||||||
|
// Parameter value specified.
|
||||||
|
if(reader.Peek(true) == '='){
|
||||||
|
reader.Char(false);
|
||||||
|
|
||||||
|
string v = reader.Word();
|
||||||
|
// Normally value may not be null, but following case: paramName=EOS.
|
||||||
|
if(v != null){
|
||||||
|
value = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 2231 encoded/splitted parameter.
|
||||||
|
if(name.IndexOf('*') > -1){
|
||||||
|
string[] name_x_no_x = name.Split('*');
|
||||||
|
name = name_x_no_x[0];
|
||||||
|
|
||||||
|
Encoding charset = Encoding.ASCII;
|
||||||
|
StringBuilder valueBuffer = new StringBuilder();
|
||||||
|
// We must have charset'language'value.
|
||||||
|
// Examples:
|
||||||
|
// URL*=utf-8''test;
|
||||||
|
// URL*0*=utf-8''"test";
|
||||||
|
if((name_x_no_x.Length == 2 && name_x_no_x[1] == "") || name_x_no_x.Length == 3){
|
||||||
|
string[] charset_language_value = value.Split('\'');
|
||||||
|
charset = Encoding.GetEncoding(charset_language_value[0]);
|
||||||
|
valueBuffer.Append(DecodeExtOctet(charset_language_value[2],charset));
|
||||||
|
}
|
||||||
|
// No encoding, probably just splitted ASCII value.
|
||||||
|
// Example:
|
||||||
|
// URL*0="value1";
|
||||||
|
// URL*1="value2";
|
||||||
|
else{
|
||||||
|
valueBuffer.Append(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read while value continues.
|
||||||
|
while(true){
|
||||||
|
// End os stream reached.
|
||||||
|
if(reader.Peek(true) == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Next parameter start, just eat that char.
|
||||||
|
else if(reader.Peek(true) == ';'){
|
||||||
|
reader.Char(false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if(!reader.StartsWith(name + "*")){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.Token();
|
||||||
|
|
||||||
|
// Parameter value specified.
|
||||||
|
if(reader.Peek(true) == '='){
|
||||||
|
reader.Char(false);
|
||||||
|
|
||||||
|
string v = reader.Word();
|
||||||
|
// Normally value may not be null, but following case: paramName=EOS.
|
||||||
|
if(v != null){
|
||||||
|
valueBuffer.Append(DecodeExtOctet(v,charset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this[name] = valueBuffer.ToString();
|
||||||
|
}
|
||||||
|
// Regular parameter.
|
||||||
|
else{
|
||||||
|
this[name] = MIME_Encoding_EncodedWord.DecodeS(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_IsModified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region static method DecodeExtOctet
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes non-ascii text with MIME <b>ext-octet</b> method. Defined in RFC 2231 7.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to decode,</param>
|
||||||
|
/// <param name="charset">Charset to use.</param>
|
||||||
|
/// <returns>Returns decoded text.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> or <b>charset</b> is null.</exception>
|
||||||
|
private static string DecodeExtOctet(string text,Encoding charset)
|
||||||
|
{
|
||||||
|
if(text == null){
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
if(charset == null){
|
||||||
|
throw new ArgumentNullException("charset");
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
byte[] decodedBuffer = new byte[text.Length];
|
||||||
|
for(int i=0;i<text.Length;i++){
|
||||||
|
if(text[i] == '%'){
|
||||||
|
decodedBuffer[offset++] = byte.Parse(text[i + 1].ToString() + text[i + 2].ToString(),System.Globalization.NumberStyles.HexNumber);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
decodedBuffer[offset++] = (byte)text[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charset.GetString(decodedBuffer,0,offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region interface IEnumerator
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return m_pParameters.Values.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field parameters are modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(m_IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
foreach(MIME_h_Parameter parameter in this.ToArray()){
|
||||||
|
if(parameter.IsModified){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets owner MIME header field.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h Owner
|
||||||
|
{
|
||||||
|
get{ return m_pOwner; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets number of items in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get{ return m_pParameters.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets specified header field parameter value. Value null means not specified.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <returns>Returns specified header field value or null if specified parameter doesn't exist.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> is null reference.</exception>
|
||||||
|
public string this[string name]
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h_Parameter retVal = null;
|
||||||
|
if(m_pParameters.TryGetValue(name,out retVal)){
|
||||||
|
return retVal.Value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h_Parameter retVal = null;
|
||||||
|
if(m_pParameters.TryGetValue(name,out retVal)){
|
||||||
|
retVal.Value = value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_pParameters.Add(name,new MIME_h_Parameter(name,value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
103
MailServer/Misc/MIME/MIME_h_Provider.cs
Normal file
103
MailServer/Misc/MIME/MIME_h_Provider.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents MIME headers provider.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_Provider
|
||||||
|
{
|
||||||
|
private Type m_pDefaultHeaderField = null;
|
||||||
|
private Dictionary<string,Type> m_pHeadrFields = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public MIME_h_Provider()
|
||||||
|
{
|
||||||
|
m_pDefaultHeaderField = typeof(MIME_h_Unstructured);
|
||||||
|
|
||||||
|
m_pHeadrFields = new Dictionary<string,Type>(StringComparer.CurrentCultureIgnoreCase);
|
||||||
|
m_pHeadrFields.Add("Content-Type",typeof(MIME_h_ContentType));
|
||||||
|
m_pHeadrFields.Add("Content-Disposition",typeof(MIME_h_ContentDisposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses specified header field.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">Header field string (Name: value).</param>
|
||||||
|
/// <returns>Returns parsed header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>field</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public MIME_h Parse(string field)
|
||||||
|
{
|
||||||
|
if(field == null){
|
||||||
|
throw new ArgumentNullException("field");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h headerField = null;
|
||||||
|
string[] name_value = field.Split(new char[]{':'},2);
|
||||||
|
string name = name_value[0].Trim();
|
||||||
|
if(name == string.Empty){
|
||||||
|
throw new ParseException("Invalid header field value '" + field + "'.");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
try{
|
||||||
|
if(m_pHeadrFields.ContainsKey(name)){
|
||||||
|
headerField = (MIME_h)m_pHeadrFields[name].GetMethod("Parse").Invoke(null,new object[]{field});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
headerField = (MIME_h)m_pDefaultHeaderField.GetMethod("Parse").Invoke(null,new object[]{field});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception x){
|
||||||
|
headerField = new MIME_h_Unparsed(field,x.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets default header field what is used to reperesent unknown header fields.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This property value value must be based on <see cref="MIME_h"/> class.</remarks>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when null reference passed.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when invalid value is passed.</exception>
|
||||||
|
public Type DefaultHeaderField
|
||||||
|
{
|
||||||
|
get{ return m_pDefaultHeaderField; }
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("DefaultHeaderField");
|
||||||
|
}
|
||||||
|
if(!value.GetType().IsSubclassOf(typeof(MIME_h))){
|
||||||
|
throw new ArgumentException("Property 'DefaultHeaderField' value must be based on MIME_h class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pDefaultHeaderField = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header fields parsers collection.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string,Type> HeaderFields
|
||||||
|
{
|
||||||
|
get{ return m_pHeadrFields; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
112
MailServer/Misc/MIME/MIME_h_Unparsed.cs
Normal file
112
MailServer/Misc/MIME/MIME_h_Unparsed.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represent header field what parsing has failed.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_Unparsed : MIME_h
|
||||||
|
{
|
||||||
|
private string m_ParseValue = null;
|
||||||
|
private string m_Name = null;
|
||||||
|
private string m_Value = null;
|
||||||
|
private Exception m_pException = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field value. Header field name must be included. For example: 'Content-Type: text/plain'.</param>
|
||||||
|
/// <param name="exception">Parsing error.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
internal MIME_h_Unparsed(string value,Exception exception)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] name_value = value.Split(new char[]{':'},2);
|
||||||
|
if(name_value.Length != 2){
|
||||||
|
throw new ParseException("Invalid Content-Type: header field value '" + value + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Name = name_value[0];
|
||||||
|
m_Value = name_value[1].Trim();
|
||||||
|
m_ParseValue = value;
|
||||||
|
m_pException = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses header field from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field value. Header field name must be included. For example: 'Content-Type: text/plain'.</param>
|
||||||
|
/// <returns>Returns parsed header field.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Is alwyas raised when this mewthod is accsessed.</exception>
|
||||||
|
public static MIME_h_Unparsed Parse(string value)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public override string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
return m_ParseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header field name.
|
||||||
|
/// </summary>
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return m_Name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header field value.
|
||||||
|
/// </summary>
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get{ return m_Value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets error happened during parse.
|
||||||
|
/// </summary>
|
||||||
|
public Exception Exception
|
||||||
|
{
|
||||||
|
get{ return m_pException; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
177
MailServer/Misc/MIME/MIME_h_Unstructured.cs
Normal file
177
MailServer/Misc/MIME/MIME_h_Unstructured.cs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LumiSoft.Net.MIME
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents normal unstructured text header field.
|
||||||
|
/// </summary>
|
||||||
|
public class MIME_h_Unstructured : MIME_h
|
||||||
|
{
|
||||||
|
private string m_ParseValue = null;
|
||||||
|
private string m_Name = "";
|
||||||
|
private string m_Value = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Header field name.</param>
|
||||||
|
/// <param name="value">Header field value.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>name</b> or <b>value</b> is null reference.</exception>
|
||||||
|
public MIME_h_Unstructured(string name,string value)
|
||||||
|
{
|
||||||
|
if(name == null){
|
||||||
|
throw new ArgumentNullException("name");
|
||||||
|
}
|
||||||
|
if(name == string.Empty){
|
||||||
|
throw new ArgumentException("Argument 'name' value must be specified.","name");
|
||||||
|
}
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Name = name;
|
||||||
|
m_Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal parser constructor.
|
||||||
|
/// </summary>
|
||||||
|
private MIME_h_Unstructured()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region static method Parse
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses header field from the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Header field value. Header field name must be included. For example: 'Content-Type: text/plain'.</param>
|
||||||
|
/// <returns>Returns parsed header field.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ParseException">Is raised when header field parsing errors.</exception>
|
||||||
|
public static MIME_h_Unstructured Parse(string value)
|
||||||
|
{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_h_Unstructured retVal = new MIME_h_Unstructured();
|
||||||
|
|
||||||
|
string[] name_value = value.Split(new char[]{':'},2);
|
||||||
|
if(name_value[0].Trim() == string.Empty){
|
||||||
|
throw new ParseException("Invalid header field '" + value + "' syntax.");
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.m_Name = name_value[0];
|
||||||
|
|
||||||
|
// There may be multiple encoded-words and they can be mixed with atom/quoted-string ... .
|
||||||
|
try{
|
||||||
|
StringBuilder v = new StringBuilder();
|
||||||
|
MIME_Reader r = new MIME_Reader(MIME_Utils.UnfoldHeader(name_value.Length == 2 ? name_value[1].TrimStart() : ""));
|
||||||
|
while(true){
|
||||||
|
string whiteSpaces = r.ToFirstChar();
|
||||||
|
if(!string.IsNullOrEmpty(whiteSpaces)){
|
||||||
|
v.Append(whiteSpaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
string phrase = r.Phrase();
|
||||||
|
if(phrase == null){
|
||||||
|
if(r.Available == 0){
|
||||||
|
retVal.m_Value = v.ToString().TrimStart();
|
||||||
|
}
|
||||||
|
// Parsing failed, leave raw unparsed value.
|
||||||
|
else{
|
||||||
|
retVal.m_Value = MIME_Utils.UnfoldHeader(name_value.Length == 2 ? name_value[1].TrimStart() : "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
v.Append(phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
// Parsing failed, leave raw unparsed value.
|
||||||
|
retVal.m_Value = MIME_Utils.UnfoldHeader(name_value.Length == 2 ? name_value[1].TrimStart() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal.m_ParseValue = value;
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region override method ToString
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns header field as string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wordEncoder">8-bit words ecnoder. Value null means that words are not encoded.</param>
|
||||||
|
/// <param name="parmetersCharset">Charset to use to encode 8-bit characters. Value null means parameters not encoded.</param>
|
||||||
|
/// <returns>Returns header field as string.</returns>
|
||||||
|
public override string ToString(MIME_Encoding_EncodedWord wordEncoder,Encoding parmetersCharset)
|
||||||
|
{
|
||||||
|
if(m_ParseValue != null){
|
||||||
|
return m_ParseValue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if(wordEncoder != null){
|
||||||
|
return m_Name + ": " + wordEncoder.Encode(m_Value) + "\r\n";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return m_Name + ": " + m_Value + "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this header field is modified since it has loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>All new added header fields has <b>IsModified = true</b>.</remarks>
|
||||||
|
/// <exception cref="ObjectDisposedException">Is riased when this class is disposed and this property is accessed.</exception>
|
||||||
|
public override bool IsModified
|
||||||
|
{
|
||||||
|
get{ return m_ParseValue == null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets header field name.
|
||||||
|
/// </summary>
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return m_Name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets header field value.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when when null reference is passed.</exception>
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get{ return m_Value; }
|
||||||
|
|
||||||
|
set{
|
||||||
|
if(value == null){
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Value = value;
|
||||||
|
// Reset parse value.
|
||||||
|
m_ParseValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
651
MailServer/Misc/Net_Utils/Net_Utils.cs
Normal file
651
MailServer/Misc/Net_Utils/Net_Utils.cs
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common utility methods.
|
||||||
|
/// </summary>
|
||||||
|
public class Net_Utils
|
||||||
|
{
|
||||||
|
#region static method GetLocalHostName
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets local host name or argument <b>hostName</b> value if it's specified.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostName">Host name or null.</param>
|
||||||
|
/// <returns>Returns local host name or argument <b>hostName</b> value if it's specified.</returns>
|
||||||
|
public static string GetLocalHostName(string hostName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hostName))
|
||||||
|
{
|
||||||
|
return System.Net.Dns.GetHostName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return hostName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method CompareArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares if specified array itmes equals.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array1">Array 1.</param>
|
||||||
|
/// <param name="array2">Array 2</param>
|
||||||
|
/// <returns>Returns true if both arrays are equal.</returns>
|
||||||
|
public static bool CompareArray(Array array1, Array array2)
|
||||||
|
{
|
||||||
|
return CompareArray(array1, array2, array2.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares if specified array itmes equals.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array1">Array 1.</param>
|
||||||
|
/// <param name="array2">Array 2</param>
|
||||||
|
/// <param name="array2Count">Number of bytes in array 2 used for compare.</param>
|
||||||
|
/// <returns>Returns true if both arrays are equal.</returns>
|
||||||
|
public static bool CompareArray(Array array1, Array array2, int array2Count)
|
||||||
|
{
|
||||||
|
if (array1 == null && array2 == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (array1 == null && array2 != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (array1 != null && array2 == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (array1.Length != array2Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < array1.Length; i++)
|
||||||
|
{
|
||||||
|
if (!array1.GetValue(i).Equals(array2.GetValue(i)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ReverseArray
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reverses the specified array elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">Array elements to reverse.</param>
|
||||||
|
/// <returns>Returns array with reversed items.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>array</b> is null.</exception>
|
||||||
|
public static Array ReverseArray(Array array)
|
||||||
|
{
|
||||||
|
if (array == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("array");
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Reverse(array);
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method StreamCopy
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies <b>source</b> stream data to <b>target</b> stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Source stream. Reading starts from stream current position.</param>
|
||||||
|
/// <param name="target">Target stream. Writing starts from stream current position.</param>
|
||||||
|
/// <param name="blockSize">Specifies transfer block size in bytes.</param>
|
||||||
|
/// <returns>Returns number of bytes copied.</returns>
|
||||||
|
public static long StreamCopy(Stream source, Stream target, int blockSize)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("source");
|
||||||
|
}
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("target");
|
||||||
|
}
|
||||||
|
if (blockSize < 1024)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Argument 'blockSize' value must be >= 1024.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[blockSize];
|
||||||
|
long totalReaded = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int readedCount = source.Read(buffer, 0, buffer.Length);
|
||||||
|
// We reached end of stream, we readed all data sucessfully.
|
||||||
|
if (readedCount == 0)
|
||||||
|
{
|
||||||
|
return totalReaded;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.Write(buffer, 0, readedCount);
|
||||||
|
totalReaded += readedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method IsIPAddress
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified string value is IP address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to check.</param>
|
||||||
|
/// <returns>Returns true if specified value is IP address.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public static bool IsIPAddress(string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress ip = null;
|
||||||
|
|
||||||
|
return IPAddress.TryParse(value, out ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsMulticastAddress
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified IP address is multicast address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ip">IP address.</param>
|
||||||
|
/// <returns>Returns true if <b>ip</b> is muticast address, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> s null reference.</exception>
|
||||||
|
public static bool IsMulticastAddress(IPAddress ip)
|
||||||
|
{
|
||||||
|
if (ip == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 multicast 224.0.0.0 to 239.255.255.255
|
||||||
|
|
||||||
|
if (ip.IsIPv6Multicast)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (ip.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
byte[] bytes = ip.GetAddressBytes();
|
||||||
|
if (bytes[0] >= 224 && bytes[0] <= 239)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsPrivateIP
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ip">IP address to check.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
|
||||||
|
/// <returns>Returns true if IP is private IP.</returns>
|
||||||
|
public static bool IsPrivateIP(string ip)
|
||||||
|
{
|
||||||
|
if (ip == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsPrivateIP(IPAddress.Parse(ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if specified IP address is private LAN IP address. For example 192.168.x.x is private ip.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ip">IP address to check.</param>
|
||||||
|
/// <returns>Returns true if IP is private IP.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>ip</b> is null reference.</exception>
|
||||||
|
public static bool IsPrivateIP(IPAddress ip)
|
||||||
|
{
|
||||||
|
if (ip == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("ip");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
byte[] ipBytes = ip.GetAddressBytes();
|
||||||
|
|
||||||
|
/* Private IPs:
|
||||||
|
First Octet = 192 AND Second Octet = 168 (Example: 192.168.X.X)
|
||||||
|
First Octet = 172 AND (Second Octet >= 16 AND Second Octet <= 31) (Example: 172.16.X.X - 172.31.X.X)
|
||||||
|
First Octet = 10 (Example: 10.X.X.X)
|
||||||
|
First Octet = 169 AND Second Octet = 254 (Example: 169.254.X.X)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ipBytes[0] == 192 && ipBytes[1] == 168)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ipBytes[0] == 172 && ipBytes[1] >= 16 && ipBytes[1] <= 31)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ipBytes[0] == 10)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ipBytes[0] == 169 && ipBytes[1] == 254)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method ParseIPEndPoint
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses IPEndPoint from the specified string value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">IPEndPoint string value.</param>
|
||||||
|
/// <returns>Returns parsed IPEndPoint.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
|
||||||
|
public static IPEndPoint ParseIPEndPoint(string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] ip_port = value.Split(':');
|
||||||
|
|
||||||
|
return new IPEndPoint(IPAddress.Parse(ip_port[0]), Convert.ToInt32(ip_port[1]));
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid IPEndPoint value.", "value", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method IsInteger
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if specified string is integer(int/long).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns>Returns true if specified string is integer.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public static bool IsInteger(string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
long l = 0;
|
||||||
|
|
||||||
|
return long.TryParse(value, out l);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method IsAscii
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the specified string is ASCII string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">String value.</param>
|
||||||
|
/// <returns>Returns true if specified string is ASCII string, otherwise false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>value</b> is null reference.</exception>
|
||||||
|
public static bool IsAscii(string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (char c in value)
|
||||||
|
{
|
||||||
|
if ((int)c > 127)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method IsIoCompletionPortsSupported
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if IO completion ports supported by OS.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsIoCompletionPortsSupported()
|
||||||
|
{
|
||||||
|
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
|
||||||
|
e.SetBuffer(new byte[0], 0, 0);
|
||||||
|
e.RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 111);
|
||||||
|
s.SendToAsync(e);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (NotSupportedException nX)
|
||||||
|
{
|
||||||
|
string dummy = nX.Message;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
s.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method CreateSocket
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new socket for the specified end point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localEP">Local end point.</param>
|
||||||
|
/// <param name="protocolType">Protocol type.</param>
|
||||||
|
/// <returns>Retruns newly created socket.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>localEP</b> is null reference.</exception>
|
||||||
|
public static Socket CreateSocket(IPEndPoint localEP, ProtocolType protocolType)
|
||||||
|
{
|
||||||
|
if (localEP == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("localEP");
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketType socketType = SocketType.Stream;
|
||||||
|
if (protocolType == ProtocolType.Udp)
|
||||||
|
{
|
||||||
|
socketType = SocketType.Dgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEP.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
Socket socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType);
|
||||||
|
socket.Bind(localEP);
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
else if (localEP.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
|
{
|
||||||
|
Socket socket = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType);
|
||||||
|
socket.Bind(localEP);
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid IPEndPoint address family.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method ToHex
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts specified data to HEX string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to convert.</param>
|
||||||
|
/// <returns>Returns hex string.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
|
||||||
|
public static string ToHex(byte[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return BitConverter.ToString(data).ToLower().Replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts specified string to HEX string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">String to convert.</param>
|
||||||
|
/// <returns>Returns hex string.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
|
||||||
|
public static string ToHex(string text)
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
return BitConverter.ToString(Encoding.Default.GetBytes(text)).ToLower().Replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method FromHex
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts hex byte data to normal byte data. Hex data must be in two bytes pairs, for example: 0F,FF,A3,... .
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hexData">Hex data.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>hexData</b> is null reference.</exception>
|
||||||
|
public static byte[] FromHex(byte[] hexData)
|
||||||
|
{
|
||||||
|
if (hexData == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("hexData");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hexData.Length < 2 || (hexData.Length / (double)2 != Math.Floor(hexData.Length / (double)2)))
|
||||||
|
{
|
||||||
|
throw new Exception("Illegal hex data, hex data must be in two bytes pairs, for example: 0F,FF,A3,... .");
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryStream retVal = new MemoryStream(hexData.Length / 2);
|
||||||
|
// Loop hex value pairs
|
||||||
|
for (int i = 0; i < hexData.Length; i += 2)
|
||||||
|
{
|
||||||
|
byte[] hexPairInDecimal = new byte[2];
|
||||||
|
// We need to convert hex char to decimal number, for example F = 15
|
||||||
|
for (int h = 0; h < 2; h++)
|
||||||
|
{
|
||||||
|
if (((char)hexData[i + h]) == '0')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 0;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '1')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 1;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '2')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 2;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '3')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 3;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '4')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 4;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '5')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 5;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '6')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 6;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '7')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 7;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '8')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 8;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == '9')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 9;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'A' || ((char)hexData[i + h]) == 'a')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 10;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'B' || ((char)hexData[i + h]) == 'b')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 11;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'C' || ((char)hexData[i + h]) == 'c')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 12;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'D' || ((char)hexData[i + h]) == 'd')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 13;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'E' || ((char)hexData[i + h]) == 'e')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 14;
|
||||||
|
}
|
||||||
|
else if (((char)hexData[i + h]) == 'F' || ((char)hexData[i + h]) == 'f')
|
||||||
|
{
|
||||||
|
hexPairInDecimal[h] = 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join hex 4 bit(left hex cahr) + 4bit(right hex char) in bytes 8 it
|
||||||
|
retVal.WriteByte((byte)((hexPairInDecimal[0] << 4) | hexPairInDecimal[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region static method FromBase64
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes specified base64 data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Base64 string.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
|
||||||
|
public static byte[] FromBase64(string data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
Base64 base64 = new Base64();
|
||||||
|
|
||||||
|
return base64.Decode(data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes specified base64 data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Base64 data.</param>
|
||||||
|
/// <returns>Returns decoded data.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>data</b> is null reference.</exception>
|
||||||
|
public static byte[] FromBase64(byte[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
Base64 base64 = new Base64();
|
||||||
|
|
||||||
|
return base64.Decode(data, 0, data.Length, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region static method ComputeMd5
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes md5 hash.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to hash.</param>
|
||||||
|
/// <param name="hex">Specifies if md5 value is returned as hex string.</param>
|
||||||
|
/// <returns>Returns md5 value or md5 hex value.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Is raised when <b>text</b> is null reference.</exception>
|
||||||
|
public static string ComputeMd5(string text, bool hex)
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
|
||||||
|
byte[] hash = md5.ComputeHash(Encoding.Default.GetBytes(text));
|
||||||
|
|
||||||
|
if (hex)
|
||||||
|
{
|
||||||
|
return ToHex(System.Text.Encoding.Default.GetString(hash)).ToLower();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return System.Text.Encoding.Default.GetString(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
23
MailServer/Misc/SSLMode.cs
Normal file
23
MailServer/Misc/SSLMode.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This enum holds SSL modes.
|
||||||
|
/// </summary>
|
||||||
|
public enum SslMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No SSL is used.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connection is SSL.
|
||||||
|
/// </summary>
|
||||||
|
SSL,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connection will be switched to SSL with start TLS.
|
||||||
|
/// </summary>
|
||||||
|
TLS
|
||||||
|
}
|
||||||
|
}
|
43
MailServer/Misc/SaslAuthTypes.cs
Normal file
43
MailServer/Misc/SaslAuthTypes.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SASL authentications
|
||||||
|
/// </summary>
|
||||||
|
public enum SaslAuthTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Non authentication
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plain text authentication. For POP3 USER/PASS commands, for IMAP LOGIN command.
|
||||||
|
/// </summary>
|
||||||
|
Plain = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LOGIN.
|
||||||
|
/// </summary>
|
||||||
|
Login = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRAM-MD5
|
||||||
|
/// </summary>
|
||||||
|
Cram_md5 = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DIGEST-MD5.
|
||||||
|
/// </summary>
|
||||||
|
Digest_md5 = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All authentications.
|
||||||
|
/// </summary>
|
||||||
|
All = 0xF,
|
||||||
|
}
|
||||||
|
}
|
44
MailServer/Misc/SocketServer/Log_EventArgs/Log_EventArgs.cs
Normal file
44
MailServer/Misc/SocketServer/Log_EventArgs/Log_EventArgs.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public class Log_EventArgs
|
||||||
|
{
|
||||||
|
private SocketLogger m_pLoggger = null;
|
||||||
|
private bool m_FirstLogPart = true;
|
||||||
|
private bool m_LastLogPart = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Socket logger.</param>
|
||||||
|
/// <param name="firstLogPart">Specifies if first log part of multipart log.</param>
|
||||||
|
/// <param name="lastLogPart">Specifies if last log part (logging ended).</param>
|
||||||
|
public Log_EventArgs(SocketLogger logger, bool firstLogPart, bool lastLogPart)
|
||||||
|
{
|
||||||
|
m_pLoggger = logger;
|
||||||
|
m_FirstLogPart = firstLogPart;
|
||||||
|
m_LastLogPart = lastLogPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets log text.
|
||||||
|
/// </summary>
|
||||||
|
public string LogText
|
||||||
|
{
|
||||||
|
get { return SocketLogger.LogEntriesToString(m_pLoggger, m_FirstLogPart, m_LastLogPart); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets logger.
|
||||||
|
/// </summary>
|
||||||
|
public SocketLogger Logger
|
||||||
|
{
|
||||||
|
get { return m_pLoggger; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
74
MailServer/Misc/SocketServer/ReadExecption/ReadExecption.cs
Normal file
74
MailServer/Misc/SocketServer/ReadExecption/ReadExecption.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
#region public enum ReadReplyCode
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reply reading return codes.
|
||||||
|
/// </summary>
|
||||||
|
public enum ReadReplyCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read completed successfully.
|
||||||
|
/// </summary>
|
||||||
|
Ok = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read timed out.
|
||||||
|
/// </summary>
|
||||||
|
TimeOut = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum allowed Length exceeded.
|
||||||
|
/// </summary>
|
||||||
|
LengthExceeded = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connected client closed connection.
|
||||||
|
/// </summary>
|
||||||
|
SocketClosed = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UnKnown error, eception raised.
|
||||||
|
/// </summary>
|
||||||
|
UnKnownError = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Summary description for ReadException.
|
||||||
|
/// </summary>
|
||||||
|
public class ReadException : System.Exception
|
||||||
|
{
|
||||||
|
private ReadReplyCode m_ReadReplyCode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public ReadException(ReadReplyCode code, string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
m_ReadReplyCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets read error.
|
||||||
|
/// </summary>
|
||||||
|
public ReadReplyCode ReadReplyCode
|
||||||
|
{
|
||||||
|
get { return m_ReadReplyCode; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
38
MailServer/Misc/SocketServer/SocketCallBackResult.cs
Normal file
38
MailServer/Misc/SocketServer/SocketCallBackResult.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public delegate void SocketCallBack(SocketCallBackResult result, long count, Exception x, object tag);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronous command execute result.
|
||||||
|
/// </summary>
|
||||||
|
public enum SocketCallBackResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Operation was successfull.
|
||||||
|
/// </summary>
|
||||||
|
Ok,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exceeded maximum allowed size.
|
||||||
|
/// </summary>
|
||||||
|
LengthExceeded,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connected client closed connection.
|
||||||
|
/// </summary>
|
||||||
|
SocketClosed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception happened.
|
||||||
|
/// </summary>
|
||||||
|
Exception
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
private delegate void BufferDataBlockCompleted(Exception x, object tag);
|
||||||
|
}
|
||||||
|
}
|
213
MailServer/Misc/SocketServer/SocketEx/SocketEx.Network.cs
Normal file
213
MailServer/Misc/SocketServer/SocketEx/SocketEx.Network.cs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connects to the specified host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">IP endpoint where to connect.</param>
|
||||||
|
public void Connect(IPEndPoint endpoint)
|
||||||
|
{
|
||||||
|
Connect(endpoint.Address.ToString(), endpoint.Port, false);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Connects to the specified host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">IP endpoint where to connect.</param>
|
||||||
|
/// <param name="ssl">Specifies if to connected via SSL.</param>
|
||||||
|
public void Connect(IPEndPoint endpoint, bool ssl)
|
||||||
|
{
|
||||||
|
Connect(endpoint.Address.ToString(), endpoint.Port, ssl);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Connects to the specified host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Host name or IP where to connect.</param>
|
||||||
|
/// <param name="port">TCP port number where to connect.</param>
|
||||||
|
public void Connect(string host, int port)
|
||||||
|
{
|
||||||
|
Connect(host, port, false);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Connects to the specified host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">Host name or IP where to connect.</param>
|
||||||
|
/// <param name="port">TCP port number where to connect.</param>
|
||||||
|
/// <param name="ssl">Specifies if to connected via SSL.</param>
|
||||||
|
public void Connect(string host, int port, bool ssl)
|
||||||
|
{
|
||||||
|
m_pSocket.Connect(new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0], port));
|
||||||
|
// mono won't support it
|
||||||
|
//m_pSocket.Connect(host,port);
|
||||||
|
|
||||||
|
m_Host = host;
|
||||||
|
m_pSocketStream = new NetworkStream(m_pSocket, false);
|
||||||
|
|
||||||
|
if (ssl)
|
||||||
|
{
|
||||||
|
SwitchToSSL_AsClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects socket.
|
||||||
|
/// </summary>
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (m_pSocket != null)
|
||||||
|
{
|
||||||
|
m_pSocket.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_SSL = false;
|
||||||
|
m_pSocketStream = null;
|
||||||
|
m_pSslStream = null;
|
||||||
|
m_pSocket = null;
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
m_Host = "";
|
||||||
|
m_ReadedCount = 0;
|
||||||
|
m_WrittenCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Shutdowns socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="how"></param>
|
||||||
|
public void Shutdown(SocketShutdown how)
|
||||||
|
{
|
||||||
|
m_pSocket.Shutdown(how);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Associates a Socket with a local endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loaclEP"></param>
|
||||||
|
public void Bind(EndPoint loaclEP)
|
||||||
|
{
|
||||||
|
m_pSocket.Bind(loaclEP);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Places a Socket in a listening state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backlog">The maximum length of the pending connections queue. </param>
|
||||||
|
public void Listen(int backlog)
|
||||||
|
{
|
||||||
|
m_pSocket.Listen(backlog);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// TODO:
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ssl"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SocketEx Accept(bool ssl)
|
||||||
|
{
|
||||||
|
Socket s = m_pSocket.Accept();
|
||||||
|
return new SocketEx(s);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Sends data to the specified end point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to send.</param>
|
||||||
|
/// <param name="remoteEP">Remote endpoint where to send data.</param>
|
||||||
|
/// <returns>Returns number of bytes actualy sent.</returns>
|
||||||
|
public int SendTo(byte[] data, EndPoint remoteEP)
|
||||||
|
{
|
||||||
|
return m_pSocket.SendTo(data, remoteEP);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Buffers data from socket if needed. If there is data in buffer, no buffering is done.
|
||||||
|
/// </summary>
|
||||||
|
private void BufferDataBlock()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
// There is no data in buffer, buffer next data block
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_AvailableInBuffer = m_pSslStream.Read(m_Buffer, 0, m_Buffer.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_AvailableInBuffer = m_pSocket.Receive(m_Buffer);
|
||||||
|
}
|
||||||
|
m_ReadedCount += m_AvailableInBuffer;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Start buffering data from socket asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous data buffering operation is completed.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
private void BeginBufferDataBlock(BufferDataBlockCompleted callback, object tag)
|
||||||
|
{
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.BeginRead(m_Buffer, 0, m_Buffer.Length, this.OnBeginBufferDataBlockCallback, new object[] { callback, tag });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocket.BeginReceive(m_Buffer, 0, m_Buffer.Length, SocketFlags.None, this.OnBeginBufferDataBlockCallback, new object[] { callback, tag });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous BeginBufferDataBlock is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ar"></param>
|
||||||
|
private void OnBeginBufferDataBlockCallback(IAsyncResult ar)
|
||||||
|
{
|
||||||
|
object[] param = (object[])ar.AsyncState;
|
||||||
|
BufferDataBlockCompleted callback = (BufferDataBlockCompleted)param[0];
|
||||||
|
object tag = param[1];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Socket closed by this.Disconnect() or closed by remote host.
|
||||||
|
if (m_pSocket == null || !m_pSocket.Connected)
|
||||||
|
{
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_AvailableInBuffer = m_pSslStream.EndRead(ar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_AvailableInBuffer = m_pSocket.EndReceive(ar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_ReadedCount += m_AvailableInBuffer;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(null, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(x, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
MailServer/Misc/SocketServer/SocketEx/SocketEx.Properties.cs
Normal file
122
MailServer/Misc/SocketServer/SocketEx/SocketEx.Properties.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets socket default encoding.
|
||||||
|
/// </summary>
|
||||||
|
public Encoding Encoding
|
||||||
|
{
|
||||||
|
get { return m_pEncoding; }
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (m_pEncoding == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("Encoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEncoding = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets logging source. If this is setted, reads/writes are logged to it.
|
||||||
|
/// </summary>
|
||||||
|
public SocketLogger Logger
|
||||||
|
{
|
||||||
|
get { return m_pLogger; }
|
||||||
|
|
||||||
|
set { m_pLogger = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets raw uderlaying socket.
|
||||||
|
/// </summary>
|
||||||
|
public Socket RawSocket
|
||||||
|
{
|
||||||
|
get { return m_pSocket; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if socket is connected.
|
||||||
|
/// </summary>
|
||||||
|
public bool Connected
|
||||||
|
{
|
||||||
|
get { return m_pSocket != null && m_pSocket.Connected; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public EndPoint LocalEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_pSocket == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m_pSocket.LocalEndPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the remote endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public EndPoint RemoteEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_pSocket == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m_pSocket.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if socket is connected via SSL.
|
||||||
|
/// </summary>
|
||||||
|
public bool SSL
|
||||||
|
{
|
||||||
|
get { return m_SSL; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many bytes are readed through this socket.
|
||||||
|
/// </summary>
|
||||||
|
public long ReadedCount
|
||||||
|
{
|
||||||
|
get { return m_ReadedCount; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many bytes are written through this socket.
|
||||||
|
/// </summary>
|
||||||
|
public long WrittenCount
|
||||||
|
{
|
||||||
|
get { return m_WrittenCount; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets when was last socket(read or write) activity.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastActivity
|
||||||
|
{
|
||||||
|
get { return m_LastActivityDate; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
851
MailServer/Misc/SocketServer/SocketEx/SocketEx.Read.cs
Normal file
851
MailServer/Misc/SocketServer/SocketEx/SocketEx.Read.cs
Normal file
@ -0,0 +1,851 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads byte from socket. Returns readed byte or -1 if socket is shutdown and tehre is no more data available.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns readed byte or -1 if socket is shutdown and tehre is no more data available.</returns>
|
||||||
|
public int ReadByte()
|
||||||
|
{
|
||||||
|
BufferDataBlock();
|
||||||
|
// Socket is shutdown
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_OffsetInBuffer++;
|
||||||
|
m_AvailableInBuffer--;
|
||||||
|
|
||||||
|
return m_Buffer[m_OffsetInBuffer - 1];
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads line from socket. Maximum line length is 4000 bytes. NOTE: CRLF isn't written to destination stream.
|
||||||
|
/// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after line reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns readed line.</returns>
|
||||||
|
public string ReadLine()
|
||||||
|
{
|
||||||
|
return ReadLine(4000);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads line from socket.NOTE: CRLF isn't written to destination stream.
|
||||||
|
/// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after line reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxLineLength">Maximum line length in bytes.</param>
|
||||||
|
/// <returns>Returns readed line.</returns>
|
||||||
|
public string ReadLine(int maxLineLength)
|
||||||
|
{
|
||||||
|
return m_pEncoding.GetString(ReadLineByte(maxLineLength));
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads line from socket.NOTE: CRLF isn't written to destination stream.
|
||||||
|
/// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after line reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxLineLength">Maximum line length in bytes.</param>
|
||||||
|
/// <returns>Returns readed line.</returns>
|
||||||
|
public byte[] ReadLineByte(int maxLineLength)
|
||||||
|
{
|
||||||
|
MemoryStream strmLineBuf = new MemoryStream();
|
||||||
|
ReadLine(strmLineBuf, maxLineLength);
|
||||||
|
|
||||||
|
return strmLineBuf.ToArray();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads line from socket and stores it to specified stream. NOTE: CRLF isn't written to destination stream.
|
||||||
|
/// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after line reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed line.</param>
|
||||||
|
/// <param name="maxLineLength">Maximum line length in bytes.</param>
|
||||||
|
public void ReadLine(Stream stream, int maxLineLength)
|
||||||
|
{
|
||||||
|
// Delay last byte writing, this is because CR, if next is LF, then skip CRLF and terminate reading.
|
||||||
|
|
||||||
|
int lastByte = ReadByte();
|
||||||
|
int currentByte = ReadByte();
|
||||||
|
int readedCount = 2;
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We got line
|
||||||
|
if (lastByte == (byte)'\r' && currentByte == (byte)'\n')
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), readedCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary line, readed " + readedCount.ToString() + " bytes.", readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Flush();
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded
|
||||||
|
if (readedCount > maxLineLength)
|
||||||
|
{
|
||||||
|
throw new ReadException(ReadReplyCode.LengthExceeded, "Maximum allowed line length exceeded !");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Maximum allowed length exceeded, just don't store data.
|
||||||
|
if (readedCount < maxLineLength)
|
||||||
|
{
|
||||||
|
stream.WriteByte((byte)lastByte);
|
||||||
|
}
|
||||||
|
lastByte = currentByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should not reach there, if so then socket closed
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket !");
|
||||||
|
}
|
||||||
|
throw new ReadException(ReadReplyCode.SocketClosed, "Connected host closed socket, read line terminated unexpectedly !");
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads specified length of data from socket and store to specified stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lengthToRead">Specifies how much data to read from socket.</param>
|
||||||
|
/// <param name="storeStream">Stream where to store data.</param>
|
||||||
|
public void ReadSpecifiedLength(int lengthToRead, Stream storeStream)
|
||||||
|
{
|
||||||
|
while (lengthToRead > 0)
|
||||||
|
{
|
||||||
|
BufferDataBlock();
|
||||||
|
// Socket is shutdown
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket, all data wans't readed !");
|
||||||
|
}
|
||||||
|
throw new Exception("Remote host closed socket, all data wans't readed !");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have all data in buffer what we need.
|
||||||
|
if (m_AvailableInBuffer >= lengthToRead)
|
||||||
|
{
|
||||||
|
storeStream.Write(m_Buffer, m_OffsetInBuffer, lengthToRead);
|
||||||
|
storeStream.Flush();
|
||||||
|
|
||||||
|
m_OffsetInBuffer += lengthToRead;
|
||||||
|
m_AvailableInBuffer -= lengthToRead;
|
||||||
|
lengthToRead = 0;
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (storeStream.CanSeek && storeStream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[storeStream.Length];
|
||||||
|
storeStream.Position = 0;
|
||||||
|
storeStream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), lengthToRead);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary data, readed " + lengthToRead.ToString() + " bytes.", lengthToRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We need more data than buffer has,read all buffer data.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storeStream.Write(m_Buffer, m_OffsetInBuffer, m_AvailableInBuffer);
|
||||||
|
storeStream.Flush();
|
||||||
|
|
||||||
|
lengthToRead -= m_AvailableInBuffer;
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads period terminated string. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>".
|
||||||
|
/// When a line of text is received, it checks the line. If the line is composed of a single period,
|
||||||
|
/// it is treated as the end of data indicator. If the first character is a period and there are
|
||||||
|
/// other characters on the line, the first character is deleted.
|
||||||
|
/// If maximum allowed data length is exceeded data is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after data reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxLength">Maximum data length in bytes.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string ReadPeriodTerminated(int maxLength)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
ReadPeriodTerminated(ms, maxLength);
|
||||||
|
|
||||||
|
return m_pEncoding.GetString(ms.ToArray());
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads period terminated data. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>".
|
||||||
|
/// When a line of text is received, it checks the line. If the line is composed of a single period,
|
||||||
|
/// it is treated as the end of data indicator. If the first character is a period and there are
|
||||||
|
/// other characters on the line, the first character is deleted.
|
||||||
|
/// If maximum allowed data length is exceeded data is read to end, but isn't stored to stream and exception
|
||||||
|
/// is thrown after data reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="maxLength">Maximum data length in bytes.</param>
|
||||||
|
public void ReadPeriodTerminated(Stream stream, int maxLength)
|
||||||
|
{
|
||||||
|
/* When a line of text is received by the server, it checks the line.
|
||||||
|
If the line is composed of a single period, it is treated as the
|
||||||
|
end of data indicator. If the first character is a period and
|
||||||
|
there are other characters on the line, the first character is deleted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Delay last byte writing, this is because CR, if next is LF, then last byte isn't written.
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8000];
|
||||||
|
int positionInBuffer = 0;
|
||||||
|
|
||||||
|
int lastByte = ReadByte();
|
||||||
|
int currentByte = ReadByte();
|
||||||
|
int readedCount = 2;
|
||||||
|
bool lineBreak = false;
|
||||||
|
bool expectCRLF = false;
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We got <CRLF> + 1 char, we must skip that char if it is '.'.
|
||||||
|
if (lineBreak)
|
||||||
|
{
|
||||||
|
lineBreak = false;
|
||||||
|
|
||||||
|
// We must skip that char if it is '.'
|
||||||
|
if (currentByte == '.')
|
||||||
|
{
|
||||||
|
expectCRLF = true;
|
||||||
|
|
||||||
|
currentByte = ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We got <CRLF>
|
||||||
|
else if (lastByte == (byte)'\r' && currentByte == (byte)'\n')
|
||||||
|
{
|
||||||
|
lineBreak = true;
|
||||||
|
|
||||||
|
// We have <CRLF>.<CRLF>, skip last <CRLF>.
|
||||||
|
if (expectCRLF)
|
||||||
|
{
|
||||||
|
// There is data in buffer, flush it
|
||||||
|
if (positionInBuffer > 0)
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), readedCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary data, readed " + readedCount.ToString() + " bytes.", readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded
|
||||||
|
if (readedCount > maxLength)
|
||||||
|
{
|
||||||
|
throw new ReadException(ReadReplyCode.LengthExceeded, "Maximum allowed line length exceeded !");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// current char isn't CRLF part, so it isn't <CRLF>.<CRLF> terminator.
|
||||||
|
if (expectCRLF && !(currentByte == (byte)'\r' || currentByte == (byte)'\n'))
|
||||||
|
{
|
||||||
|
expectCRLF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded, just don't store data.
|
||||||
|
if (readedCount < maxLength)
|
||||||
|
{
|
||||||
|
// Buffer is filled up, write buffer to stream
|
||||||
|
if (positionInBuffer > (buffer.Length - 2))
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)lastByte;
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
lastByte = currentByte;
|
||||||
|
currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We never should reach there, only if data isn't <CRLF>.<CRLF> terminated.
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket and data wasn't <CRLF>.<CRLF> terminated !");
|
||||||
|
}
|
||||||
|
throw new Exception("Remote host closed socket and data wasn't <CRLF>.<CRLF> terminated !");
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins reading line from socket asynchrounously.
|
||||||
|
/// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
|
||||||
|
/// is thrown after line reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed line.</param>
|
||||||
|
/// <param name="maxLineLength">Maximum line length in bytes.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous line read operation is completed.</param>
|
||||||
|
public void BeginReadLine(Stream stream, int maxLineLength, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
TryToReadLine(callback, tag, stream, maxLineLength, -1, 0);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to read line from socket data buffer. If buffer doesn't contain line,
|
||||||
|
/// next buffer data block is getted asynchronously and this method is called again.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous line read operation is completed.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="maxLineLength">Specifies maximum line legth.</param>
|
||||||
|
/// <param name="lastByte">Last byte what was readed pevious method call or -1 if first method call.</param>
|
||||||
|
/// <param name="readedCount">Specifies count of bytes readed.</param>
|
||||||
|
private void TryToReadLine(SocketCallBack callback, object tag, Stream stream, int maxLineLength, int lastByte, int readedCount)
|
||||||
|
{
|
||||||
|
// There is no data in buffer, buffer next block asynchronously.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted, new object[] { callback, tag, stream, maxLineLength, lastByte, readedCount });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay last byte writing, this is because CR, if next is LF, then skip CRLF and terminate reading.
|
||||||
|
|
||||||
|
// This is first method call, buffer 1 byte
|
||||||
|
if (lastByte == -1)
|
||||||
|
{
|
||||||
|
lastByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
|
||||||
|
// We use last byte, buffer next block asynchronously.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted, new object[] { callback, tag, stream, maxLineLength, lastByte, readedCount });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We got line
|
||||||
|
if (lastByte == (byte)'\r' && currentByte == (byte)'\n')
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), readedCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary line, readed " + readedCount.ToString() + " bytes.", readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded
|
||||||
|
if (readedCount > maxLineLength)
|
||||||
|
{
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.LengthExceeded, 0, new ReadException(ReadReplyCode.LengthExceeded, "Maximum allowed data length exceeded !"), tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line readed ok, call callback.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Ok, readedCount, null, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Maximum allowed length exceeded, just don't store data.
|
||||||
|
if (readedCount < maxLineLength)
|
||||||
|
{
|
||||||
|
stream.WriteByte((byte)lastByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
lastByte = currentByte;
|
||||||
|
if (m_AvailableInBuffer > 0)
|
||||||
|
{
|
||||||
|
currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
}
|
||||||
|
// We have use all data in the buffer, buffer next block asynchronously.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted, new object[] { callback, tag, stream, maxLineLength, lastByte, readedCount });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should not reach there, if so then socket closed
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket !");
|
||||||
|
}
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.SocketClosed, 0, new ReadException(ReadReplyCode.SocketClosed, "Connected host closed socket, read line terminated unexpectedly !"), tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous data buffering is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
private void OnBeginReadLineBufferingCompleted(Exception x, object tag)
|
||||||
|
{
|
||||||
|
object[] param = (object[])tag;
|
||||||
|
SocketCallBack callback = (SocketCallBack)param[0];
|
||||||
|
object callbackTag = (object)param[1];
|
||||||
|
Stream stream = (Stream)param[2];
|
||||||
|
int maxLineLength = (int)param[3];
|
||||||
|
int lastByte = (int)param[4];
|
||||||
|
int readedCount = (int)param[5];
|
||||||
|
|
||||||
|
if (x == null)
|
||||||
|
{
|
||||||
|
// We didn't get data, this can only happen if socket closed.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket !");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(SocketCallBackResult.SocketClosed, 0, null, callbackTag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TryToReadLine(callback, callbackTag, stream, maxLineLength, lastByte, readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Exception, 0, x, callbackTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins reading specified amount of data from socket asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="lengthToRead">Specifies number of bytes to read from socket.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
|
||||||
|
public void BeginReadSpecifiedLength(Stream stream, int lengthToRead, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
TryToReadReadSpecifiedLength(stream, lengthToRead, tag, callback, 0);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to read specified length of data from socket data buffer. If buffer doesn't contain data,
|
||||||
|
/// next buffer data block is getted asynchronously and this method is called again.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="lengthToRead">Specifies number of bytes to read from socket.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
|
||||||
|
/// <param name="readedCount">Specifies count of bytes readed.</param>
|
||||||
|
private void TryToReadReadSpecifiedLength(Stream stream, int lengthToRead, object tag, SocketCallBack callback, int readedCount)
|
||||||
|
{
|
||||||
|
if (lengthToRead == 0)
|
||||||
|
{
|
||||||
|
// Data readed ok, call callback.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Ok, readedCount, null, tag);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no data in buffer, buffer next block asynchronously.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadSpecifiedLengthBufferingCompleted, new object[] { callback, tag, stream, lengthToRead, readedCount });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer has less data than that we need
|
||||||
|
int lengthLeftForReading = lengthToRead - readedCount;
|
||||||
|
if (lengthLeftForReading > m_AvailableInBuffer)
|
||||||
|
{
|
||||||
|
stream.Write(m_Buffer, m_OffsetInBuffer, m_AvailableInBuffer);
|
||||||
|
stream.Flush();
|
||||||
|
|
||||||
|
readedCount += m_AvailableInBuffer;
|
||||||
|
// We used buffer directly, sync buffer info !!!
|
||||||
|
m_OffsetInBuffer = 0;
|
||||||
|
m_AvailableInBuffer = 0;
|
||||||
|
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadSpecifiedLengthBufferingCompleted, new object[] { callback, tag, stream, lengthToRead, readedCount });
|
||||||
|
}
|
||||||
|
// Buffer contains all data we need
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Write(m_Buffer, m_OffsetInBuffer, lengthLeftForReading);
|
||||||
|
stream.Flush();
|
||||||
|
|
||||||
|
readedCount += lengthLeftForReading;
|
||||||
|
// We used buffer directly, sync buffer info !!!
|
||||||
|
m_OffsetInBuffer += lengthLeftForReading;
|
||||||
|
m_AvailableInBuffer -= lengthLeftForReading;
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), lengthToRead);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary data, readed " + readedCount + " bytes.", readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data readed ok, call callback.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Ok, readedCount, null, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous data buffering is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
private void OnBeginReadSpecifiedLengthBufferingCompleted(Exception x, object tag)
|
||||||
|
{
|
||||||
|
object[] param = (object[])tag;
|
||||||
|
SocketCallBack callback = (SocketCallBack)param[0];
|
||||||
|
object callbackTag = (object)param[1];
|
||||||
|
Stream stream = (Stream)param[2];
|
||||||
|
int lengthToRead = (int)param[3];
|
||||||
|
int readedCount = (int)param[4];
|
||||||
|
|
||||||
|
if (x == null)
|
||||||
|
{
|
||||||
|
// We didn't get data, this can only happen if socket closed.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket !");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(SocketCallBackResult.SocketClosed, 0, null, callbackTag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TryToReadReadSpecifiedLength(stream, lengthToRead, callbackTag, callback, readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Exception, 0, x, callbackTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins reading period terminated data. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>".
|
||||||
|
/// When a line of text is received, it checks the line. If the line is composed of a single period,
|
||||||
|
/// it is treated as the end of data indicator. If the first character is a period and there are
|
||||||
|
/// other characters on the line, the first character is deleted.
|
||||||
|
/// If maximum allowed data length is exceeded data is read to end, but isn't stored to stream and exception
|
||||||
|
/// is thrown after data reading.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="maxLength">Maximum data length in bytes.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
|
||||||
|
public void BeginReadPeriodTerminated(Stream stream, int maxLength, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
TryToReadPeriodTerminated(callback, tag, stream, maxLength, -1, 0, false, false);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to read period terminated data from socket data buffer. If buffer doesn't contain
|
||||||
|
/// period terminated data,next buffer data block is getted asynchronously and this method is called again.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous period terminated read operation is completed.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="stream">Stream where to store readed data.</param>
|
||||||
|
/// <param name="maxLength">Specifies maximum data legth in bytes.</param>
|
||||||
|
/// <param name="readedCount">Specifies count of bytes readed.</param>
|
||||||
|
/// <param name="lastByte">Last byte what was readed pevious method call or -1 if first method call.</param>
|
||||||
|
/// <param name="lineBreak">Specifies if there is active line break.</param>
|
||||||
|
/// <param name="expectCRLF">Specifies if terminating CRLF is expected.</param>
|
||||||
|
private void TryToReadPeriodTerminated(SocketCallBack callback, object tag, Stream stream, int maxLength, int lastByte, int readedCount, bool lineBreak, bool expectCRLF)
|
||||||
|
{
|
||||||
|
/* When a line of text is received by the server, it checks the line.
|
||||||
|
If the line is composed of a single period, it is treated as the
|
||||||
|
end of data indicator. If the first character is a period and
|
||||||
|
there are other characters on the line, the first character is deleted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// There is no data in buffer, buffer next block asynchronously.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted, new object[] { callback, tag, stream, maxLength, lastByte, readedCount, lineBreak, expectCRLF });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay last byte writing, this is because CR, if next is LF, then last byte isn't written.
|
||||||
|
|
||||||
|
// This is first method call, buffer 1 byte
|
||||||
|
if (lastByte == -1)
|
||||||
|
{
|
||||||
|
lastByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
|
||||||
|
// We used last byte, buffer next block asynchronously.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted, new object[] { callback, tag, stream, maxLength, lastByte, readedCount, lineBreak, expectCRLF });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8000];
|
||||||
|
int positionInBuffer = 0;
|
||||||
|
|
||||||
|
int currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We got <CRLF> + 1 char, we must skip that char if it is '.'.
|
||||||
|
if (lineBreak)
|
||||||
|
{
|
||||||
|
lineBreak = false;
|
||||||
|
|
||||||
|
// We must skip this char if it is '.'
|
||||||
|
if (currentByte == '.')
|
||||||
|
{
|
||||||
|
expectCRLF = true;
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
if (m_AvailableInBuffer > 0)
|
||||||
|
{
|
||||||
|
currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
}
|
||||||
|
// We have use all data in the buffer, buffer next block asynchronously.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There is data in buffer, flush it
|
||||||
|
if (positionInBuffer > 0)
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted, new object[] { callback, tag, stream, maxLength, lastByte, readedCount, lineBreak, expectCRLF });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We got <CRLF>
|
||||||
|
else if (lastByte == (byte)'\r' && currentByte == (byte)'\n')
|
||||||
|
{
|
||||||
|
lineBreak = true;
|
||||||
|
|
||||||
|
// We have <CRLF>.<CRLF>, skip last <CRLF>.
|
||||||
|
if (expectCRLF)
|
||||||
|
{
|
||||||
|
// There is data in buffer, flush it
|
||||||
|
if (positionInBuffer > 0)
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] readedData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(readedData, 0, readedData.Length);
|
||||||
|
m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData), readedCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddReadEntry("Big binary data, readed " + readedCount.ToString() + " bytes.", readedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded
|
||||||
|
if (readedCount > maxLength)
|
||||||
|
{
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.LengthExceeded, 0, new ReadException(ReadReplyCode.LengthExceeded, "Maximum allowed data length exceeded !"), tag);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data readed ok, call callback.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Ok, readedCount, null, tag);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// current char isn't CRLF part, so it isn't <CRLF>.<CRLF> terminator.
|
||||||
|
if (expectCRLF && !(currentByte == (byte)'\r' || currentByte == (byte)'\n'))
|
||||||
|
{
|
||||||
|
expectCRLF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum allowed length exceeded, just don't store data.
|
||||||
|
if (readedCount < maxLength)
|
||||||
|
{
|
||||||
|
// Buffer is filled up, write buffer to stream
|
||||||
|
if (positionInBuffer > (buffer.Length - 2))
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)lastByte;
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read next byte
|
||||||
|
lastByte = currentByte;
|
||||||
|
if (m_AvailableInBuffer > 0)
|
||||||
|
{
|
||||||
|
currentByte = ReadByte();
|
||||||
|
readedCount++;
|
||||||
|
}
|
||||||
|
// We have use all data in the buffer, buffer next block asynchronously.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There is data in buffer, flush it
|
||||||
|
if (positionInBuffer > 0)
|
||||||
|
{
|
||||||
|
stream.Write(buffer, 0, positionInBuffer);
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted, new object[] { callback, tag, stream, maxLength, lastByte, readedCount, lineBreak, expectCRLF });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should never reach here.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Exception, 0, new Exception("Never should reach there ! method TryToReadPeriodTerminated out of while loop."), tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous data buffering is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
private void OnBeginReadPeriodTerminatedBufferingCompleted(Exception x, object tag)
|
||||||
|
{
|
||||||
|
object[] param = (object[])tag;
|
||||||
|
SocketCallBack callback = (SocketCallBack)param[0];
|
||||||
|
object callbackTag = (object)param[1];
|
||||||
|
Stream stream = (Stream)param[2];
|
||||||
|
int maxLength = (int)param[3];
|
||||||
|
int lastByte = (int)param[4];
|
||||||
|
int readedCount = (int)param[5];
|
||||||
|
bool lineBreak = (bool)param[6];
|
||||||
|
bool expectCRLF = (bool)param[7];
|
||||||
|
|
||||||
|
if (x == null)
|
||||||
|
{
|
||||||
|
// We didn't get data, this can only happen if socket closed.
|
||||||
|
if (m_AvailableInBuffer == 0)
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
m_pLogger.AddTextEntry("Remote host closed socket !");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(SocketCallBackResult.SocketClosed, 0, null, callbackTag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TryToReadPeriodTerminated(callback, callbackTag, stream, maxLength, lastByte, readedCount, lineBreak, expectCRLF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Exception, 0, x, callbackTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
MailServer/Misc/SocketServer/SocketEx/SocketEx.Ssl.cs
Normal file
54
MailServer/Misc/SocketServer/SocketEx/SocketEx.Ssl.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System;
|
||||||
|
using System.Net.Security;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Switches socket to SSL mode. Throws excpetion is socket is already in SSL mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificate">Certificate to use for SSL.</param>
|
||||||
|
public void SwitchToSSL(X509Certificate certificate)
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
throw new Exception("Error can't switch to SSL, socket is already in SSL mode !");
|
||||||
|
}
|
||||||
|
|
||||||
|
SslStream sslStream = new SslStream(m_pSocketStream);
|
||||||
|
sslStream.AuthenticateAsServer(certificate);
|
||||||
|
|
||||||
|
m_SSL = true;
|
||||||
|
m_pSslStream = sslStream;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Switches socket to SSL mode. Throws excpetion is socket is already in SSL mode.
|
||||||
|
/// </summary>
|
||||||
|
public void SwitchToSSL_AsClient()
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
throw new Exception("Error can't switch to SSL, socket is already in SSL mode !");
|
||||||
|
}
|
||||||
|
|
||||||
|
SslStream sslStream = new SslStream(m_pSocketStream, true, this.RemoteCertificateValidationCallback);
|
||||||
|
sslStream.AuthenticateAsClient(m_Host);
|
||||||
|
|
||||||
|
m_SSL = true;
|
||||||
|
m_pSslStream = sslStream;
|
||||||
|
}
|
||||||
|
private bool RemoteCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
if (sslPolicyErrors == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Do not allow this client to communicate with unauthenticated servers.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
785
MailServer/Misc/SocketServer/SocketEx/SocketEx.Write.cs
Normal file
785
MailServer/Misc/SocketServer/SocketEx/SocketEx.Write.cs
Normal file
@ -0,0 +1,785 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketEx
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to write to socket.</param>
|
||||||
|
public void Write(string data)
|
||||||
|
{
|
||||||
|
Write(new MemoryStream(m_pEncoding.GetBytes(data)));
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to to wite to socket.</param>
|
||||||
|
public void Write(byte[] data)
|
||||||
|
{
|
||||||
|
Write(new MemoryStream(data));
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to to wite to socket.</param>
|
||||||
|
/// <param name="offset">Offset in data from where to start sending data.</param>
|
||||||
|
/// <param name="length">Lengh of data to send.</param>
|
||||||
|
public void Write(byte[] data, int offset, int length)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(data);
|
||||||
|
ms.Position = offset;
|
||||||
|
Write(ms, length);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and will be readed to EOS.</param>
|
||||||
|
public void Write(Stream stream)
|
||||||
|
{
|
||||||
|
m_pSocket.NoDelay = false;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4000];
|
||||||
|
int sentCount = 0;
|
||||||
|
int readedCount = stream.Read(buffer, 0, buffer.Length);
|
||||||
|
while (readedCount > 0)
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(buffer, 0, readedCount);
|
||||||
|
m_pSslStream.Flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(buffer, 0, readedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
sentCount += readedCount;
|
||||||
|
m_WrittenCount += readedCount;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
readedCount = stream.Read(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (sentCount < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer, 0, sentCount), sentCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Big binary data, sent " + sentCount.ToString() + " bytes.", sentCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and specified count will be readed.</param>
|
||||||
|
/// <param name="count">Number of bytes to read from stream and write to socket.</param>
|
||||||
|
public void Write(Stream stream, long count)
|
||||||
|
{
|
||||||
|
m_pSocket.NoDelay = false;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4000];
|
||||||
|
int sentCount = 0;
|
||||||
|
int readedCount = 0;
|
||||||
|
if ((count - sentCount) > buffer.Length)
|
||||||
|
{
|
||||||
|
readedCount = stream.Read(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readedCount = stream.Read(buffer, 0, (int)(count - sentCount));
|
||||||
|
}
|
||||||
|
while (sentCount < count)
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(buffer, 0, readedCount);
|
||||||
|
m_pSslStream.Flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(buffer, 0, readedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
sentCount += readedCount;
|
||||||
|
m_WrittenCount += readedCount;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
if ((count - sentCount) > buffer.Length)
|
||||||
|
{
|
||||||
|
readedCount = stream.Read(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readedCount = stream.Read(buffer, 0, (int)(count - sentCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (sentCount < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer, 0, sentCount), sentCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Big binary data, sent " + sentCount.ToString() + " bytes.", sentCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified line to socket. If line isn't CRLF terminated, CRLF is added automatically.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line to write to socket.</param>
|
||||||
|
public void WriteLine(string line)
|
||||||
|
{
|
||||||
|
WriteLine(m_pEncoding.GetBytes(line));
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes specified line to socket. If line isn't CRLF terminated, CRLF is added automatically.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line to write to socket.</param>
|
||||||
|
public void WriteLine(byte[] line)
|
||||||
|
{
|
||||||
|
// Don't allow to wait after we send data, because there won't no more data
|
||||||
|
m_pSocket.NoDelay = true;
|
||||||
|
|
||||||
|
// <CRF> is missing, add it
|
||||||
|
if (line.Length < 2 || (line[line.Length - 2] != (byte)'\r' && line[line.Length - 1] != (byte)'\n'))
|
||||||
|
{
|
||||||
|
byte[] newLine = new byte[line.Length + 2];
|
||||||
|
Array.Copy(line, newLine, line.Length);
|
||||||
|
newLine[newLine.Length - 2] = (byte)'\r';
|
||||||
|
newLine[newLine.Length - 1] = (byte)'\n';
|
||||||
|
|
||||||
|
line = newLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(line);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(line, 0, line.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_WrittenCount += line.Length;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (line.Length < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(line), line.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Big binary line, sent " + line.Length.ToString() + " bytes.", line.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Writes period terminated string to socket. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>". Before sending a line of text, check the first
|
||||||
|
/// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">String data to write.</param>
|
||||||
|
public void WritePeriodTerminated(string data)
|
||||||
|
{
|
||||||
|
WritePeriodTerminated(new MemoryStream(m_pEncoding.GetBytes(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes period terminated data to socket. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>". Before sending a line of text, check the first
|
||||||
|
/// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
|
||||||
|
public void WritePeriodTerminated(Stream stream)
|
||||||
|
{
|
||||||
|
/* Before sending a line of text, check the first character of the line.
|
||||||
|
If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int countSent = 0;
|
||||||
|
byte[] buffer = new byte[4000];
|
||||||
|
int positionInBuffer = 0;
|
||||||
|
bool CRLF = false;
|
||||||
|
int lastByte = -1;
|
||||||
|
int currentByte = stream.ReadByte();
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We have CRLF, mark it up
|
||||||
|
if (lastByte == '\r' && currentByte == '\n')
|
||||||
|
{
|
||||||
|
CRLF = true;
|
||||||
|
}
|
||||||
|
// There is CRLF + current byte
|
||||||
|
else if (CRLF)
|
||||||
|
{
|
||||||
|
// If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
if (currentByte == '.')
|
||||||
|
{
|
||||||
|
buffer[positionInBuffer] = (byte)'.';
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRLF handled, reset it
|
||||||
|
CRLF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)currentByte;
|
||||||
|
positionInBuffer++;
|
||||||
|
|
||||||
|
lastByte = currentByte;
|
||||||
|
|
||||||
|
// Buffer is filled up, write buffer to socket.
|
||||||
|
if (positionInBuffer > (4000 - 10))
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
countSent += positionInBuffer;
|
||||||
|
m_WrittenCount += positionInBuffer;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
positionInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentByte = stream.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have readed all data, write budder data + .<CRLF> or <CRLF>.<CRLF> if data not <CRLF> terminated.
|
||||||
|
if (!CRLF)
|
||||||
|
{
|
||||||
|
buffer[positionInBuffer] = (byte)'\r';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\n';
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)'.';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\r';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\n';
|
||||||
|
positionInBuffer++;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
countSent += positionInBuffer;
|
||||||
|
m_WrittenCount += positionInBuffer;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
//-------------------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (countSent < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer), buffer.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Binary data, sent " + countSent.ToString() + " bytes.", countSent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins writing specified data to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and will be readed to EOS.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
|
||||||
|
public void BeginWrite(Stream stream, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
// Allow socket to optimise sends
|
||||||
|
m_pSocket.NoDelay = false;
|
||||||
|
|
||||||
|
BeginProcessingWrite(stream, tag, callback, 0);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Starts sending data block to socket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous write operation is completed</param>
|
||||||
|
/// <param name="countSent">Specifies how many data is sent.</param>
|
||||||
|
private void BeginProcessingWrite(Stream stream, object tag, SocketCallBack callback, int countSent)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[4000];
|
||||||
|
int readedCount = stream.Read(buffer, 0, buffer.Length);
|
||||||
|
// There data to send
|
||||||
|
if (readedCount > 0)
|
||||||
|
{
|
||||||
|
countSent += readedCount;
|
||||||
|
m_WrittenCount += readedCount;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.BeginWrite(buffer, 0, readedCount, new AsyncCallback(this.OnBeginWriteCallback), new object[] { stream, tag, callback, countSent });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.BeginWrite(buffer, 0, readedCount, new AsyncCallback(this.OnBeginWriteCallback), new object[] { stream, tag, callback, countSent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have sent all data
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (stream.CanSeek && stream.Length < 200)
|
||||||
|
{
|
||||||
|
byte[] sentData = new byte[stream.Length];
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.Read(sentData, 0, sentData.Length);
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(sentData), countSent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Big binary data, sent " + countSent.ToString() + " bytes.", countSent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line sent ok, call callback.
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(SocketCallBackResult.Ok, countSent, null, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous datablock send is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ar"></param>
|
||||||
|
private void OnBeginWriteCallback(IAsyncResult ar)
|
||||||
|
{
|
||||||
|
object[] param = (object[])ar.AsyncState;
|
||||||
|
Stream stream = (Stream)param[0];
|
||||||
|
object tag = param[1];
|
||||||
|
SocketCallBack callBack = (SocketCallBack)param[2];
|
||||||
|
int countSent = (int)param[3];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
BeginProcessingWrite(stream, tag, callBack, countSent);
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
if (callBack != null)
|
||||||
|
{
|
||||||
|
callBack(SocketCallBackResult.Exception, 0, x, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins specified line sending to socket asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line to send.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous line write operation is completed.</param>
|
||||||
|
public void BeginWriteLine(string line, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
// Don't allow to wait after we send data, because there won't no more data
|
||||||
|
m_pSocket.NoDelay = true;
|
||||||
|
|
||||||
|
BeginWriteLine(line, null, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins specified line sending to socket asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line to send.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous line write operation is completed.</param>
|
||||||
|
public void BeginWriteLine(string line, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
// Don't allow to wait after we send data, because there won't no more data
|
||||||
|
m_pSocket.NoDelay = true;
|
||||||
|
|
||||||
|
if (!line.EndsWith("\r\n"))
|
||||||
|
{
|
||||||
|
line += "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] lineBytes = m_pEncoding.GetBytes(line);
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.BeginWrite(lineBytes, 0, lineBytes.Length, this.OnBeginWriteLineCallback, new object[] { tag, callback, lineBytes });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.BeginWrite(lineBytes, 0, lineBytes.Length, this.OnBeginWriteLineCallback, new object[] { tag, callback, lineBytes });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous WriteLine is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ar"></param>
|
||||||
|
private void OnBeginWriteLineCallback(IAsyncResult ar)
|
||||||
|
{
|
||||||
|
object[] param = (object[])ar.AsyncState;
|
||||||
|
object tag = param[0];
|
||||||
|
SocketCallBack callBack = (SocketCallBack)param[1];
|
||||||
|
byte[] lineBytes = (byte[])param[2];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_WrittenCount += lineBytes.Length;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (lineBytes.Length < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(lineBytes), lineBytes.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Big binary line, sent " + lineBytes.Length.ToString() + " bytes.", lineBytes.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line sent ok, call callback.
|
||||||
|
if (callBack != null)
|
||||||
|
{
|
||||||
|
callBack(SocketCallBackResult.Ok, lineBytes.Length, null, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
if (callBack != null)
|
||||||
|
{
|
||||||
|
callBack(SocketCallBackResult.Exception, 0, x, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// BeginWritePeriodTerminated state obejct.
|
||||||
|
/// </summary>
|
||||||
|
private struct _BeginWritePeriodTerminated_State
|
||||||
|
{
|
||||||
|
private Stream m_Stream;
|
||||||
|
private bool m_CloseStream;
|
||||||
|
private object m_Tag;
|
||||||
|
private SocketCallBack m_Callback;
|
||||||
|
private bool m_HasCRLF;
|
||||||
|
private int m_LastByte;
|
||||||
|
private int m_CountSent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Source stream.</param>
|
||||||
|
/// <param name="closeStream">Specifies if stream must be closed after reading is completed.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">Callback what to call if asynchronous data writing completes.</param>
|
||||||
|
public _BeginWritePeriodTerminated_State(Stream stream, bool closeStream, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
m_Stream = stream;
|
||||||
|
m_CloseStream = closeStream;
|
||||||
|
m_Tag = tag;
|
||||||
|
m_Callback = callback;
|
||||||
|
m_HasCRLF = false;
|
||||||
|
m_LastByte = -1;
|
||||||
|
m_CountSent = 0;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets source stream.
|
||||||
|
/// </summary>
|
||||||
|
public Stream Stream
|
||||||
|
{
|
||||||
|
get { return m_Stream; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if stream must be closed if reading completed.
|
||||||
|
/// </summary>
|
||||||
|
public bool CloseStream
|
||||||
|
{
|
||||||
|
get { return m_CloseStream; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets user data.
|
||||||
|
/// </summary>
|
||||||
|
public object Tag
|
||||||
|
{
|
||||||
|
get { return m_Tag; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets callback what must be called if asynchronous write ends.
|
||||||
|
/// </summary>
|
||||||
|
public SocketCallBack Callback
|
||||||
|
{
|
||||||
|
get { return m_Callback; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if last sent data ends with CRLF.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasCRLF
|
||||||
|
{
|
||||||
|
get { return m_HasCRLF; }
|
||||||
|
|
||||||
|
set { m_HasCRLF = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets what is last sent byte.
|
||||||
|
/// </summary>
|
||||||
|
public int LastByte
|
||||||
|
{
|
||||||
|
get { return m_LastByte; }
|
||||||
|
|
||||||
|
set { m_LastByte = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets how many bytes has written to socket.
|
||||||
|
/// </summary>
|
||||||
|
public int CountSent
|
||||||
|
{
|
||||||
|
get { return m_CountSent; }
|
||||||
|
|
||||||
|
set { m_CountSent = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Begins writing period terminated data to socket. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>". Before sending a line of text, check the first
|
||||||
|
/// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
|
||||||
|
public void BeginWritePeriodTerminated(Stream stream, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
BeginWritePeriodTerminated(stream, false, tag, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins writing period terminated data to socket. The data is terminated by a line containing only a period, that is,
|
||||||
|
/// the character sequence "<CRLF>.<CRLF>". Before sending a line of text, check the first
|
||||||
|
/// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
|
||||||
|
/// <param name="closeStream">Specifies if stream is closed after write operation has completed.</param>
|
||||||
|
/// <param name="tag">User data.</param>
|
||||||
|
/// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
|
||||||
|
public void BeginWritePeriodTerminated(Stream stream, bool closeStream, object tag, SocketCallBack callback)
|
||||||
|
{
|
||||||
|
// Allow socket to optimise sends
|
||||||
|
m_pSocket.NoDelay = false;
|
||||||
|
|
||||||
|
_BeginWritePeriodTerminated_State state = new _BeginWritePeriodTerminated_State(stream, closeStream, tag, callback);
|
||||||
|
BeginProcessingWritePeriodTerminated(state);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Reads data block from state.Stream and begins writing it to socket.
|
||||||
|
/// This method is looped while all data has been readed from state.Stream, then sate.Callback is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">State info.</param>
|
||||||
|
private void BeginProcessingWritePeriodTerminated(_BeginWritePeriodTerminated_State state)
|
||||||
|
{
|
||||||
|
/* Before sending a line of text, check the first character of the line.
|
||||||
|
If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4000];
|
||||||
|
int positionInBuffer = 0;
|
||||||
|
int currentByte = state.Stream.ReadByte();
|
||||||
|
while (currentByte > -1)
|
||||||
|
{
|
||||||
|
// We have CRLF, mark it up
|
||||||
|
if (state.LastByte == '\r' && currentByte == '\n')
|
||||||
|
{
|
||||||
|
state.HasCRLF = true;
|
||||||
|
}
|
||||||
|
// There is CRLF + current byte
|
||||||
|
else if (state.HasCRLF)
|
||||||
|
{
|
||||||
|
// If it is a period, one additional period is inserted at the beginning of the line.
|
||||||
|
if (currentByte == '.')
|
||||||
|
{
|
||||||
|
buffer[positionInBuffer] = (byte)'.';
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRLF handled, reset it
|
||||||
|
state.HasCRLF = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)currentByte;
|
||||||
|
positionInBuffer++;
|
||||||
|
|
||||||
|
state.LastByte = currentByte;
|
||||||
|
|
||||||
|
// Buffer is filled up, begin writing buffer to socket.
|
||||||
|
if (positionInBuffer > (4000 - 10))
|
||||||
|
{
|
||||||
|
state.CountSent += positionInBuffer;
|
||||||
|
m_WrittenCount += positionInBuffer;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.BeginWrite(buffer, 0, positionInBuffer, this.OnBeginWritePeriodTerminatedCallback, state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.BeginWrite(buffer, 0, positionInBuffer, this.OnBeginWritePeriodTerminatedCallback, state);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentByte = state.Stream.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have readed all data, write .<CRLF> or <CRLF>.<CRLF> if data not <CRLF> terminated.
|
||||||
|
if (!state.HasCRLF)
|
||||||
|
{
|
||||||
|
buffer[positionInBuffer] = (byte)'\r';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\n';
|
||||||
|
positionInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[positionInBuffer] = (byte)'.';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\r';
|
||||||
|
positionInBuffer++;
|
||||||
|
buffer[positionInBuffer] = (byte)'\n';
|
||||||
|
positionInBuffer++;
|
||||||
|
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.Write(buffer, 0, positionInBuffer);
|
||||||
|
}
|
||||||
|
state.CountSent += positionInBuffer;
|
||||||
|
m_WrittenCount += positionInBuffer;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
//-------------------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
// Logging stuff
|
||||||
|
if (m_pLogger != null)
|
||||||
|
{
|
||||||
|
if (state.CountSent < 200)
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer), buffer.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pLogger.AddSendEntry("Binary data, sent " + state.CountSent.ToString() + " bytes.", state.CountSent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need stream any more, close it
|
||||||
|
if (state.CloseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state.Stream.Close();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data sent ok, call callback.
|
||||||
|
if (state.Callback != null)
|
||||||
|
{
|
||||||
|
state.Callback(SocketCallBackResult.Ok, state.CountSent, null, state.Tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called after asynchronous datablock send is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ar"></param>
|
||||||
|
private void OnBeginWritePeriodTerminatedCallback(IAsyncResult ar)
|
||||||
|
{
|
||||||
|
_BeginWritePeriodTerminated_State state = (_BeginWritePeriodTerminated_State)ar.AsyncState;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (m_SSL)
|
||||||
|
{
|
||||||
|
m_pSslStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pSocketStream.EndWrite(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
BeginProcessingWritePeriodTerminated(state);
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
// We don't need stream any more, close it
|
||||||
|
if (state.CloseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state.Stream.Close();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Callback != null)
|
||||||
|
{
|
||||||
|
state.Callback(SocketCallBackResult.Exception, 0, x, state.Tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
MailServer/Misc/SocketServer/SocketEx/SocketEx.cs
Normal file
67
MailServer/Misc/SocketServer/SocketEx/SocketEx.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class implements extended socket, provides usefull methods for reading and writing data to socket.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use TCP_Client or TCP_Server instead.")]
|
||||||
|
public partial class SocketEx : IDisposable
|
||||||
|
{
|
||||||
|
private Socket m_pSocket = null;
|
||||||
|
private NetworkStream m_pSocketStream = null;
|
||||||
|
private SslStream m_pSslStream = null;
|
||||||
|
private bool m_SSL = false;
|
||||||
|
private byte[] m_Buffer = null;
|
||||||
|
private int m_OffsetInBuffer = 0;
|
||||||
|
private int m_AvailableInBuffer = 0;
|
||||||
|
private Encoding m_pEncoding = null;
|
||||||
|
private SocketLogger m_pLogger = null;
|
||||||
|
private string m_Host = "";
|
||||||
|
private DateTime m_LastActivityDate;
|
||||||
|
private long m_ReadedCount = 0;
|
||||||
|
private long m_WrittenCount = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public SocketEx()
|
||||||
|
{
|
||||||
|
m_Buffer = new byte[8000];
|
||||||
|
m_pEncoding = Encoding.UTF8;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
m_pSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
m_pSocket.ReceiveTimeout = 60000;
|
||||||
|
m_pSocket.SendTimeout = 60000;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Socket wrapper. NOTE: You must pass connected socket here !
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket">Socket.</param>
|
||||||
|
public SocketEx(Socket socket)
|
||||||
|
{
|
||||||
|
m_Buffer = new byte[8000];
|
||||||
|
m_pEncoding = Encoding.UTF8;
|
||||||
|
m_LastActivityDate = DateTime.Now;
|
||||||
|
|
||||||
|
m_pSocket = socket;
|
||||||
|
|
||||||
|
if (socket.ProtocolType == ProtocolType.Tcp)
|
||||||
|
{
|
||||||
|
m_pSocketStream = new NetworkStream(socket, false);
|
||||||
|
}
|
||||||
|
m_pSocket.ReceiveTimeout = 60000;
|
||||||
|
m_pSocket.SendTimeout = 60000;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resouces being used.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public class SocketLogEntry
|
||||||
|
{
|
||||||
|
private string m_Text = "";
|
||||||
|
private long m_Size = 0;
|
||||||
|
private SocketLogEntryType m_Type = SocketLogEntryType.FreeText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Log text.</param>
|
||||||
|
/// <param name="size">Data size.</param>
|
||||||
|
/// <param name="type">Log entry type</param>
|
||||||
|
public SocketLogEntry(string text, long size, SocketLogEntryType type)
|
||||||
|
{
|
||||||
|
m_Text = text;
|
||||||
|
m_Type = type;
|
||||||
|
m_Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets log text.
|
||||||
|
/// </summary>
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get { return m_Text; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets size of data readed or sent.
|
||||||
|
/// </summary>
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get { return m_Size; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets log entry type.
|
||||||
|
/// </summary>
|
||||||
|
public SocketLogEntryType Type
|
||||||
|
{
|
||||||
|
get { return m_Type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
20
MailServer/Misc/SocketServer/SocketLogEntryType.cs
Normal file
20
MailServer/Misc/SocketServer/SocketLogEntryType.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public enum SocketLogEntryType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data is readed from remote endpoint.
|
||||||
|
/// </summary>
|
||||||
|
ReadFromRemoteEP = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data is sent to remote endpoint.
|
||||||
|
/// </summary>
|
||||||
|
SendToRemoteEP = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comment log entry.
|
||||||
|
/// </summary>
|
||||||
|
FreeText = 2,
|
||||||
|
}
|
||||||
|
}
|
151
MailServer/Misc/SocketServer/SocketLogger/SocketLogger.Entry.cs
Normal file
151
MailServer/Misc/SocketServer/SocketLogger/SocketLogger.Entry.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketLogger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts log entries to string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Socket logger.</param>
|
||||||
|
/// <param name="firstLogPart">Specifies if first log part of multipart log.</param>
|
||||||
|
/// <param name="lastLogPart">Specifies if last log part (logging ended).</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string LogEntriesToString(SocketLogger logger, bool firstLogPart, bool lastLogPart)
|
||||||
|
{
|
||||||
|
string logText = "//----- Sys: 'Session:'" + logger.SessionID + " added " + DateTime.Now + "\r\n";
|
||||||
|
if (!firstLogPart)
|
||||||
|
{
|
||||||
|
logText = "//----- Sys: 'Session:'" + logger.SessionID + " partial log continues " + DateTime.Now + "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SocketLogEntry entry in logger.LogEntries)
|
||||||
|
{
|
||||||
|
if (entry.Type == SocketLogEntryType.ReadFromRemoteEP)
|
||||||
|
{
|
||||||
|
logText += CreateEntry(logger, entry.Text, ">>>");
|
||||||
|
}
|
||||||
|
else if (entry.Type == SocketLogEntryType.SendToRemoteEP)
|
||||||
|
{
|
||||||
|
logText += CreateEntry(logger, entry.Text, "<<<");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logText += CreateEntry(logger, entry.Text, "---");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastLogPart)
|
||||||
|
{
|
||||||
|
logText += "//----- Sys: 'Session:'" + logger.SessionID + " removed " + DateTime.Now + "\r\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logText += "//----- Sys: 'Session:'" + logger.SessionID + " partial log " + DateTime.Now + "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return logText;
|
||||||
|
}
|
||||||
|
private static string CreateEntry(SocketLogger logger, string text, string prefix)
|
||||||
|
{
|
||||||
|
string retVal = "";
|
||||||
|
|
||||||
|
if (text.EndsWith("\r\n"))
|
||||||
|
{
|
||||||
|
text = text.Substring(0, text.Length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
string remIP = "xxx.xxx.xxx.xxx";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (logger.RemoteEndPoint != null)
|
||||||
|
{
|
||||||
|
remIP = ((IPEndPoint)logger.RemoteEndPoint).Address.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] lines = text.Replace("\r\n", "\n").Split('\n');
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
retVal += "SessionID: " + logger.SessionID + " RemIP: " + remIP + " " + prefix + " '" + line + "'\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Adds data read(from remoteEndpoint) entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Log text.</param>
|
||||||
|
/// <param name="size">Readed text size.</param>
|
||||||
|
public void AddReadEntry(string text, long size)
|
||||||
|
{
|
||||||
|
if (m_pLoaclEndPoint == null || m_pRemoteEndPoint == null)
|
||||||
|
{
|
||||||
|
m_pLoaclEndPoint = (IPEndPoint)m_pSocket.LocalEndPoint;
|
||||||
|
m_pRemoteEndPoint = (IPEndPoint)m_pSocket.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEntries.Add(new SocketLogEntry(text, size, SocketLogEntryType.ReadFromRemoteEP));
|
||||||
|
|
||||||
|
OnEntryAdded();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Adds data send(to remoteEndpoint) entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Log text.</param>
|
||||||
|
/// <param name="size">Sent text size.</param>
|
||||||
|
public void AddSendEntry(string text, long size)
|
||||||
|
{
|
||||||
|
if (m_pLoaclEndPoint == null || m_pRemoteEndPoint == null)
|
||||||
|
{
|
||||||
|
m_pLoaclEndPoint = (IPEndPoint)m_pSocket.LocalEndPoint;
|
||||||
|
m_pRemoteEndPoint = (IPEndPoint)m_pSocket.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEntries.Add(new SocketLogEntry(text, size, SocketLogEntryType.SendToRemoteEP));
|
||||||
|
|
||||||
|
OnEntryAdded();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Adds free text entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Log text.</param>
|
||||||
|
public void AddTextEntry(string text)
|
||||||
|
{
|
||||||
|
m_pEntries.Add(new SocketLogEntry(text, 0, SocketLogEntryType.FreeText));
|
||||||
|
|
||||||
|
OnEntryAdded();
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Requests to write all in memory log entries to log log file.
|
||||||
|
/// </summary>
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
if (m_pLogHandler != null)
|
||||||
|
{
|
||||||
|
m_pLogHandler(this, new Log_EventArgs(this, m_FirstLogPart, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called when new loge entry has added.
|
||||||
|
/// </summary>
|
||||||
|
private void OnEntryAdded()
|
||||||
|
{
|
||||||
|
// Ask to server to write partial log
|
||||||
|
if (m_pEntries.Count > 100)
|
||||||
|
{
|
||||||
|
if (m_pLogHandler != null)
|
||||||
|
{
|
||||||
|
m_pLogHandler(this, new Log_EventArgs(this, m_FirstLogPart, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pEntries.Clear();
|
||||||
|
m_FirstLogPart = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public partial class SocketLogger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets session ID.
|
||||||
|
/// </summary>
|
||||||
|
public string SessionID
|
||||||
|
{
|
||||||
|
get { return m_SessionID; }
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_SessionID = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets authenticated user name.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName
|
||||||
|
{
|
||||||
|
get { return m_UserName; }
|
||||||
|
|
||||||
|
set { m_UserName = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets current cached log entries.
|
||||||
|
/// </summary>
|
||||||
|
public SocketLogEntry[] LogEntries
|
||||||
|
{
|
||||||
|
get { return m_pEntries.ToArray(); }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets local endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint LocalEndPoint
|
||||||
|
{
|
||||||
|
get { return m_pLoaclEndPoint; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets remote endpoint.
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint RemoteEndPoint
|
||||||
|
{
|
||||||
|
get { return m_pRemoteEndPoint; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
MailServer/Misc/SocketServer/SocketLogger/SocketLogger.cs
Normal file
34
MailServer/Misc/SocketServer/SocketLogger/SocketLogger.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Socket logger.
|
||||||
|
/// </summary>
|
||||||
|
public partial class SocketLogger
|
||||||
|
{
|
||||||
|
private Socket m_pSocket = null;
|
||||||
|
private string m_SessionID = "";
|
||||||
|
private string m_UserName = "";
|
||||||
|
private IPEndPoint m_pLoaclEndPoint = null;
|
||||||
|
private IPEndPoint m_pRemoteEndPoint = null;
|
||||||
|
private LogEventHandler m_pLogHandler = null;
|
||||||
|
private List<SocketLogEntry> m_pEntries = null;
|
||||||
|
private bool m_FirstLogPart = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket"></param>
|
||||||
|
/// <param name="logHandler"></param>
|
||||||
|
public SocketLogger(Socket socket, LogEventHandler logHandler)
|
||||||
|
{
|
||||||
|
m_pSocket = socket;
|
||||||
|
m_pLogHandler = logHandler;
|
||||||
|
|
||||||
|
m_pEntries = new List<SocketLogEntry>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when server or session has system error(unhandled error).
|
||||||
|
/// </summary>
|
||||||
|
public event ErrorEventHandler SysError = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <param name="x"></param>
|
||||||
|
internal protected void OnSysError(string text, Exception x)
|
||||||
|
{
|
||||||
|
if (this.SysError != null)
|
||||||
|
{
|
||||||
|
this.SysError(this, new Error_EventArgs(x, new StackTrace()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// This method must get timedout sessions and end them.
|
||||||
|
/// </summary>
|
||||||
|
private void OnSessionTimeoutTimer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Close/Remove timed out sessions
|
||||||
|
lock (m_pSessions)
|
||||||
|
{
|
||||||
|
SocketServerSession[] sessions = this.Sessions;
|
||||||
|
|
||||||
|
// Loop sessions and and call OnSessionTimeout() for timed out sessions.
|
||||||
|
for (int i = 0; i < sessions.Length; i++)
|
||||||
|
{
|
||||||
|
// If session throws exception, handle it here or next sessions timouts are not handled.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Session timed out
|
||||||
|
if (DateTime.Now > sessions[i].SessionLastDataTime.AddMilliseconds(this.SessionIdleTimeOut))
|
||||||
|
{
|
||||||
|
sessions[i].OnSessionTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
OnSysError("OnTimer:", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
OnSysError("WE MUST NEVER REACH HERE !!! OnTimer:", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void m_pTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
OnSessionTimeoutTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or set socket binding info. Use this property to specify on which IP,port server
|
||||||
|
/// listnes and also if is SSL or STARTTLS support.
|
||||||
|
/// </summary>
|
||||||
|
public IPBindInfo[] BindInfo
|
||||||
|
{
|
||||||
|
get { return m_pBindInfo; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("BindInfo can't be null !");
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- See if bindinfo has changed -----------
|
||||||
|
bool changed = false;
|
||||||
|
if (m_pBindInfo.Length != value.Length)
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_pBindInfo.Length; i++)
|
||||||
|
{
|
||||||
|
if (!m_pBindInfo[i].Equals(value[i]))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-------------------------------------------
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
// If server is currently running, stop it before applying bind info.
|
||||||
|
bool running = m_Running;
|
||||||
|
if (running)
|
||||||
|
{
|
||||||
|
StopServer();
|
||||||
|
}
|
||||||
|
m_pBindInfo = value;
|
||||||
|
// We need to restart server to take effect IP or Port change
|
||||||
|
if (running)
|
||||||
|
{
|
||||||
|
StartServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets maximum allowed connections.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxConnections
|
||||||
|
{
|
||||||
|
get { return m_MaxConnections; }
|
||||||
|
set { m_MaxConnections = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Runs and stops server.
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get { return m_Running; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != m_Running & !this.DesignMode)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
StartServer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets if to log commands.
|
||||||
|
/// </summary>
|
||||||
|
public bool LogCommands
|
||||||
|
{
|
||||||
|
get { return m_LogCmds; }
|
||||||
|
set { m_LogCmds = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Session idle timeout in milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
public int SessionIdleTimeOut
|
||||||
|
{
|
||||||
|
get { return m_SessionIdleTimeOut; }
|
||||||
|
set { m_SessionIdleTimeOut = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets maximum bad commands allowed to session.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxBadCommands
|
||||||
|
{
|
||||||
|
get { return m_MaxBadCommands; }
|
||||||
|
set { m_MaxBadCommands = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets active sessions.
|
||||||
|
/// </summary>
|
||||||
|
public SocketServerSession[] Sessions
|
||||||
|
{
|
||||||
|
get { return m_pSessions.ToArray(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds specified session to sessions collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Session to add.</param>
|
||||||
|
internal protected void AddSession(SocketServerSession session)
|
||||||
|
{
|
||||||
|
lock (m_pSessions)
|
||||||
|
{
|
||||||
|
m_pSessions.Add(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Removes specified session from sessions collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Session to remove.</param>
|
||||||
|
internal protected void RemoveSession(SocketServerSession session)
|
||||||
|
{
|
||||||
|
lock (m_pSessions)
|
||||||
|
{
|
||||||
|
m_pSessions.Remove(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize and start new session here. Session isn't added to session list automatically,
|
||||||
|
/// session must add itself to server session list by calling AddSession().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket">Connected client socket.</param>
|
||||||
|
/// <param name="bindInfo">BindInfo what accepted socket.</param>
|
||||||
|
protected virtual void InitNewSession(Socket socket, IPBindInfo bindInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts server.
|
||||||
|
/// </summary>
|
||||||
|
public void StartServer()
|
||||||
|
{
|
||||||
|
if (!m_Running)
|
||||||
|
{
|
||||||
|
m_Running = true;
|
||||||
|
|
||||||
|
// Start accepting ang queueing connections
|
||||||
|
Thread tr = new Thread(new ThreadStart(this.StartProcCons));
|
||||||
|
tr.Start();
|
||||||
|
|
||||||
|
// Start proccessing queued connections
|
||||||
|
Thread trSessionCreator = new Thread(new ThreadStart(this.StartProcQueuedCons));
|
||||||
|
trSessionCreator.Start();
|
||||||
|
|
||||||
|
m_pTimer.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Stops server. NOTE: Active sessions aren't cancled.
|
||||||
|
/// </summary>
|
||||||
|
public void StopServer()
|
||||||
|
{
|
||||||
|
if (m_Running)
|
||||||
|
{
|
||||||
|
m_Running = false;
|
||||||
|
|
||||||
|
// Stop accepting new connections
|
||||||
|
foreach (IPBindInfo bindInfo in m_pBindInfo)
|
||||||
|
{
|
||||||
|
if (bindInfo.Tag != null)
|
||||||
|
{
|
||||||
|
((Socket)bindInfo.Tag).Close();
|
||||||
|
bindInfo.Tag = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait method StartProcCons to exit
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Starts proccessiong incoming connections (Accepts and queues connections).
|
||||||
|
/// </summary>
|
||||||
|
private void StartProcCons()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CircleCollection<IPBindInfo> binds = new CircleCollection<IPBindInfo>();
|
||||||
|
foreach (IPBindInfo bindInfo in m_pBindInfo)
|
||||||
|
{
|
||||||
|
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
s.Bind(new IPEndPoint(bindInfo.IP, bindInfo.Port));
|
||||||
|
s.Listen(500);
|
||||||
|
|
||||||
|
bindInfo.Tag = s;
|
||||||
|
binds.Add(bindInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept connections and queue them
|
||||||
|
while (m_Running)
|
||||||
|
{
|
||||||
|
// We have reached maximum connection limit
|
||||||
|
if (m_pSessions.Count > m_MaxConnections)
|
||||||
|
{
|
||||||
|
// Wait while some active connectins are closed
|
||||||
|
while (m_pSessions.Count > m_MaxConnections)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get incomong connection
|
||||||
|
IPBindInfo bindInfo = binds.Next();
|
||||||
|
|
||||||
|
// There is waiting connection
|
||||||
|
if (m_Running && ((Socket)bindInfo.Tag).Poll(0, SelectMode.SelectRead))
|
||||||
|
{
|
||||||
|
// Accept incoming connection
|
||||||
|
Socket s = ((Socket)bindInfo.Tag).Accept();
|
||||||
|
|
||||||
|
// Add session to queue
|
||||||
|
lock (m_pQueuedConnections)
|
||||||
|
{
|
||||||
|
m_pQueuedConnections.Enqueue(new QueuedConnection(s, bindInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException x)
|
||||||
|
{
|
||||||
|
// Socket listening stopped, happens when StopServer is called.
|
||||||
|
// We need just skip this error.
|
||||||
|
if (x.ErrorCode == 10004)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnSysError("WE MUST NEVER REACH HERE !!! StartProcCons:", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
OnSysError("WE MUST NEVER REACH HERE !!! StartProcCons:", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Starts queueed connections proccessing (Creates and starts session foreach queued connection).
|
||||||
|
/// </summary>
|
||||||
|
private void StartProcQueuedCons()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (m_Running)
|
||||||
|
{
|
||||||
|
// There are queued connections, start sessions.
|
||||||
|
if (m_pQueuedConnections.Count > 0)
|
||||||
|
{
|
||||||
|
QueuedConnection connection;
|
||||||
|
lock (m_pQueuedConnections)
|
||||||
|
{
|
||||||
|
connection = m_pQueuedConnections.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InitNewSession(connection.Socket, connection.BindInfo);
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
OnSysError("StartProcQueuedCons InitNewSession():", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There are no connections to proccess, delay proccessing. We need to it
|
||||||
|
// because if there are no connections to proccess, while loop takes too much CPU.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
OnSysError("WE MUST NEVER REACH HERE !!! StartProcQueuedCons:", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This struct holds queued connection info.
|
||||||
|
/// </summary>
|
||||||
|
private struct QueuedConnection
|
||||||
|
{
|
||||||
|
private Socket m_pSocket;
|
||||||
|
private IPBindInfo m_pBindInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket">Socket.</param>
|
||||||
|
/// <param name="bindInfo">Bind info.</param>
|
||||||
|
public QueuedConnection(Socket socket, IPBindInfo bindInfo)
|
||||||
|
{
|
||||||
|
m_pSocket = socket;
|
||||||
|
m_pBindInfo = bindInfo;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets socket.
|
||||||
|
/// </summary>
|
||||||
|
public Socket Socket
|
||||||
|
{
|
||||||
|
get { return m_pSocket; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets bind info.
|
||||||
|
/// </summary>
|
||||||
|
public IPBindInfo BindInfo
|
||||||
|
{
|
||||||
|
get { return m_pBindInfo; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
MailServer/Misc/SocketServer/SocketServer/SocketServer.cs
Normal file
44
MailServer/Misc/SocketServer/SocketServer/SocketServer.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is base class for Socket and Session based servers.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use TCP_Server class instead")]
|
||||||
|
public abstract partial class SocketServer : System.ComponentModel.Component
|
||||||
|
{
|
||||||
|
private List<SocketServerSession> m_pSessions = null;
|
||||||
|
private Queue<QueuedConnection> m_pQueuedConnections = null;
|
||||||
|
private bool m_Running = false;
|
||||||
|
private System.Timers.Timer m_pTimer = null;
|
||||||
|
private IPBindInfo[] m_pBindInfo = null;
|
||||||
|
private int m_SessionIdleTimeOut = 30000;
|
||||||
|
private int m_MaxConnections = 1000;
|
||||||
|
private int m_MaxBadCommands = 8;
|
||||||
|
private bool m_LogCmds = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public SocketServer()
|
||||||
|
{
|
||||||
|
m_pSessions = new List<SocketServerSession>();
|
||||||
|
m_pQueuedConnections = new Queue<QueuedConnection>();
|
||||||
|
m_pTimer = new System.Timers.Timer(15000);
|
||||||
|
m_pBindInfo = new IPBindInfo[] { new IPBindInfo(System.Net.Dns.GetHostName(), IPAddress.Any, 10000, SslMode.None, null) };
|
||||||
|
|
||||||
|
m_pTimer.AutoReset = true;
|
||||||
|
m_pTimer.Elapsed += new System.Timers.ElapsedEventHandler(this.m_pTimer_Elapsed);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used and stops server.
|
||||||
|
/// </summary>
|
||||||
|
public new void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
StopServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServerSession
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Kills session.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Kill()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Times session out.
|
||||||
|
/// </summary>
|
||||||
|
internal protected virtual void OnSessionTimeout()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Sets property UserName value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userName">User name.</param>
|
||||||
|
protected void SetUserName(string userName)
|
||||||
|
{
|
||||||
|
m_UserName = userName;
|
||||||
|
|
||||||
|
if (m_pSocket.Logger != null)
|
||||||
|
{
|
||||||
|
m_pSocket.Logger.UserName = m_UserName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MailServer.Misc.SocketServer
|
||||||
|
{
|
||||||
|
public abstract partial class SocketServerSession
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets session ID.
|
||||||
|
/// </summary>
|
||||||
|
public string SessionID
|
||||||
|
{
|
||||||
|
get { return m_SessionID; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets session start time.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime SessionStartTime
|
||||||
|
{
|
||||||
|
get { return m_SessionStartTime; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if session is authenticated.
|
||||||
|
/// </summary>
|
||||||
|
public bool Authenticated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_UserName.Length > 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets authenticated user name.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName
|
||||||
|
{
|
||||||
|
get { return m_UserName; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many seconds has left before timout is triggered.
|
||||||
|
/// </summary>
|
||||||
|
public int ExpectedTimeout
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (int)((m_pServer.SessionIdleTimeOut - ((DateTime.Now.Ticks - SessionLastDataTime.Ticks) / 10000)) / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets last data activity time.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime SessionLastDataTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_pSocket == null)
|
||||||
|
{
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m_pSocket.LastActivity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets EndPoint which accepted conection.
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint LocalEndPoint
|
||||||
|
{
|
||||||
|
get { return (IPEndPoint)m_pSocket.LocalEndPoint; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets connected Host(client) EndPoint.
|
||||||
|
/// </summary>
|
||||||
|
public IPEndPoint RemoteEndPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (IPEndPoint)m_pSocket.RemoteEndPoint;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{ // Socket closed/disposed already
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets custom user data.
|
||||||
|
/// </summary>
|
||||||
|
public object Tag
|
||||||
|
{
|
||||||
|
get { return m_Tag; }
|
||||||
|
|
||||||
|
set { m_Tag = value; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets log entries that are currently in log buffer.
|
||||||
|
/// </summary>
|
||||||
|
public SocketLogger SessionActiveLog
|
||||||
|
{
|
||||||
|
get { return m_pSocket.Logger; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many bytes are readed through this session.
|
||||||
|
/// </summary>
|
||||||
|
public long ReadedCount
|
||||||
|
{
|
||||||
|
get { return m_pSocket.ReadedCount; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets how many bytes are written through this session.
|
||||||
|
/// </summary>
|
||||||
|
public long WrittenCount
|
||||||
|
{
|
||||||
|
get { return m_pSocket.WrittenCount; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the connection is an SSL connection.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get { return m_pSocket.SSL; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets access to SocketEx.
|
||||||
|
/// </summary>
|
||||||
|
protected SocketEx Socket
|
||||||
|
{
|
||||||
|
get { return m_pSocket; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets access to BindInfo what accepted socket.
|
||||||
|
/// </summary>
|
||||||
|
protected IPBindInfo BindInfo
|
||||||
|
{
|
||||||
|
get { return m_pBindInfo; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user