You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2142 lines
86 KiB
2142 lines
86 KiB
//
|
|
// Message.cs
|
|
//
|
|
// Author: Kees van Spelde <sicos2002@hotmail.com>
|
|
//
|
|
// Copyright (c) 2013-2018 Magic-Sessions. (www.magic-sessions.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.Pkcs;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using MsgReader.Exceptions;
|
|
using MsgReader.Helpers;
|
|
using MsgReader.Localization;
|
|
using MsgReader.Mime.Header;
|
|
using OpenMcdf;
|
|
|
|
namespace MsgReader.Outlook
|
|
{
|
|
#region Enum MessageType
|
|
/// <summary>
|
|
/// The message types
|
|
/// </summary>
|
|
public enum MessageType
|
|
{
|
|
/// <summary>
|
|
/// The message type is unknown
|
|
/// </summary>
|
|
Unknown,
|
|
|
|
/// <summary>
|
|
/// The message is a normal E-mail
|
|
/// </summary>
|
|
Email,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a standard E-mail (REPORT.IPM.NOTE.NDR)
|
|
/// </summary>
|
|
EmailNonDeliveryReport,
|
|
|
|
/// <summary>
|
|
/// Delivery receipt for a standard E-mail (REPORT.IPM.NOTE.DR)
|
|
/// </summary>
|
|
EmailDeliveryReport,
|
|
|
|
/// <summary>
|
|
/// Delivery receipt for a delayed E-mail (REPORT.IPM.NOTE.DELAYED)
|
|
/// </summary>
|
|
EmailDelayedDeliveryReport,
|
|
|
|
/// <summary>
|
|
/// Read receipt for a standard E-mail (REPORT.IPM.NOTE.IPNRN)
|
|
/// </summary>
|
|
EmailReadReceipt,
|
|
|
|
/// <summary>
|
|
/// Non-read receipt for a standard E-mail (REPORT.IPM.NOTE.IPNNRN)
|
|
/// </summary>
|
|
EmailNonReadReceipt,
|
|
|
|
/// <summary>
|
|
/// The message in an E-mail that is encrypted and can also be signed (IPM.Note.SMIME)
|
|
/// </summary>
|
|
EmailEncryptedAndMaybeSigned,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a Secure MIME (S/MIME) encrypted and opaque-signed E-mail (REPORT.IPM.NOTE.SMIME.NDR)
|
|
/// </summary>
|
|
EmailEncryptedAndMaybeSignedNonDelivery,
|
|
|
|
/// <summary>
|
|
/// Delivery report for a Secure MIME (S/MIME) encrypted and opaque-signed E-mail (REPORT.IPM.NOTE.SMIME.DR)
|
|
/// </summary>
|
|
EmailEncryptedAndMaybeSignedDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is an E-mail that is clear signed (IPM.Note.SMIME.MultipartSigned)
|
|
/// </summary>
|
|
EmailClearSigned,
|
|
|
|
/// <summary>
|
|
/// The message is a secure read receipt for an E-mail (IPM.Note.Receipt.SMIME)
|
|
/// </summary>
|
|
EmailClearSignedReadReceipt,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for an S/MIME clear-signed E-mail (REPORT.IPM.NOTE.SMIME.MULTIPARTSIGNED.NDR)
|
|
/// </summary>
|
|
EmailClearSignedNonDelivery,
|
|
|
|
/// <summary>
|
|
/// Delivery receipt for an S/MIME clear-signed E-mail (REPORT.IPM.NOTE.SMIME.MULTIPARTSIGNED.DR)
|
|
/// </summary>
|
|
EmailClearSignedDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is an E-mail that is generared signed (IPM.Note.BMA.Stub)
|
|
/// </summary>
|
|
EmailBmaStub,
|
|
|
|
/// <summary>
|
|
/// The message is a short message service (IPM.Note.Mobile.SMS)
|
|
/// </summary>
|
|
EmailSms,
|
|
|
|
/// <summary>
|
|
/// The message is an appointment (IPM.Appointment)
|
|
/// </summary>
|
|
Appointment,
|
|
|
|
/// <summary>
|
|
/// The message is a notification for an appointment (IPM.Notification.Meeting)
|
|
/// </summary>
|
|
AppointmentNotification,
|
|
|
|
/// <summary>
|
|
/// The message is a schedule for an appointment (IPM.Schedule.Meeting)
|
|
/// </summary>
|
|
AppointmentSchedule,
|
|
|
|
/// <summary>
|
|
/// The message is a request for an appointment (IPM.Schedule.Meeting.Request)
|
|
/// </summary>
|
|
AppointmentRequest,
|
|
|
|
/// <summary>
|
|
/// The message is a request for an appointment (REPORT.IPM.SCHEDULE.MEETING.REQUEST.NDR)
|
|
/// </summary>
|
|
AppointmentRequestNonDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is a response to an appointment (IPM.Schedule.Response)
|
|
/// </summary>
|
|
AppointmentResponse,
|
|
|
|
/// <summary>
|
|
/// The message is a positive response to an appointment (IPM.Schedule.Resp.Pos)
|
|
/// </summary>
|
|
AppointmentResponsePositive,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a positive meeting response (accept) (REPORT.IPM.SCHEDULE.MEETING.RESP.POS.NDR)
|
|
/// </summary>
|
|
AppointmentResponsePositiveNonDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is a negative response to an appointment (IPM.Schedule.Resp.Neg)
|
|
/// </summary>
|
|
AppointmentResponseNegative,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a negative meeting response (declinet) (REPORT.IPM.SCHEDULE.MEETING.RESP.NEG.NDR)
|
|
/// </summary>
|
|
AppointmentResponseNegativeNonDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is a response to tentatively accept the meeting request (IPM.Schedule.Meeting.Resp.Tent)
|
|
/// </summary>
|
|
AppointmentResponseTentative,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a Tentative meeting response (REPORT.IPM.SCHEDULE.MEETING.RESP.TENT.NDR)
|
|
/// </summary>
|
|
AppointmentResponseTentativeNonDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is a cancelation an appointment (IPM.Schedule.Meeting.Canceled)
|
|
/// </summary>
|
|
AppointmentResponseCanceled,
|
|
|
|
/// <summary>
|
|
/// Non-delivery report for a cancelled meeting notification (REPORT.IPM.SCHEDULE.MEETING.CANCELED.NDR)
|
|
/// </summary>
|
|
AppointmentResponseCanceledNonDelivery,
|
|
|
|
/// <summary>
|
|
/// The message is a contact card (IPM.Contact)
|
|
/// </summary>
|
|
Contact,
|
|
|
|
/// <summary>
|
|
/// The message is a task (IPM.Task)
|
|
/// </summary>
|
|
Task,
|
|
|
|
/// <summary>
|
|
/// The message is a task request accept (IPM.TaskRequest.Accept)
|
|
/// </summary>
|
|
TaskRequestAccept,
|
|
|
|
/// <summary>
|
|
/// The message is a task request decline (IPM.TaskRequest.Decline)
|
|
/// </summary>
|
|
TaskRequestDecline,
|
|
|
|
/// <summary>
|
|
/// The message is a task request update (IPM.TaskRequest.Update)
|
|
/// </summary>
|
|
TaskRequestUpdate,
|
|
|
|
/// <summary>
|
|
/// The message is a sticky note (IPM.StickyNote)
|
|
/// </summary>
|
|
StickyNote,
|
|
|
|
/// <summary>
|
|
/// The message is Cisco Unity Voice message (IPM.Note.Custom.Cisco.Unity.Voice)
|
|
/// </summary>
|
|
CiscoUnityVoiceMessage,
|
|
|
|
/// <summary>
|
|
/// IPM.NOTE.RIGHTFAX.ADV
|
|
/// </summary>
|
|
RightFaxAdv,
|
|
|
|
/// <summary>
|
|
/// The message is Skype for Business missed message (IPM.Note.Microsoft.Missed)
|
|
/// </summary>
|
|
SkypeForBusinessMissedMessage,
|
|
|
|
/// <summary>
|
|
/// The message is a Skype for Business conversation (IPM.Note.Microsoft.Conversation)
|
|
/// </summary>
|
|
SkypeForBusinessConversation
|
|
}
|
|
#endregion
|
|
|
|
#region Enum MessageImportance
|
|
/// <summary>
|
|
/// The importancy of the message
|
|
/// </summary>
|
|
public enum MessageImportance
|
|
{
|
|
/// <summary>
|
|
/// Low
|
|
/// </summary>
|
|
Low = 0,
|
|
|
|
/// <summary>
|
|
/// Normal
|
|
/// </summary>
|
|
Normal = 1,
|
|
|
|
/// <summary>
|
|
/// High
|
|
/// </summary>
|
|
High = 2
|
|
}
|
|
#endregion
|
|
|
|
public partial class Storage
|
|
{
|
|
/// <summary>
|
|
/// Class represent a MSG object
|
|
/// </summary>
|
|
public class Message : Storage
|
|
{
|
|
#region Fields
|
|
/// <summary>
|
|
/// The name of the <see cref="CFStorage"/> stream that contains this message
|
|
/// </summary>
|
|
internal string StorageName { get; }
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="MessageType"/> of this Message
|
|
/// </summary>
|
|
private MessageType _type = MessageType.Unknown;
|
|
|
|
/// <summary>
|
|
/// contains the name of the <see cref="Storage.Message"/> file
|
|
/// </summary>
|
|
private string _fileName;
|
|
|
|
/// <summary>
|
|
/// Contains the date and time when the message was created or null
|
|
/// when not available
|
|
/// </summary>
|
|
private DateTime? _creationTime;
|
|
|
|
/// <summary>
|
|
/// Contains the name of the last user (or creator) that has changed the Message object or
|
|
/// null when not available
|
|
/// </summary>
|
|
private string _lastModifierName;
|
|
|
|
/// <summary>
|
|
/// Contains the date and time when the message was created or null
|
|
/// when not available
|
|
/// </summary>
|
|
private DateTime? _lastModificationTime;
|
|
|
|
/// <summary>
|
|
/// contains all the <see cref="Storage.Recipient"/> objects
|
|
/// </summary>
|
|
private readonly List<Recipient> _recipients = new List<Recipient>();
|
|
|
|
/// <summary>
|
|
/// Contains an URL to the help page of a mailing list
|
|
/// </summary>
|
|
private string _mailingListHelp;
|
|
|
|
/// <summary>
|
|
/// Contains an URL to the subscribe page of a mailing list
|
|
/// </summary>
|
|
private string _mailingListSubscribe;
|
|
|
|
/// <summary>
|
|
/// Contains an URL to the unsubscribe page of a mailing list
|
|
/// </summary>
|
|
private string _mailingListUnsubscribe;
|
|
|
|
/// <summary>
|
|
/// Contains the date/time in UTC format when the <see cref="Storage.Message"/> object has been sent,
|
|
/// null when not available
|
|
/// </summary>
|
|
private DateTime? _sentOn;
|
|
|
|
/// <summary>
|
|
/// Contains the date/time in UTC format when the <see cref="Storage.Message"/> object has been received,
|
|
/// null when not available
|
|
/// </summary>
|
|
private DateTime? _receivedOn;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="MessageImportance"/> of the <see cref="Storage.Message"/> object,
|
|
/// null when not available
|
|
/// </summary>
|
|
private MessageImportance? _importance;
|
|
|
|
/// <summary>
|
|
/// Contains all the <see cref="Storage.Attachment"/> and <see cref="Storage.Message"/> objects.
|
|
/// </summary>
|
|
private readonly List<object> _attachments = new List<object>();
|
|
|
|
/// <summary>
|
|
/// Contains the subject prefix of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _subjectPrefix;
|
|
|
|
/// <summary>
|
|
/// Contains the subject of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _subject;
|
|
|
|
/// <summary>
|
|
/// Contains the normalized subject of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _subjectNormalized;
|
|
|
|
/// <summary>
|
|
/// Contains the text body of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _bodyText;
|
|
|
|
/// <summary>
|
|
/// Contains the html body of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _bodyHtml;
|
|
|
|
/// <summary>
|
|
/// Contains the rtf body of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
private string _bodyRtf;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Encoding"/> that is used for the <see cref="BodyText"/> or <see cref="BodyHtml"/>.
|
|
/// It will contain null when the codepage could not be read from the <see cref="Storage.Message"/>
|
|
/// </summary>
|
|
private Encoding _internetCodepage;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Encoding"/> that is used for the <see cref="BodyRtf"/>.
|
|
/// It will contain null when the codepage could not be read from the <see cref="Storage.Message"/>
|
|
/// </summary>
|
|
private Encoding _messageCodepage;
|
|
|
|
/// <summary>
|
|
/// Contains the the Windows LCID of the end user who created this <see cref = "Storage.Message" />
|
|
/// </summary>
|
|
private RegionInfo _messageLocalId;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Storage.Flag"/> object
|
|
/// </summary>
|
|
private Flag _flag;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Storage.Task"/> object
|
|
/// </summary>
|
|
private Task _task;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Storage.Appointment"/> object
|
|
/// </summary>
|
|
private Appointment _appointment;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Storage.Contact"/> object
|
|
/// </summary>
|
|
private Contact _contact;
|
|
|
|
/// <summary>
|
|
/// Contains the <see cref="Storage.ReceivedBy"/> object
|
|
/// </summary>
|
|
private ReceivedBy _receivedBy;
|
|
|
|
/// <summary>
|
|
/// The conversation index
|
|
/// </summary>
|
|
private string _conversationIndex;
|
|
|
|
/// <summary>
|
|
/// The conversation topic
|
|
/// </summary>
|
|
private string _conversationTopic;
|
|
|
|
/// <summary>
|
|
/// The message size
|
|
/// </summary>
|
|
private int? _messageSize;
|
|
|
|
/// <summary>
|
|
/// The transport message headers
|
|
/// </summary>
|
|
private string _TransportMessageHeaders;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// Returns the ID of the message when the MSG file has been sent across the internet
|
|
/// (as specified in [RFC2822]). Null when not available
|
|
/// </summary>
|
|
public string Id => GetMapiPropertyString(MapiTags.PR_INTERNET_MESSAGE_ID);
|
|
|
|
#region Type
|
|
/// <summary>
|
|
/// Gives the <see cref="MessageType">type</see> of this message object
|
|
/// </summary>
|
|
public MessageType Type
|
|
{
|
|
get
|
|
{
|
|
if (_type != MessageType.Unknown)
|
|
return _type;
|
|
|
|
var type = GetMapiPropertyString(MapiTags.PR_MESSAGE_CLASS);
|
|
|
|
if (type == null)
|
|
return MessageType.Unknown;
|
|
|
|
switch (type.ToUpperInvariant())
|
|
{
|
|
case "IPM.NOTE":
|
|
_type = MessageType.Email;
|
|
break;
|
|
|
|
case "IPM.NOTE.MOBILE.SMS":
|
|
_type = MessageType.EmailSms;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.NDR":
|
|
_type = MessageType.EmailNonDeliveryReport;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.DR":
|
|
_type = MessageType.EmailDeliveryReport;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.DELAYED":
|
|
_type = MessageType.EmailDelayedDeliveryReport;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.IPNRN":
|
|
_type = MessageType.EmailReadReceipt;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.IPNNRN":
|
|
_type = MessageType.EmailNonReadReceipt;
|
|
break;
|
|
|
|
case "IPM.NOTE.SMIME":
|
|
_type = MessageType.EmailEncryptedAndMaybeSigned;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.SMIME.NDR":
|
|
_type = MessageType.EmailEncryptedAndMaybeSignedNonDelivery;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.SMIME.DR":
|
|
_type = MessageType.EmailEncryptedAndMaybeSignedDelivery;
|
|
break;
|
|
|
|
case "IPM.NOTE.SMIME.MULTIPARTSIGNED":
|
|
_type = MessageType.EmailClearSigned;
|
|
break;
|
|
|
|
case "IPM.NOTE.RECEIPT.SMIME.MULTIPARTSIGNED":
|
|
_type = MessageType.EmailClearSigned;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.SMIME.MULTIPARTSIGNED.NDR":
|
|
_type = MessageType.EmailClearSignedNonDelivery;
|
|
break;
|
|
|
|
case "REPORT.IPM.NOTE.SMIME.MULTIPARTSIGNED.DR":
|
|
_type = MessageType.EmailClearSignedDelivery;
|
|
break;
|
|
|
|
case "IPM.NOTE.BMA.STUB":
|
|
_type = MessageType.EmailBmaStub;
|
|
break;
|
|
|
|
case "IPM.APPOINTMENT":
|
|
_type = MessageType.Appointment;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING":
|
|
_type = MessageType.AppointmentSchedule;
|
|
break;
|
|
|
|
case "IPM.NOTIFICATION.MEETING":
|
|
_type = MessageType.AppointmentNotification;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.REQUEST":
|
|
_type = MessageType.AppointmentRequest;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.REQUEST.NDR":
|
|
_type = MessageType.AppointmentRequestNonDelivery;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.CANCELED":
|
|
_type = MessageType.AppointmentResponseCanceled;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.CANCELED.NDR":
|
|
_type = MessageType.AppointmentResponseCanceledNonDelivery;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESPONSE":
|
|
_type = MessageType.AppointmentResponse;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.POS":
|
|
_type = MessageType.AppointmentResponsePositive;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.POS.NDR":
|
|
_type = MessageType.AppointmentResponsePositiveNonDelivery;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.NEG":
|
|
_type = MessageType.AppointmentResponseNegative;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.NEG.NDR":
|
|
_type = MessageType.AppointmentResponseNegativeNonDelivery;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.TENT":
|
|
_type = MessageType.AppointmentResponseTentative;
|
|
break;
|
|
|
|
case "IPM.SCHEDULE.MEETING.RESP.TENT.NDR":
|
|
_type = MessageType.AppointmentResponseTentativeNonDelivery;
|
|
break;
|
|
|
|
case "IPM.CONTACT":
|
|
_type = MessageType.Contact;
|
|
break;
|
|
|
|
case "IPM.TASK":
|
|
_type = MessageType.Task;
|
|
break;
|
|
|
|
case "IPM.TASKREQUEST.ACCEPT":
|
|
_type = MessageType.TaskRequestAccept;
|
|
break;
|
|
|
|
case "IPM.TASKREQUEST.DECLINE":
|
|
_type = MessageType.TaskRequestDecline;
|
|
break;
|
|
|
|
case "IPM.TASKREQUEST.UPDATE":
|
|
_type = MessageType.TaskRequestUpdate;
|
|
break;
|
|
|
|
case "IPM.STICKYNOTE":
|
|
_type = MessageType.StickyNote;
|
|
break;
|
|
|
|
case "IPM.NOTE.CUSTOM.CISCO.UNITY.VOICE":
|
|
_type = MessageType.CiscoUnityVoiceMessage;
|
|
break;
|
|
|
|
case "IPM.NOTE.RIGHTFAX.ADV":
|
|
_type = MessageType.RightFaxAdv;
|
|
break;
|
|
|
|
case "IPM.NOTE.MICROSOFT.MISSED":
|
|
_type = MessageType.SkypeForBusinessMissedMessage;
|
|
break;
|
|
|
|
case "IPM.NOTE.MICROSOFT.CONVERSATION":
|
|
_type = MessageType.SkypeForBusinessConversation;
|
|
break;
|
|
}
|
|
|
|
return _type;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns the filename of the message object. For message objects Outlook uses the subject. It strips
|
|
/// invalid filename characters. When there is no filename the name from <see cref="LanguageConsts.NameLessFileName"/>
|
|
/// will be used
|
|
/// </summary>
|
|
public string FileName
|
|
{
|
|
get
|
|
{
|
|
if (_fileName != null)
|
|
return _fileName;
|
|
|
|
_fileName = GetMapiPropertyString(MapiTags.PR_SUBJECT);
|
|
|
|
if (string.IsNullOrEmpty(_fileName))
|
|
_fileName = LanguageConsts.NameLessFileName;
|
|
|
|
_fileName = FileManager.RemoveInvalidFileNameChars(_fileName) + ".msg";
|
|
return _fileName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the date and time when the message was created or null
|
|
/// when not available
|
|
/// </summary>
|
|
public DateTime? CreationTime => _creationTime ?? (_creationTime = GetMapiPropertyDateTime(MapiTags.PR_CREATION_TIME));
|
|
|
|
/// <summary>
|
|
/// Returns the name of the last user (or creator) that has changed the Message object or
|
|
/// null when not available
|
|
/// </summary>
|
|
public string LastModifierName => _lastModifierName ??
|
|
(_lastModifierName = GetMapiPropertyString(MapiTags.PR_LAST_MODIFIER_NAME_W));
|
|
|
|
/// <summary>
|
|
/// Returns the date and time when the message was last modified or null
|
|
/// when not available
|
|
/// </summary>
|
|
public DateTime? LastModificationTime => _lastModificationTime ??
|
|
(_lastModificationTime = GetMapiPropertyDateTime(MapiTags.PR_LAST_MODIFICATION_TIME));
|
|
|
|
/// <summary>
|
|
/// Returns the raw Transport Message Headers
|
|
/// </summary>
|
|
public string TransportMessageHeaders => _TransportMessageHeaders;
|
|
/// <summary>
|
|
/// Returns the sender of the Message
|
|
/// </summary>
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
public new Sender Sender { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns the representing sender of the Message, null when not available
|
|
/// </summary>
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
public new SenderRepresenting SenderRepresenting { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns the list of recipients in the message object
|
|
/// </summary>
|
|
public List<Recipient> Recipients => _recipients;
|
|
|
|
/// <summary>
|
|
/// Returns an URL to the help page of an mailing list when this message is part of a mailing
|
|
/// or null when not available
|
|
/// </summary>
|
|
public string MailingListHelp
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(_mailingListHelp))
|
|
return _mailingListHelp;
|
|
|
|
_mailingListHelp = GetMapiPropertyString(MapiTags.PR_LIST_HELP);
|
|
|
|
if (_mailingListHelp == null) return null;
|
|
|
|
if (_mailingListHelp.StartsWith("<"))
|
|
_mailingListHelp = _mailingListHelp.Substring(1);
|
|
|
|
if (_mailingListHelp.EndsWith(">"))
|
|
_mailingListHelp = _mailingListHelp.Substring(0, _mailingListHelp.Length - 1);
|
|
|
|
return _mailingListHelp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an URL to the subscribe page of an mailing list when this message is part of a mailing
|
|
/// or null when not available
|
|
/// </summary>
|
|
public string MailingListSubscribe
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(_mailingListSubscribe))
|
|
return _mailingListSubscribe;
|
|
|
|
_mailingListSubscribe = GetMapiPropertyString(MapiTags.PR_LIST_SUBSCRIBE);
|
|
|
|
if (_mailingListSubscribe == null) return null;
|
|
|
|
if (_mailingListSubscribe.StartsWith("<"))
|
|
_mailingListSubscribe = _mailingListSubscribe.Substring(1);
|
|
|
|
if (_mailingListSubscribe.EndsWith(">"))
|
|
_mailingListSubscribe = _mailingListSubscribe.Substring(0, _mailingListSubscribe.Length - 1);
|
|
|
|
return _mailingListSubscribe;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an URL to the unsubscribe page of an mailing list when this message is part of a mailing
|
|
/// </summary>
|
|
public string MailingListUnsubscribe
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(_mailingListUnsubscribe))
|
|
return _mailingListUnsubscribe;
|
|
|
|
_mailingListUnsubscribe = GetMapiPropertyString(MapiTags.PR_LIST_UNSUBSCRIBE);
|
|
|
|
if (_mailingListUnsubscribe == null) return null;
|
|
|
|
if (_mailingListUnsubscribe.StartsWith("<"))
|
|
_mailingListUnsubscribe = _mailingListUnsubscribe.Substring(1);
|
|
|
|
if (_mailingListUnsubscribe.EndsWith(">"))
|
|
_mailingListUnsubscribe = _mailingListUnsubscribe.Substring(0, _mailingListUnsubscribe.Length - 1);
|
|
|
|
return _mailingListUnsubscribe;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the date/time in UTC format when the message object has been sent, null when not available
|
|
/// </summary>
|
|
public DateTime? SentOn
|
|
{
|
|
get
|
|
{
|
|
if (_sentOn != null)
|
|
return _sentOn;
|
|
|
|
_sentOn = GetMapiPropertyDateTime(MapiTags.PR_CLIENT_SUBMIT_TIME) ??
|
|
GetMapiPropertyDateTime(MapiTags.PR_PROVIDER_SUBMIT_TIME);
|
|
|
|
if (_sentOn == null && Headers != null)
|
|
_sentOn = Headers.DateSent.ToLocalTime();
|
|
|
|
return _sentOn;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// PR_MESSAGE_DELIVERY_TIME is the time that the message was delivered to the store and
|
|
/// PR_CLIENT_SUBMIT_TIME is the time when the message was sent by the client (Outlook) to the server.
|
|
/// Now in this case when the Outlook is offline, it refers to the local store. Therefore when an email is sent,
|
|
/// it gets submitted to the local store and PR_MESSAGE_DELIVERY_TIME gets set the that time. Once the Outlook is
|
|
/// online at that point the message gets submitted by the client to the server and the PR_CLIENT_SUBMIT_TIME gets stamped.
|
|
/// Null when not available
|
|
/// </summary>
|
|
public DateTime? ReceivedOn
|
|
{
|
|
get
|
|
{
|
|
if (_receivedOn != null)
|
|
return _receivedOn;
|
|
|
|
_receivedOn = GetMapiPropertyDateTime(MapiTags.PR_MESSAGE_DELIVERY_TIME);
|
|
|
|
if (_receivedOn == null && Headers?.Received != null && Headers.Received.Count > 0)
|
|
_receivedOn = Headers.Received[0].Date.ToLocalTime();
|
|
|
|
return _receivedOn;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the <see cref="MessageImportance"/> of the <see cref="Storage.Message"/> object, null when not available
|
|
/// </summary>
|
|
public MessageImportance? Importance
|
|
{
|
|
get
|
|
{
|
|
if (_importance != null)
|
|
return _importance;
|
|
|
|
var importance = GetMapiPropertyInt32(MapiTags.PR_IMPORTANCE);
|
|
if (importance == null)
|
|
{
|
|
_importance = MessageImportance.Normal;
|
|
return _importance;
|
|
}
|
|
|
|
switch (importance)
|
|
{
|
|
case 0:
|
|
_importance = MessageImportance.Low;
|
|
break;
|
|
|
|
case 1:
|
|
_importance = MessageImportance.Normal;
|
|
break;
|
|
|
|
case 2:
|
|
_importance = MessageImportance.High;
|
|
break;
|
|
}
|
|
|
|
return _importance;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the <see cref="MessageImportance"/> of the <see cref="Storage.Message"/> object object as text
|
|
/// </summary>
|
|
public string ImportanceText
|
|
{
|
|
get
|
|
{
|
|
if (Importance == null)
|
|
return LanguageConsts.ImportanceNormalText;
|
|
|
|
switch (Importance)
|
|
{
|
|
case MessageImportance.Low:
|
|
return LanguageConsts.ImportanceLowText;
|
|
|
|
case MessageImportance.Normal:
|
|
return LanguageConsts.ImportanceNormalText;
|
|
|
|
case MessageImportance.High:
|
|
return LanguageConsts.ImportanceHighText;
|
|
|
|
}
|
|
|
|
return LanguageConsts.ImportanceNormalText;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list with <see cref="Storage.Attachment"/> and/or <see cref="Storage.Message"/>
|
|
/// objects that are attachted to the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
public List<Object> Attachments => _attachments;
|
|
|
|
/// <summary>
|
|
/// Returns the rendering position of this <see cref="Storage.Message"/> object when it was added to another
|
|
/// <see cref="Storage.Message"/> object and the body type was set to RTF
|
|
/// </summary>
|
|
public int RenderingPosition { get; }
|
|
|
|
/// <summary>
|
|
/// Returns or sets the subject prefix of the E-mail
|
|
/// </summary>
|
|
public string SubjectPrefix
|
|
{
|
|
get
|
|
{
|
|
if (_subjectPrefix != null)
|
|
return _subjectPrefix;
|
|
|
|
_subjectPrefix = GetMapiPropertyString(MapiTags.PR_SUBJECT_PREFIX);
|
|
if (string.IsNullOrEmpty(_subjectPrefix))
|
|
_subjectPrefix = string.Empty;
|
|
|
|
return _subjectPrefix;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the subject of the <see cref="Storage.Message"/> object
|
|
/// </summary>
|
|
public string Subject
|
|
{
|
|
get
|
|
{
|
|
if (_subject != null)
|
|
return _subject;
|
|
|
|
_subject = GetMapiPropertyString(MapiTags.PR_SUBJECT);
|
|
if (string.IsNullOrEmpty(_subject))
|
|
_subject = string.Empty;
|
|
|
|
return _subject;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the normalized subject of the E-mail
|
|
/// </summary>
|
|
public string SubjectNormalized
|
|
{
|
|
get
|
|
{
|
|
if (_subjectNormalized != null)
|
|
return _subjectNormalized;
|
|
|
|
_subjectNormalized = GetMapiPropertyString(MapiTags.PR_NORMALIZED_SUBJECT);
|
|
if (string.IsNullOrEmpty(_subjectNormalized))
|
|
_subjectNormalized = string.Empty;
|
|
|
|
return _subjectNormalized;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the available E-mail headers. These are only filled when the message
|
|
/// has been sent accross the internet. Returns null when there aren't any message headers
|
|
/// </summary>
|
|
public MessageHeader Headers { get; private set; }
|
|
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
/// <summary>
|
|
/// Returns a <see cref="Flag"/> object when a flag has been set on the <see cref="Storage.Message"/>.
|
|
/// Returns null when not available.
|
|
/// </summary>
|
|
public new Flag Flag
|
|
{
|
|
get
|
|
{
|
|
if (_flag != null)
|
|
return _flag;
|
|
|
|
var flag = new Flag(this);
|
|
|
|
if (flag.Request != null)
|
|
_flag = flag;
|
|
|
|
return _flag;
|
|
}
|
|
}
|
|
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
/// <summary>
|
|
/// Returns an <see cref="Appointment"/> object when the <see cref="MessageType"/> is a <see cref="MessageType.Appointment"/>.
|
|
/// Returns null when not available.
|
|
/// </summary>
|
|
public new Appointment Appointment
|
|
{
|
|
get
|
|
{
|
|
if (_appointment != null)
|
|
return _appointment;
|
|
|
|
switch (Type)
|
|
{
|
|
case MessageType.AppointmentRequest:
|
|
case MessageType.Appointment:
|
|
case MessageType.AppointmentResponse:
|
|
case MessageType.AppointmentResponsePositive:
|
|
case MessageType.AppointmentResponseNegative:
|
|
break;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
_appointment = new Appointment(this);
|
|
return _appointment;
|
|
}
|
|
}
|
|
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
/// <summary>
|
|
/// Returns a <see cref="Task"/> object. This property is only available when: <br/>
|
|
/// - The <see cref="Storage.Message.Type"/> is an <see cref="Storage.Message.MessageType.Email"/> and the <see cref="Flag"/> object is not null<br/>
|
|
/// - The <see cref="Storage.Message.Type"/> is an <see cref="Storage.Message.MessageType.Task"/> or <see cref="Storage.Message.MessageType.TaskRequestAccept"/> <br/>
|
|
/// </summary>
|
|
public new Task Task
|
|
{
|
|
get
|
|
{
|
|
if (_task != null)
|
|
return _task;
|
|
|
|
switch (_type)
|
|
{
|
|
case MessageType.Email:
|
|
if (Flag == null)
|
|
return null;
|
|
break;
|
|
|
|
case MessageType.Task:
|
|
case MessageType.TaskRequestAccept:
|
|
break;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
_task = new Task(this);
|
|
return _task;
|
|
}
|
|
}
|
|
|
|
// ReSharper disable once CSharpWarnings::CS0109
|
|
/// <summary>
|
|
/// Returns a <see cref="Storage.Contact"/> object when the <see cref="MessageType"/> is a <see cref="MessageType.Contact"/>.
|
|
/// Returns null when not available.
|
|
/// </summary>
|
|
public new Contact Contact
|
|
{
|
|
get
|
|
{
|
|
if (_contact != null)
|
|
return _contact;
|
|
|
|
switch (Type)
|
|
{
|
|
case MessageType.Contact:
|
|
break;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
_contact = new Contact(this);
|
|
return _contact;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the categories that are placed in the Outlook message.
|
|
/// Only supported for outlook messages from Outlook 2007 or higher
|
|
/// </summary>
|
|
public ReadOnlyCollection<string> Categories => GetMapiPropertyStringList(MapiTags.Keywords);
|
|
|
|
/// <summary>
|
|
/// Returns the body of the Outlook message in plain text format.
|
|
/// </summary>
|
|
/// <value> The body of the Outlook message in plain text format. </value>
|
|
public string BodyText
|
|
{
|
|
get
|
|
{
|
|
if (_bodyText != null)
|
|
return _bodyText;
|
|
|
|
_bodyText = GetMapiPropertyString(MapiTags.PR_BODY);
|
|
return _bodyText;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the body of the Outlook message in RTF format.
|
|
/// </summary>
|
|
/// <value> The body of the Outlook message in RTF format. </value>
|
|
public string BodyRtf
|
|
{
|
|
get
|
|
{
|
|
if (_bodyRtf != null)
|
|
return _bodyRtf;
|
|
|
|
// Get value for the RTF compressed MAPI property
|
|
var rtfBytes = GetMapiPropertyBytes(MapiTags.PR_RTF_COMPRESSED);
|
|
|
|
// Return null if no property value exists
|
|
if (rtfBytes == null || rtfBytes.Length == 0)
|
|
return null;
|
|
|
|
rtfBytes = RtfDecompressor.DecompressRtf(rtfBytes);
|
|
_bodyRtf = MessageCodePage.GetString(rtfBytes);
|
|
return _bodyRtf;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the body of the Outlook message in HTML format.
|
|
/// </summary>
|
|
/// <value> The body of the Outlook message in HTML format. </value>
|
|
public string BodyHtml
|
|
{
|
|
get
|
|
{
|
|
if (_bodyHtml != null)
|
|
return _bodyHtml;
|
|
|
|
// Get value for the HTML MAPI property
|
|
var htmlObject = GetMapiProperty(MapiTags.PR_BODY_HTML);
|
|
string html = null;
|
|
|
|
if (htmlObject is string)
|
|
html = htmlObject as string;
|
|
else if (htmlObject is byte[])
|
|
{
|
|
var htmlByteArray = htmlObject as byte[];
|
|
html = InternetCodePage.GetString(htmlByteArray);
|
|
}
|
|
|
|
// When there is no HTML found
|
|
if (html == null)
|
|
{
|
|
// Check if we have HTML embedded into rtf
|
|
var bodyRtf = BodyRtf;
|
|
if (bodyRtf != null)
|
|
{
|
|
var rtfDomDocument = new Rtf.DomDocument();
|
|
rtfDomDocument.LoadRtfText(bodyRtf);
|
|
if (!string.IsNullOrEmpty(rtfDomDocument.HtmlContent))
|
|
html = rtfDomDocument.HtmlContent.Trim('\r', '\n');
|
|
}
|
|
}
|
|
|
|
_bodyHtml = html;
|
|
return _bodyHtml;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the <see cref="Encoding"/> that is used for the <see cref="BodyText"/>
|
|
/// or <see cref="BodyHtml"/>. It will return <see cref="MessageLocalId"/> when the
|
|
/// codepage could not be read from the <see cref="Storage.Message"/>
|
|
/// <remarks>
|
|
/// See the <see cref="MessageCodePage"/> property when dealing with the <see cref="BodyRtf"/>
|
|
/// </remarks>
|
|
/// </summary>
|
|
public Encoding InternetCodePage
|
|
{
|
|
get
|
|
{
|
|
if (_internetCodepage != null)
|
|
return _internetCodepage;
|
|
|
|
var codePage = GetMapiPropertyInt32(MapiTags.PR_INTERNET_CPID);
|
|
_internetCodepage = codePage == null ? Encoding.Default : Encoding.GetEncoding((int)codePage);
|
|
return _internetCodepage;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the <see cref="Encoding"/> that is used for the <see cref="BodyRtf"/>.
|
|
/// It will return the systems default encoding when the codepage could not be read from
|
|
/// the <see cref="Storage.Message"/>
|
|
/// <remarks>
|
|
/// See the <see cref="InternetCodePage"/> property when dealing with the <see cref="BodyRtf"/>
|
|
/// </remarks>
|
|
/// </summary>
|
|
public Encoding MessageCodePage
|
|
{
|
|
get
|
|
{
|
|
if (_messageCodepage != null)
|
|
return _messageCodepage;
|
|
|
|
var codePage = GetMapiPropertyInt32(MapiTags.PR_MESSAGE_CODEPAGE);
|
|
|
|
try
|
|
{
|
|
_messageCodepage = codePage != null ? Encoding.GetEncoding((int)codePage) : InternetCodePage;
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
_messageCodepage = InternetCodePage;
|
|
}
|
|
|
|
return _messageCodepage;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the the <see cref="RegionInfo"/> for the Windows LCID of the end user who created this
|
|
/// <see cref="Storage.Message"/> It will return <c>null</c> when the the Windows LCID could not be
|
|
/// read from the <see cref="Storage.Message"/>
|
|
/// </summary>
|
|
public RegionInfo MessageLocalId
|
|
{
|
|
get
|
|
{
|
|
if (_messageLocalId != null)
|
|
return _messageLocalId;
|
|
|
|
var lcid = GetMapiPropertyInt32(MapiTags.PR_MESSAGE_LOCALE_ID);
|
|
|
|
if (!lcid.HasValue) return null;
|
|
_messageLocalId = new RegionInfo(lcid.Value);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true when the signature is valid when the <see cref="MessageType"/> is a <see cref="MessageType.EmailEncryptedAndMaybeSigned"/>.
|
|
/// It will return null when the signature is invalid or the <see cref="Storage.Message"/> has another <see cref="MessageType"/>
|
|
/// </summary>
|
|
public bool? SignatureIsValid { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns the name of the person who signed the <see cref="Storage.Message"/> when the <see cref="MessageType"/> is a
|
|
/// <see cref="MessageType.EmailEncryptedAndMaybeSigned"/>. It will return null when the signature is invalid or the <see cref="Storage.Message"/>
|
|
/// has another <see cref="MessageType"/>
|
|
/// </summary>
|
|
public string SignedBy { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns the date and time when the <see cref="Storage.Message"/> has been signed when the <see cref="MessageType"/> is a
|
|
/// <see cref="MessageType.EmailEncryptedAndMaybeSigned"/>. It will return null when the signature is invalid or the <see cref="Storage.Message"/>
|
|
/// has another <see cref="MessageType"/>
|
|
/// </summary>
|
|
public DateTime? SignedOn { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns the certificate that has been used to sign the <see cref="Storage.Message"/> when the <see cref="MessageType"/> is a
|
|
/// <see cref="MessageType.EmailEncryptedAndMaybeSigned"/>. It will return null when the signature is invalid or the <see cref="Storage.Message"/>
|
|
/// has another <see cref="MessageType"/>
|
|
/// </summary>
|
|
public X509Certificate2 SignedCertificate { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns information about who has received this message. This information is only
|
|
/// set when a message has been received and when the message provider stamped this
|
|
/// information into this message. Null when not available.
|
|
/// </summary>
|
|
#pragma warning disable 109
|
|
public new ReceivedBy ReceivedBy
|
|
#pragma warning restore 109
|
|
{
|
|
get
|
|
{
|
|
if (_receivedBy != null)
|
|
return _receivedBy;
|
|
|
|
_receivedBy = new ReceivedBy(
|
|
GetMapiPropertyString(MapiTags.PR_RECEIVED_BY_ADDRTYPE),
|
|
GetMapiPropertyString(MapiTags.PR_RECEIVED_BY_EMAIL_ADDRESS),
|
|
GetMapiPropertyString(MapiTags.PR_RECEIVED_BY_NAME));
|
|
return _receivedBy;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the index of the conversation. When not available <c>null</c> is returned
|
|
/// </summary>
|
|
public string ConversationIndex
|
|
{
|
|
get
|
|
{
|
|
if (_conversationIndex != null)
|
|
return _conversationIndex;
|
|
var conversationIndexBytes= GetMapiProperty(MapiTags.PR_CONVERSATION_INDEX);
|
|
if(conversationIndexBytes != null && conversationIndexBytes is byte[])
|
|
{
|
|
_conversationIndex = BitConverter.ToString((byte[])conversationIndexBytes, 0);
|
|
if (!string.IsNullOrWhiteSpace(_conversationIndex) && _conversationIndex.Contains("-"))
|
|
_conversationIndex = _conversationIndex.Replace("-", "");
|
|
|
|
}
|
|
if (_conversationIndex == null)
|
|
_conversationIndex = string.Empty;
|
|
|
|
return _conversationIndex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the topic of the conversation. When not available <c>null</c> is returned
|
|
/// </summary>
|
|
public string ConversationTopic
|
|
{
|
|
get
|
|
{
|
|
if (_conversationTopic != null)
|
|
return _conversationTopic;
|
|
|
|
_conversationTopic = GetMapiPropertyString(MapiTags.PR_CONVERSATION_TOPIC);
|
|
return _conversationTopic;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the size of the message. When not available <c>null</c> is returned
|
|
/// </summary>
|
|
public int? Size
|
|
{
|
|
get
|
|
{
|
|
if (_messageSize != null)
|
|
return _messageSize;
|
|
|
|
_messageSize = GetMapiPropertyInt32(MapiTags.PR_MESSAGE_SIZE);
|
|
return _messageSize;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Storage.Message" /> class from a msg file.
|
|
/// </summary>
|
|
/// <param name="msgfile">The msg file to load</param>
|
|
/// <param name="fileAccess">FileAcces mode, default is Read</param>
|
|
public Message(string msgfile, FileAccess fileAccess = FileAccess.Read) : base(msgfile, fileAccess) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Storage.Message" /> class from a <see cref="Stream" /> containing an IStorage.
|
|
/// </summary>
|
|
/// <param name="storageStream"> The <see cref="Stream" /> containing an IStorage. </param>
|
|
/// <param name="fileAccess">FileAcces mode, default is Read</param>
|
|
public Message(Stream storageStream, FileAccess fileAccess = FileAccess.Read) : base(storageStream, fileAccess) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Storage.Message" /> class on the specified <see cref="CFStorage"/>.
|
|
/// </summary>
|
|
/// <param name="storage"> The storage to create the <see cref="Storage.Message" /> on. </param>
|
|
/// <param name="renderingPosition"></param>
|
|
/// <param name="storageName">The name of the <see cref="CFStorage"/> that contains this message</param>
|
|
internal Message(CFStorage storage, int renderingPosition, string storageName) : base(storage)
|
|
{
|
|
StorageName = storageName;
|
|
_propHeaderSize = MapiTags.PropertiesStreamHeaderTop;
|
|
RenderingPosition = renderingPosition;
|
|
}
|
|
#endregion
|
|
|
|
#region GetHeaders
|
|
/// <summary>
|
|
/// Try's to read the E-mail transport headers. They are only there when a msg file has been
|
|
/// sent over the internet. When a message stays inside an Exchange server there are not any headers
|
|
/// </summary>
|
|
private void GetHeaders()
|
|
{
|
|
_TransportMessageHeaders = GetMapiPropertyString(MapiTags.PR_TRANSPORT_MESSAGE_HEADERS);
|
|
if (!string.IsNullOrEmpty(_TransportMessageHeaders))
|
|
Headers = HeaderExtractor.GetHeaders(_TransportMessageHeaders);
|
|
}
|
|
#endregion
|
|
|
|
#region LoadStorage
|
|
/// <summary>
|
|
/// Processes sub storages on the specified storage to capture attachment and recipient data.
|
|
/// </summary>
|
|
/// <param name="storage"> The storage to check for attachment and recipient data. </param>
|
|
protected override void LoadStorage(CFStorage storage)
|
|
{
|
|
base.LoadStorage(storage);
|
|
|
|
foreach (var storageStatistic in _subStorageStatistics)
|
|
{
|
|
// Run specific load method depending on sub storage name prefix
|
|
if (storageStatistic.Key.StartsWith(MapiTags.RecipStoragePrefix))
|
|
{
|
|
var recipient = new Recipient(new Storage(storageStatistic.Value));
|
|
_recipients.Add(recipient);
|
|
}
|
|
else if (storageStatistic.Key.StartsWith(MapiTags.AttachStoragePrefix))
|
|
{
|
|
switch (Type)
|
|
{
|
|
case MessageType.EmailClearSigned:
|
|
LoadClearSignedMessage(storageStatistic.Value);
|
|
break;
|
|
|
|
case MessageType.EmailEncryptedAndMaybeSigned:
|
|
LoadEncryptedAndMeabySignedMessage(storageStatistic.Value);
|
|
break;
|
|
|
|
default:
|
|
LoadAttachmentStorage(storageStatistic.Value, storageStatistic.Key);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
GetHeaders();
|
|
SetEmailSenderAndRepresentingSender();
|
|
|
|
// Check if there is a named substorage and if so open it and map all the named MAPI properties
|
|
if (_subStorageStatistics.ContainsKey(MapiTags.NameIdStorage))
|
|
{
|
|
var mappingValues = new List<string>();
|
|
|
|
// Get all the named properties from the _streamStatistics
|
|
foreach (var streamStatistic in _streamStatistics)
|
|
{
|
|
var name = streamStatistic.Key;
|
|
|
|
if (name.StartsWith(MapiTags.SubStgVersion1))
|
|
{
|
|
// Get the property value
|
|
var propIdentString = name.Substring(12, 4);
|
|
|
|
// Convert it to a short
|
|
var value = ushort.Parse(propIdentString, NumberStyles.HexNumber);
|
|
|
|
// Check if the value is in the named property range (8000 to FFFE (Hex))
|
|
if (value >= 32768 && value <= 65534)
|
|
{
|
|
// If so then add it to perform mapping later on
|
|
if (!mappingValues.Contains(propIdentString))
|
|
mappingValues.Add(propIdentString);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if there is also a properties stream and if so get all the named MAPI properties from it
|
|
if (_streamStatistics.ContainsKey(MapiTags.PropertiesStream))
|
|
{
|
|
// Get the raw bytes for the property stream
|
|
var propBytes = GetStreamBytes(MapiTags.PropertiesStream);
|
|
|
|
for (var i = _propHeaderSize; i < propBytes.Length; i = i + 16)
|
|
{
|
|
// Get property identifer located in 3rd and 4th bytes as a hexdecimal string
|
|
var propIdent = new[] { propBytes[i + 3], propBytes[i + 2] };
|
|
var propIdentString = BitConverter.ToString(propIdent).Replace("-", string.Empty);
|
|
|
|
// Convert it to a short
|
|
var value = ushort.Parse(propIdentString, NumberStyles.HexNumber);
|
|
|
|
// Check if the value is in the named property range (8000 to FFFE (Hex))
|
|
if (value >= 32768 && value <= 65534)
|
|
{
|
|
// If so then add it to perform mapping later on
|
|
if (!mappingValues.Contains(propIdentString))
|
|
mappingValues.Add(propIdentString);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if there is something to map
|
|
if (mappingValues.Count <= 0) return;
|
|
// Get the Named Id Storage, we need this one to perform the mapping
|
|
var subStorage = _subStorageStatistics[MapiTags.NameIdStorage];
|
|
|
|
// Load the subStorage into our mapping class that does all the mapping magic
|
|
var mapiToOom = new MapiTagMapper(new Storage(subStorage));
|
|
|
|
// Get the mapped properties
|
|
_namedProperties = mapiToOom.GetMapping(mappingValues);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ProcessSignedContent
|
|
/// <summary>
|
|
/// Processes the signed content
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <returns></returns>
|
|
private void ProcessSignedContent(byte[] data)
|
|
{
|
|
var signedCms = new SignedCms();
|
|
signedCms.Decode(data);
|
|
|
|
try
|
|
{
|
|
//signedCms.CheckSignature(signedCms.Certificates, false);
|
|
foreach (var cert in signedCms.Certificates)
|
|
SignatureIsValid = cert.Verify();
|
|
|
|
SignatureIsValid = true;
|
|
foreach (var cryptographicAttributeObject in signedCms.SignerInfos[0].SignedAttributes)
|
|
{
|
|
if (cryptographicAttributeObject.Values[0] is Pkcs9SigningTime)
|
|
{
|
|
var pkcs9SigningTime = (Pkcs9SigningTime)cryptographicAttributeObject.Values[0];
|
|
SignedOn = pkcs9SigningTime.SigningTime.ToLocalTime();
|
|
}
|
|
}
|
|
|
|
var certificate = signedCms.SignerInfos[0].Certificate;
|
|
if (certificate != null)
|
|
{
|
|
SignedCertificate = certificate;
|
|
SignedBy = certificate.GetNameInfo(X509NameType.SimpleName, false);
|
|
}
|
|
}
|
|
catch (CryptographicException)
|
|
{
|
|
SignatureIsValid = false;
|
|
}
|
|
|
|
// Get the decoded attachment
|
|
using (var memoryStream = new MemoryStream(signedCms.ContentInfo.Content))
|
|
{
|
|
var eml = Mime.Message.Load(memoryStream);
|
|
if (eml.TextBody != null)
|
|
_bodyText = eml.TextBody.GetBodyAsText();
|
|
|
|
if (eml.HtmlBody != null)
|
|
_bodyHtml = eml.HtmlBody.GetBodyAsText();
|
|
|
|
foreach (var emlAttachment in eml.Attachments)
|
|
_attachments.Add(new Attachment(emlAttachment));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region LoadEncryptedSignedMessage
|
|
/// <summary>
|
|
/// Load's and parses a signed message. The signed message should be in an attachment called smime.p7m
|
|
/// </summary>
|
|
/// <param name="storage"></param>
|
|
private void LoadEncryptedAndMeabySignedMessage(CFStorage storage)
|
|
{
|
|
// Create attachment from attachment storage
|
|
var attachment = new Attachment(new Storage(storage), null);
|
|
|
|
if (attachment.FileName.ToUpperInvariant() != "SMIME.P7M")
|
|
throw new MRInvalidSignedFile(
|
|
"The signed file is not valid, it should contain an attachment called smime.p7m but it didn't");
|
|
|
|
ProcessSignedContent(attachment.Data);
|
|
}
|
|
#endregion
|
|
|
|
#region LoadEncryptedSignedMessage
|
|
/// <summary>
|
|
/// Load's and parses a signed message
|
|
/// </summary>
|
|
/// <param name="storage"></param>
|
|
private void LoadClearSignedMessage(CFStorage storage)
|
|
{
|
|
// Create attachment from attachment storage
|
|
var attachment = new Attachment(new Storage(storage), null);
|
|
|
|
// Get the decoded attachment
|
|
using (var memoryStream = new MemoryStream(attachment.Data))
|
|
{
|
|
var eml = Mime.Message.Load(memoryStream);
|
|
if (eml.TextBody != null)
|
|
_bodyText = eml.TextBody.GetBodyAsText();
|
|
|
|
if (eml.HtmlBody != null)
|
|
_bodyHtml = eml.HtmlBody.GetBodyAsText();
|
|
|
|
foreach (var emlAttachment in eml.Attachments)
|
|
{
|
|
if (emlAttachment.FileName.ToUpperInvariant() == "SMIME.P7S")
|
|
ProcessSignedContent(emlAttachment.Body);
|
|
else
|
|
_attachments.Add(new Attachment(emlAttachment));
|
|
}
|
|
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region LoadAttachmentStorage
|
|
/// <summary>
|
|
/// Loads the attachment data out of the specified storage.
|
|
/// </summary>
|
|
/// <param name="storage"> The attachment storage. </param>
|
|
/// <param name="storageName">The name of the <see cref="CFStorage"/></param>
|
|
private void LoadAttachmentStorage(CFStorage storage, string storageName)
|
|
{
|
|
// Create attachment from attachment storage
|
|
var attachment = new Attachment(new Storage(storage), storageName);
|
|
|
|
var attachMethod = attachment.GetMapiPropertyInt32(MapiTags.PR_ATTACH_METHOD);
|
|
switch (attachMethod)
|
|
{
|
|
case MapiTags.ATTACH_EMBEDDED_MSG:
|
|
// Create new Message and set parent and header size
|
|
var subStorage = attachment.GetMapiProperty(MapiTags.PR_ATTACH_DATA_BIN) as CFStorage;
|
|
var subMsg = new Message(subStorage, attachment.RenderingPosition, storageName)
|
|
{
|
|
_parentMessage = this,
|
|
_propHeaderSize = MapiTags.PropertiesStreamHeaderEmbeded
|
|
};
|
|
_attachments.Add(subMsg);
|
|
break;
|
|
|
|
default:
|
|
// Add attachment to attachment list
|
|
_attachments.Add(attachment);
|
|
break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region DeleteAttachment
|
|
/// <summary>
|
|
/// Removes the given <paramref name="attachment"/> from the <see cref="Storage.Message"/> object.
|
|
/// </summary>
|
|
/// <example>
|
|
/// message.DeleteAttachment(message.Attachments[0]);
|
|
/// </example>
|
|
/// <param name="attachment"></param>
|
|
/// <exception cref="MRCannotRemoveAttachment">Raised when it is not possible to remove the <see cref="Storage.Attachment"/> or <see cref="Storage.Message"/> from
|
|
/// the <see cref="Storage.Message"/></exception>
|
|
public void DeleteAttachment(object attachment)
|
|
{
|
|
if (FileAccess == FileAccess.Read)
|
|
throw new MRCannotRemoveAttachment("Cannot remove attachments when the file is not opened in Write or ReadWrite mode");
|
|
|
|
foreach (var attachmentObject in _attachments)
|
|
{
|
|
if (attachmentObject.Equals(attachment))
|
|
{
|
|
string storageName;
|
|
var attach = attachmentObject as Attachment;
|
|
if (attach != null)
|
|
{
|
|
if (string.IsNullOrEmpty(attach.StorageName))
|
|
throw new MRCannotRemoveAttachment("The attachment '" + attach.FileName +
|
|
"' can not be removed, the storage name is unknown");
|
|
|
|
storageName = attach.StorageName;
|
|
attach.Dispose();
|
|
}
|
|
else
|
|
{
|
|
var msg = attachmentObject as Message;
|
|
if (msg == null)
|
|
throw new MRCannotRemoveAttachment(
|
|
"The attachment can not be removed, could not convert the attachment to an Attachment or Message object");
|
|
|
|
storageName = msg.StorageName;
|
|
msg.Dispose();
|
|
}
|
|
|
|
_attachments.Remove(attachment);
|
|
TopParent._rootStorage.Delete(storageName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Copy
|
|
/// <summary>
|
|
/// Copies the given <paramref name="source"/> to the given <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="destination"></param>
|
|
private static void Copy(CFStorage source, CFStorage destination)
|
|
{
|
|
source.VisitEntries(action =>
|
|
{
|
|
if (action.IsStorage)
|
|
{
|
|
var destinationStorage = destination.AddStorage(action.Name);
|
|
destinationStorage.CLSID = action.CLSID;
|
|
destinationStorage.CreationDate = action.CreationDate;
|
|
destinationStorage.ModifyDate = action.ModifyDate;
|
|
Copy(action as CFStorage, destinationStorage);
|
|
}
|
|
else
|
|
{
|
|
var sourceStream = action as CFStream;
|
|
var destinationStream = destination.AddStream(action.Name);
|
|
if (sourceStream != null) destinationStream.SetData(sourceStream.GetData());
|
|
}
|
|
|
|
}, false);
|
|
}
|
|
#endregion
|
|
|
|
#region Save
|
|
/// <summary>
|
|
/// Saves this <see cref="Storage.Message" /> to the specified <paramref name="fileName"/>
|
|
/// </summary>
|
|
/// <param name="fileName"> Name of the file. </param>
|
|
public void Save(string fileName)
|
|
{
|
|
using (var saveFileStream = File.Open(fileName, FileMode.Create, FileAccess.ReadWrite))
|
|
Save(saveFileStream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves this <see cref="Storage.Message"/> to the specified <paramref name="stream"/>
|
|
/// </summary>
|
|
/// <param name="stream"> The stream to save to. </param>
|
|
public void Save(Stream stream)
|
|
{
|
|
if (IsTopParent)
|
|
{
|
|
_compoundFile.Save(stream);
|
|
}
|
|
else
|
|
{
|
|
var compoundFile = new CompoundFile();
|
|
var sourceNameIdStorage = TopParent._rootStorage.GetStorage(MapiTags.NameIdStorage);
|
|
var rootStorage = compoundFile.RootStorage;
|
|
var destinationNameIdStorage = rootStorage.AddStorage(MapiTags.NameIdStorage);
|
|
|
|
Copy(sourceNameIdStorage, destinationNameIdStorage);
|
|
Copy(_rootStorage, rootStorage);
|
|
|
|
var propertiesStream = rootStorage.GetStream(MapiTags.PropertiesStream);
|
|
var sourceData = propertiesStream.GetData();
|
|
var destinationData = new byte[sourceData.Length + 8];
|
|
Buffer.BlockCopy(sourceData, 0, destinationData, 0, 24);
|
|
Buffer.BlockCopy(sourceData, 24, destinationData, 32, sourceData.Length - 24);
|
|
propertiesStream.SetData(destinationData);
|
|
|
|
compoundFile.Save(stream);
|
|
compoundFile.Close();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region SetEmailSenderAndRepresentingSender
|
|
/// <summary>
|
|
/// Gets the <see cref="Sender"/> and <see cref="SenderRepresenting"/> from the <see cref="Storage.Message"/>
|
|
/// object and sets the <see cref="Storage.Message.Sender"/> and <see cref="Storage.Message.SenderRepresenting"/>
|
|
/// </summary>
|
|
private void SetEmailSenderAndRepresentingSender()
|
|
{
|
|
var tempEmail = GetMapiPropertyString(MapiTags.PR_SENDER_EMAIL_ADDRESS);
|
|
|
|
if (string.IsNullOrEmpty(tempEmail) || tempEmail.IndexOf('@') == -1)
|
|
tempEmail = GetMapiPropertyString(MapiTags.PR_SENDER_SMTP_ADDRESS);
|
|
|
|
if (string.IsNullOrEmpty(tempEmail))
|
|
tempEmail = GetMapiPropertyString(MapiTags.InternetAccountName);
|
|
|
|
if (string.IsNullOrEmpty(tempEmail))
|
|
tempEmail = GetMapiPropertyString(MapiTags.SenderSmtpAddressAlternate);
|
|
|
|
MessageHeader headers = null;
|
|
|
|
if (string.IsNullOrEmpty(tempEmail) || tempEmail.IndexOf("@", StringComparison.Ordinal) < 0)
|
|
{
|
|
var senderAddressType = GetMapiPropertyString(MapiTags.PR_SENDER_ADDRTYPE);
|
|
if (senderAddressType != null && senderAddressType != "EX")
|
|
{
|
|
// Get address from email headers. The headers are not present when the addressType = "EX"
|
|
var header = GetStreamAsString(MapiTags.HeaderStreamName, Encoding.Unicode);
|
|
if (!string.IsNullOrEmpty(header))
|
|
headers = HeaderExtractor.GetHeaders(header);
|
|
}
|
|
}
|
|
|
|
// PR_PRIMARY_SEND_ACCT can contain the smtp address of an exchange account
|
|
if (string.IsNullOrEmpty(tempEmail) || tempEmail.IndexOf("@", StringComparison.Ordinal) < 0)
|
|
{
|
|
var testEmail = GetMapiPropertyString(MapiTags.PR_PRIMARY_SEND_ACCT);
|
|
if(!string.IsNullOrEmpty(testEmail) && testEmail.IndexOf("\u0001", StringComparison.Ordinal) > 0)
|
|
{
|
|
tempEmail = EmailAddress.GetValidEmailAddress(testEmail);
|
|
}
|
|
}
|
|
|
|
tempEmail = EmailAddress.RemoveSingleQuotes(tempEmail);
|
|
var tempDisplayName = EmailAddress.RemoveSingleQuotes(GetMapiPropertyString(MapiTags.PR_SENDER_NAME));
|
|
|
|
if (string.IsNullOrEmpty(tempEmail) && headers?.From != null)
|
|
tempEmail = EmailAddress.RemoveSingleQuotes(headers.From.Address);
|
|
|
|
if (string.IsNullOrEmpty(tempDisplayName) && headers?.From != null)
|
|
tempDisplayName = headers.From.DisplayName;
|
|
|
|
var email = tempEmail;
|
|
var displayName = tempDisplayName;
|
|
|
|
// Sometimes the E-mail address and displayname get swapped so check if they are valid
|
|
if (!EmailAddress.IsEmailAddressValid(tempEmail) && EmailAddress.IsEmailAddressValid(tempDisplayName))
|
|
{
|
|
// Swap then
|
|
email = tempDisplayName;
|
|
displayName = tempEmail;
|
|
}
|
|
else if (EmailAddress.IsEmailAddressValid(tempDisplayName))
|
|
{
|
|
// If the displayname is an emailAddress then move it
|
|
email = tempDisplayName;
|
|
displayName = tempDisplayName;
|
|
}
|
|
|
|
if (string.Equals(tempEmail, tempDisplayName, StringComparison.InvariantCultureIgnoreCase))
|
|
displayName = string.Empty;
|
|
|
|
// Set the representing sender if it is there
|
|
Sender = new Sender(email, displayName);
|
|
var representingAddressType = GetMapiPropertyString(MapiTags.PR_SENT_REPRESENTING_ADDRTYPE);
|
|
tempEmail = GetMapiPropertyString(MapiTags.PR_SENT_REPRESENTING_EMAIL_ADDRESS);
|
|
tempEmail = EmailAddress.RemoveSingleQuotes(tempEmail);
|
|
tempDisplayName = EmailAddress.RemoveSingleQuotes(GetMapiPropertyString(MapiTags.PR_SENT_REPRESENTING_NAME));
|
|
|
|
email = tempEmail;
|
|
displayName = tempDisplayName;
|
|
|
|
// Sometimes the E-mail address and displayname get swapped so check if they are valid
|
|
if (!EmailAddress.IsEmailAddressValid(tempEmail) && EmailAddress.IsEmailAddressValid(tempDisplayName))
|
|
{
|
|
// Swap then
|
|
email = tempDisplayName;
|
|
displayName = tempEmail;
|
|
}
|
|
else if (EmailAddress.IsEmailAddressValid(tempDisplayName))
|
|
{
|
|
// If the displayname is an emailAddress then move it
|
|
email = tempDisplayName;
|
|
displayName = tempDisplayName;
|
|
}
|
|
|
|
if (string.Equals(tempEmail, tempDisplayName, StringComparison.InvariantCultureIgnoreCase))
|
|
displayName = string.Empty;
|
|
|
|
// Set the representing sender
|
|
if (!string.IsNullOrWhiteSpace(email))
|
|
SenderRepresenting = new SenderRepresenting(email, displayName, representingAddressType);
|
|
}
|
|
#endregion
|
|
|
|
#region GetEmailSender
|
|
/// <summary>
|
|
/// Returns the E-mail sender address in RFC822 format, e.g.
|
|
/// "Pan, P (Peter)" <Peter.Pan@neverland.com>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string GetEmailSenderRfc822Format()
|
|
{
|
|
var output = string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(Sender.DisplayName))
|
|
output = "\"" + Sender.DisplayName + "\"";
|
|
|
|
if (!string.IsNullOrEmpty(Sender.Email))
|
|
{
|
|
if (!string.IsNullOrEmpty(output))
|
|
output += " ";
|
|
|
|
output += "<" + Sender.Email + ">";
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the E-mail sender address in a human readable format
|
|
/// </summary>
|
|
/// <param name="html">Set to true to return the E-mail address as an html string</param>
|
|
/// <param name="convertToHref">Set to true to convert the E-mail addresses to a hyperlink.
|
|
/// Will be ignored when <paramref name="html"/> is set to false</param>
|
|
/// <returns></returns>
|
|
public string GetEmailSender(bool html, bool convertToHref)
|
|
{
|
|
var output = string.Empty;
|
|
|
|
var emailAddress = Sender.Email;
|
|
var representingEmailAddress = string.Empty;
|
|
var displayName = Sender.DisplayName;
|
|
var representingDisplayName = string.Empty;
|
|
var representingAddressType = string.Empty;
|
|
|
|
if (SenderRepresenting != null)
|
|
{
|
|
representingEmailAddress = SenderRepresenting.Email;
|
|
representingDisplayName = SenderRepresenting.DisplayName;
|
|
representingAddressType = SenderRepresenting.AddressType;
|
|
}
|
|
|
|
if (html)
|
|
{
|
|
emailAddress = WebUtility.HtmlEncode(emailAddress);
|
|
displayName = WebUtility.HtmlEncode(displayName);
|
|
representingEmailAddress = WebUtility.HtmlEncode(representingEmailAddress);
|
|
representingDisplayName = WebUtility.HtmlEncode(representingDisplayName);
|
|
}
|
|
|
|
// If we want hyperlinks and the outputformat is html and the email address is set
|
|
if (convertToHref && html &&
|
|
!string.IsNullOrEmpty(emailAddress))
|
|
{
|
|
output += "<a href=\"mailto:" + emailAddress + "\">" +
|
|
(!string.IsNullOrEmpty(displayName)
|
|
? displayName
|
|
: emailAddress) + "</a>";
|
|
|
|
if (!string.IsNullOrEmpty(representingEmailAddress) &&
|
|
!string.IsNullOrEmpty(emailAddress) &&
|
|
!emailAddress.Equals(representingEmailAddress, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
output += " " + LanguageConsts.EmailOnBehalfOf + " <a href=\"mailto:" + representingEmailAddress +
|
|
"\">" +
|
|
(!string.IsNullOrEmpty(representingDisplayName)
|
|
? representingDisplayName
|
|
: representingEmailAddress) + "</a> ";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string beginTag;
|
|
string endTag;
|
|
if (html)
|
|
{
|
|
beginTag = " <";
|
|
endTag = ">";
|
|
}
|
|
else
|
|
{
|
|
beginTag = " <";
|
|
endTag = ">";
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(displayName))
|
|
output += displayName;
|
|
|
|
if (!string.IsNullOrEmpty(emailAddress))
|
|
output += beginTag + emailAddress + endTag;
|
|
|
|
if (!string.IsNullOrEmpty(representingEmailAddress) &&
|
|
!string.IsNullOrEmpty(emailAddress) &&
|
|
!emailAddress.Equals(representingEmailAddress, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
output += " " + LanguageConsts.EmailOnBehalfOf + " ";
|
|
|
|
if (!string.IsNullOrEmpty(representingDisplayName))
|
|
output += representingDisplayName;
|
|
|
|
if (!string.IsNullOrEmpty(representingEmailAddress) && representingAddressType != "EX")
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(representingDisplayName)) output += beginTag;
|
|
output += representingEmailAddress;
|
|
if (!string.IsNullOrWhiteSpace(representingDisplayName)) output += endTag;
|
|
}
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
#endregion
|
|
|
|
#region GetEmailRecipients
|
|
/// <summary>
|
|
/// Returns all the recipient for the given <paramref name="type"/>
|
|
/// </summary>
|
|
/// <param name="type">The <see cref="RecipientType"/> to return</param>
|
|
/// <returns></returns>
|
|
private List<RecipientPlaceHolder> GetEmailRecipients(RecipientType type)
|
|
{
|
|
var recipients = new List<RecipientPlaceHolder>();
|
|
|
|
// ReSharper disable once LoopCanBeConvertedToQuery
|
|
foreach (var recipient in Recipients)
|
|
{
|
|
// First we filter for the correct recipient type
|
|
if (recipient.Type == type)
|
|
recipients.Add(new RecipientPlaceHolder(recipient.Email, recipient.DisplayName, recipient.AddressType));
|
|
}
|
|
|
|
if (recipients.Count == 0 && Headers != null)
|
|
{
|
|
switch (type)
|
|
{
|
|
case RecipientType.To:
|
|
if (Headers.To != null)
|
|
recipients.AddRange(
|
|
Headers.To.Select(
|
|
to => new RecipientPlaceHolder(to.Address, to.DisplayName, string.Empty)));
|
|
break;
|
|
|
|
case RecipientType.Cc:
|
|
if (Headers.Cc != null)
|
|
recipients.AddRange(
|
|
Headers.Cc.Select(
|
|
cc => new RecipientPlaceHolder(cc.Address, cc.DisplayName, string.Empty)));
|
|
break;
|
|
|
|
case RecipientType.Bcc:
|
|
if (Headers.Bcc != null)
|
|
recipients.AddRange(
|
|
Headers.Bcc.Select(
|
|
bcc => new RecipientPlaceHolder(bcc.Address, bcc.DisplayName, string.Empty)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return recipients;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the E-mail recipients in RFC822 format, e.g.
|
|
/// "Pan, P (Peter)" <Peter.Pan@neverland.com>
|
|
/// </summary>
|
|
/// <param name="type">Selects the Recipient type to retrieve</param>
|
|
/// <returns></returns>
|
|
public string GetEmailRecipientsRfc822Format(RecipientType type)
|
|
{
|
|
var output = string.Empty;
|
|
|
|
var recipients = GetEmailRecipients(type);
|
|
if (Appointment?.UnsendableRecipients != null)
|
|
recipients.AddRange(Appointment.UnsendableRecipients.GetEmailRecipients(type));
|
|
|
|
foreach (var recipient in recipients)
|
|
{
|
|
if (output != string.Empty)
|
|
output += ", ";
|
|
|
|
var tempOutput = string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(recipient.DisplayName))
|
|
tempOutput += "\"" + recipient.DisplayName + "\"";
|
|
|
|
if (!string.IsNullOrEmpty(recipient.Email))
|
|
{
|
|
if (!string.IsNullOrEmpty(tempOutput))
|
|
tempOutput += " ";
|
|
|
|
tempOutput += "<" + recipient.Email + ">";
|
|
}
|
|
|
|
output += tempOutput;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the E-mail recipients in a human readable format
|
|
/// </summary>
|
|
/// <param name="type">Selects the Recipient type to retrieve</param>
|
|
/// <param name="html">Set to true to return the E-mail address as an html string</param>
|
|
/// <param name="convertToHref">Set to true to convert the E-mail addresses to hyperlinks.
|
|
/// Will be ignored when <param ref="html"/> is set to false</param>
|
|
/// <returns></returns>
|
|
public string GetEmailRecipients(RecipientType type,
|
|
bool html,
|
|
bool convertToHref)
|
|
{
|
|
var output = string.Empty;
|
|
|
|
var recipients = GetEmailRecipients(type);
|
|
if (Appointment?.UnsendableRecipients != null)
|
|
recipients.AddRange(Appointment.UnsendableRecipients.GetEmailRecipients(type));
|
|
|
|
foreach (var recipient in recipients)
|
|
{
|
|
if (output != string.Empty)
|
|
output += "; ";
|
|
|
|
var emailAddress = recipient.Email;
|
|
var displayName = recipient.DisplayName;
|
|
|
|
if (convertToHref && html && !string.IsNullOrEmpty(emailAddress))
|
|
output += "<a href=\"mailto:" + emailAddress + "\">" +
|
|
(!string.IsNullOrEmpty(displayName)
|
|
? displayName
|
|
: emailAddress) + "</a>";
|
|
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(displayName))
|
|
output += displayName;
|
|
|
|
var beginTag = string.Empty;
|
|
var endTag = string.Empty;
|
|
if (!string.IsNullOrEmpty(displayName))
|
|
{
|
|
if (html)
|
|
{
|
|
beginTag = " <";
|
|
endTag = ">";
|
|
}
|
|
else
|
|
{
|
|
beginTag = " <";
|
|
endTag = ">";
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(emailAddress))
|
|
output += beginTag + emailAddress + endTag;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
#endregion
|
|
|
|
#region GetAttachmentNames
|
|
/// <summary>
|
|
/// Returns the attachments names as a comma seperated string
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string GetAttachmentNames()
|
|
{
|
|
var result = new List<string>();
|
|
|
|
foreach (var attachment in Attachments)
|
|
{
|
|
// ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull
|
|
if (attachment is Attachment)
|
|
{
|
|
var attach = (Attachment)attachment;
|
|
result.Add(attach.FileName);
|
|
}
|
|
// ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull
|
|
else if (attachment is Message)
|
|
{
|
|
var msg = (Message)attachment;
|
|
result.Add(msg.FileName);
|
|
}
|
|
}
|
|
|
|
return string.Join(", ", result);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
} |