OpenSim.Modules.EMail/src/MailKit/BodyPart.cs

717 lines
16 KiB
C#

//
// BodyPart.cs
//
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
//
// Copyright (c) 2013-2020 .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.Text;
using System.Collections.Generic;
using MimeKit;
using MimeKit.Utils;
namespace MailKit {
/// <summary>
/// An abstract body part of a message.
/// </summary>
/// <remarks>
/// Each body part will actually be a <see cref="BodyPartBasic"/>,
/// <see cref="BodyPartText"/>, <see cref="BodyPartMessage"/>, or
/// <see cref="BodyPartMultipart"/>.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\ImapExamples.cs" region="DownloadBodyParts"/>
/// </example>
public abstract class BodyPart
{
/// <summary>
/// Initializes a new instance of the <see cref="MailKit.BodyPart"/> class.
/// </summary>
/// <remarks>
/// Creates a new <see cref="MailKit.BodyPart"/>.
/// </remarks>
protected BodyPart ()
{
}
/// <summary>
/// Gets the Content-Type of the body part.
/// </summary>
/// <remarks>
/// Gets the Content-Type of the body part.
/// </remarks>
/// <value>The content type.</value>
public ContentType ContentType {
get; set;
}
/// <summary>
/// Gets the part specifier.
/// </summary>
/// <remarks>
/// Gets the part specifier.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\ImapExamples.cs" region="DownloadBodyParts"/>
/// </example>
/// <value>The part specifier.</value>
public string PartSpecifier {
get; set;
}
/// <summary>
/// Dispatches to the specific visit method for this MIME body part.
/// </summary>
/// <remarks>
/// This default implementation for <see cref="MailKit.BodyPart"/> nodes
/// calls <see cref="MailKit.BodyPartVisitor.VisitBodyPart"/>. Override this
/// method to call into a more specific method on a derived visitor class
/// of the <see cref="MailKit.BodyPartVisitor"/> class. However, it should still
/// support unknown visitors by calling
/// <see cref="MailKit.BodyPartVisitor.VisitBodyPart"/>.
/// </remarks>
/// <param name="visitor">The visitor.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="visitor"/> is <c>null</c>.
/// </exception>
public abstract void Accept (BodyPartVisitor visitor);
internal static void Encode (StringBuilder builder, uint value)
{
builder.Append (value.ToString ());
}
internal static void Encode (StringBuilder builder, string value)
{
if (value != null)
builder.Append (MimeUtils.Quote (value));
else
builder.Append ("NIL");
}
internal static void Encode (StringBuilder builder, Uri location)
{
if (location != null)
builder.Append (MimeUtils.Quote (location.ToString ()));
else
builder.Append ("NIL");
}
internal static void Encode (StringBuilder builder, string[] values)
{
if (values == null || values.Length == 0) {
builder.Append ("NIL");
return;
}
builder.Append ('(');
for (int i = 0; i < values.Length; i++) {
if (i > 0)
builder.Append (' ');
Encode (builder, values[i]);
}
builder.Append (')');
}
internal static void Encode (StringBuilder builder, IList<Parameter> parameters)
{
if (parameters == null || parameters.Count == 0) {
builder.Append ("NIL");
return;
}
builder.Append ('(');
for (int i = 0; i < parameters.Count; i++) {
if (i > 0)
builder.Append (' ');
Encode (builder, parameters[i].Name);
builder.Append (' ');
Encode (builder, parameters[i].Value);
}
builder.Append (')');
}
internal static void Encode (StringBuilder builder, ContentDisposition disposition)
{
if (disposition == null) {
builder.Append ("NIL");
return;
}
builder.Append ('(');
Encode (builder, disposition.Disposition);
builder.Append (' ');
Encode (builder, disposition.Parameters);
builder.Append (')');
}
internal static void Encode (StringBuilder builder, ContentType contentType)
{
Encode (builder, contentType.MediaType);
builder.Append (' ');
Encode (builder, contentType.MediaSubtype);
builder.Append (' ');
Encode (builder, contentType.Parameters);
}
internal static void Encode (StringBuilder builder, BodyPartCollection parts)
{
if (parts == null || parts.Count == 0) {
builder.Append ("NIL");
return;
}
for (int i = 0; i < parts.Count; i++) {
if (i > 0)
builder.Append (' ');
Encode (builder, parts[i]);
}
}
internal static void Encode (StringBuilder builder, Envelope envelope)
{
if (envelope == null) {
builder.Append ("NIL");
return;
}
envelope.Encode (builder);
}
internal static void Encode (StringBuilder builder, BodyPart body)
{
if (body == null) {
builder.Append ("NIL");
return;
}
builder.Append ('(');
body.Encode (builder);
builder.Append (')');
}
/// <summary>
/// Encodes the <see cref="BodyPart"/> into the <see cref="System.Text.StringBuilder"/>.
/// </summary>
/// <remarks>
/// Encodes the <see cref="BodyPart"/> into the <see cref="System.Text.StringBuilder"/>.
/// </remarks>
/// <param name="builder">The string builder.</param>
protected abstract void Encode (StringBuilder builder);
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="MailKit.BodyPart"/>.
/// </summary>
/// <remarks>
/// <para>Returns a <see cref="System.String"/> that represents the current <see cref="MailKit.BodyPart"/>.</para>
/// <note type="note">The syntax of the string returned, while similar to IMAP's BODYSTRUCTURE syntax,
/// is not completely compatible.</note>
/// </remarks>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="MailKit.BodyPart"/>.</returns>
public override string ToString ()
{
var builder = new StringBuilder ();
builder.Append ('(');
Encode (builder);
builder.Append (')');
return builder.ToString ();
}
static bool TryParse (string text, ref int index, out uint value)
{
while (index < text.Length && text[index] == ' ')
index++;
int startIndex = index;
value = 0;
while (index < text.Length && char.IsDigit (text[index]))
value = (value * 10) + (uint) (text[index++] - '0');
return index > startIndex;
}
static bool TryParse (string text, ref int index, out string nstring)
{
nstring = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length)
return false;
if (text[index] != '"') {
if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
index += 3;
return true;
}
return false;
}
var token = new StringBuilder ();
bool escaped = false;
index++;
while (index < text.Length) {
if (text[index] == '"' && !escaped)
break;
if (escaped || text[index] != '\\') {
token.Append (text[index]);
escaped = false;
} else {
escaped = true;
}
index++;
}
if (index >= text.Length)
return false;
nstring = token.ToString ();
index++;
return true;
}
static bool TryParse (string text, ref int index, out string[] values)
{
values = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length)
return false;
if (text[index] != '(') {
if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
index += 3;
return true;
}
return false;
}
index++;
if (index >= text.Length)
return false;
var list = new List<string> ();
string value;
do {
if (text[index] == ')')
break;
if (!TryParse (text, ref index, out value))
return false;
list.Add (value);
} while (index < text.Length);
if (index >= text.Length || text[index] != ')')
return false;
values = list.ToArray ();
index++;
return true;
}
static bool TryParse (string text, ref int index, out Uri uri)
{
string nstring;
uri = null;
if (!TryParse (text, ref index, out nstring))
return false;
if (!string.IsNullOrEmpty (nstring)) {
if (Uri.IsWellFormedUriString (nstring, UriKind.Absolute))
uri = new Uri (nstring, UriKind.Absolute);
else if (Uri.IsWellFormedUriString (nstring, UriKind.Relative))
uri = new Uri (nstring, UriKind.Relative);
}
return true;
}
static bool TryParse (string text, ref int index, out IList<Parameter> parameters)
{
string name, value;
parameters = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length)
return false;
if (text[index] != '(') {
if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
parameters = new List<Parameter> ();
index += 3;
return true;
}
return false;
}
index++;
if (index >= text.Length)
return false;
parameters = new List<Parameter> ();
do {
if (text[index] == ')')
break;
if (!TryParse (text, ref index, out name))
return false;
if (!TryParse (text, ref index, out value))
return false;
parameters.Add (new Parameter (name, value));
} while (index < text.Length);
if (index >= text.Length || text[index] != ')')
return false;
index++;
return true;
}
static bool TryParse (string text, ref int index, out ContentDisposition disposition)
{
IList<Parameter> parameters;
string value;
disposition = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length)
return false;
if (text[index] != '(') {
if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
index += 3;
return true;
}
return false;
}
index++;
if (!TryParse (text, ref index, out value))
return false;
if (!TryParse (text, ref index, out parameters))
return false;
if (index >= text.Length || text[index] != ')')
return false;
index++;
disposition = new ContentDisposition (value);
foreach (var param in parameters)
disposition.Parameters.Add (param);
return true;
}
static bool TryParse (string text, ref int index, bool multipart, out ContentType contentType)
{
IList<Parameter> parameters;
string type, subtype;
contentType = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length)
return false;
if (!multipart) {
if (!TryParse (text, ref index, out type))
return false;
} else {
type = "multipart";
}
if (!TryParse (text, ref index, out subtype))
return false;
if (!TryParse (text, ref index, out parameters))
return false;
contentType = new ContentType (type ?? "application", subtype ?? "octet-stream");
foreach (var param in parameters)
contentType.Parameters.Add (param);
return true;
}
static bool TryParse (string text, ref int index, string prefix, out IList<BodyPart> children)
{
BodyPart part;
string path;
int id = 1;
children = null;
if (index >= text.Length)
return false;
children = new List<BodyPart> ();
do {
if (text[index] != '(')
break;
path = prefix + id;
if (!TryParse (text, ref index, path, out part))
return false;
while (index < text.Length && text[index] == ' ')
index++;
children.Add (part);
id++;
} while (index < text.Length);
return index < text.Length;
}
static bool TryParse (string text, ref int index, string path, out BodyPart part)
{
ContentDisposition disposition;
ContentType contentType;
string[] array;
string nstring;
Uri location;
uint number;
part = null;
while (index < text.Length && text[index] == ' ')
index++;
if (index >= text.Length || text[index] != '(') {
if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") {
index += 3;
return true;
}
return false;
}
index++;
if (index >= text.Length)
return false;
if (text[index] == '(') {
var prefix = path.Length > 0 ? path + "." : string.Empty;
var multipart = new BodyPartMultipart ();
IList<BodyPart> children;
if (!TryParse (text, ref index, prefix, out children))
return false;
foreach (var child in children)
multipart.BodyParts.Add (child);
if (!TryParse (text, ref index, true, out contentType))
return false;
multipart.ContentType = contentType;
if (!TryParse (text, ref index, out disposition))
return false;
multipart.ContentDisposition = disposition;
if (!TryParse (text, ref index, out array))
return false;
multipart.ContentLanguage = array;
if (!TryParse (text, ref index, out location))
return false;
multipart.ContentLocation = location;
part = multipart;
} else {
BodyPartMessage message = null;
BodyPartText txt = null;
BodyPartBasic basic;
if (!TryParse (text, ref index, false, out contentType))
return false;
if (contentType.IsMimeType ("message", "rfc822"))
basic = message = new BodyPartMessage ();
else if (contentType.IsMimeType ("text", "*"))
basic = txt = new BodyPartText ();
else
basic = new BodyPartBasic ();
basic.ContentType = contentType;
if (!TryParse (text, ref index, out nstring))
return false;
basic.ContentId = nstring;
if (!TryParse (text, ref index, out nstring))
return false;
basic.ContentDescription = nstring;
if (!TryParse (text, ref index, out nstring))
return false;
basic.ContentTransferEncoding = nstring;
if (!TryParse (text, ref index, out number))
return false;
basic.Octets = number;
if (!TryParse (text, ref index, out nstring))
return false;
basic.ContentMd5 = nstring;
if (!TryParse (text, ref index, out disposition))
return false;
basic.ContentDisposition = disposition;
if (!TryParse (text, ref index, out array))
return false;
basic.ContentLanguage = array;
if (!TryParse (text, ref index, out location))
return false;
basic.ContentLocation = location;
if (message != null) {
Envelope envelope;
BodyPart body;
if (!Envelope.TryParse (text, ref index, out envelope))
return false;
message.Envelope = envelope;
if (!TryParse (text, ref index, path, out body))
return false;
message.Body = body;
if (!TryParse (text, ref index, out number))
return false;
message.Lines = number;
} else if (txt != null) {
if (!TryParse (text, ref index, out number))
return false;
txt.Lines = number;
}
part = basic;
}
part.PartSpecifier = path;
if (index >= text.Length || text[index] != ')')
return false;
index++;
return true;
}
/// <summary>
/// Tries to parse the given text into a new <see cref="MailKit.BodyPart"/> instance.
/// </summary>
/// <remarks>
/// <para>Parses a body part from the specified text.</para>
/// <note type="note">This syntax, while similar to IMAP's BODYSTRUCTURE syntax, is not completely
/// compatible.</note>
/// </remarks>
/// <returns><c>true</c>, if the body part was successfully parsed, <c>false</c> otherwise.</returns>
/// <param name="text">The text to parse.</param>
/// <param name="part">The parsed body part.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="text"/> is <c>null</c>.
/// </exception>
public static bool TryParse (string text, out BodyPart part)
{
if (text == null)
throw new ArgumentNullException (nameof (text));
int index = 0;
return TryParse (text, ref index, string.Empty, out part) && index == text.Length;
}
}
}