Skip to content
Closed
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
35 changes: 35 additions & 0 deletions src/SmtpServer.Tests/SmtpParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,41 @@ public void CanMakeRcpt(string input, string user, string host)
Assert.Equal(host, ((RcptCommand)command).Address.Host);
}

[Fact]
public void CanMakeRcptWithParameters()
{
// arrange
var reader = CreateReader("RCPT TO:<cain.osullivan@gmail.com> NOTIFY=SUCCESS,FAILURE ORCPT=rfc822;cain.osullivan@gmail.com");

// act
var result = Parser.TryMakeRcpt(ref reader, out var command, out var errorResponse);

// assert
Assert.True(result);
Assert.True(command is RcptCommand);
var rcpt = (RcptCommand)command;
Assert.Equal("cain.osullivan", rcpt.Address.User);
Assert.Equal("gmail.com", rcpt.Address.Host);
Assert.Equal(2, rcpt.Parameters.Count);
Assert.Equal("SUCCESS,FAILURE", rcpt.Parameters["NOTIFY"]);
Assert.Equal("rfc822;cain.osullivan@gmail.com", rcpt.Parameters["ORCPT"]);
}

[Fact]
public void CanNotMakeRcptWithInvalidParameters()
{
// arrange
var reader = CreateReader("RCPT TO:<cain.osullivan@gmail.com> NOTIFY=");

// act
var result = Parser.TryMakeRcpt(ref reader, out var command, out var errorResponse);

// assert
Assert.False(result);
Assert.Null(command);
Assert.NotNull(errorResponse);
}

[Theory]
[InlineData("RCPT TO:<someone@@example.com>")]
[InlineData("RCPT TO:<someone@example..com>")]
Expand Down
9 changes: 9 additions & 0 deletions src/SmtpServer/Protocol/ISmtpCommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public interface ISmtpCommandFactory
/// <returns>The RCPT command.</returns>
SmtpCommand CreateRcpt(IMailbox address);

/// <summary>
/// Create a RCPT command.
/// </summary>
/// <param name="address">The address that the mail is to.</param>
/// <param name="parameters">The optional parameters for the recipient.</param>
/// <returns>The RCPT command.</returns>
SmtpCommand CreateRcpt(IMailbox address, IReadOnlyDictionary<string, string> parameters)
=> CreateRcpt(address);

/// <summary>
/// Create a DATA command.
/// </summary>
Expand Down
18 changes: 17 additions & 1 deletion src/SmtpServer/Protocol/RcptCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SmtpServer.ComponentModel;
Expand All @@ -22,9 +23,19 @@ public sealed class RcptCommand : SmtpCommand
/// Constructor.
/// </summary>
/// <param name="address">The address.</param>
public RcptCommand(IMailbox address) : base(Command)
public RcptCommand(IMailbox address) : this(address, new Dictionary<string, string>())
{
}

/// <summary>
/// Constructor.
/// </summary>
/// <param name="address">The address.</param>
/// <param name="parameters">The optional parameters for the recipient.</param>
public RcptCommand(IMailbox address, IReadOnlyDictionary<string, string> parameters) : base(Command)
{
Address = address;
Parameters = parameters;
}

/// <summary>
Expand Down Expand Up @@ -59,5 +70,10 @@ internal override async Task<bool> ExecuteAsync(SmtpSessionContext context, Canc
/// Gets the address that the mail is to.
/// </summary>
public IMailbox Address { get; }

/// <summary>
/// The list of extended recipient parameters.
/// </summary>
public IReadOnlyDictionary<string, string> Parameters { get; }
}
}
8 changes: 7 additions & 1 deletion src/SmtpServer/Protocol/SmtpCommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ public virtual SmtpCommand CreateMail(IMailbox address, IReadOnlyDictionary<stri
/// <inheritdoc />
public virtual SmtpCommand CreateRcpt(IMailbox address)
{
return new RcptCommand(address);
return CreateRcpt(address, new Dictionary<string, string>());
}

/// <inheritdoc />
public virtual SmtpCommand CreateRcpt(IMailbox address, IReadOnlyDictionary<string, string> parameters)
{
return new RcptCommand(address, parameters);
}

/// <inheritdoc />
Expand Down
15 changes: 13 additions & 2 deletions src/SmtpServer/Protocol/SmtpParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,20 @@ public bool TryMakeRcpt(ref TokenReader reader, out SmtpCommand command, out Smt
return false;
}

// TODO: support optional service extension parameters here
reader.Skip(TokenKind.Space);

IReadOnlyDictionary<string, string> parameters;
if (reader.Peek().Kind == TokenKind.None)
{
parameters = new Dictionary<string, string>();
}
else if (reader.TryMake(TryMakeMailParameters, out parameters) == false)
{
errorResponse = SmtpResponse.SyntaxError;
return false;
}

command = _smtpCommandFactory.CreateRcpt(mailbox);
command = _smtpCommandFactory.CreateRcpt(mailbox, parameters);
return true;
}

Expand Down
4 changes: 3 additions & 1 deletion src/SmtpServer/Tracing/TracingSmtpCommandVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ protected override void Visit(QuitCommand command)
/// <param name="command">The command that is being visited.</param>
protected override void Visit(RcptCommand command)
{
_output.WriteLine("RCPT: Address={0}", command.Address.AsAddress());
_output.WriteLine("RCPT: Address={0} Parameters={1}",
command.Address.AsAddress(),
string.Join(",", command.Parameters.Select(kvp => $"{kvp.Key}={kvp.Value}")));
}

/// <summary>
Expand Down