Skip to content

Commit

Permalink
Added support for PickupDirectoryLocation (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
snakefoot authored Aug 30, 2023
1 parent 5bf876b commit 8a25f6b
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 55 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ same options as the original mail target, see [docs of the original mailTarget](

Currently not implemented:

- PickupDirectory
- NTLM auth

This library is integration tested with the [SmtpServer NuGet package](https://www.nuget.org/packages/SmtpServer/)
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ variables:
Solution: 'src/NLog.MailKit.sln'
BuildPlatform: 'Any CPU'
BuildConfiguration: 'Release'
Version: '5.1.0'
Version: '5.2.0'
FullVersion: '$(Version).$(Build.BuildId)'

steps:
Expand Down
152 changes: 100 additions & 52 deletions src/NLog.MailKit/MailTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public Layout Body
/// <remarks>Warning: zero is not infinite waiting</remarks>
public Layout<int> Timeout { get; set; } = 10000;

/// <summary>
/// Gets or sets the folder where applications save mail messages to be processed by the local SMTP server.
/// </summary>
/// <docgen category='SMTP Options' order='17' />
public Layout PickupDirectoryLocation { get; set; }

/// <summary>
/// Gets the array of email headers that are transmitted with this email message
/// </summary>
Expand Down Expand Up @@ -305,58 +311,20 @@ private void ProcessSingleMailMessage(IEnumerable<AsyncLogEventInfo> events)

var message = CreateMailMessage(lastEvent, bodyBuffer.ToString());

using (var client = new SmtpClient())
var pickupFolder = RenderLogEvent(PickupDirectoryLocation, lastEvent);
if (!string.IsNullOrEmpty(pickupFolder))
{
client.Timeout = RenderLogEvent(Timeout, lastEvent);

var renderedHost = RenderLogEvent(SmtpServer, lastEvent);
if (string.IsNullOrEmpty(renderedHost))
{
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpServer)));
}

var enableSsl = RenderLogEvent(EnableSsl, lastEvent);
var secureSocketOptions = enableSsl ? SecureSocketOptions.SslOnConnect : RenderLogEvent(SecureSocketOption, lastEvent, DefaultSecureSocketOption);
var smtpPort = RenderLogEvent(SmtpPort, lastEvent);
InternalLogger.Debug("Sending mail to {0} using {1}:{2}", message.To, renderedHost, smtpPort);
InternalLogger.Trace(" Subject: '{0}'", message.Subject);
InternalLogger.Trace(" From: '{0}'", message.From);

var skipCertificateValidation = RenderLogEvent(SkipCertificateValidation, lastEvent);
if (skipCertificateValidation)
{
client.ServerCertificateValidationCallback += (s, cert, chain, sslPolicyErrors) => true;
}


client.Connect(renderedHost, smtpPort, secureSocketOptions);
InternalLogger.Trace("{0}: Connecting succesfull", this);

// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");

// Note: only needed if the SMTP server requires authentication

var smtpAuthentication = RenderLogEvent(SmtpAuthentication, lastEvent);
if (smtpAuthentication == SmtpAuthenticationMode.Basic)
{
var userName = RenderLogEvent(SmtpUserName, lastEvent);
var password = RenderLogEvent(SmtpPassword, lastEvent);

InternalLogger.Trace("{0}: Authenticate with username '{1}'", this, userName);
client.Authenticate(userName, password);
}

client.Send(message);
InternalLogger.Trace("{0}: Sending mail done. Disconnecting", this);
client.Disconnect(true);
InternalLogger.Trace("{0}: Disconnected", this);

foreach (var ev in events)
{
ev.Continuation(null);
}
var emailFilePath = ResolvePickupDirectoryLocationFilePath(pickupFolder);
message.WriteTo(emailFilePath);
}
else
{
SendMailMessage(message, lastEvent);
}

foreach (var ev in events)
{
ev.Continuation(null);
}
}
catch (Exception exception)
Expand All @@ -374,6 +342,57 @@ private void ProcessSingleMailMessage(IEnumerable<AsyncLogEventInfo> events)
}
}

private void SendMailMessage(MimeMessage message, LogEventInfo lastEvent)
{
using (var client = new SmtpClient())
{
client.Timeout = RenderLogEvent(Timeout, lastEvent);

var renderedHost = RenderLogEvent(SmtpServer, lastEvent);
if (string.IsNullOrEmpty(renderedHost))
{
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpServer)));
}

var enableSsl = RenderLogEvent(EnableSsl, lastEvent);
var secureSocketOptions = enableSsl ? SecureSocketOptions.SslOnConnect : RenderLogEvent(SecureSocketOption, lastEvent, DefaultSecureSocketOption);
var smtpPort = RenderLogEvent(SmtpPort, lastEvent);
InternalLogger.Debug("Sending mail to {0} using {1}:{2}", message.To, renderedHost, smtpPort);
InternalLogger.Trace(" Subject: '{0}'", message.Subject);
InternalLogger.Trace(" From: '{0}'", message.From);

var skipCertificateValidation = RenderLogEvent(SkipCertificateValidation, lastEvent);
if (skipCertificateValidation)
{
client.ServerCertificateValidationCallback += (s, cert, chain, sslPolicyErrors) => true;
}

client.Connect(renderedHost, smtpPort, secureSocketOptions);
InternalLogger.Trace("{0}: Connecting succesfull", this);

// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");

// Note: only needed if the SMTP server requires authentication

var smtpAuthentication = RenderLogEvent(SmtpAuthentication, lastEvent);
if (smtpAuthentication == SmtpAuthenticationMode.Basic)
{
var userName = RenderLogEvent(SmtpUserName, lastEvent);
var password = RenderLogEvent(SmtpPassword, lastEvent);

InternalLogger.Trace("{0}: Authenticate with username '{1}'", this, userName);
client.Authenticate(userName, password);
}

client.Send(message);
InternalLogger.Trace("{0}: Sending mail done. Disconnecting", this);
client.Disconnect(true);
InternalLogger.Trace("{0}: Disconnected", this);
}
}

/// <summary>
/// Create buffer for body
/// </summary>
Expand Down Expand Up @@ -414,6 +433,35 @@ private StringBuilder CreateBodyBuffer(IEnumerable<AsyncLogEventInfo> events, Lo
return bodyBuffer;
}

/// <summary>
/// Handle <paramref name="pickupDirectoryLocation"/> if it is a virtual directory.
/// </summary>
internal string ResolvePickupDirectoryLocationFilePath(string pickupDirectoryLocation)
{
const string virtualPathPrefix = "~/";

if (pickupDirectoryLocation.StartsWith(virtualPathPrefix, StringComparison.Ordinal))
{
// Support for Virtual Paths
var root = AppDomain.CurrentDomain.BaseDirectory;
var directory = pickupDirectoryLocation.Substring(virtualPathPrefix.Length).Replace('/', System.IO.Path.DirectorySeparatorChar);
pickupDirectoryLocation = System.IO.Path.Combine(root, directory);
}

string filename;
string pathAndFilename;
while (true)
{
filename = Guid.NewGuid().ToString() + ".eml";
pathAndFilename = System.IO.Path.Combine(pickupDirectoryLocation, filename);
if (!System.IO.File.Exists(pathAndFilename))
break;
}

InternalLogger.Debug("{0}: Writing mail to file: {1}", this, pathAndFilename);
return pathAndFilename;
}

private void CheckRequiredParameters()
{
var smtpAuthentication = RenderLogEvent(SmtpAuthentication, LogEventInfo.CreateNullEvent());
Expand Down Expand Up @@ -479,7 +527,7 @@ private MimeMessage CreateMailMessage(LogEventInfo lastEvent, string body)
newBody = newBody?.Replace(Environment.NewLine, "<br/>");
if (newBody?.IndexOf('\n') >= 0)
{
newBody = newBody?.Replace("\n", "<br/>");
newBody = newBody.Replace("\n", "<br/>");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/NLog.MailKit/NLog.MailKit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ If the mail target was already available on your platform, this package will ove

* Compared to the original MailTarget, the following options aren't implemented:

- PickupDirectory
- NTLM auth

* MailKit gives more control of the sockets, so you get the `secureSocketOption` option for free!
Expand All @@ -29,6 +28,7 @@ If the mail target was already available on your platform, this package will ove
<AssemblyOriginatorKeyFile>NLog.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<PackageReleaseNotes>
- Added support for PickupDirectoryLocation
- Added support for email headers
- Added target-alias mailkit
- Updated to NLog v5.2.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,34 @@ public void SendMailWithHeaderFooter()
Assert.Contains("*** End ***", mailBody);
}

[Fact]
public void SendMailWitPickupFolder()
{
// Arrange
var tempFolder = Path.Combine(Path.GetTempPath(), "NLog_MailKit_" + Guid.NewGuid().ToString());
try
{
Directory.CreateDirectory(tempFolder);

// Act
var mailTarget = CreateNLogConfig();
mailTarget.PickupDirectoryLocation = tempFolder;
var logger = LogManager.GetLogger("logger1");
var expectedMessage = "hello first mail!";
logger.Info(expectedMessage);

// Assert
var files = Directory.GetFiles(tempFolder);
Assert.Single(files);
var msg = MimeKit.MimeMessage.Load(files[0]);
Assert.Contains(expectedMessage, msg.Body.ToString());
}
finally
{
Directory.Delete(tempFolder, true);
}
}

[Fact]
public void SendMailBatch()
{
Expand Down

0 comments on commit 8a25f6b

Please sign in to comment.