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.
279 lines
11 KiB
279 lines
11 KiB
//
|
|
// Attachment.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.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using MsgReader.Exceptions;
|
|
using MsgReader.Helpers;
|
|
using MsgReader.Localization;
|
|
using OpenMcdf;
|
|
|
|
namespace MsgReader.Outlook
|
|
{
|
|
public partial class Storage
|
|
{
|
|
/// <summary>
|
|
/// Class represents an attachment
|
|
/// </summary>
|
|
public sealed class Attachment : Storage
|
|
{
|
|
#region Fields
|
|
/// <summary>
|
|
/// contains the data of the attachment as an byte array
|
|
/// </summary>
|
|
private byte[] _data;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// The name of the <see cref="CFStorage"/> that contains this attachment
|
|
/// </summary>
|
|
internal string StorageName { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the filename of the attachment
|
|
/// </summary>
|
|
public string FileName { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Retuns the data
|
|
/// </summary>
|
|
public byte[] Data => _data ?? GetMapiPropertyBytes(MapiTags.PR_ATTACH_DATA_BIN);
|
|
|
|
/// <summary>
|
|
/// Returns the content id or null when not available
|
|
/// </summary>
|
|
public string ContentId { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the rendering position or -1 when unknown
|
|
/// </summary>
|
|
public int RenderingPosition { get; }
|
|
|
|
/// <summary>
|
|
/// True when the attachment is inline
|
|
/// </summary>
|
|
public bool IsInline { get; }
|
|
|
|
/// <summary>
|
|
/// True when the attachment is a contact photo. This can only be true
|
|
/// when the <see cref="Storage.Message"/> object is an
|
|
/// <see cref="Storage.Contact"/> object.
|
|
/// </summary>
|
|
public bool IsContactPhoto { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the date and time when the attachment was created or null
|
|
/// when not available
|
|
/// </summary>
|
|
public DateTime? CreationTime { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the date and time when the attachment was last modified or null
|
|
/// when not available
|
|
/// </summary>
|
|
public DateTime? LastModificationTime { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the MAPI Property Hidden, the value may only exist when it has been set True
|
|
/// </summary>
|
|
public bool Hidden { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Returns <c>true</c> when the attachment is an OLE attachment
|
|
/// </summary>
|
|
public bool OleAttachment { get; }
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Creates an attachment object from a <see cref="Mime.MessagePart"/>
|
|
/// </summary>
|
|
/// <param name="attachment"></param>
|
|
internal Attachment(Mime.MessagePart attachment)
|
|
{
|
|
ContentId = attachment.ContentId;
|
|
IsInline = ContentId != null;
|
|
IsContactPhoto = false;
|
|
RenderingPosition = -1;
|
|
_data = attachment.Body;
|
|
FileName = FileManager.RemoveInvalidFileNameChars(attachment.FileName);
|
|
StorageName = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Storage.Attachment" /> class.
|
|
/// </summary>
|
|
/// <param name="message"> The message. </param>
|
|
/// <param name="storageName">The name of the <see cref="CFStorage"/> that contains this attachment</param>
|
|
internal Attachment(Storage message, string storageName) : base(message._rootStorage)
|
|
{
|
|
StorageName = storageName;
|
|
|
|
GC.SuppressFinalize(message);
|
|
_propHeaderSize = MapiTags.PropertiesStreamHeaderAttachOrRecip;
|
|
|
|
CreationTime = GetMapiPropertyDateTime(MapiTags.PR_CREATION_TIME);
|
|
LastModificationTime = GetMapiPropertyDateTime(MapiTags.PR_LAST_MODIFICATION_TIME);
|
|
|
|
ContentId = GetMapiPropertyString(MapiTags.PR_ATTACH_CONTENTID);
|
|
IsInline = ContentId != null;
|
|
|
|
var isHidden = GetMapiPropertyBool(MapiTags.PR_ATTACHMENT_HIDDEN);
|
|
if (isHidden != null)
|
|
Hidden = isHidden.Value;
|
|
|
|
var isContactPhoto = GetMapiPropertyBool(MapiTags.PR_ATTACHMENT_CONTACTPHOTO);
|
|
if (isContactPhoto == null)
|
|
IsContactPhoto = false;
|
|
else
|
|
IsContactPhoto = (bool) isContactPhoto;
|
|
|
|
var renderingPosition = GetMapiPropertyInt32(MapiTags.PR_RENDERING_POSITION);
|
|
if (renderingPosition == null)
|
|
RenderingPosition = -1;
|
|
else
|
|
RenderingPosition = (int) renderingPosition;
|
|
|
|
var fileName = GetMapiPropertyString(MapiTags.PR_ATTACH_LONG_FILENAME);
|
|
|
|
if (string.IsNullOrEmpty(fileName))
|
|
fileName = GetMapiPropertyString(MapiTags.PR_ATTACH_FILENAME);
|
|
|
|
if (string.IsNullOrEmpty(fileName))
|
|
fileName = GetMapiPropertyString(MapiTags.PR_DISPLAY_NAME);
|
|
|
|
FileName = fileName != null
|
|
? FileManager.RemoveInvalidFileNameChars(fileName)
|
|
: LanguageConsts.NameLessFileName;
|
|
|
|
var attachmentMethod = GetMapiPropertyInt32(MapiTags.PR_ATTACH_METHOD);
|
|
|
|
switch (attachmentMethod)
|
|
{
|
|
case MapiTags.ATTACH_BY_REFERENCE:
|
|
case MapiTags.ATTACH_BY_REF_RESOLVE:
|
|
case MapiTags.ATTACH_BY_REF_ONLY:
|
|
ResolveAttachment();
|
|
break;
|
|
|
|
case MapiTags.ATTACH_OLE:
|
|
var storage = GetMapiProperty(MapiTags.PR_ATTACH_DATA_BIN) as CFStorage;
|
|
var attachmentOle = new Attachment(new Storage(storage), null);
|
|
_data = attachmentOle.GetStreamBytes("CONTENTS");
|
|
if (_data != null)
|
|
{
|
|
var fileTypeInfo = FileTypeSelector.GetFileTypeFileInfo(Data);
|
|
|
|
if (string.IsNullOrEmpty(FileName))
|
|
FileName = fileTypeInfo.Description;
|
|
|
|
FileName += "." + fileTypeInfo.Extension.ToLower();
|
|
}
|
|
else
|
|
// http://www.devsuperpage.com/search/Articles.aspx?G=10&ArtID=142729
|
|
_data = attachmentOle.GetStreamBytes("\u0002OlePres000");
|
|
|
|
if (_data != null)
|
|
{
|
|
try
|
|
{
|
|
SaveImageAsPng(40);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
SaveImageAsPng(0);
|
|
}
|
|
}
|
|
else
|
|
throw new MRUnknownAttachmentFormat("Can not read the attachment");
|
|
|
|
OleAttachment = true;
|
|
IsInline = true;
|
|
break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ResolveAttachment
|
|
/// <summary>
|
|
/// Tries to resolve an attachment when the <see cref="MapiTags.PR_ATTACH_METHOD"/> is of the type
|
|
/// <see cref="MapiTags.ATTACH_BY_REFERENCE"/>, <see cref="MapiTags.ATTACH_BY_REF_RESOLVE"/> or
|
|
/// <see cref="MapiTags.ATTACH_BY_REF_ONLY"/>
|
|
/// </summary>
|
|
private void ResolveAttachment()
|
|
{
|
|
//The PR_ATTACH_PATHNAME or PR_ATTACH_LONG_PATHNAME property contains a fully qualified path identifying the attachment
|
|
var attachPathName = GetMapiPropertyString(MapiTags.PR_ATTACH_PATHNAME);
|
|
var attachLongPathName = GetMapiPropertyString(MapiTags.PR_ATTACH_LONG_PATHNAME);
|
|
|
|
// Because we are not sure we can access the files we put everything in a try catch
|
|
try
|
|
{
|
|
if (attachLongPathName != null)
|
|
{
|
|
_data = File.ReadAllBytes(attachLongPathName);
|
|
return;
|
|
}
|
|
|
|
if (attachPathName == null) return;
|
|
_data = File.ReadAllBytes(attachPathName);
|
|
}
|
|
// ReSharper disable once EmptyGeneralCatchClause
|
|
catch {}
|
|
}
|
|
#endregion
|
|
|
|
#region SaveImageAsPng
|
|
/// <summary>
|
|
/// Tries to save an attachment as a png file with the user specified buffer
|
|
/// </summary>
|
|
private void SaveImageAsPng(int bufferOffset)
|
|
{
|
|
if (bufferOffset > _data.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(bufferOffset), bufferOffset,
|
|
@"Buffer Offset value cannot be greater than the length of the image byte array!");
|
|
|
|
var length = _data.Length - bufferOffset;
|
|
var bytes = new byte[length];
|
|
Buffer.BlockCopy(_data, bufferOffset, bytes, 0, length);
|
|
using (var inputStream = new MemoryStream(bytes))
|
|
using (var image = Image.FromStream(inputStream))
|
|
using (var outputStream = new MemoryStream())
|
|
{
|
|
image.Save(outputStream, ImageFormat.Png);
|
|
outputStream.Position = 0;
|
|
_data = outputStream.ToByteArray();
|
|
FileName = "ole0.bmp";
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
}
|