Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Mail2Bug/App.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="Iterations" value="200" />
Expand All @@ -13,6 +13,7 @@
<add key="log4net.Config.Watch" value="True" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
<add key="Microsoft.ServiceBus.ConnectionString" value="" />
<add key="EnforceTls12" value="true" />
</appSettings>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
Expand Down
1 change: 1 addition & 0 deletions Mail2Bug/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public enum ProcessingStrategyType
public bool ApplyOverridesDuringUpdate { get; set; }
public bool AttachOriginalMessage { get; set; }
public bool AttachUpdateMessages { get; set; }
public bool EnableExperimentalHtmlFeatures { get; set; }

public ProcessingStrategyType ProcessingStrategy = ProcessingStrategyType.SimpleBugStrategy;
}
Expand Down
6 changes: 6 additions & 0 deletions Mail2Bug/Email/EWS/EWSIncomingFileAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public string SaveAttachmentToFile(string filename)
return filename;
}

public string ContentId
{
get => _attachment.ContentId;
set => _attachment.ContentId = value;
}

private static readonly ILog Logger = LogManager.GetLogger(typeof(EWSIncomingFileAttachment));
}
}
6 changes: 6 additions & 0 deletions Mail2Bug/Email/EWS/EWSIncomingItemAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public string SaveAttachmentToFile(string filename)
return filename;
}

public string ContentId
{
get => _attachment.ContentId;
set => _attachment.ContentId = value;
}

private static readonly ILog Logger = LogManager.GetLogger(typeof (EWSIncomingItemAttachment));
}
}
4 changes: 2 additions & 2 deletions Mail2Bug/Email/EWS/EWSIncomingMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ public string SaveToFile(string filename)
return filename;
}

public string GetLastMessageText()
public string GetLastMessageText(bool enableExperimentalHtmlFeatures)
{
return EmailBodyProcessingUtils.GetLastMessageText(this);
return EmailBodyProcessingUtils.GetLastMessageText(this, enableExperimentalHtmlFeatures);
}

public void Delete()
Expand Down
129 changes: 114 additions & 15 deletions Mail2Bug/Email/EmailBodyProcessingUtils.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CsQuery;

namespace Mail2Bug.Email
{
public class EmailBodyProcessingUtils
{
public static string GetLastMessageText(IIncomingEmailMessage message)
public static string GetLastMessageText(IIncomingEmailMessage message, bool enableExperimentalHtmlFeatures)
{
return enableExperimentalHtmlFeatures && message.IsHtmlBody ? GetLastMessageText_Html(message.RawBody) : GetLastMessageText_PlainText(message);
}

private static string GetLastMessageText_PlainText(IIncomingEmailMessage message)
{
var lastMessage = new StringBuilder();
lastMessage.Append(message.PlainTextBody);
Expand All @@ -23,27 +29,90 @@ public static string GetLastMessageText(IIncomingEmailMessage message)
return lastMessage.ToString();
}

public static string GetLastMessageText_Html(string rawBody)
{
CQ dom = rawBody;

const string outlookDesktopSeparatorStyle = "border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0in 0in 0in";
const string outlookMobileSeparatorStyle = "display:inline-block;width:98%";

// There's no well-defined way to parse the latest email from a thread
// We have to use heuristics to cover different email clients
foreach (IDomObject element in dom["*"])
{
// Lots of email clients insert html elements as message delimiters which have styling but no inner text
// This block checks for some of these patterns
if (string.Equals(element.NodeName, "div", StringComparison.OrdinalIgnoreCase) &&
(element.Id == "divRplyFwdMsg" || element.Id == "x_divRplyFwdMsg" || outlookDesktopSeparatorStyle.Equals(element.GetAttribute("style"))))
{
IDomContainer parent = element.ParentNode;
RemoveSubsequent(parent);
parent.Remove();
break;
}

if (string.Equals(element.NodeName, "hr", StringComparison.OrdinalIgnoreCase) &&
outlookMobileSeparatorStyle.Equals(element.GetAttribute("style")))
{
RemoveSubsequent(element);
element.Remove();
break;
}

if (!element.ChildElements.Any() && !string.IsNullOrWhiteSpace(element.InnerText))
{
var separatorIndex = IndexOfAny(element.InnerText, MessageBorderMarkers);
if (separatorIndex.HasValue)
{
element.InnerText = element.InnerText.Substring(0, separatorIndex.Value);
RemoveSubsequent(element);
break;
}
}
}

return dom.Render();
}

private static void RemoveSubsequent(IDomObject element)
{
IDomObject nextNode;
while ((nextNode = element.NextSibling) != null)
{
nextNode.Remove();
}

if (element.ParentNode is IDomObject parentElement)
{
RemoveSubsequent(parentElement);
}
}

private static readonly string[] MessageBorderMarkers =
{
"_____",
"-----Original Message",
"From:",
};

/// <summary>
/// Get the seperate denotation between reply content and original content.
/// </summary>
/// <param name="description"></param>
/// <returns></returns>
private static int GetReplySeperatorIndex(string description)
{
var indices = new List<int>
{
description.IndexOf("_____", StringComparison.Ordinal),
description.IndexOf("-----Original Message", StringComparison.Ordinal),
description.IndexOf("From:", StringComparison.Ordinal)
};

indices.RemoveAll(x => x < 0);
if (indices.Count == 0)
{
return description.Length;
}
return IndexOfAny(description, MessageBorderMarkers) ?? description.Length;
}

return indices.Min();
private static int? IndexOfAny(string text, IEnumerable<string> searchTerms)
{
return searchTerms
.Select(search => text.IndexOf(search, StringComparison.Ordinal))
.Where(index => index >= 0)
.OrderBy(index => index)
.Cast<int?>()
.FirstOrDefault();
}

public static string ConvertHtmlMessageToPlainText(string text)
Expand Down Expand Up @@ -78,5 +147,35 @@ private static string ResolveOridnalToChar(Match ordinalMatch)
var unicodeChar = (char)int.Parse(ordinalMatch.Groups["ordinal"].Value);
return new string(new[] {unicodeChar});
}

/// <summary>
/// If an email embeds an email inline in its html, that embedded image won't display correctly unless we modify the html.
/// This method does that, given information about email's known attachments
/// </summary>
public static string UpdateEmbeddedImageLinks(string originalHtml, System.Collections.Generic.IReadOnlyCollection<MessageAttachmentInfo> attachments)
{
if (attachments == null || attachments.Count == 0)
{
return originalHtml;
}

CQ dom = originalHtml;
foreach (var attachment in attachments)
{
string originalImgSrc = $"cid:{attachment.ContentId}";
var matchingImgLinks = dom[$"img[src$='{originalImgSrc}']"];

// This may point to the file on the local file-system if we haven't yet uploaded the attachment
// However, the work item APIs seem to magically 'just work' with this and update the URI to point to the uploaded location
// If for some reason that stops working, we'd need to either infer the uploaded URI or upload first and mutate the html afterward
var newSrc = new Uri(attachment.FilePath);
foreach (IDomObject img in matchingImgLinks)
{
img.SetAttribute("src", newSrc.AbsoluteUri);
}
}

return dom.Render();
}
}
}
1 change: 1 addition & 0 deletions Mail2Bug/Email/IIncomingEmailAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public interface IIncomingEmailAttachment
{
string SaveAttachmentToFile();
string SaveAttachmentToFile(string filename);
string ContentId { get; set; }
}
}
2 changes: 1 addition & 1 deletion Mail2Bug/Email/IIncomingEmailMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public interface IIncomingEmailMessage
/// messages dropped, whereas the body includes all of that "history".
/// </summary>
/// <returns></returns>
string GetLastMessageText();
string GetLastMessageText(bool enableExperimentalHtmlFeatures);

/// <summary>
/// Deletes the message
Expand Down
41 changes: 41 additions & 0 deletions Mail2Bug/Email/MessageAttachmentCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;

namespace Mail2Bug.Email
{
/// <summary>
/// Collection of Exchange email attachments that have been downloaded locally
/// </summary>
public class MessageAttachmentCollection : IDisposable
{
private readonly List<MessageAttachmentInfo> _attachments;
private readonly TempFileCollection _tempFileCollection;

public IReadOnlyCollection<MessageAttachmentInfo> Attachments => _attachments;
public IEnumerable<string> LocalFilePaths => _attachments.Select(a => a.FilePath);

public MessageAttachmentCollection()
{
_attachments = new List<MessageAttachmentInfo>();
_tempFileCollection = new TempFileCollection();
}

public void Add(string localFilePath, string contentId)
{
_attachments.Add(new MessageAttachmentInfo(localFilePath, contentId));
_tempFileCollection.AddFile(localFilePath, keepFile: false);
}

public void DeleteLocalFiles()
{
_tempFileCollection.Delete();
}

public void Dispose()
{
DeleteLocalFiles();
}
}
}
18 changes: 18 additions & 0 deletions Mail2Bug/Email/MessageAttachmentInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Mail2Bug.Email
{
/// <summary>
/// Represents basic information about an Exchange email attachment that has been downloaded locally and has a known exchange content id
/// </summary>
public class MessageAttachmentInfo
{
public MessageAttachmentInfo(string filePath, string contentId)
{
FilePath = filePath;
ContentId = contentId;
}

public string FilePath { get; }

public string ContentId { get; }
}
}
9 changes: 7 additions & 2 deletions Mail2Bug/Mail2Bug.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CsQuery, Version=1.3.3.249, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll</HintPath>
</Reference>
<Reference Include="Hyak.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net">
<HintPath>..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
<HintPath>..\packages\log4net.2.0.10\lib\net40\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll</HintPath>
Expand Down Expand Up @@ -320,6 +323,8 @@
<Compile Include="MessageProcessingStrategies\DateBasedValueResolver.cs" />
<Compile Include="MessageProcessingStrategies\IMessageProcessingStrategy.cs" />
<Compile Include="MessageProcessingStrategies\INameResolver.cs" />
<Compile Include="Email\MessageAttachmentCollection.cs" />
<Compile Include="Email\MessageAttachmentInfo.cs" />
<Compile Include="MessageProcessingStrategies\NameResolver.cs" />
<Compile Include="MessageProcessingStrategies\NameResolverMock.cs" />
<Compile Include="MessageProcessingStrategies\OverridesExtractor.cs" />
Expand Down Expand Up @@ -373,4 +378,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
7 changes: 7 additions & 0 deletions Mail2Bug/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using log4net;
Expand All @@ -30,6 +31,12 @@ public static void Main(string[] args) // string[] args

try
{
// Enforce TLS 1.2
if (ReadBoolFromAppConfig("EnforceTls12", false))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
}

string configPath = ConfigurationManager.AppSettings["ConfigPath"];
string configsFilePattern = ConfigurationManager.AppSettings["ConfigFilePattern"];

Expand Down
Loading