diff --git a/Src/SampleApp/Program.cs b/Src/SampleApp/Program.cs index 18bc272..6b18fd1 100644 --- a/Src/SampleApp/Program.cs +++ b/Src/SampleApp/Program.cs @@ -25,7 +25,7 @@ static void Main(string[] args) // .Append(ToByteArray("pqrstu")) // .Append(ToByteArray("vwxyz")); - //var sequence = new ReadOnlySequence(start, 0, end, end.Memory.Length); ; + //var sequence = new ReadOnlySequence(start, 0, end, end.Memory.Length); //Span pattern = stackalloc byte[3]; //pattern[0] = (byte)'s'; @@ -43,7 +43,6 @@ static void Main(string[] args) // Console.WriteLine(StringUtil.Create(found)); //} - HERE: fix DotStuffing SimpleExample.Run(); //////var text = new ReadOnlySequence(Encoding.ASCII.GetBytes("EHLO abc-1-def.mail.com")); diff --git a/Src/SmtpServer/Extensions/StringExtensions.cs b/Src/SmtpServer/Extensions/StringExtensions.cs index 037716e..874ecd9 100644 --- a/Src/SmtpServer/Extensions/StringExtensions.cs +++ b/Src/SmtpServer/Extensions/StringExtensions.cs @@ -1,23 +1,23 @@ -using System; +//using System; -namespace SmtpServer -{ - public static class StringExtensions - { - /// - /// Returns a value indicating whether or not the given string is equal on a case insensitive comparisson. - /// - /// The string to compare. - /// The string to test against. - /// true if the strings are equal on a case insensitive comparisson. - public static bool CaseInsensitiveEquals(this string str, string test) - { - if (str == null) - { - throw new ArgumentNullException(nameof(str)); - } +//namespace SmtpServer +//{ +// public static class StringExtensions +// { +// /// +// /// Returns a value indicating whether or not the given string is equal on a case insensitive comparisson. +// /// +// /// The string to compare. +// /// The string to test against. +// /// true if the strings are equal on a case insensitive comparisson. +// public static bool CaseInsensitiveEquals(this string str, string test) +// { +// if (str == null) +// { +// throw new ArgumentNullException(nameof(str)); +// } - return String.Equals(str, test, StringComparison.OrdinalIgnoreCase); - } - } -} \ No newline at end of file +// return string.Equals(str, test, StringComparison.OrdinalIgnoreCase); +// } +// } +//} \ No newline at end of file diff --git a/Src/SmtpServer/Extensions/TaskExtensions.cs b/Src/SmtpServer/Extensions/TaskExtensions.cs index 35b9080..25b2767 100644 --- a/Src/SmtpServer/Extensions/TaskExtensions.cs +++ b/Src/SmtpServer/Extensions/TaskExtensions.cs @@ -49,4 +49,4 @@ public static async Task WithCancellation(this Task task, CancellationT return await task.ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/Src/SmtpServer/IO/ByteArraySegment.cs b/Src/SmtpServer/IO/ByteArraySegment.cs new file mode 100644 index 0000000..c3971ef --- /dev/null +++ b/Src/SmtpServer/IO/ByteArraySegment.cs @@ -0,0 +1,39 @@ +using System; +using System.Buffers; + +namespace SmtpServer.IO +{ + internal sealed class ByteArraySegment : ReadOnlySequenceSegment + { + internal ByteArraySegment(ReadOnlyMemory memory) + { + Memory = memory; + } + + internal ByteArraySegment Append(ref ReadOnlySequence sequence) + { + var segment = this; + + var position = sequence.GetPosition(0); + + while (sequence.TryGet(ref position, out var memory)) + { + segment = segment.Append(memory); + } + + return segment; + } + + internal ByteArraySegment Append(ReadOnlyMemory memory) + { + var segment = new ByteArraySegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = segment; + + return segment; + } + } +} \ No newline at end of file diff --git a/Src/SmtpServer/IO/ByteArrayStream.cs b/Src/SmtpServer/IO/ByteArrayStream.cs deleted file mode 100644 index 2fc9d03..0000000 --- a/Src/SmtpServer/IO/ByteArrayStream.cs +++ /dev/null @@ -1,163 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.IO; -//using System.Linq; - -//namespace SmtpServer.IO -//{ -// internal sealed class ByteArrayStream : Stream -// { -// readonly IReadOnlyList> _segments; -// readonly int _length; -// int _index = 0; -// int _position = 0; - -// /// -// /// Constructor. -// /// -// /// The list of array segments to read from. -// internal ByteArrayStream(IReadOnlyList> segments) -// { -// _segments = segments; -// _length = segments.Sum(segment => segment.Count); -// } - -// /// -// /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. -// /// -// /// An I/O error occurs. -// public override void Flush() -// { -// throw new NotImplementedException(); -// } - -// /// -// /// When overridden in a derived class, sets the position within the current stream. -// /// -// /// A byte offset relative to the parameter. -// /// A value of type indicating the reference point used to obtain the new position. -// /// The new position within the current stream. -// public override long Seek(long offset, SeekOrigin origin) -// { -// throw new NotImplementedException(); -// } - -// /// -// /// When overridden in a derived class, sets the length of the current stream. -// /// -// /// The desired length of the current stream in bytes. -// public override void SetLength(long value) -// { -// throw new NotImplementedException(); -// } - -// /// -// /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. -// /// -// /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. -// /// The zero-based byte offset in at which to begin storing the data read from the current stream. -// /// The maximum number of bytes to be read from the current stream. -// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. -// public override int Read(byte[] buffer, int offset, int count) -// { -// var remaining = count; - -// while (remaining > 0 && EnsureDataIsAvailable()) -// { -// var length = Math.Min(_segments[_index].Count - _position, remaining); - -// Buffer.BlockCopy(_segments[_index].Array, _segments[_index].Offset + _position, buffer, offset + count - remaining, length); - -// _position += length; -// remaining -= length; -// } - -// return count - remaining; -// } - -// /// -// /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. -// /// -// /// An array of bytes. This method copies bytes from to the current stream. -// /// The zero-based byte offset in at which to begin copying bytes to the current stream. -// /// The number of bytes to be written to the current stream. -// public override void Write(byte[] buffer, int offset, int count) -// { -// throw new NotImplementedException(); -// } - -// /// -// /// Ensure that data is available for the operation. -// /// -// /// true if there is data available, false if not. -// bool EnsureDataIsAvailable() -// { -// if (_index < _segments.Count && _position >= _segments[_index].Count) -// { -// _index++; -// _position = 0; -// } - -// return _index < _segments.Count; -// } - -// /// -// /// When overridden in a derived class, gets a value indicating whether the current stream supports reading. -// /// -// /// true if the stream supports reading; otherwise, false. -// public override bool CanRead => true; - -// /// -// /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking. -// /// -// /// true if the stream supports seeking; otherwise, false. -// public override bool CanSeek => true; - -// /// -// /// When overridden in a derived class, gets a value indicating whether the current stream supports writing. -// /// -// /// true if the stream supports writing; otherwise, false. -// public override bool CanWrite => false; - -// /// -// /// When overridden in a derived class, gets the length in bytes of the stream. -// /// -// /// A long value representing the length of the stream in bytes. -// public override long Length => _length; - -// /// -// /// When overridden in a derived class, gets or sets the position within the current stream. -// /// -// /// The current position within the stream. -// public override long Position -// { -// get -// { -// var position = 0; - -// for (var i = 0; i < _index; i++) -// { -// position += _segments[i].Count; -// } - -// return position + _position; -// } -// set -// { -// var position = (int)value; - -// for (_index = 0; _index < _segments.Count; _index++) -// { -// if (position < _segments[_index].Count) -// { -// break; -// } - -// position -= _segments[_index].Count; -// } - -// _position = position; -// } -// } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/IO/INetworkClient.cs b/Src/SmtpServer/IO/INetworkClient.cs deleted file mode 100644 index bbd6666..0000000 --- a/Src/SmtpServer/IO/INetworkClient.cs +++ /dev/null @@ -1,340 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.IO.Pipelines; -//using System.Linq; -//using System.Text; -//using System.Threading; -//using System.Threading.Tasks; -//using SmtpServer.Protocol; - -//namespace SmtpServer.IO -//{ -// //public interface INetworkClient : IDisposable -// //{ -// // /// -// // /// Returns a series a buffer segments until the continuation predicate indicates that the method should complete. -// // /// -// // /// The predicate to apply to the byte to determine if the function should continue reading. -// // /// The number of bytes to consume. -// // /// The cancellation token. -// // /// The list of buffers that contain the bytes matching while the predicate was true. -// // Task>> ReadAsync(Func @continue, long count = Int64.MaxValue, CancellationToken cancellationToken = default); - -// // /// -// // /// Write a list of byte array segments. -// // /// -// // /// The list of array segment buffers to write. -// // /// The cancellation token. -// // /// A task that asynchronously performs the operation. -// // Task WriteAsync(IReadOnlyList> buffers, CancellationToken cancellationToken = default); - -// // /// -// // /// Flush the write buffers to the stream. -// // /// -// // /// The cancellation token. -// // /// A task that represents the asynchronous flush operation. -// // Task FlushAsync(CancellationToken cancellationToken = default); - -// // /// -// // /// Returns the underlying Network stream instance. -// // /// -// // INetworkStream Stream { get; } -// //} - -// //public static class NetworkClientExtensions -// //{ -// // /// -// // /// Returns a continuous segment of bytes until the given sequence is reached. -// // /// -// // /// The byte stream to perform the operation on. -// // /// The sequence to match to enable the read operation to complete. -// // /// The cancellation token. -// // /// The array segment that defines a continuous segment of characters that have matched the predicate. -// // public static async Task>> ReadUntilAsync(this INetworkClient client, byte[] sequence, CancellationToken cancellationToken) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // //var found = 0; -// // //return await client.ReadAsync(current => -// // //{ -// // // found = current == sequence[found] -// // // ? found + 1 -// // // : current == sequence[0] ? 1 : 0; - -// // // return found < sequence.Length; -// // //}, -// // //cancellationToken: cancellationToken).ConfigureAwait(false); - -// // throw new NotImplementedException(); -// // } - -// // /// -// // /// Read a line from the byte stream. -// // /// -// // /// The stream to read a line from. -// // /// The cancellation token. -// // /// The string that was read from the stream. -// // public static async Task>> ReadLineAsync(this INetworkClient client, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // return Trim(await client.ReadUntilAsync(new byte[] { 13, 10 }, cancellationToken).ConfigureAwait(false), new byte[] { 13, 10 }); -// // } - -// // /// -// // /// Read a line from the byte stream. -// // /// -// // /// The stream to read a line from. -// // /// The encoding to use when converting to a string representation. -// // /// The cancellation token. -// // /// The string that was read from the stream. -// // public static async Task ReadLineAsync(this INetworkClient client, Encoding encoding, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // var blocks = await client.ReadLineAsync(cancellationToken).ConfigureAwait(false); - -// // return blocks.Count == 0 -// // ? null -// // : encoding.GetString(blocks.SelectMany(block => block).ToArray()); -// // } - -// // /// -// // /// Writes a byte array to the underlying client stream. -// // /// -// // /// The stream to write the line to. -// // /// The byte array buffer to write to the client stream. -// // /// The cancellation token. -// // /// A task which asynchronously performs the operation. -// // public static Task WriteAsync(this INetworkClient client, byte[] buffer, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // //return client.WriteAsync(new [] { new ArraySegment(buffer) }, cancellationToken); - -// // throw new NotImplementedException(); -// // } - -// // /// -// // /// Writes a line to the client stream. -// // /// -// // /// The stream to write the line to. -// // /// The text to write to the client stream. -// // /// The cancellation token. -// // /// A task which asynchronously performs the operation. -// // public static Task WriteLineAsync(this INetworkClient client, string text, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // return WriteLineAsync(client, text, Encoding.ASCII, cancellationToken); -// // } - -// // /// -// // /// Read a line from the byte stream. -// // /// -// // /// The stream to write the line to. -// // /// The text to write to the client stream. -// // /// The encoding to use when converting the bytes to a text representation. -// // /// The cancellation token. -// // /// A task which asynchronously performs the operation. -// // public static Task WriteLineAsync(this INetworkClient client, string text, Encoding encoding, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // var newLine = new string(new[] {(char) 13, (char) 10}); -// // return client.WriteAsync(encoding.GetBytes(text + newLine), cancellationToken); -// // } - -// // /// -// // /// Read a blank-line delimited block. -// // /// -// // /// The stream to read a line from. -// // /// The cancellation token. -// // /// The buffers that were read until the block was terminated. -// // public static async Task>> ReadBlockAsync(this INetworkClient client, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // var blocks = await client.ReadUntilAsync(new byte[] { 13, 10, 13, 10 }, cancellationToken).ConfigureAwait(false); - -// // return Unstuff(Trim(blocks, new byte[] { 13, 10, 13, 10 })).ToList(); -// // } - -// // /// -// // /// Read a dot terminated block. -// // /// -// // /// The stream to read a line from. -// // /// The cancellation token. -// // /// The buffers that were read until the block was terminated. -// // public static async Task>> ReadDotBlockAsync(this INetworkClient client, CancellationToken cancellationToken = default) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // var blocks = await client.ReadUntilAsync(new byte[] { 13, 10, 46, 13, 10 }, cancellationToken).ConfigureAwait(false); - -// // return Unstuff(Trim(blocks, new byte[] { 13, 10, 46, 13, 10 })).ToList(); -// // } - -// // /// -// // /// Trim a given number of bytes from the end. -// // /// -// // /// The list of segments to trim the sequence from. -// // /// The number of bytes to remove from the end. -// // /// The list of segments that have been trimmed by the given number of bytes. -// // static IReadOnlyList> Trim(IReadOnlyList> segments, int count) -// // { -// // var list = new List>(segments); - -// // var remaining = count; -// // for (var i = list.Count - 1; i >= 0 && remaining > 0; i--) -// // { -// // count = Math.Min(remaining, list[i].Count); - -// // if (count == list[i].Count) -// // { -// // list.RemoveAt(i); -// // } -// // else -// // { -// // list[i] = Trim(list[i], count); -// // } - -// // remaining -= count; -// // } - -// // return list; -// // } - -// // /// -// // /// Trim a given number of bytes from a segment. -// // /// -// // /// The segment to truncate. -// // /// The number of bytes to trim. -// // /// The segment that represents the original segment with the given number of bytes trimmed. -// // static ArraySegment Trim(ArraySegment segment, int count) -// // { -// // return new ArraySegment(segment.Array, segment.Offset, segment.Count - count); -// // } - -// // /// -// // /// Trim a given sequence from the end of the segment list. -// // /// -// // /// The list of segments to trim the sequence from. -// // /// The sequence to trim from the end of the block. -// // /// The list of segments that have been trimmed and have the sequence removed. -// // static IReadOnlyList> Trim(IReadOnlyList> segments, byte[] sequence) -// // { -// // if (EndsWith(segments, sequence)) -// // { -// // return Trim(segments, sequence.Length); -// // } - -// // return segments; -// // } - -// // /// -// // /// Returns a value indicating whether or not the list of segments end with the given sequence of bytes. -// // /// -// // /// The segments to test the byte sequence against. -// // /// The sequence of bytes to test at the end of the segments. -// // /// true if the segments end with the given sequence, false if not. -// // static bool EndsWith(IReadOnlyList> segments, byte[] sequence) -// // { -// // var state = sequence.Length - 1; - -// // for (var i = segments.Count - 1; i >= 0 && state >= 0; i--) -// // { -// // for (var j = segments[i].Count - 1; j >= 0 && state >= 0; j--) -// // { -// // if (segments[i].Array[segments[i].Offset + j] != sequence[state--]) -// // { -// // return false; -// // } -// // } -// // } - -// // return state < 0; -// // } - -// // /// -// // /// Unstuff the Dot Stuffing that can appear within a stream. -// // /// -// // /// The list of segments to remove the dot-stuffing from. -// // /// The list of segments that have the dot-stuffing removed. -// // static IEnumerable> Unstuff(IReadOnlyList> segments) -// // { -// // var sequence = new byte[] { 13, 10, 46, 46 }; -// // var state = 0; - -// // foreach (var segment in segments) -// // { -// // var start = 0; -// // for (var i = 0; i < segment.Count; i++) -// // { -// // if (segment.Array[segment.Offset + i] != sequence[state++]) -// // { -// // state = segment.Array[segment.Offset + i] == sequence[0] ? 1 : 0; -// // continue; -// // } - -// // if (state >= sequence.Length) -// // { -// // yield return new ArraySegment(segment.Array, segment.Offset + start, i - start); - -// // state = segment.Array[segment.Offset + i] == sequence[0] ? 1 : 0; -// // start = i + 1; -// // } -// // } - -// // if (start < segment.Count) -// // { -// // yield return new ArraySegment(segment.Array, segment.Offset + start, segment.Count - start); -// // } -// // } -// // } - -// // /// -// // /// Reply to the client. -// // /// -// // /// The text stream to perform the operation on. -// // /// The response. -// // /// The cancellation token. -// // /// A task which performs the operation. -// // public static async Task ReplyAsync(this INetworkClient client, SmtpResponse response, CancellationToken cancellationToken) -// // { -// // if (client == null) -// // { -// // throw new ArgumentNullException(nameof(client)); -// // } - -// // //await client.WriteLineAsync($"{(int)response.ReplyCode} {response.Message}", cancellationToken).ConfigureAwait(false); -// // //await client.FlushAsync(cancellationToken).ConfigureAwait(false); - -// // throw new NotImplementedException(); -// // } -// //} -//} \ No newline at end of file diff --git a/Src/SmtpServer/IO/INetworkPipe.cs b/Src/SmtpServer/IO/INetworkPipe.cs deleted file mode 100644 index f26ee76..0000000 --- a/Src/SmtpServer/IO/INetworkPipe.cs +++ /dev/null @@ -1,162 +0,0 @@ -//using System; -//using System.Buffers; -//using System.IO.Pipelines; -//using System.Security.Authentication; -//using System.Security.Cryptography.X509Certificates; -//using System.Text; -//using System.Threading; -//using System.Threading.Tasks; -//using SmtpServer.Protocol; - -//namespace SmtpServer.IO -//{ -// public interface INetworkPipe : IDuplexPipe, IDisposable -// { -// /// -// /// Upgrade to a secure pipeline. -// /// -// /// The X509Certificate used to authenticate the server. -// /// The value that represents the protocol used for authentication. -// /// The cancellation token. -// /// A task that asynchronously performs the operation. -// Task UpgradeAsync(X509Certificate certificate, SslProtocols protocols, CancellationToken cancellationToken = default); - -// /// -// /// Returns a value indicating whether or not the current pipeline is secure. -// /// -// bool IsSecure { get; } -// } - - -// //public static class NetworkPipeExtensions -// //{ -// // //public static async Task ReadLineAsync(this INetworkPipe pipe, Func, T> func, CancellationToken cancellationToken) -// // //{ -// // // if (pipe == null) -// // // { -// // // throw new ArgumentNullException(nameof(pipe)); -// // // } - -// // // HERE: probably still INetworkClient but it has a Pipe? or an INetworkPipe instead? or ISecureDuplexPipe?? - -// // // while (true) -// // // { -// // // var read = await pipe.Input.ReadAsync(cancellationToken); - -// // // if (read.IsCanceled) -// // // { -// // // // TODO: throw exception here -// // // throw new Exception("what type??"); -// // // } - -// // // var buffer = read.Buffer; - -// // // if (TryReadLine(buffer, out var position)) -// // // { -// // // // ReSharper disable once PossibleInvalidOperationException -// // // var result = func(buffer.Slice(buffer.Start, position.Value)); - -// // // pipe.Input.AdvanceTo(position.Value); - -// // // return result; -// // // } - -// // // pipe.Input.AdvanceTo(buffer.Start, buffer.End); - -// // // if (read.IsCompleted) -// // // { -// // // // TODO: throw exception here -// // // throw new Exception("what type??"); -// // // } -// // // } - -// // // static bool TryReadLine(ReadOnlySequence buffer, out SequencePosition? position) -// // // { -// // // // ReSharper disable once InconsistentNaming -// // // const byte CR = 13; -// // // // ReSharper disable once InconsistentNaming -// // // const byte LF = 10; - -// // // position = buffer.Find(new[] { CR, LF }); - -// // // return position != null; -// // // } -// // //} - -// // /// -// // /// Write a line of text to the pipe. -// // /// -// // /// The pipe to perform the operation on. -// // /// The text to write to the writer. -// // public static void WriteLine(this INetworkPipe pipe, string text) -// // { -// // if (pipe == null) -// // { -// // throw new ArgumentNullException(nameof(pipe)); -// // } - -// // WriteLine(pipe, Encoding.ASCII, text); -// // } - -// // /// -// // /// Write a line of text to the writer. -// // /// -// // /// The pipe to perform the operation on. -// // /// The encoding to use for the text. -// // /// The text to write to the writer. -// // static unsafe void WriteLine(this INetworkPipe pipe, Encoding encoding, string text) -// // { -// // if (pipe == null) -// // { -// // throw new ArgumentNullException(nameof(pipe)); -// // } - -// // fixed (char* ptr = text) -// // { -// // var count = encoding.GetByteCount(ptr, text.Length); - -// // fixed (byte* b = pipe.Output.GetSpan(count + 2)) -// // { -// // encoding.GetBytes(ptr, text.Length, b, count); - -// // b[count + 0] = 13; -// // b[count + 1] = 10; -// // } - -// // pipe.Output.Advance(count + 2); -// // } -// // } - -// // /// -// // /// Flush the output of the pipe. -// // /// -// // /// The pipe to perform the operation on. -// // /// The cancellation token. -// // /// A value indicating whether any more data should be written. -// // public static async ValueTask FlushAsync(this INetworkPipe pipe, CancellationToken cancellationToken = default) -// // { -// // var flush = await pipe.Output.FlushAsync(cancellationToken); - -// // return flush.IsCanceled == false && flush.IsCompleted == false; -// // } - -// // /// -// // /// Write a reply to the client. -// // /// -// // /// The pipe to perform the operation on. -// // /// The response to write. -// // /// The cancellation token. -// // /// A task which performs the operation. -// // public static ValueTask ReplyAsync(this INetworkPipe pipe, SmtpResponse response, CancellationToken cancellationToken) -// // { -// // if (pipe == null) -// // { -// // throw new ArgumentNullException(nameof(pipe)); -// // } - -// // pipe.WriteLine($"{(int)response.ReplyCode} {response.Message}"); - -// // return pipe.FlushAsync(cancellationToken); -// // } -// //} -//} \ No newline at end of file diff --git a/Src/SmtpServer/IO/INetworkStream.cs b/Src/SmtpServer/IO/INetworkStream.cs deleted file mode 100644 index 61cff32..0000000 --- a/Src/SmtpServer/IO/INetworkStream.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -namespace SmtpServer.IO -{ - public interface INetworkStream : IDisposable - { - /// - /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests. - /// - /// The buffer to write data from. - /// The zero-based byte offset in buffer from which to begin copying bytes to the stream. - /// The maximum number of bytes to write. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous write operation. - Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); - - /// - /// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests. - /// - /// The buffer to write the data into. - /// The byte offset in buffer at which to begin writing data from the stream. - /// The maximum number of bytes to read. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous read operation. The value of the TResult parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached. - Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); - - /// - /// Flush the write buffers to the stream. - /// - /// The cancellation token. - /// A task that represents the asynchronous flush operation. - Task FlushAsync(CancellationToken cancellationToken = default); - - /// - /// Upgrade to a secure stream. - /// - /// The X509Certificate used to authenticate the server. - /// The value that represents the protocol used for authentication. - /// The cancellation token. - /// A task that asynchronously performs the operation. - Task UpgradeAsync(X509Certificate certificate, SslProtocols protocols, CancellationToken cancellationToken = default); - - /// - /// Returns a value indicating whether or not the current client is secure. - /// - bool IsSecure { get; } - } -} \ No newline at end of file diff --git a/Src/SmtpServer/IO/NetworkClient.cs b/Src/SmtpServer/IO/NetworkClient.cs deleted file mode 100644 index c4079eb..0000000 --- a/Src/SmtpServer/IO/NetworkClient.cs +++ /dev/null @@ -1,154 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Threading; -//using System.Threading.Tasks; - -//namespace SmtpServer.IO -//{ -// public sealed class NetworkClient : INetworkClient -// { -// readonly int _bufferLength; -// readonly INetworkStream _stream; -// byte[] _buffer; -// int _bytesRead = -1; -// int _index; - -// /// -// /// Constructor. -// /// -// /// The stream to return the tokens from. -// /// The buffer length to read. -// internal NetworkClient(INetworkStream stream, int bufferLength) -// { -// _stream = stream; -// _bufferLength = bufferLength; -// } - -// /// -// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. -// /// -// public void Dispose() -// { -// _stream.Dispose(); -// } - -// /// -// /// Returns a series a buffer segments until the continuation predicate indicates that the method should complete. -// /// -// /// The predicate to apply to the byte to determine if the function should continue reading. -// /// The number of bytes to consume. -// /// The cancellation token. -// /// The list of buffers that contain the bytes matching while the predicate was true. -// public async Task>> ReadAsync(Func @continue, long count, CancellationToken cancellationToken = default) -// { -// if (await ReadBufferAsync(cancellationToken).ConfigureAwait(false) == false) -// { -// return new List>(); -// } - -// if (TryConsume(@continue, ref count, out var segment) == false) -// { -// return new List> { segment }; -// } - -// var segments = new List> { segment }; - -// while (_index >= _bytesRead && count > 0) -// { -// cancellationToken.ThrowIfCancellationRequested(); - -// if (await ReadBufferAsync(cancellationToken).ConfigureAwait(false) == false) -// { -// return segments; -// } - -// if (TryConsume(@continue, ref count, out segment) == false) -// { -// segments.Add(segment); -// return segments; -// } - -// segments.Add(segment); -// } - -// return segments; -// } - -// /// -// /// Write a list of byte array segments. -// /// -// /// The list of array segment buffers to write. -// /// The cancellation token. -// /// A task that asynchronously performs the operation. -// public async Task WriteAsync(IReadOnlyList> buffers, CancellationToken cancellationToken = default) -// { -// foreach (var buffer in buffers) -// { -// await _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken).ConfigureAwait(false); - -// cancellationToken.ThrowIfCancellationRequested(); -// } -// } - -// /// -// /// Flush the write buffers to the stream. -// /// -// /// The cancellation token. -// /// A task that represents the asynchronous flush operation. -// public Task FlushAsync(CancellationToken cancellationToken = default) -// { -// return _stream.FlushAsync(cancellationToken); -// } - -// /// -// /// Ensure that the buffer is full for a read operation. -// /// -// /// The cancellation token. -// /// Returns a value indicating whether there was no more data to fill the buffer. -// async Task ReadBufferAsync(CancellationToken cancellationToken) -// { -// cancellationToken.ThrowIfCancellationRequested(); - -// if (_index >= _bytesRead) -// { -// _index = 0; -// _buffer = new byte[_bufferLength]; -// _bytesRead = await _stream.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken).ConfigureAwait(false); -// } - -// return _bytesRead > 0; -// } - -// /// -// /// Consumes the bytes from the buffer until the continuation function indicates that it should complete. -// /// -// /// The continuation function to determine whether the consume operation should stop. -// /// The limit to the number of bytes to read. -// /// The buffer that contains the data that was consumed. -// /// true if the operation should continue reading, false if not. -// bool TryConsume(Func @continue, ref long limit, out ArraySegment buffer) -// { -// var start = _index; - -// var current = _buffer[_index]; -// while (limit-- > 0 && ++_index < _bytesRead) -// { -// if (@continue(current) == false) -// { -// buffer = new ArraySegment(_buffer, start, _index - start); -// return false; -// } - -// current = _buffer[_index]; -// } - -// buffer = new ArraySegment(_buffer, start, _index - start); -// return @continue(current); -// } - -// /// -// /// Returns the underlying Network stream instance. -// /// -// public INetworkStream Stream => _stream; -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/IO/NetworkStream.cs b/Src/SmtpServer/IO/NetworkStream.cs deleted file mode 100644 index 7e9f906..0000000 --- a/Src/SmtpServer/IO/NetworkStream.cs +++ /dev/null @@ -1,111 +0,0 @@ -//using System; -//using System.IO; -//using System.Net.Security; -//using System.Security.Authentication; -//using System.Security.Cryptography.X509Certificates; -//using System.Threading; -//using System.Threading.Tasks; - -//namespace SmtpServer.IO -//{ -// internal sealed class NetworkStream : INetworkStream -// { -// readonly Action _disposeAction; -// Stream _stream; -// bool _disposed; - -// /// -// /// Constructor. -// /// -// /// The underlying stream to use. -// /// The action to execute when the stream has been disposed. -// internal NetworkStream(Stream stream, Action disposeAction) -// { -// _stream = stream; -// _disposeAction = disposeAction; -// } - -// /// -// /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests. -// /// -// /// The buffer to write data from. -// /// The zero-based byte offset in buffer from which to begin copying bytes to the stream. -// /// The maximum number of bytes to write. -// /// The token to monitor for cancellation requests. The default value is . -// /// A task that represents the asynchronous write operation. -// public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) -// { -// return _stream.WriteAsync(buffer, offset, count, cancellationToken); -// } - -// /// -// /// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests. -// /// -// /// The buffer to write the data into. -// /// The byte offset in buffer at which to begin writing data from the stream. -// /// The maximum number of bytes to read. -// /// The token to monitor for cancellation requests. The default value is . -// /// A task that represents the asynchronous read operation. The value of the TResult parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached. -// public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) -// { -// return _stream.ReadAsync(buffer, offset, count, cancellationToken); -// } - -// /// -// /// Flush the write buffers to the stream. -// /// -// /// The cancellation token. -// /// A task that represents the asynchronous flush operation. -// public Task FlushAsync(CancellationToken cancellationToken = default) -// { -// return _stream.FlushAsync(cancellationToken); -// } - -// /// -// /// Upgrade to a secure stream. -// /// -// /// The X509Certificate used to authenticate the server. -// /// The value that represents the protocol used for authentication. -// /// The cancellation token. -// /// A task that asynchronously performs the operation. -// public async Task UpgradeAsync(X509Certificate certificate, SslProtocols protocols, CancellationToken cancellationToken = default) -// { -// var stream = new SslStream(_stream, true); - -// await stream.AuthenticateAsServerAsync(certificate, false, protocols, true).ConfigureAwait(false); - -// _stream = stream; -// } - -// /// -// /// Releases the unmanaged resources used by the stream and optionally releases the managed resources. -// /// -// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. -// void Dispose(bool disposing) -// { -// if (_disposed == false) -// { -// if (disposing) -// { -// _disposeAction(); -// _stream = null; -// } - -// _disposed = true; -// } -// } - -// /// -// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. -// /// -// public void Dispose() -// { -// Dispose(true); -// } - -// /// -// /// Returns a value indicating whether or not the current client is secure. -// /// -// public bool IsSecure => _stream is SslStream; -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/IO/PipeReaderExtensions.cs b/Src/SmtpServer/IO/PipeReaderExtensions.cs index 376472c..937fc0c 100644 --- a/Src/SmtpServer/IO/PipeReaderExtensions.cs +++ b/Src/SmtpServer/IO/PipeReaderExtensions.cs @@ -1,8 +1,10 @@ using System; using System.Buffers; using System.IO.Pipelines; +using System.Text; using System.Threading; using System.Threading.Tasks; +using SmtpServer.Text; namespace SmtpServer.IO { @@ -11,43 +13,7 @@ internal static class PipeReaderExtensions // ReSharper disable once InconsistentNaming static readonly byte[] CRLF = { 13, 10 }; static readonly byte[] DotBlock = { 13, 10, 46, 13, 10 }; - - ///// - ///// Read from the reader until the sequence is found. - ///// - ///// The element type of the return value. - ///// The reader to read from. - ///// The sequence to find to terminate the read operation. - ///// The function to convert the buffer that was read into the output. - ///// The cancellation token. - ///// The value that was read from the buffer. - //static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func, T> func, CancellationToken cancellationToken) - //{ - // if (reader == null) - // { - // throw new ArgumentNullException(nameof(reader)); - // } - - // var read = await reader.ReadAsync(cancellationToken); - - // while (read.IsCanceled == false && read.IsCompleted == false && read.Buffer.IsEmpty == false) - // { - // if (read.Buffer.TryFind(sequence, out var head, out var tail)) - // { - // var result = func(read.Buffer.Slice(read.Buffer.Start, head)); - - // reader.AdvanceTo(tail); - - // return result; - // } - - // reader.AdvanceTo(read.Buffer.Start, read.Buffer.End); - - // read = await reader.ReadAsync(cancellationToken); - // } - - // return default; - //} + static readonly byte[] DotBlockStuffing = { 13, 10, 46, 46 }; /// /// Read from the reader until the sequence is found. @@ -101,6 +67,50 @@ internal static ValueTask ReadLineAsync(this PipeReader reader, Func + /// Reads a line from the reader. + /// + /// The reader to read from. + /// The cancellation token. + /// A task that can be used to wait on the operation on complete. + internal static ValueTask ReadLineAsync(this PipeReader reader, CancellationToken cancellationToken) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + return reader.ReadLineAsync(Encoding.ASCII, cancellationToken); + } + + /// + /// Reads a line from the reader. + /// + /// The reader to read from. + /// The encoding to use when converting the input. + /// The cancellation token. + /// A task that can be used to wait on the operation on complete. + internal static async ValueTask ReadLineAsync(this PipeReader reader, Encoding encoding, CancellationToken cancellationToken) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + var text = string.Empty; + + await reader.ReadLineAsync( + buffer => + { + text = StringUtil.Create(buffer, encoding); + + return Task.CompletedTask; + }, + cancellationToken); + + return text; + } + /// /// Reads a line from the reader. /// @@ -115,15 +125,35 @@ internal static async ValueTask ReadDotBlockAsync(this PipeReader reader, Func + { + buffer = Unstuff(buffer); - try - { - await ReadUntilAsync(reader, DotBlock, func, cancellationToken); - } - catch (Exception e) + return func(buffer); + }, + cancellationToken); + + static ReadOnlySequence Unstuff(ReadOnlySequence buffer) { - Console.WriteLine(e); + var head = buffer.GetPosition(0); + var start = head; + + var segment = new ByteArraySegment(new ReadOnlyMemory()); + + while (buffer.TryFind(DotBlockStuffing, ref head, out var tail)) + { + var slice = buffer.Slice(start, head); + + segment.Append(ref slice); + + start = tail; + head = tail; + } + + return buffer; } } } diff --git a/Src/SmtpServer/Mail/IMessage.cs b/Src/SmtpServer/Mail/IMessage.cs deleted file mode 100644 index 0581a7d..0000000 --- a/Src/SmtpServer/Mail/IMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -//namespace SmtpServer.Mail -//{ -// public interface IMessage -// { -// /// -// /// Returns the message type. -// /// -// MessageType MessageType { get; } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/IMessageSerializer.cs b/Src/SmtpServer/Mail/IMessageSerializer.cs deleted file mode 100644 index 5c05c04..0000000 --- a/Src/SmtpServer/Mail/IMessageSerializer.cs +++ /dev/null @@ -1,17 +0,0 @@ -//using System.Threading; -//using System.Threading.Tasks; -//using SmtpServer.IO; - -//namespace SmtpServer.Mail -//{ -// public interface IMessageSerializer -// { -// ///// -// ///// Deserialize a message from the stream. -// ///// -// ///// The network client to deserialize the message from. -// ///// The cancellation token. -// ///// The message that was deserialized. -// //Task DeserializeAsync(INetworkClient networkClient, CancellationToken cancellationToken = default(CancellationToken)); -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/ITextMessage.cs b/Src/SmtpServer/Mail/ITextMessage.cs deleted file mode 100644 index 50f3b7e..0000000 --- a/Src/SmtpServer/Mail/ITextMessage.cs +++ /dev/null @@ -1,12 +0,0 @@ -//using System.IO; - -//namespace SmtpServer.Mail -//{ -// public interface ITextMessage : IMessage -// { -// /// -// /// The message content. -// /// -// Stream Content { get; } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/MessageSerializerFactory.cs b/Src/SmtpServer/Mail/MessageSerializerFactory.cs deleted file mode 100644 index 09703a0..0000000 --- a/Src/SmtpServer/Mail/MessageSerializerFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -//namespace SmtpServer.Mail -//{ -// public sealed class MessageSerializerFactory -// { -// /// -// /// Create an instance of a message serializer. -// /// -// /// An instance of a message serializer. -// public IMessageSerializer CreateInstance() -// { -// return new TextMessageSerializer(); -// } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/MessageType.cs b/Src/SmtpServer/Mail/MessageType.cs deleted file mode 100644 index 7cd7b27..0000000 --- a/Src/SmtpServer/Mail/MessageType.cs +++ /dev/null @@ -1,10 +0,0 @@ -//namespace SmtpServer.Mail -//{ -// public enum MessageType -// { -// /// -// /// A plain text message that is ended with a dot-block annotation. -// /// -// Text -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/TextMessage.cs b/Src/SmtpServer/Mail/TextMessage.cs deleted file mode 100644 index 618722d..0000000 --- a/Src/SmtpServer/Mail/TextMessage.cs +++ /dev/null @@ -1,26 +0,0 @@ -//using System.IO; - -//namespace SmtpServer.Mail -//{ -// internal sealed class TextMessage : ITextMessage -// { -// /// -// /// Constructor. -// /// -// /// The content for the message. -// public TextMessage(Stream content) -// { -// Content = content; -// } - -// /// -// /// Returns the message type. -// /// -// public MessageType MessageType => MessageType.Text; - -// /// -// /// The message content. -// /// -// public Stream Content { get; } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Mail/TextMessageSerializer.cs b/Src/SmtpServer/Mail/TextMessageSerializer.cs deleted file mode 100644 index 9328ea3..0000000 --- a/Src/SmtpServer/Mail/TextMessageSerializer.cs +++ /dev/null @@ -1,25 +0,0 @@ -//using System; -//using System.Threading; -//using System.Threading.Tasks; -//using SmtpServer.IO; - -//namespace SmtpServer.Mail -//{ -// internal sealed class TextMessageSerializer : IMessageSerializer -// { -// ///// -// ///// Deserialize a message from the stream. -// ///// -// ///// The network client to deserialize the message from. -// ///// The cancellation token. -// ///// The message that was deserialized. -// //public async Task DeserializeAsync(INetworkClient networkClient, CancellationToken cancellationToken = default(CancellationToken)) -// //{ -// // //var stream = new ByteArrayStream(await networkClient.ReadDotBlockAsync(cancellationToken).ConfigureAwait(false)); - -// // //return new TextMessage(stream); - -// // throw new NotImplementedException(); -// //} -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Protocol/AuthCommand.cs b/Src/SmtpServer/Protocol/AuthCommand.cs index e475381..7dfd5bb 100644 --- a/Src/SmtpServer/Protocol/AuthCommand.cs +++ b/Src/SmtpServer/Protocol/AuthCommand.cs @@ -1,4 +1,5 @@ using System; +using System.IO.Pipelines; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -11,9 +12,9 @@ namespace SmtpServer.Protocol { public class AuthCommand : SmtpCommand { - readonly IUserAuthenticatorFactory _userAuthenticatorFactory; public const string Command = "AUTH"; + readonly IUserAuthenticatorFactory _userAuthenticatorFactory; string _user; string _password; @@ -42,139 +43,135 @@ internal override async Task ExecuteAsync(SmtpSessionContext context, Canc { context.Authentication = AuthenticationContext.Unauthenticated; - //switch (Method) - //{ - // case AuthenticationMethod.Plain: - // if (await TryPlainAsync(context, cancellationToken).ConfigureAwait(false) == false) - // { - // await context.NetworkPipe.ReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); - // return false; - // } - // break; - - // case AuthenticationMethod.Login: - // if (await TryLoginAsync(context, cancellationToken).ConfigureAwait(false) == false) - // { - // await context.NetworkPipe.ReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); - // return false; - // } - // break; - //} - - //using (var container = new DisposableContainer(Options.UserAuthenticatorFactory.CreateInstance(context))) - //{ - // if (await container.Instance.AuthenticateAsync(context, _user, _password, cancellationToken).ConfigureAwait(false) == false) - // { - // var remaining = context.ServerOptions.MaxAuthenticationAttempts - ++context.AuthenticationAttempts; - // var response = new SmtpResponse(SmtpReplyCode.AuthenticationFailed, $"authentication failed, {remaining} attempt(s) remaining."); - - // await context.NetworkPipe.ReplyAsync(response, cancellationToken).ConfigureAwait(false); - - // if (remaining <= 0) - // { - // throw new SmtpResponseException(SmtpResponse.ServiceClosingTransmissionChannel, true); - // } - - // return false; - // } - //} - - //await context.NetworkPipe.ReplyAsync(SmtpResponse.AuthenticationSuccessful, cancellationToken).ConfigureAwait(false); - - //context.Authentication = new AuthenticationContext(_user); - //context.RaiseSessionAuthenticated(); - - //return true; - - throw new NotImplementedException(); + switch (Method) + { + case AuthenticationMethod.Plain: + if (await TryPlainAsync(context, cancellationToken).ConfigureAwait(false) == false) + { + await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); + return false; + } + break; + + case AuthenticationMethod.Login: + if (await TryLoginAsync(context, cancellationToken).ConfigureAwait(false) == false) + { + await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); + return false; + } + break; + } + + using (var container = new DisposableContainer(_userAuthenticatorFactory.CreateInstance(context))) + { + if (await container.Instance.AuthenticateAsync(context, _user, _password, cancellationToken).ConfigureAwait(false) == false) + { + var remaining = context.ServerOptions.MaxAuthenticationAttempts - ++context.AuthenticationAttempts; + var response = new SmtpResponse(SmtpReplyCode.AuthenticationFailed, $"authentication failed, {remaining} attempt(s) remaining."); + + await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false); + + if (remaining <= 0) + { + throw new SmtpResponseException(SmtpResponse.ServiceClosingTransmissionChannel, true); + } + + return false; + } + } + + await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationSuccessful, cancellationToken).ConfigureAwait(false); + + context.Authentication = new AuthenticationContext(_user); + context.RaiseSessionAuthenticated(); + + return true; + } + + /// + /// Attempt a PLAIN login sequence. + /// + /// The execution context to operate on. + /// The cancellation token. + /// true if the PLAIN login sequence worked, false if not. + async Task TryPlainAsync(ISessionContext context, CancellationToken cancellationToken) + { + var authentication = Parameter; + + if (string.IsNullOrWhiteSpace(authentication)) + { + await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, " "), cancellationToken).ConfigureAwait(false); + + authentication = await context.Pipe.Input.ReadLineAsync(Encoding.ASCII, cancellationToken).ConfigureAwait(false); + } + + if (TryExtractFromBase64(authentication) == false) + { + await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); + return false; + } + + return true; + } + + /// + /// Attempt to extract the user name and password combination from a single line base64 encoded string. + /// + /// The base64 encoded string to extract the user name and password from. + /// true if the user name and password were extracted from the base64 encoded string, false if not. + bool TryExtractFromBase64(string base64) + { + var match = Regex.Match(Encoding.UTF8.GetString(Convert.FromBase64String(base64)), "\x0000(?.*)\x0000(?.*)"); + + if (match.Success == false) + { + return false; + } + + _user = match.Groups["user"].Value; + _password = match.Groups["password"].Value; + + return true; } - ///// - ///// Attempt a PLAIN login sequence. - ///// - ///// The execution context to operate on. - ///// The cancellation token. - ///// true if the PLAIN login sequence worked, false if not. - //async Task TryPlainAsync(SmtpSessionContext context, CancellationToken cancellationToken) - //{ - // var authentication = Parameter; - - // if (String.IsNullOrWhiteSpace(authentication)) - // { - // await context.NetworkPipe.ReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, " "), cancellationToken).ConfigureAwait(false); - - // authentication = await context.NetworkPipe.ReadLineAsync(Encoding.ASCII, cancellationToken).ConfigureAwait(false); - // } - - // if (TryExtractFromBase64(authentication) == false) - // { - // await context.NetworkPipe.ReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false); - // return false; - // } - - // return true; - //} - - ///// - ///// Attempt to extract the user name and password combination from a single line base64 encoded string. - ///// - ///// The base64 encoded string to extract the user name and password from. - ///// true if the user name and password were extracted from the base64 encoded string, false if not. - //bool TryExtractFromBase64(string base64) - //{ - // var match = Regex.Match(Encoding.UTF8.GetString(Convert.FromBase64String(base64)), "\x0000(?.*)\x0000(?.*)"); - - // if (match.Success == false) - // { - // return false; - // } - - // _user = match.Groups["user"].Value; - // _password = match.Groups["password"].Value; - - // return true; - //} - - ///// - ///// Attempt a LOGIN login sequence. - ///// - ///// The execution context to operate on. - ///// The cancellation token. - ///// true if the LOGIN login sequence worked, false if not. - //async Task TryLoginAsync(SmtpSessionContext context, CancellationToken cancellationToken) - //{ - // if (String.IsNullOrWhiteSpace(Parameter) == false) - // { - // _user = Encoding.UTF8.GetString(Convert.FromBase64String(Parameter)); - // } - // else - // { - // await context.NetworkPipe.ReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "VXNlcm5hbWU6"), cancellationToken).ConfigureAwait(false); - - // _user = await ReadBase64EncodedLineAsync(context.NetworkPipe, cancellationToken).ConfigureAwait(false); - // } - - // await context.NetworkPipe.ReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "UGFzc3dvcmQ6"), cancellationToken).ConfigureAwait(false); - - // _password = await ReadBase64EncodedLineAsync(context.NetworkPipe, cancellationToken).ConfigureAwait(false); - - // return true; - //} - - ///// - ///// Read a Base64 encoded line. - ///// - ///// The pipe to read from. - ///// The cancellation token. - ///// The decoded Base64 string. - //async Task ReadBase64EncodedLineAsync(INetworkPipe pipe, CancellationToken cancellationToken) - //{ - // var text = await pipe.ReadLineAsync(Encoding.ASCII, cancellationToken).ConfigureAwait(false); - - // return text == null - // ? String.Empty - // : Encoding.UTF8.GetString(Convert.FromBase64String(text)); - //} + /// + /// Attempt a LOGIN login sequence. + /// + /// The execution context to operate on. + /// The cancellation token. + /// true if the LOGIN login sequence worked, false if not. + async Task TryLoginAsync(ISessionContext context, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(Parameter) == false) + { + _user = Encoding.UTF8.GetString(Convert.FromBase64String(Parameter)); + } + else + { + await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "VXNlcm5hbWU6"), cancellationToken).ConfigureAwait(false); + + _user = await ReadBase64EncodedLineAsync(context.Pipe.Input, cancellationToken).ConfigureAwait(false); + } + + await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "UGFzc3dvcmQ6"), cancellationToken).ConfigureAwait(false); + + _password = await ReadBase64EncodedLineAsync(context.Pipe.Input, cancellationToken).ConfigureAwait(false); + + return true; + } + + /// + /// Read a Base64 encoded line. + /// + /// The pipe to read from. + /// The cancellation token. + /// The decoded Base64 string. + static async Task ReadBase64EncodedLineAsync(PipeReader reader, CancellationToken cancellationToken) + { + var text = await reader.ReadLineAsync(cancellationToken); + + return Encoding.UTF8.GetString(Convert.FromBase64String(text)); + } /// /// The authentication method. diff --git a/Src/SmtpServer/Protocol/HeloCommand.cs b/Src/SmtpServer/Protocol/HeloCommand.cs index 4aa0eff..f193720 100644 --- a/Src/SmtpServer/Protocol/HeloCommand.cs +++ b/Src/SmtpServer/Protocol/HeloCommand.cs @@ -8,13 +8,18 @@ public sealed class HeloCommand : SmtpCommand { public const string Command = "HELO"; + readonly string _greeting; + /// /// Constructor. /// /// The domain name. - internal HeloCommand(string domainOrAddress) : base(Command) + /// The greeting text. + internal HeloCommand(string domainOrAddress, string greeting) : base(Command) { DomainOrAddress = domainOrAddress; + + _greeting = greeting; } /// @@ -26,7 +31,7 @@ internal HeloCommand(string domainOrAddress) : base(Command) /// if the current state is to be maintained. internal override async Task ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken) { - var response = new SmtpResponse(SmtpReplyCode.Ok, $"Hello {DomainOrAddress}, haven't we met before?"); + var response = new SmtpResponse(SmtpReplyCode.Ok, _greeting); await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false); diff --git a/Src/SmtpServer/Protocol/MailCommand.cs b/Src/SmtpServer/Protocol/MailCommand.cs index ae30484..d6321da 100644 --- a/Src/SmtpServer/Protocol/MailCommand.cs +++ b/Src/SmtpServer/Protocol/MailCommand.cs @@ -17,7 +17,6 @@ public sealed class MailCommand : SmtpCommand /// /// Constructor. /// - /// The server options. /// The address. /// The list of extended (ESMTP) parameters. /// The Mailbox Filter factory to create Mailbox filters. diff --git a/Src/SmtpServer/Protocol/SmtpCommandFactory.cs b/Src/SmtpServer/Protocol/SmtpCommandFactory.cs index bdcde3a..8b80858 100644 --- a/Src/SmtpServer/Protocol/SmtpCommandFactory.cs +++ b/Src/SmtpServer/Protocol/SmtpCommandFactory.cs @@ -23,7 +23,9 @@ public SmtpCommandFactory(ISmtpServerOptions options) /// The HELO command. public virtual SmtpCommand CreateHelo(string domainOrAddress) { - return new HeloCommand(domainOrAddress); + var greeting = $"{Options.ServerName} Hello {domainOrAddress}, haven't we met before?"; + + return new HeloCommand(domainOrAddress, greeting); } /// diff --git a/Src/SmtpServer/Protocol/SmtpParser.cs b/Src/SmtpServer/Protocol/SmtpParser.cs index 6b46527..5fb9031 100644 --- a/Src/SmtpServer/Protocol/SmtpParser.cs +++ b/Src/SmtpServer/Protocol/SmtpParser.cs @@ -1,1618 +1,1818 @@ -//using System; -//using System.Collections.Generic; -//using System.Globalization; -//using System.Linq; -//using System.Net; -//using SmtpServer.Mail; -//using SmtpServer.Text; - -//namespace SmtpServer.Protocol -//{ -// /// -// /// This class is responsible for parsing the SMTP command arguments according to the ANBF described in -// /// the RFC http://tools.ietf.org/html/rfc5321#section-4.1.2 -// /// -// public class SmtpParser : TokenParser -// { -// #region Tokens - -// static class Tokens -// { -// // ReSharper disable InconsistentNaming -// internal static readonly Token Hyphen = Token.Create('-'); -// internal static readonly Token Colon = Token.Create(':'); -// internal static readonly Token LessThan = Token.Create('<'); -// internal static readonly Token GreaterThan = Token.Create('>'); -// internal static readonly Token Comma = Token.Create(','); -// internal static readonly Token At = Token.Create('@'); -// internal static readonly Token Period = Token.Create('.'); -// internal static readonly Token LeftBracket = Token.Create('['); -// internal static readonly Token RightBracket = Token.Create(']'); -// internal static readonly Token Quote = Token.Create('"'); -// internal static readonly Token Equal = Token.Create('='); -// internal static readonly Token BackSlash = Token.Create('\\'); - -// internal static class Text -// { -// internal static readonly Token From = Token.Create("FROM"); -// internal static readonly Token To = Token.Create("TO"); -// internal static readonly Token IpVersionTag = Token.Create("IPv"); -// internal static readonly Token Unknown = Token.Create("UNKNOWN"); -// internal static readonly Token Tcp = Token.Create("TCP"); -// } - -// internal static class Numbers -// { -// internal static readonly Token Four = Token.Create(TokenKind.Number, "4"); -// internal static readonly Token Six = Token.Create(TokenKind.Number, "6"); -// } -// // ReSharper restore InconsistentNaming -// } - -// #endregion - -// readonly ISmtpServerOptions _options; - -// /// -// /// Constructor. -// /// -// /// The SMTP server options. -// /// The token enumerator to handle the incoming tokens. -// public SmtpParser(ISmtpServerOptions options, ITokenEnumerator enumerator) : base(enumerator) -// { -// _options = options; -// } - -// /// -// /// Make a QUIT command. -// /// -// /// The QUIT command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeQuit(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); - -// if (TryMakeEnd() == false) -// { -// _options.Logger.LogVerbose("QUIT command can not have parameters."); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new QuitCommand(_options); -// return true; -// } - -// /// -// /// Make an NOOP command from the given enumerator. -// /// -// /// The NOOP command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeNoop(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); - -// if (TryMakeEnd() == false) -// { -// _options.Logger.LogVerbose("NOOP command can not have parameters."); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new NoopCommand(_options); -// return true; -// } - -// /// -// /// Make an RSET command from the given enumerator. -// /// -// /// The RSET command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeRset(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); - -// if (TryMakeEnd() == false) -// { -// _options.Logger.LogVerbose("RSET command can not have parameters."); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new RsetCommand(_options); -// return true; -// } - -// /// -// /// Make a HELO command from the given enumerator. -// /// -// /// The HELO command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeDomain(out var domain)) -// { -// command = new HeloCommand(_options, domain); -// return true; -// } - -// // according to RFC5321 the HELO command should only accept the Domain -// // and not the address literal, however some mail clients will send the -// // address literal and there is no harm in accepting it -// if (TryMakeAddressLiteral(out var address)) -// { -// command = new HeloCommand(_options, address); -// return true; -// } - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// /// -// /// Make an EHLO command from the given enumerator. -// /// -// /// The EHLO command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeEhlo(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeDomain(out var domain)) -// { -// command = new EhloCommand(_options, domain); -// return true; -// } - -// if (TryMakeAddressLiteral(out var address)) -// { -// command = new EhloCommand(_options, address); -// return true; -// } - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// /// -// /// Make a MAIL command from the given enumerator. -// /// -// /// The MAIL command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeMail(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (Enumerator.Take() != Tokens.Text.From || Enumerator.Take() != Tokens.Colon) -// { -// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); -// return false; -// } - -// // according to the spec, whitespace isnt allowed here but most servers send it -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeReversePath(out var mailbox) == false) -// { -// _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); - -// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); -// return false; -// } - -// Enumerator.Skip(TokenKind.Space); - -// // match the optional (ESMTP) parameters -// if (TryMakeMailParameters(out var parameters) == false) -// { -// parameters = new Dictionary(); -// } - -// command = new MailCommand(_options, mailbox, parameters); -// return true; -// } - -// /// -// /// Make a RCTP command from the given enumerator. -// /// -// /// The RCTP command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeRcpt(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (Enumerator.Take() != Tokens.Text.To || Enumerator.Take() != Tokens.Colon) -// { -// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); -// return false; -// } - -// // according to the spec, whitespace isnt allowed here anyway -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakePath(out var mailbox) == false) -// { -// _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// // TODO: support optional service extension parameters here - -// command = new RcptCommand(_options, mailbox); -// return true; -// } - -// /// -// /// Support proxy protocol version 1 header for use with HAProxy. -// /// Documented at http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt -// /// -// /// The PROXY command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeProxy(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// // ABNF -// // proxy = "PROXY" space ( unknown-proxy | tcp4-proxy | tcp6-proxy ) -// // unknown-proxy = "UNKNOWN" -// // tcp4-proxy = "TCP4" space ipv4-address-literal space ipv4-address-literal space ip-port-number space ip-port-number -// // tcp6-proxy = "TCP6" space ipv6-address-literal space ipv6-address-literal space ip-port-number space ip-port-number -// // space = " " -// // ip-port = wnum -// // wnum = 1*5DIGIT ; in the range of 0-65535 - -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (TryMake(TryMakeUnknownProxy, out command)) -// { -// return true; -// } - -// if (TryMake(TryMakeTcp4Proxy, out command)) -// { -// return true; -// } - -// return TryMakeTcp6Proxy(out command); -// } - -// /// -// /// Attempt to make the Unknown Proxy command. -// /// -// /// The command that was made. -// /// true if the command was made, false if not. -// bool TryMakeUnknownProxy(out SmtpCommand command) -// { -// command = new ProxyCommand(_options); - -// return Enumerator.Take() == Tokens.Text.Unknown; -// } - -// bool TryMakeTcp4Proxy(out SmtpCommand command) -// { -// command = null; - -// if (Enumerator.Take() != Tokens.Text.Tcp) -// { -// return false; -// } - -// if (Enumerator.Take() != Tokens.Numbers.Four) -// { -// return false; -// } - -// return TryMakeProxyAddresses(TryMakeIPv4AddressLiteral, out command); -// } - -// bool TryMakeTcp6Proxy(out SmtpCommand command) -// { -// command = null; - -// if (Enumerator.Take() != Tokens.Text.Tcp) -// { -// return false; -// } - -// if (Enumerator.Take() != Tokens.Numbers.Six) -// { -// return false; -// } - -// return TryMakeProxyAddresses(TryMakeIPv6Address, out command); -// } - -// bool TryMakeProxyAddresses(TryMakeDelegate tryMakeAddressDelegate, out SmtpCommand command) -// { -// command = null; +using System; +using System.Buffers; +using System.Collections.Generic; +using SmtpServer.Mail; +using SmtpServer.Text; + +namespace SmtpServer.Protocol +{ + public sealed class SmtpParser + { + delegate bool TryMakeDelegate(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse); + + readonly ISmtpCommandFactory _smtpCommandFactory; + + public SmtpParser(ISmtpCommandFactory smtpCommandFactory) + { + _smtpCommandFactory = smtpCommandFactory; + } + + /// + /// Make a command from the buffer. + /// + /// The buffer to read the command from. + /// The command that is defined within the token reader. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMake(ref ReadOnlySequence buffer, out SmtpCommand command, out SmtpResponse errorResponse) + { + return TryMake(buffer, TryMakeEhlo, out command, out errorResponse) + || TryMake(buffer, TryMakeHelo, out command, out errorResponse) + || TryMake(buffer, TryMakeMail, out command, out errorResponse) + || TryMake(buffer, TryMakeRcpt, out command, out errorResponse) + || TryMake(buffer, TryMakeData, out command, out errorResponse) + || TryMake(buffer, TryMakeQuit, out command, out errorResponse) + || TryMake(buffer, TryMakeRset, out command, out errorResponse) + || TryMake(buffer, TryMakeNoop, out command, out errorResponse) + || TryMake(buffer, TryMakeStartTls, out command, out errorResponse) + || TryMake(buffer, TryMakeAuth, out command, out errorResponse) + || TryMake(buffer, TryMakeProxy, out command, out errorResponse); + + static bool TryMake(ReadOnlySequence buffer, TryMakeDelegate tryMakeDelegate, out SmtpCommand command, out SmtpResponse errorResponse) + { + var reader = new TokenReader(buffer); + + return tryMakeDelegate(ref reader, out command, out errorResponse); + } + } + + /// + /// Make a HELO command from the given enumerator. + /// + /// The reader to perform the operation on. + /// The HELO command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeHelo(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (TryMakeHelo(ref reader) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (reader.TryMake(TryMakeDomain, out var domain)) + { + command = _smtpCommandFactory.CreateHelo(StringUtil.Create(domain)); + return true; + } + + // according to RFC5321 the HELO command should only accept the Domain + // and not the address literal, however some mail clients will send the + // address literal and there is no harm in accepting it + if (reader.TryMake(TryMakeAddressLiteral, out var address)) + { + command = _smtpCommandFactory.CreateHelo(StringUtil.Create(address)); + return true; + } + + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + /// + /// Try to make the HELO text sequence. + /// + /// The reader to perform the operation on. + /// true if the HELO text sequence could be made, false if not. + public bool TryMakeHelo(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'H'; + command[1] = 'E'; + command[2] = 'L'; + command[3] = 'O'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make an EHLO command from the given reader. + /// + /// The token reader to parse the command from. + /// The EHLO command that is defined within the token reader. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeEhlo(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (TryMakeEhlo(ref reader) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (reader.TryMake(TryMakeDomain, out var domain)) + { + command = _smtpCommandFactory.CreateEhlo(StringUtil.Create(domain)); + return true; + } + + if (reader.TryMake(TryMakeAddressLiteral, out var address)) + { + command = _smtpCommandFactory.CreateEhlo(StringUtil.Create(address)); + return true; + } + + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + /// + /// Try to make the EHLO text sequence. + /// + /// The reader to perform the operation on. + /// true if the EHLO text sequence could be made, false if not. + public bool TryMakeEhlo(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'E'; + command[1] = 'H'; + command[2] = 'L'; + command[3] = 'O'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a MAIL command from the given enumerator. + /// + /// The token reader to parse the command from. + /// The MAIL command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeMail(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (TryMakeMail(ref reader) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (TryMakeFrom(ref reader) == false || reader.Take().Kind != TokenKind.Colon) + { + errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); + return false; + } + + // according to the spec, whitespace isnt allowed here but most servers send it + reader.Skip(TokenKind.Space); + + if (reader.TryMake(TryMakeReversePath, out IMailbox mailbox) == false) + { + errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); + return false; + } + + reader.Skip(TokenKind.Space); + + // match the optional (ESMTP) parameters + if (reader.TryMake(TryMakeMailParameters, out IReadOnlyDictionary parameters) == false) + { + parameters = new Dictionary(); + } + + command = _smtpCommandFactory.CreateMail(mailbox, parameters); + return true; + } + + /// + /// Try to make the MAIL text sequence. + /// + /// The reader to perform the operation on. + /// true if the MAIL text sequence could be made, false if not. + public bool TryMakeMail(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'M'; + command[1] = 'A'; + command[2] = 'I'; + command[3] = 'L'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make the FROM text sequence. + /// + /// The reader to perform the operation on. + /// true if the FROM text sequence could be made, false if not. + public bool TryMakeFrom(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'F'; + command[1] = 'R'; + command[2] = 'O'; + command[3] = 'M'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a RCTP command from the given reader. + /// + /// The reader to perform the operation on. + /// The RCTP command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeRcpt(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (TryMakeRcpt(ref reader) == false) + { + return false; + } -// Enumerator.Skip(TokenKind.Space); - -// if (tryMakeAddressDelegate(out var sourceAddress) == false) -// { -// return false; -// } - -// Enumerator.Skip(TokenKind.Space); - -// if (tryMakeAddressDelegate(out var destinationAddress) == false) -// { -// return false; -// } - -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeWnum(out var sourcePort) == false) -// { -// return false; -// } - -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeWnum(out var destinationPort) == false) -// { -// return false; -// } - -// command = new ProxyCommand(_options, new IPEndPoint(IPAddress.Parse(sourceAddress), sourcePort), new IPEndPoint(IPAddress.Parse(destinationAddress), destinationPort)); -// return true; -// } - -// bool TryMakeWnum(out int wnum) -// { -// wnum = default; - -// var token = Enumerator.Take(); - -// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out wnum)) -// { -// return wnum >= 0 && wnum <= 65535; -// } - -// return false; -// } - -// /// -// /// Make a DATA command from the given enumerator. -// /// -// /// The DATA command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeData(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeEnd() == false) -// { -// _options.Logger.LogVerbose("DATA command can not have parameters."); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new DataCommand(_options); -// return true; -// } - -// /// -// /// Make an STARTTLS command from the given enumerator. -// /// -// /// The STARTTLS command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeStartTls(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (TryMakeEnd() == false) -// { -// _options.Logger.LogVerbose("STARTTLS command can not have parameters."); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new StartTlsCommand(_options); -// return true; -// } - -// /// -// /// Make an AUTH command from the given enumerator. -// /// -// /// The AUTH command that is defined within the token enumerator. -// /// The error that indicates why the command could not be made. -// /// Returns true if a command could be made, false if not. -// public bool TryMakeAuth(out SmtpCommand command, out SmtpResponse errorResponse) -// { -// command = null; -// errorResponse = null; - -// Enumerator.Take(); -// Enumerator.Skip(TokenKind.Space); - -// if (Enum.TryParse(Enumerator.Take().Text, true, out AuthenticationMethod method) == false) -// { -// _options.Logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// Enumerator.Take(); - -// string parameter = null; -// if (TryMake(TryMakeEnd) == false && TryMakeBase64(out parameter) == false) -// { -// _options.Logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); - -// errorResponse = SmtpResponse.SyntaxError; -// return false; -// } - -// command = new AuthCommand(_options, method, parameter); -// return true; -// } - -// /// -// /// Try to make a reverse path. -// /// -// /// The reverse path that was made, or undefined if it was not made. -// /// true if the reverse path was made, false if not. -// /// "]]> -// public bool TryMakeReversePath(out IMailbox mailbox) -// { -// if (TryMake(TryMakePath, out mailbox)) -// { -// return true; -// } - -// if (Enumerator.Take() != Tokens.LessThan) -// { -// return false; -// } - -// // not valid according to the spec but some senders do it -// Enumerator.Skip(TokenKind.Space); - -// if (Enumerator.Take() != Tokens.GreaterThan) -// { -// return false; -// } - -// mailbox = Mailbox.Empty; - -// return true; -// } - -// /// -// /// Try to make a path. -// /// -// /// The path that was made, or undefined if it was not made. -// /// true if the path was made, false if not. -// /// "]]> -// public bool TryMakePath(out IMailbox mailbox) -// { -// mailbox = Mailbox.Empty; - -// if (Enumerator.Take() != Tokens.LessThan) -// { -// return false; -// } - -// // Note, the at-domain-list must be matched, but also must be ignored -// // http://tools.ietf.org/html/rfc5321#appendix-C -// if (TryMake(TryMakeAtDomainList, out string atDomainList)) -// { -// // if the @domain list was matched then it needs to be followed by a colon -// if (Enumerator.Take() != Tokens.Colon) -// { -// return false; -// } -// } - -// if (TryMake(TryMakeMailbox, out mailbox) == false) -// { -// return false; -// } - -// return Enumerator.Take() == Tokens.GreaterThan; -// } - -// /// -// /// Try to make an @domain list. -// /// -// /// The @domain list that was made, or undefined if it was not made. -// /// true if the @domain list was made, false if not. -// /// -// public bool TryMakeAtDomainList(out string atDomainList) -// { -// if (TryMake(TryMakeAtDomain, out atDomainList) == false) -// { -// return false; -// } - -// // match the optional list -// while (Enumerator.Peek() == Tokens.Comma) -// { -// Enumerator.Take(); - -// if (TryMake(TryMakeAtDomain, out string atDomain) == false) -// { -// return false; -// } - -// atDomainList += $",{atDomain}"; -// } - -// return true; -// } - -// /// -// /// Try to make an @domain. -// /// -// /// The @domain that was made, or undefined if it was not made. -// /// true if the @domain was made, false if not. -// /// -// public bool TryMakeAtDomain(out string atDomain) -// { -// atDomain = null; - -// if (Enumerator.Take() != Tokens.At) -// { -// return false; -// } - -// if (TryMake(TryMakeDomain, out string domain) == false) -// { -// return false; -// } - -// atDomain = $"@{domain}"; - -// return true; -// } - -// /// -// /// Try to make a mailbox. -// /// -// /// The mailbox that was made, or undefined if it was not made. -// /// true if the mailbox was made, false if not. -// /// -// public bool TryMakeMailbox(out IMailbox mailbox) -// { -// mailbox = Mailbox.Empty; - -// if (TryMake(TryMakeLocalPart, out string localpart) == false) -// { -// return false; -// } - -// if (Enumerator.Take() != Tokens.At) -// { -// return false; -// } - -// if (TryMake(TryMakeDomain, out string domain)) -// { -// mailbox = new Mailbox(localpart, domain); -// return true; -// } - -// if (TryMake(TryMakeAddressLiteral, out string address)) -// { -// mailbox = new Mailbox(localpart, address); -// return true; -// } - -// return false; -// } - -// /// -// /// Try to make a domain name. -// /// -// /// The domain name that was made, or undefined if it was not made. -// /// true if the domain name was made, false if not. -// /// -// public bool TryMakeDomain(out string domain) -// { -// if (TryMake(TryMakeSubdomain, out domain) == false) -// { -// return false; -// } - -// while (Enumerator.Peek() == Tokens.Period) -// { -// Enumerator.Take(); - -// if (TryMake(TryMakeSubdomain, out string subdomain) == false) -// { -// return false; -// } - -// domain += string.Concat(".", subdomain); -// } - -// return true; -// } - -// /// -// /// Try to make a subdomain name. -// /// -// /// The subdomain name that was made, or undefined if it was not made. -// /// true if the subdomain name was made, false if not. -// /// -// public bool TryMakeSubdomain(out string subdomain) -// { -// if (TryMake(TryMakeTextOrNumber, out subdomain) == false) -// { -// return false; -// } - -// if (TryMake(TryMakeTextOrNumberOrHyphenString, out string letterNumberHyphen) == false) -// { -// return subdomain != null; -// } - -// subdomain += letterNumberHyphen; - -// return true; -// } - -// /// -// /// Try to make a address. -// /// -// /// The address that was made, or undefined if it was not made. -// /// true if the address was made, false if not. -// /// -// public bool TryMakeAddressLiteral(out string address) -// { -// address = null; - -// if (Enumerator.Take() != Tokens.LeftBracket) -// { -// return false; -// } - -// // skip any whitespace -// Enumerator.Skip(TokenKind.Space); - -// if (TryMake(TryMakeIPv4AddressLiteral, out address) == false && TryMake(TryMakeIPv6AddressLiteral, out address) == false) -// { -// return false; -// } - -// // skip any whitespace -// Enumerator.Skip(TokenKind.Space); - -// if (Enumerator.Take() != Tokens.RightBracket) -// { -// return false; -// } - -// return address != null; -// } - -// /// -// /// Try to make an IPv4 address literal. -// /// -// /// The address that was made, or undefined if it was not made. -// /// true if the address was made, false if not. -// /// -// public bool TryMakeIPv4AddressLiteral(out string address) -// { -// address = null; - -// if (TryMake(TryMakeSnum, out int snum) == false) -// { -// return false; -// } - -// address = snum.ToString(CultureInfo.InvariantCulture); - -// for (var i = 0; i < 3; i++) -// { -// if (Enumerator.Take() != Tokens.Period) -// { -// return false; -// } - -// if (TryMake(TryMakeSnum, out snum) == false) -// { -// return false; -// } - -// address = string.Concat(address, '.', snum); -// } - -// return true; -// } - -// /// -// /// Try to make an Snum (number in the range of 0-255). -// /// -// /// The snum that was made, or undefined if it was not made. -// /// true if the snum was made, false if not. -// /// -// public bool TryMakeSnum(out int snum) -// { -// snum = default; - -// var token = Enumerator.Take(); - -// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out snum)) -// { -// return snum >= 0 && snum <= 255; -// } - -// return false; -// } - -// /// -// /// Try to make Ip version from ip version tag which is a formatted text IPv[Version]: -// /// -// /// IP version. IPv6 is supported atm. -// /// true if ip version tag can be extracted. -// public bool TryMakeIpVersion(out int version) -// { -// version = default; + reader.Skip(TokenKind.Space); + + if (TryMakeTo(ref reader) == false) + { + return false; + } + + if (reader.Take().Kind != TokenKind.Colon) + { + errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); + return false; + } + + // according to the spec, whitespace isnt allowed here anyway + reader.Skip(TokenKind.Space); + + if (TryMakePath(ref reader, out var mailbox) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + // TODO: support optional service extension parameters here + + command = _smtpCommandFactory.CreateRcpt(mailbox); + return true; + } + + /// + /// Try to make the RCPT text sequence. + /// + /// The reader to perform the operation on. + /// true if the RCPT text sequence could be made, false if not. + public bool TryMakeRcpt(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'R'; + command[1] = 'C'; + command[2] = 'P'; + command[3] = 'T'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make the TO text sequence. + /// + /// The reader to perform the operation on. + /// true if the TO text sequence could be made, false if not. + public bool TryMakeTo(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[2]; + command[0] = 'T'; + command[1] = 'O'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a DATA command from the given enumerator. + /// + /// The reader to perform the operation on. + /// The DATA command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeData(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeData) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (reader.TryMake(TryMakeEnd) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + command = _smtpCommandFactory.CreateData(); + return true; + } + + /// + /// Try to make the DATA text sequence. + /// + /// The reader to perform the operation on. + /// true if the DATA text sequence could be made, false if not. + public bool TryMakeData(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'D'; + command[1] = 'A'; + command[2] = 'T'; + command[3] = 'A'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a QUIT command. + /// + /// The reader to perform the operation on. + /// The QUIT command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeQuit(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeQuit) == false) + { + return false; + } + + if (TryMakeEnd(ref reader) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + command = _smtpCommandFactory.CreateQuit(); + return true; + } + + /// + /// Try to make the QUIT text sequence. + /// + /// The reader to perform the operation on. + /// true if the QUIT text sequence could be made, false if not. + public bool TryMakeQuit(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'Q'; + command[1] = 'U'; + command[2] = 'I'; + command[3] = 'T'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a NOOP command. + /// + /// The reader to perform the operation on. + /// The NOOP command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeNoop(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeNoop) == false) + { + return false; + } + + if (TryMakeEnd(ref reader) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + command = _smtpCommandFactory.CreateNoop(); + return true; + } + + /// + /// Try to make the NOOP text sequence. + /// + /// The reader to perform the operation on. + /// true if the NOOP text sequence could be made, false if not. + public bool TryMakeNoop(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'N'; + command[1] = 'O'; + command[2] = 'O'; + command[3] = 'P'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make a RSET command. + /// + /// The reader to perform the operation on. + /// The RSET command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeRset(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeRset) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (TryMakeEnd(ref reader) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + command = _smtpCommandFactory.CreateRset(); + return true; + } + + /// + /// Try to make the RSET text sequence. + /// + /// The reader to perform the operation on. + /// true if the RSET text sequence could be made, false if not. + public bool TryMakeRset(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'R'; + command[1] = 'S'; + command[2] = 'E'; + command[3] = 'T'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make an STARTTLS command from the given enumerator. + /// + /// The reader to perform the operation on. + /// The STARTTLS command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeStartTls(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeStartTls) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (TryMakeEnd(ref reader) == false) + { + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + command = _smtpCommandFactory.CreateStartTls(); + return true; + } + + /// + /// Try to make the STARTTLS text sequence. + /// + /// The reader to perform the operation on. + /// true if the STARTTLS text sequence could be made, false if not. + public bool TryMakeStartTls(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[8]; + command[0] = 'S'; + command[1] = 'T'; + command[2] = 'A'; + command[3] = 'R'; + command[4] = 'T'; + command[5] = 'T'; + command[6] = 'L'; + command[7] = 'S'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Make an AUTH command from the given enumerator. + /// + /// The reader to perform the operation on. + /// The AUTH command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeAuth(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeAuth) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (TryMakeAuthenticationMethod(ref reader, out var authenticationMethod) == false) + { + return false; + } + + reader.Take(); + + if (reader.TryMake(TryMakeEnd)) + { + command = _smtpCommandFactory.CreateAuth(authenticationMethod, null); + return true; + } + + if (reader.TryMake(TryMakeBase64, out var base64) == false) + { + command = _smtpCommandFactory.CreateAuth(authenticationMethod, StringUtil.Create(base64)); + return true; + } + + errorResponse = SmtpResponse.SyntaxError; + return false; + } + + /// + /// Try to make the Authentication method. + /// + /// The reader to perform the operation on. + /// The authentication method that was made. + /// true if the authentication method could be made, false if not. + public bool TryMakeAuthenticationMethod(ref TokenReader reader, out AuthenticationMethod authenticationMethod) + { + if (reader.TryMake(TryMakeLoginText)) + { + authenticationMethod = AuthenticationMethod.Login; + return true; + } + + if (reader.TryMake(TryMakePlainText)) + { + authenticationMethod = AuthenticationMethod.Plain; + return true; + } + + authenticationMethod = default; + return false; + } + + /// + /// Try to make the AUTH text sequence. + /// + /// The reader to perform the operation on. + /// true if the AUTH text sequence could be made, false if not. + public bool TryMakeAuth(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'A'; + command[1] = 'U'; + command[2] = 'T'; + command[3] = 'H'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make the LOGIN text sequence. + /// + /// The reader to perform the operation on. + /// true if the LOGIN text sequence could be made, false if not. + public bool TryMakeLoginText(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'L'; + command[1] = 'O'; + command[2] = 'G'; + command[3] = 'I'; + command[4] = 'N'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make the PLAIN text sequence. + /// + /// The reader to perform the operation on. + /// true if the PLAIN text sequence could be made, false if not. + public bool TryMakePlainText(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'P'; + command[1] = 'L'; + command[2] = 'A'; + command[3] = 'I'; + command[4] = 'N'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Support proxy protocol version 1 header for use with HAProxy. + /// Documented at http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + /// + /// The reader to perform the operation on. + /// The PROXY command that is defined within the token enumerator. + /// The error that indicates why the command could not be made. + /// Returns true if a command could be made, false if not. + public bool TryMakeProxy(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) + { + // ABNF + // proxy = "PROXY" space ( unknown-proxy | tcp4-proxy | tcp6-proxy ) + // unknown-proxy = "UNKNOWN" + // tcp4-proxy = "TCP4" space ipv4-address-literal space ipv4-address-literal space ip-port-number space ip-port-number + // tcp6-proxy = "TCP6" space ipv6-address-literal space ipv6-address-literal space ip-port-number space ip-port-number + // space = " " + // ip-port = wnum + // wnum = 1*5DIGIT ; in the range of 0-65535 + + command = null; + errorResponse = null; + + if (reader.TryMake(TryMakeProxy) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + //if (TryMake(TryMakeUnknownProxy, out command)) + //{ + // return true; + //} + + //if (TryMake(TryMakeTcp4Proxy, out command)) + //{ + // return true; + //} + + //return TryMakeTcp6Proxy(out command); + + throw new NotImplementedException(); + } + + /// + /// Try to make the PROXY text sequence. + /// + /// The reader to perform the operation on. + /// true if the PROXY text sequence could be made, false if not. + public bool TryMakeProxy(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[4]; + command[0] = 'P'; + command[1] = 'R'; + command[2] = 'O'; + command[3] = 'X'; + command[4] = 'Y'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make the end of sequence. + /// + /// The reader to perform the operation on. + /// true if the end was made, false if not. + public bool TryMakeEnd(ref TokenReader reader) + { + reader.Skip(TokenKind.Space); + + return reader.Take() == default; + } + + /// + /// Try to make a reverse path. + /// + /// The reader to perform the operation on. + /// The mailbox that was made. + /// true if the reverse path was made, false if not. + /// "]]> + public bool TryMakeReversePath(ref TokenReader reader, out IMailbox mailbox) + { + if (reader.TryMake(TryMakePath, out mailbox)) + { + return true; + } + + if (TryMakeEmptyPath(ref reader)) + { + mailbox = Mailbox.Empty; + return true; + } + + return false; + } + + /// + /// Try to make an empty path. + /// + /// The reader to perform the operation on. + /// true if the empty path was made, false if not. + /// "]]> + public bool TryMakeEmptyPath(ref TokenReader reader) + { + if (reader.Take().Kind != TokenKind.LessThan) + { + return false; + } + + // not valid according to the spec but some senders do it + reader.Skip(TokenKind.Space); + + return reader.Take().Kind == TokenKind.GreaterThan; + } + + /// + /// Try to make a path. + /// + /// The reader to perform the operation on. + /// The mailbox that was made. + /// true if the path was made, false if not. + /// "]]> + public bool TryMakePath(ref TokenReader reader, out IMailbox mailbox) + { + mailbox = null; + + if (reader.Take().Kind != TokenKind.LessThan) + { + return false; + } + + // Note, the at-domain-list must be matched, but also must be ignored + // http://tools.ietf.org/html/rfc5321#appendix-C + if (reader.TryMake(TryMakeAtDomainList)) + { + // if the @domain list was matched then it needs to be followed by a colon + if (reader.Take().Kind != TokenKind.Colon) + { + return false; + } + } + + if (TryMakeMailbox(ref reader, out mailbox) == false) + { + return false; + } + + return reader.Take().Kind == TokenKind.GreaterThan; + } + + /// + /// Try to make an @domain list. + /// + /// The reader to perform the operation on. + /// true if the @domain list was made, false if not. + /// + public bool TryMakeAtDomainList(ref TokenReader reader) + { + if (TryMakeAtDomain(ref reader) == false) + { + return false; + } + + while (reader.Peek().Kind == TokenKind.Comma) + { + reader.Take(); + + if (TryMakeAtDomain(ref reader) == false) + { + return false; + } + } + + return true; + } + + /// + /// Try to make an @domain. + /// + /// The reader to perform the operation on. + /// true if the @domain was made, false if not. + /// + public bool TryMakeAtDomain(ref TokenReader reader) + { + if (reader.Take().Kind != TokenKind.At) + { + return false; + } + + return TryMakeDomain(ref reader); + } + + /// + /// Try to make a mailbox. + /// + /// The reader to perform the operation on. + /// The mailbox that was made. + /// true if the mailbox was made, false if not. + /// + public bool TryMakeMailbox(ref TokenReader reader, out IMailbox mailbox) + { + mailbox = null; + + if (reader.TryMake(TryMakeLocalPart, out var localpart) == false) + { + return false; + } + + if (reader.Take().Kind != TokenKind.At) + { + return false; + } + + if (reader.TryMake(TryMakeDomain, out var domain)) + { + mailbox = new Mailbox(StringUtil.Create(localpart), StringUtil.Create(domain)); + return true; + } + + if (reader.TryMake(TryMakeAddressLiteral, out var address)) + { + mailbox = new Mailbox(StringUtil.Create(localpart), StringUtil.Create(address)); + return true; + } + + return false; + } + + /// + /// Try to make a domain name. + /// + /// The reader to perform the operation on. + /// true if the domain name was made, false if not. + /// + public bool TryMakeDomain(ref TokenReader reader) + { + if (TryMakeSubdomain(ref reader) == false) + { + return false; + } + + while (reader.Peek().Kind == TokenKind.Period) + { + reader.Take(); + + if (TryMakeSubdomain(ref reader) == false) + { + return false; + } + } + + return true; + } + + /// + /// Try to make a subdomain name. + /// + /// The reader to perform the operation on. + /// true if the subdomain name was made, false if not. + /// + public bool TryMakeSubdomain(ref TokenReader reader) + { + if (TryMakeTextOrNumber(ref reader) == false) + { + return false; + } + + // this is optional + reader.TryMake(TryMakeTextOrNumberOrHyphenString); + + return true; + } + + /// + /// Try to make a address. + /// + /// The reader to perform the operation on. + /// true if the address was made, false if not. + /// + public bool TryMakeAddressLiteral(ref TokenReader reader) + { + if (reader.Take().Kind != TokenKind.LeftBracket) + { + return false; + } + + reader.Skip(TokenKind.Space); + + if (reader.TryMake(TryMakeIPv4AddressLiteral) == false && reader.TryMake(TryMakeIPv6AddressLiteral) == false) + { + return false; + } + + reader.Skip(TokenKind.Space); + + return reader.Take().Kind == TokenKind.RightBracket; + } + + /// + /// Try to make an IPv4 address literal. + /// + /// The reader to perform the operation on. + /// true if the address was made, false if not. + /// + public bool TryMakeIPv4AddressLiteral(ref TokenReader reader) + { + if (reader.TryMake(TryMakeSnum) == false) + { + return false; + } + + for (var i = 0; i < 3; i++) + { + if (reader.Take().Kind != TokenKind.Period) + { + return false; + } + + if (reader.TryMake(TryMakeSnum) == false) + { + return false; + } + } + + return true; + } + + /// + /// Try to make an Snum (number in the range of 0-255). + /// + /// The reader to perform the operation on. + /// true if the snum was made, false if not. + /// + public bool TryMakeSnum(ref TokenReader reader) + { + if (reader.TryMake(TryMakeNumber, out var number) == false) + { + return false; + } + + return int.TryParse(StringUtil.Create(number), out var snum) && snum >= 0 && snum <= 255; + } + + /// + /// Try to extract IPv6 address. https://tools.ietf.org/html/rfc4291 section 2.2 used for specification. + /// This method expects the address to have the IPv6: prefix. + /// + /// The reader to perform the operation on. + /// true if a valid Ipv6 address can be extracted. + public bool TryMakeIPv6AddressLiteral(ref TokenReader reader) + { + if (TryMakeIPv6(ref reader) == false) + { + return false; + } + + return TryMakeIPv6Address(ref reader); + } + + /// + /// Try to make Ip version from ip version tag which is a formatted text IPv[Version]: + /// + /// The reader to perform the operation on. + /// true if ip version tag can be extracted. + public bool TryMakeIPv6(ref TokenReader reader) + { + if (TryMakeIPv(ref reader) == false) + { + return false; + } + + var token = reader.Take(); + + if (token.Kind != TokenKind.Number || token.Text.Length > 1) + { + return false; + } + + return token.Text[0] == '6'; + } + + /// + /// Try to make the IPv text sequence. + /// + /// The reader to perform the operation on. + /// true if IPv text sequence can be made. + public bool TryMakeIPv(ref TokenReader reader) + { + if (reader.TryMake(TryMakeText, out var text)) + { + Span command = stackalloc char[3]; + command[0] = 'I'; + command[1] = 'P'; + command[2] = 'v'; + + return text.CaseInsensitiveStringEquals(ref command); + } + + return false; + } + + /// + /// Try to make an IPv6 address. + /// + /// The reader to perform the operation on. + /// true if the address was made, false if not. + /// + public bool TryMakeIPv6Address(ref TokenReader reader) + { + return reader.TryMake(TryMakeIPv6AddressRule1) + || reader.TryMake(TryMakeIPv6AddressRule2) + || reader.TryMake(TryMakeIPv6AddressRule3) + || reader.TryMake(TryMakeIPv6AddressRule4) + || reader.TryMake(TryMakeIPv6AddressRule5) + || reader.TryMake(TryMakeIPv6AddressRule6) + || reader.TryMake(TryMakeIPv6AddressRule7) + || reader.TryMake(TryMakeIPv6AddressRule8) + || reader.TryMake(TryMakeIPv6AddressRule9); + } + + public bool TryMakeIPv6AddressRule1(ref TokenReader reader) + { + // 6( h16 ":" ) ls32 + return TryMakeIPv6HexPostamble(ref reader, 6); + } + + bool TryMakeIPv6AddressRule2(ref TokenReader reader) + { + // "::" 5( h16 ":" ) ls32 + if (reader.Take().Kind != TokenKind.Colon || reader.Take().Kind != TokenKind.Colon) + { + return false; + } + + return TryMakeIPv6HexPostamble(ref reader, 5); + } + + bool TryMakeIPv6AddressRule3(ref TokenReader reader) + { + // [ h16 ] "::" 4( h16 ":" ) ls32 + if (TryMakeIPv6HexPreamble(ref reader, 1) == false) + { + return false; + } + + return TryMakeIPv6HexPostamble(ref reader, 4); + } + + bool TryMakeIPv6AddressRule4(ref TokenReader reader) + { + // [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + if (TryMakeIPv6HexPreamble(ref reader, 2) == false) + { + return false; + } + + return TryMakeIPv6HexPostamble(ref reader, 3); + } + + bool TryMakeIPv6AddressRule5(ref TokenReader reader) + { + // [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + if (TryMakeIPv6HexPreamble(ref reader, 3) == false) + { + return false; + } + + return TryMakeIPv6HexPostamble(ref reader, 2); + } + + bool TryMakeIPv6AddressRule6(ref TokenReader reader) + { + // [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + if (TryMakeIPv6HexPreamble(ref reader, 4) == false) + { + return false; + } + + return TryMakeIPv6HexPostamble(ref reader, 1); + } + + bool TryMakeIPv6AddressRule7(ref TokenReader reader) + { + // [ *4( h16 ":" ) h16 ] "::" ls32 + if (TryMakeIPv6HexPreamble(ref reader, 5) == false) + { + return false; + } + + return TryMakeIPv6Ls32(ref reader); + } + + bool TryMakeIPv6AddressRule8(ref TokenReader reader) + { + // [ *5( h16 ":" ) h16 ] "::" h16 + if (TryMakeIPv6HexPreamble(ref reader, 6) == false) + { + return false; + } + + return TryMake16BitHex(ref reader); + } + + bool TryMakeIPv6AddressRule9(ref TokenReader reader) + { + // [ *6( h16 ":" ) h16 ] "::" + return TryMakeIPv6HexPreamble(ref reader, 7); + } + + bool TryMakeIPv6HexPreamble(ref TokenReader reader, int maximum) + { + for (var i = 0; i < maximum; i++) + { + if (reader.TryMake(TryMakeTerminal)) + { + return true; + } + + if (i > 0) + { + if (reader.Take().Kind != TokenKind.Colon) + { + return false; + } + } + + if (TryMake16BitHex(ref reader) == false) + { + return false; + } + } + + return reader.TryMake(TryMakeTerminal); + + static bool TryMakeTerminal(ref TokenReader reader) + { + return reader.Take().Kind == TokenKind.Colon && reader.Take().Kind == TokenKind.Colon; + } + } + + bool TryMakeIPv6HexPostamble(ref TokenReader reader, int count) + { + while (count-- > 0) + { + if (TryMake16BitHex(ref reader) == false) + { + return false; + } + + if (reader.Take().Kind != TokenKind.Colon) + { + return false; + } + } + + return TryMakeIPv6Ls32(ref reader); + } + + bool TryMakeIPv6Ls32(ref TokenReader reader) + { + if (reader.TryMake(TryMakeIPv4AddressLiteral)) + { + return true; + } + + if (TryMake16BitHex(ref reader) == false) + { + return false; + } + + if (reader.Take().Kind != TokenKind.Colon) + { + return false; + } + + return TryMake16BitHex(ref reader); + } + + /// + /// Try to make 16 bit hex number. + /// + /// The token reader to perform the operation on. + /// true if valid hex number can be extracted. + public bool TryMake16BitHex(ref TokenReader reader) + { + var hexLength = 0L; + + var token = reader.Peek(); + while ((token.Kind == TokenKind.Text || token.Kind == TokenKind.Number) && hexLength < 4) + { + if (token.Kind == TokenKind.Text && IsHex(ref token) == false) + { + return false; + } + + hexLength += reader.Take().Text.Length; + + token = reader.Peek(); + } + + return hexLength > 0 && hexLength <= 4; + + static bool IsHex(ref Token token) + { + var span = token.Text; + + return span.IsHex(); + } + } + + /// + /// Try to make a text/number/hyphen string. + /// + /// The reader to perform the operatio on. + /// true if a text, number or hyphen was made, false if not. + /// + public bool TryMakeTextOrNumberOrHyphenString(ref TokenReader reader) + { + var token = reader.Peek(); + + if (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token.Kind == TokenKind.Hyphen) + { + reader.Skip(kind => kind == TokenKind.Text || kind == TokenKind.Number || kind == TokenKind.Hyphen); + return true; + } + + return false; + } + + /// + /// Try to make a text or number + /// + /// The reader to perform the operatio on. + /// true if the text or number was made, false if not. + /// + public bool TryMakeTextOrNumber(ref TokenReader reader) + { + var token = reader.Peek(); + + if (token.Kind == TokenKind.Text) + { + return TryMakeText(ref reader); + } + + if (token.Kind == TokenKind.Number) + { + return TryMakeNumber(ref reader); + } + + return false; + } + + /// + /// Try to make the local part of the path. + /// + /// The reader to perform the operatio on. + /// true if the local part was made, false if not. + /// + public bool TryMakeLocalPart(ref TokenReader reader) + { + if (reader.TryMake(TryMakeDotString)) + { + return true; + } + + return TryMakeQuotedString(ref reader); + } + + /// + /// Try to make a dot-string from the tokens. + /// + /// The reader to perform the operation on. + /// true if the dot-string was made, false if not. + /// + public bool TryMakeDotString(ref TokenReader reader) + { + if (TryMakeAtom(ref reader) == false) + { + return false; + } + + while (reader.Peek().Kind == TokenKind.Period) + { + reader.Take(); + + if (TryMakeAtom(ref reader) == false) + { + return false; + } + } + + return true; + } + + /// + /// Try to make a quoted-string from the tokens. + /// + /// The reader to perform the operation on. + /// true if the quoted-string was made, false if not. + /// + public bool TryMakeQuotedString(ref TokenReader reader) + { + if (reader.Take().Kind != TokenKind.Quote) + { + return false; + } + + while (reader.Peek().Kind != TokenKind.Quote) + { + if (TryMakeQContentSmtp(ref reader) == false) + { + return false; + } + } + + return reader.Take().Kind == TokenKind.Quote; + } + + /// + /// Try to make a QcontentSMTP from the tokens. + /// + /// The reader to perform the operation on. + /// true if the quoted content was made, false if not. + /// + public bool TryMakeQContentSmtp(ref TokenReader reader) + { + if (TryMakeQTextSmtp(ref reader)) + { + return true; + } + + return TryMakeQuotedPairSmtp(ref reader); + } + + /// + /// Try to make a QTextSMTP from the tokens. + /// + /// The reader to perform the operation on. + /// true if the quoted text was made, false if not. + /// + public bool TryMakeQTextSmtp(ref TokenReader reader) + { + switch (reader.Peek().Kind) + { + case TokenKind.Text: + return TryMakeText(ref reader); + + case TokenKind.Number: + return TryMakeNumber(ref reader); + + default: + var token = reader.Take(); + switch ((char)token.Text[0]) + { + case ' ': + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return true; + } + break; + } + + return false; + } + + /// + /// Try to make a quoted pair from the tokens. + /// + /// The reader to perform the operation on. + /// true if the quoted pair was made, false if not. + /// + public bool TryMakeQuotedPairSmtp(ref TokenReader reader) + { + if (reader.Take().Kind != TokenKind.Backslash) + { + return false; + } + + return TryMakeText(ref reader); + } + + /// + /// Try to make an "Atom" from the tokens. + /// + /// The reader to perform the operation on. + /// true if the atom was made, false if not. + /// + public bool TryMakeAtom(ref TokenReader reader) + { + var count = 0; + + while (reader.TryMake(TryMakeAtext)) + { + count++; + } + + return count >= 1; + } + + /// + /// Try to make an "Atext" from the tokens. + /// + /// The reader to perform the operation on. + /// true if the atext was made, false if not. + /// + public bool TryMakeAtext(ref TokenReader reader) + { + switch (reader.Peek().Kind) + { + case TokenKind.Text: + return TryMakeText(ref reader); + + case TokenKind.Number: + return TryMakeNumber(ref reader); + + default: + var token = reader.Take(); + switch ((char)token.Text[0]) + { + case '!': + case '#': + case '%': + case '&': + case '\'': + case '*': + case '-': + case '/': + case '?': + case '_': + case '{': + case '}': + case '$': + case '+': + case '=': + case '^': + case '`': + case '|': + case '~': + return true; + } + break; + } + + return false; + } + + /// + /// Try to make an Mail-Parameters from the tokens. + /// + /// The reader to perform the operation on. + /// The mail parameters that were made. + /// true if the mail parameters can be made, false if not. + /// + public bool TryMakeMailParameters(ref TokenReader reader, out IReadOnlyDictionary parameters) + { + Dictionary dictionary = null; + + while (reader.Peek() != default) + { + if (reader.TryMake(TryMakeEsmtpParameter, out ReadOnlySequence keyword, out ReadOnlySequence value) == false) + { + parameters = null; + return false; + } + + dictionary ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + dictionary.Add(StringUtil.Create(keyword), StringUtil.Create(value)); + + reader.Skip(TokenKind.Space); + } + + parameters = dictionary; + return parameters?.Count > 0; + } + + /// + /// Try to make an Esmtp-Parameter from the tokens. + /// + /// The reader to perform the operation on. + /// The keyword that was made. + /// The value that was made. + /// true if the esmtp-parameter can be made, false if not. + /// + public bool TryMakeEsmtpParameter(ref TokenReader reader, out ReadOnlySequence keyword, out ReadOnlySequence value) + { + value = default; + + if (reader.TryMake(TryMakeEsmtpKeyword, out keyword) == false) + { + return false; + } + + if (reader.Peek().Kind == TokenKind.None || reader.Peek().Kind == TokenKind.Space) + { + return true; + } + + if (reader.Take().Kind != TokenKind.Equal) + { + return false; + } + + return reader.TryMake(TryMakeEsmtpValue, out value); + } + + /// + /// Try to make an Esmtp-Keyword from the tokens. + /// + /// The reader to perform the operation on. + /// true if the esmtp-keyword can be made, false if not. + /// + public bool TryMakeEsmtpKeyword(ref TokenReader reader) + { + var token = reader.Take(); + if (token.Kind != TokenKind.Text && token.Kind != TokenKind.Number) + { + return false; + } + + token = reader.Peek(); + while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token.Kind == TokenKind.Hyphen) + { + reader.Take(); + token = reader.Peek(); + } + + return true; + } + + /// + /// Try to make an Esmtp-Value from the tokens. + /// + /// The reader to perform the operation on. + /// true if the esmtp-value can be made, false if not. + /// + public bool TryMakeEsmtpValue(ref TokenReader reader) + { + var token = reader.Take(); + if (token.Kind == TokenKind.None || IsValid(ref token) == false) + { + return false; + } + + token = reader.Peek(); + while (token.Kind != TokenKind.None && IsValid(ref token)) + { + reader.Take(); + + token = reader.Peek(); + } + + return true; + + static bool IsValid(ref Token token) + { + var span = token.Text; + + for (var i = 0; i < span.Length; i++) + { + if ((span[i] < 33 || span[i] > 60) && (span[i] < 62 || span[i] > 127)) + { + return false; + } + } + + return true; + } + } + + /// + /// Try to make a base64 encoded string. + /// + /// The reader to perform the operation on. + /// true if the base64 encoded string can be made, false if not. + /// + public bool TryMakeBase64(ref TokenReader reader) + { + if (TryMakeBase64Text(ref reader) == false) + { + return false; + } + + if (reader.Peek().Kind == TokenKind.Equal) + { + reader.Take(); + } + + if (reader.Peek().Kind == TokenKind.Equal) + { + reader.Take(); + } -// if (Enumerator.Take() != Tokens.Text.IpVersionTag) -// { -// return false; -// } - -// var token = Enumerator.Take(); - -// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out var v)) -// { -// version = v; -// return Enumerator.Take() == Tokens.Colon; -// } - -// return false; -// } - -// /// -// /// Try to make 16 bit hex number. -// /// -// /// Extracted hex number. -// /// true if valid hex number can be extracted. -// public bool TryMake16BitHex(out string hex) -// { -// hex = string.Empty; - -// var token = Enumerator.Peek(); -// while ((token.Kind == TokenKind.Text || token.Kind == TokenKind.Number) && hex.Length < 4) -// { -// if (token.Kind == TokenKind.Text && IsHex(token.Text) == false) -// { -// return false; -// } - -// hex = string.Concat(hex, token.Text); - -// Enumerator.Take(); -// token = Enumerator.Peek(); -// } - -// return hex.Length > 0 && hex.Length <= 4; - -// bool IsHex(string text) -// { -// return text.ToUpperInvariant().All(c => c >= 'A' && c <= 'F'); -// } -// } - -// /// -// /// Try to extract IPv6 address. https://tools.ietf.org/html/rfc4291 section 2.2 used for specification. -// /// This method expects the address to have the IPv6: prefix. -// /// -// /// Extracted Ipv6 address. -// /// true if a valid Ipv6 address can be extracted. -// public bool TryMakeIPv6AddressLiteral(out string address) -// { -// address = null; - -// if (TryMake(TryMakeIpVersion, out int ipVersion) == false || ipVersion != 6) -// { -// return false; -// } - -// return TryMakeIPv6Address(out address); -// } - -// /// -// /// Try to make an IPv6 address. -// /// -// /// The address that was made, or undefined if it was not made. -// /// true if the address was made, false if not. -// /// -// public bool TryMakeIPv6Address(out string address) -// { -// return TryMake(TryMakeIPv6AddressRule1, out address) -// || TryMake(TryMakeIPv6AddressRule2, out address) -// || TryMake(TryMakeIPv6AddressRule3, out address) -// || TryMake(TryMakeIPv6AddressRule4, out address) -// || TryMake(TryMakeIPv6AddressRule5, out address) -// || TryMake(TryMakeIPv6AddressRule6, out address) -// || TryMake(TryMakeIPv6AddressRule7, out address) -// || TryMake(TryMakeIPv6AddressRule8, out address) -// || TryMake(TryMakeIPv6AddressRule9, out address); -// } - -// bool TryMakeIPv6AddressRule1(out string address) -// { -// // 6( h16 ":" ) ls32 -// return TryMakeIPv6HexPostamble(6, out address); -// } - -// bool TryMakeIPv6AddressRule2(out string address) -// { -// address = null; - -// // "::" 5( h16 ":" ) ls32 -// if (Enumerator.Take() != Tokens.Colon || Enumerator.Take() != Tokens.Colon) -// { -// return false; -// } - -// if (TryMakeIPv6HexPostamble(5, out var hexPostamble) == false) -// { -// return false; -// } - -// address = "::" + hexPostamble; -// return true; -// } - -// bool TryMakeIPv6AddressRule3(out string address) -// { -// // [ h16 ] "::" 4( h16 ":" ) ls32 -// address = null; - -// if (TryMakeIPv6HexPreamble(1, out var hexPreamble) == false) -// { -// return false; -// } - -// if (TryMakeIPv6HexPostamble(4, out var hexPostamble) == false) -// { -// return false; -// } - -// address = hexPreamble + hexPostamble; -// return true; -// } - -// bool TryMakeIPv6AddressRule4(out string address) -// { -// // [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 -// address = null; - -// if (TryMakeIPv6HexPreamble(2, out var hexPreamble) == false) -// { -// return false; -// } - -// if (TryMakeIPv6HexPostamble(3, out var hexPostamble) == false) -// { -// return false; -// } - -// address = hexPreamble + hexPostamble; -// return true; -// } - -// bool TryMakeIPv6AddressRule5(out string address) -// { -// // [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 -// address = null; - -// if (TryMakeIPv6HexPreamble(3, out var hexPreamble) == false) -// { -// return false; -// } - -// if (TryMakeIPv6HexPostamble(2, out var hexPostamble) == false) -// { -// return false; -// } - -// address = hexPreamble + hexPostamble; -// return true; -// } - -// bool TryMakeIPv6AddressRule6(out string address) -// { -// // [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 -// address = null; - -// if (TryMakeIPv6HexPreamble(4, out var hexPreamble) == false) -// { -// return false; -// } - -// if (TryMakeIPv6HexPostamble(1, out var hexPostamble) == false) -// { -// return false; -// } - -// address = hexPreamble + hexPostamble; -// return true; -// } - -// bool TryMakeIPv6AddressRule7(out string address) -// { -// // [ *4( h16 ":" ) h16 ] "::" ls32 -// address = null; - -// if (TryMakeIPv6HexPreamble(5, out var hexPreamble) == false) -// { -// return false; -// } - -// if (TryMakeIPv6Ls32(out var ls32) == false) -// { -// return false; -// } - -// address = hexPreamble + ls32; -// return true; -// } - -// bool TryMakeIPv6AddressRule8(out string address) -// { -// // [ *5( h16 ":" ) h16 ] "::" h16 -// address = null; - -// if (TryMakeIPv6HexPreamble(6, out var hexPreamble) == false) -// { -// return false; -// } + return true; + } + + /// + /// Try to make a base64 encoded string. + /// + /// The reader to perform the operation on. + /// true if the base64 encoded string can be made, false if not. + /// + public bool TryMakeBase64Text(ref TokenReader reader) + { + var count = 0; + + while (reader.TryMake(TryMakeBase64Chars)) + { + count++; + } + + return count > 0; + } + + /// + /// Try to make the allowable characters in a base64 encoded string. + /// + /// The reader to perform the operation on. + /// true if the base64-chars can be made, false if not. + /// + public bool TryMakeBase64Chars(ref TokenReader reader) + { + var token = reader.Take(); -// if (TryMake16BitHex(out var hex) == false) -// { -// return false; -// } - -// address = hexPreamble + hex; -// return true; -// } - -// bool TryMakeIPv6AddressRule9(out string address) -// { -// // [ *6( h16 ":" ) h16 ] "::" -// return TryMakeIPv6HexPreamble(7, out address); -// } - -// bool TryMakeIPv6HexPreamble(int maximum, out string hexPreamble) -// { -// hexPreamble = null; - -// for (var i = 0; i < maximum; i++) -// { -// if (TryMake(TryMakeTerminal)) -// { -// hexPreamble += "::"; -// return true; -// } - -// if (i > 0) -// { -// if (Enumerator.Take() != Tokens.Colon) -// { -// return false; -// } - -// hexPreamble += ":"; -// } - -// if (TryMake16BitHex(out var hex) == false) -// { -// return false; -// } - -// hexPreamble += hex; -// } - -// hexPreamble += "::"; - -// return TryMake(TryMakeTerminal); - -// bool TryMakeTerminal() -// { -// return Enumerator.Take() == Tokens.Colon && Enumerator.Take() == Tokens.Colon; -// } -// } - -// bool TryMakeIPv6HexPostamble(int count, out string hexPostamble) -// { -// hexPostamble = null; - -// while (count-- > 0) -// { -// if (TryMake16BitHex(out var hex) == false) -// { -// return false; -// } - -// hexPostamble += hex; - -// if (Enumerator.Take() != Tokens.Colon) -// { -// return false; -// } - -// hexPostamble += ":"; -// } - -// if (TryMakeIPv6Ls32(out var ls32) == false) -// { -// return false; -// } - -// hexPostamble += ls32; -// return true; -// } - -// bool TryMakeIPv6Ls32(out string address) -// { -// if (TryMake(TryMakeIPv4AddressLiteral, out address)) -// { -// return true; -// } - -// if (TryMake16BitHex(out var hex1) == false) -// { -// return false; -// } - -// if (Enumerator.Take() != Tokens.Colon) -// { -// return false; -// } - -// if (TryMake16BitHex(out var hex2) == false) -// { -// return false; -// } - -// address = hex1 + ":" + hex2; -// return true; -// } - -// /// -// /// Try to make a text/number/hyphen string. -// /// -// /// The text, number, or hyphen that was matched, or undefined if it was not matched. -// /// true if a text, number or hyphen was made, false if not. -// /// -// public bool TryMakeTextOrNumberOrHyphenString(out string textOrNumberOrHyphenString) -// { -// textOrNumberOrHyphenString = null; - -// var token = Enumerator.Peek(); -// while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token == Tokens.Hyphen) -// { -// textOrNumberOrHyphenString += Enumerator.Take().Text; - -// token = Enumerator.Peek(); -// } - -// // can not end with a hyphen -// return textOrNumberOrHyphenString != null && token != Tokens.Hyphen; -// } - -// /// -// /// Try to make a text or number -// /// -// /// The text or number that was made, or undefined if it was not made. -// /// true if the text or number was made, false if not. -// /// -// public bool TryMakeTextOrNumber(out string textOrNumber) -// { -// var token = Enumerator.Take(); - -// textOrNumber = token.Text; - -// return token.Kind == TokenKind.Text || token.Kind == TokenKind.Number; -// } - -// /// -// /// Try to make the local part of the path. -// /// -// /// The local part that was made, or undefined if it was not made. -// /// true if the local part was made, false if not. -// /// -// public bool TryMakeLocalPart(out string localPart) -// { -// if (TryMake(TryMakeDotString, out localPart)) -// { -// return true; -// } - -// return TryMakeQuotedString(out localPart); -// } - -// /// -// /// Try to make a dot-string from the tokens. -// /// -// /// The dot-string that was made, or undefined if it was not made. -// /// true if the dot-string was made, false if not. -// /// -// public bool TryMakeDotString(out string dotString) -// { -// if (TryMake(TryMakeAtom, out dotString) == false) -// { -// return false; -// } - -// while (Enumerator.Peek() == Tokens.Period) -// { -// // skip the punctuation -// Enumerator.Take(); - -// if (TryMake(TryMakeAtom, out string atom) == false) -// { -// return true; -// } - -// dotString += string.Concat(".", atom); -// } - -// return true; -// } - -// /// -// /// Try to make a quoted-string from the tokens. -// /// -// /// The quoted-string that was made, or undefined if it was not made. -// /// true if the quoted-string was made, false if not. -// /// -// public bool TryMakeQuotedString(out string quotedString) -// { -// quotedString = null; - -// if (Enumerator.Take() != Tokens.Quote) -// { -// return false; -// } - -// while (Enumerator.Peek() != Tokens.Quote) -// { -// if (TryMakeQContentSmtp(out var text) == false) -// { -// return false; -// } - -// quotedString += text; -// } - -// return Enumerator.Take() == Tokens.Quote; -// } - -// /// -// /// Try to make a QcontentSMTP from the tokens. -// /// -// /// The text that was made. -// /// true if the quoted content was made, false if not. -// /// -// public bool TryMakeQContentSmtp(out string text) -// { -// if (TryMake(TryMakeQTextSmtp, out text)) -// { -// return true; -// } - -// return TryMakeQuotedPairSmtp(out text); -// } - -// /// -// /// Try to make a QTextSMTP from the tokens. -// /// -// /// The text that was made. -// /// true if the quoted text was made, false if not. -// /// -// public bool TryMakeQTextSmtp(out string text) -// { -// text = null; - -// var token = Enumerator.Take(); -// switch (token.Kind) -// { -// case TokenKind.Text: -// case TokenKind.Number: -// text += token.Text; -// return true; - -// case TokenKind.Space: -// case TokenKind.Other: -// switch (token.Text[0]) -// { -// case ' ': -// case '!': -// case '#': -// case '$': -// case '%': -// case '&': -// case '\'': -// case '(': -// case ')': -// case '*': -// case '+': -// case ',': -// case '-': -// case '.': -// case '/': -// case ':': -// case ';': -// case '<': -// case '=': -// case '>': -// case '?': -// case '@': -// case '[': -// case ']': -// case '^': -// case '_': -// case '`': -// case '{': -// case '|': -// case '}': -// case '~': -// text += token.Text[0]; -// return true; -// } - -// return false; -// } - -// return false; -// } - -// /// -// /// Try to make a quoted pair from the tokens. -// /// -// /// The text that was made. -// /// true if the quoted pair was made, false if not. -// /// -// public bool TryMakeQuotedPairSmtp(out string text) -// { -// text = null; - -// if (Enumerator.Take() != Tokens.BackSlash) -// { -// return false; -// } - -// text += Enumerator.Take().Text; - -// return true; -// } - -// /// -// /// Try to make an "Atom" from the tokens. -// /// -// /// The atom that was made, or undefined if it was not made. -// /// true if the atom was made, false if not. -// /// -// public bool TryMakeAtom(out string atom) -// { -// atom = null; - -// while (TryMake(TryMakeAtext, out string atext)) -// { -// atom += atext; -// } - -// return atom != null; -// } - -// /// -// /// Try to make an "Atext" from the tokens. -// /// -// /// The atext that was made, or undefined if it was not made. -// /// true if the atext was made, false if not. -// /// -// public bool TryMakeAtext(out string atext) -// { -// atext = null; - -// var token = Enumerator.Take(); -// switch (token.Kind) -// { -// case TokenKind.Text: -// case TokenKind.Number: -// atext = token.Text; -// return true; - -// case TokenKind.Other: -// switch (token.Text[0]) -// { -// case '!': -// case '#': -// case '%': -// case '&': -// case '\'': -// case '*': -// case '-': -// case '/': -// case '?': -// case '_': -// case '{': -// case '}': -// case '$': -// case '+': -// case '=': -// case '^': -// case '`': -// case '|': -// case '~': -// atext += token.Text[0]; -// return true; -// } - -// break; -// } - -// return false; -// } - -// /// -// /// Try to make an Mail-Parameters from the tokens. -// /// -// /// The mail parameters that were made. -// /// true if the mail parameters can be made, false if not. -// /// -// public bool TryMakeMailParameters(out IReadOnlyDictionary parameters) -// { -// var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - -// while (Enumerator.Peek().Kind != TokenKind.None) -// { -// if (TryMake(TryMakeEsmtpParameter, out KeyValuePair parameter) == false) -// { -// parameters = null; -// return false; -// } - -// dictionary.Add(parameter.Key, parameter.Value); -// Enumerator.Skip(TokenKind.Space); -// } - -// parameters = dictionary; -// return parameters.Count > 0; -// } - -// /// -// /// Try to make an Esmtp-Parameter from the tokens. -// /// -// /// The esmtp-parameter that was made. -// /// true if the esmtp-parameter can be made, false if not. -// /// -// public bool TryMakeEsmtpParameter(out KeyValuePair parameter) -// { -// parameter = default; - -// if (TryMake(TryMakeEsmtpKeyword, out string keyword) == false) -// { -// return false; -// } - -// if (Enumerator.Peek().Kind == TokenKind.None || Enumerator.Peek().Kind == TokenKind.Space) -// { -// parameter = new KeyValuePair(keyword, null); -// return true; -// } - -// if (Enumerator.Peek() != Tokens.Equal) -// { -// return false; -// } - -// Enumerator.Take(); - -// if (TryMake(TryMakeEsmtpValue, out string value) == false) -// { -// return false; -// } - -// parameter = new KeyValuePair(keyword, value); - -// return true; -// } - -// /// -// /// Try to make an Esmtp-Keyword from the tokens. -// /// -// /// The esmtp-keyword that was made. -// /// true if the esmtp-keyword can be made, false if not. -// /// -// public bool TryMakeEsmtpKeyword(out string keyword) -// { -// keyword = null; - -// var token = Enumerator.Peek(); -// while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token == Tokens.Hyphen) -// { -// keyword += Enumerator.Take().Text; - -// token = Enumerator.Peek(); -// } - -// return keyword != null; -// } - -// /// -// /// Try to make an Esmtp-Value from the tokens. -// /// -// /// The esmtp-value that was made. -// /// true if the esmtp-value can be made, false if not. -// /// -// public bool TryMakeEsmtpValue(out string value) -// { -// value = null; - -// var token = Enumerator.Peek(); -// while (token.Text.Length > 0 && token.Text.ToCharArray().All(ch => (ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 127))) -// { -// value += Enumerator.Take().Text; - -// token = Enumerator.Peek(); -// } - -// return value != null; -// } - -// /// -// /// Try to make a base64 encoded string. -// /// -// /// The base64 encoded string that were found. -// /// true if the base64 encoded string can be made, false if not. -// /// -// public bool TryMakeBase64(out string base64) -// { -// if (TryMakeBase64Text(out base64) == false) -// { -// return false; -// } - -// if (Enumerator.Peek() == Tokens.Equal) -// { -// base64 += Enumerator.Take().Text; -// } - -// if (Enumerator.Peek() == Tokens.Equal) -// { -// base64 += Enumerator.Take().Text; -// } - -// // because the TryMakeBase64Chars method matches tokens, each TextValue token could make -// // up several Base64 encoded "bytes" so we ensure that we have a length divisible by 4 -// return base64 != null -// && base64.Length % 4 == 0 -// && new[] {TokenKind.None, TokenKind.Space, TokenKind.NewLine}.Contains(Enumerator.Peek().Kind); -// } - -// /// -// /// Try to make a base64 encoded string. -// /// -// /// The base64 encoded string that were found. -// /// true if the base64 encoded string can be made, false if not. -// /// -// bool TryMakeBase64Text(out string base64) -// { -// base64 = null; - -// while (TryMake(TryMakeBase64Chars, out string base64Chars)) -// { -// base64 += base64Chars; -// } - -// return true; -// } - -// /// -// /// Try to make the allowable characters in a base64 encoded string. -// /// -// /// The base64 characters that were found. -// /// true if the base64-chars can be made, false if not. -// /// -// bool TryMakeBase64Chars(out string base64Chars) -// { -// base64Chars = null; - -// var token = Enumerator.Take(); -// switch (token.Kind) -// { -// case TokenKind.Text: -// case TokenKind.Number: -// base64Chars = token.Text; -// return true; - -// case TokenKind.Other: -// switch (token.Text[0]) -// { -// case '/': -// case '+': -// base64Chars = token.Text; -// return true; -// } - -// break; -// } - -// return false; -// } - -// /// -// /// Attempt to make the end of the line. -// /// -// /// true if the end of the line could be made, false if not. -// bool TryMakeEnd() -// { -// Enumerator.Skip(TokenKind.Space); - -// return Enumerator.Take() == Token.None; -// } - -// /// -// /// Returns the complete tokenized text. -// /// -// /// The complete tokenized text. -// string CompleteTokenizedText() -// { -// return string.Concat(Enumerator.Tokens.Select(token => token.Text)); -// } -// } -//} \ No newline at end of file + switch (token.Kind) + { + case TokenKind.Text: + case TokenKind.Number: + case TokenKind.Slash: + case TokenKind.Plus: + return true; + } + + return false; + } + + /// + /// Try to make a text sequence. + /// + /// The reader to perform the operation on. + /// true if a text sequence could be made, false if not. + public bool TryMakeText(ref TokenReader reader) + { + if (reader.Peek().Kind == TokenKind.Text) + { + reader.Skip(TokenKind.Text); + return true; + } + + return false; + } + + /// + /// Try to make a number sequence. + /// + /// The reader to perform the operation on. + /// true if a number sequence could be made, false if not. + public bool TryMakeNumber(ref TokenReader reader) + { + if (reader.Peek().Kind == TokenKind.Number) + { + reader.Skip(TokenKind.Number); + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Src/SmtpServer/Protocol/SmtpParser3.cs b/Src/SmtpServer/Protocol/SmtpParser3.cs deleted file mode 100644 index 5fb9031..0000000 --- a/Src/SmtpServer/Protocol/SmtpParser3.cs +++ /dev/null @@ -1,1818 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using SmtpServer.Mail; -using SmtpServer.Text; - -namespace SmtpServer.Protocol -{ - public sealed class SmtpParser - { - delegate bool TryMakeDelegate(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse); - - readonly ISmtpCommandFactory _smtpCommandFactory; - - public SmtpParser(ISmtpCommandFactory smtpCommandFactory) - { - _smtpCommandFactory = smtpCommandFactory; - } - - /// - /// Make a command from the buffer. - /// - /// The buffer to read the command from. - /// The command that is defined within the token reader. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMake(ref ReadOnlySequence buffer, out SmtpCommand command, out SmtpResponse errorResponse) - { - return TryMake(buffer, TryMakeEhlo, out command, out errorResponse) - || TryMake(buffer, TryMakeHelo, out command, out errorResponse) - || TryMake(buffer, TryMakeMail, out command, out errorResponse) - || TryMake(buffer, TryMakeRcpt, out command, out errorResponse) - || TryMake(buffer, TryMakeData, out command, out errorResponse) - || TryMake(buffer, TryMakeQuit, out command, out errorResponse) - || TryMake(buffer, TryMakeRset, out command, out errorResponse) - || TryMake(buffer, TryMakeNoop, out command, out errorResponse) - || TryMake(buffer, TryMakeStartTls, out command, out errorResponse) - || TryMake(buffer, TryMakeAuth, out command, out errorResponse) - || TryMake(buffer, TryMakeProxy, out command, out errorResponse); - - static bool TryMake(ReadOnlySequence buffer, TryMakeDelegate tryMakeDelegate, out SmtpCommand command, out SmtpResponse errorResponse) - { - var reader = new TokenReader(buffer); - - return tryMakeDelegate(ref reader, out command, out errorResponse); - } - } - - /// - /// Make a HELO command from the given enumerator. - /// - /// The reader to perform the operation on. - /// The HELO command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeHelo(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (TryMakeHelo(ref reader) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (reader.TryMake(TryMakeDomain, out var domain)) - { - command = _smtpCommandFactory.CreateHelo(StringUtil.Create(domain)); - return true; - } - - // according to RFC5321 the HELO command should only accept the Domain - // and not the address literal, however some mail clients will send the - // address literal and there is no harm in accepting it - if (reader.TryMake(TryMakeAddressLiteral, out var address)) - { - command = _smtpCommandFactory.CreateHelo(StringUtil.Create(address)); - return true; - } - - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - /// - /// Try to make the HELO text sequence. - /// - /// The reader to perform the operation on. - /// true if the HELO text sequence could be made, false if not. - public bool TryMakeHelo(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'H'; - command[1] = 'E'; - command[2] = 'L'; - command[3] = 'O'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make an EHLO command from the given reader. - /// - /// The token reader to parse the command from. - /// The EHLO command that is defined within the token reader. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeEhlo(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (TryMakeEhlo(ref reader) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (reader.TryMake(TryMakeDomain, out var domain)) - { - command = _smtpCommandFactory.CreateEhlo(StringUtil.Create(domain)); - return true; - } - - if (reader.TryMake(TryMakeAddressLiteral, out var address)) - { - command = _smtpCommandFactory.CreateEhlo(StringUtil.Create(address)); - return true; - } - - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - /// - /// Try to make the EHLO text sequence. - /// - /// The reader to perform the operation on. - /// true if the EHLO text sequence could be made, false if not. - public bool TryMakeEhlo(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'E'; - command[1] = 'H'; - command[2] = 'L'; - command[3] = 'O'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a MAIL command from the given enumerator. - /// - /// The token reader to parse the command from. - /// The MAIL command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeMail(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (TryMakeMail(ref reader) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (TryMakeFrom(ref reader) == false || reader.Take().Kind != TokenKind.Colon) - { - errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); - return false; - } - - // according to the spec, whitespace isnt allowed here but most servers send it - reader.Skip(TokenKind.Space); - - if (reader.TryMake(TryMakeReversePath, out IMailbox mailbox) == false) - { - errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); - return false; - } - - reader.Skip(TokenKind.Space); - - // match the optional (ESMTP) parameters - if (reader.TryMake(TryMakeMailParameters, out IReadOnlyDictionary parameters) == false) - { - parameters = new Dictionary(); - } - - command = _smtpCommandFactory.CreateMail(mailbox, parameters); - return true; - } - - /// - /// Try to make the MAIL text sequence. - /// - /// The reader to perform the operation on. - /// true if the MAIL text sequence could be made, false if not. - public bool TryMakeMail(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'M'; - command[1] = 'A'; - command[2] = 'I'; - command[3] = 'L'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make the FROM text sequence. - /// - /// The reader to perform the operation on. - /// true if the FROM text sequence could be made, false if not. - public bool TryMakeFrom(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'F'; - command[1] = 'R'; - command[2] = 'O'; - command[3] = 'M'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a RCTP command from the given reader. - /// - /// The reader to perform the operation on. - /// The RCTP command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeRcpt(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (TryMakeRcpt(ref reader) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (TryMakeTo(ref reader) == false) - { - return false; - } - - if (reader.Take().Kind != TokenKind.Colon) - { - errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); - return false; - } - - // according to the spec, whitespace isnt allowed here anyway - reader.Skip(TokenKind.Space); - - if (TryMakePath(ref reader, out var mailbox) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - // TODO: support optional service extension parameters here - - command = _smtpCommandFactory.CreateRcpt(mailbox); - return true; - } - - /// - /// Try to make the RCPT text sequence. - /// - /// The reader to perform the operation on. - /// true if the RCPT text sequence could be made, false if not. - public bool TryMakeRcpt(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'R'; - command[1] = 'C'; - command[2] = 'P'; - command[3] = 'T'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make the TO text sequence. - /// - /// The reader to perform the operation on. - /// true if the TO text sequence could be made, false if not. - public bool TryMakeTo(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[2]; - command[0] = 'T'; - command[1] = 'O'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a DATA command from the given enumerator. - /// - /// The reader to perform the operation on. - /// The DATA command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeData(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeData) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (reader.TryMake(TryMakeEnd) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - command = _smtpCommandFactory.CreateData(); - return true; - } - - /// - /// Try to make the DATA text sequence. - /// - /// The reader to perform the operation on. - /// true if the DATA text sequence could be made, false if not. - public bool TryMakeData(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'D'; - command[1] = 'A'; - command[2] = 'T'; - command[3] = 'A'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a QUIT command. - /// - /// The reader to perform the operation on. - /// The QUIT command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeQuit(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeQuit) == false) - { - return false; - } - - if (TryMakeEnd(ref reader) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - command = _smtpCommandFactory.CreateQuit(); - return true; - } - - /// - /// Try to make the QUIT text sequence. - /// - /// The reader to perform the operation on. - /// true if the QUIT text sequence could be made, false if not. - public bool TryMakeQuit(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'Q'; - command[1] = 'U'; - command[2] = 'I'; - command[3] = 'T'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a NOOP command. - /// - /// The reader to perform the operation on. - /// The NOOP command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeNoop(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeNoop) == false) - { - return false; - } - - if (TryMakeEnd(ref reader) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - command = _smtpCommandFactory.CreateNoop(); - return true; - } - - /// - /// Try to make the NOOP text sequence. - /// - /// The reader to perform the operation on. - /// true if the NOOP text sequence could be made, false if not. - public bool TryMakeNoop(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'N'; - command[1] = 'O'; - command[2] = 'O'; - command[3] = 'P'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make a RSET command. - /// - /// The reader to perform the operation on. - /// The RSET command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeRset(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeRset) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (TryMakeEnd(ref reader) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - command = _smtpCommandFactory.CreateRset(); - return true; - } - - /// - /// Try to make the RSET text sequence. - /// - /// The reader to perform the operation on. - /// true if the RSET text sequence could be made, false if not. - public bool TryMakeRset(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'R'; - command[1] = 'S'; - command[2] = 'E'; - command[3] = 'T'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make an STARTTLS command from the given enumerator. - /// - /// The reader to perform the operation on. - /// The STARTTLS command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeStartTls(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeStartTls) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (TryMakeEnd(ref reader) == false) - { - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - command = _smtpCommandFactory.CreateStartTls(); - return true; - } - - /// - /// Try to make the STARTTLS text sequence. - /// - /// The reader to perform the operation on. - /// true if the STARTTLS text sequence could be made, false if not. - public bool TryMakeStartTls(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[8]; - command[0] = 'S'; - command[1] = 'T'; - command[2] = 'A'; - command[3] = 'R'; - command[4] = 'T'; - command[5] = 'T'; - command[6] = 'L'; - command[7] = 'S'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Make an AUTH command from the given enumerator. - /// - /// The reader to perform the operation on. - /// The AUTH command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeAuth(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeAuth) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (TryMakeAuthenticationMethod(ref reader, out var authenticationMethod) == false) - { - return false; - } - - reader.Take(); - - if (reader.TryMake(TryMakeEnd)) - { - command = _smtpCommandFactory.CreateAuth(authenticationMethod, null); - return true; - } - - if (reader.TryMake(TryMakeBase64, out var base64) == false) - { - command = _smtpCommandFactory.CreateAuth(authenticationMethod, StringUtil.Create(base64)); - return true; - } - - errorResponse = SmtpResponse.SyntaxError; - return false; - } - - /// - /// Try to make the Authentication method. - /// - /// The reader to perform the operation on. - /// The authentication method that was made. - /// true if the authentication method could be made, false if not. - public bool TryMakeAuthenticationMethod(ref TokenReader reader, out AuthenticationMethod authenticationMethod) - { - if (reader.TryMake(TryMakeLoginText)) - { - authenticationMethod = AuthenticationMethod.Login; - return true; - } - - if (reader.TryMake(TryMakePlainText)) - { - authenticationMethod = AuthenticationMethod.Plain; - return true; - } - - authenticationMethod = default; - return false; - } - - /// - /// Try to make the AUTH text sequence. - /// - /// The reader to perform the operation on. - /// true if the AUTH text sequence could be made, false if not. - public bool TryMakeAuth(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'A'; - command[1] = 'U'; - command[2] = 'T'; - command[3] = 'H'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make the LOGIN text sequence. - /// - /// The reader to perform the operation on. - /// true if the LOGIN text sequence could be made, false if not. - public bool TryMakeLoginText(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'L'; - command[1] = 'O'; - command[2] = 'G'; - command[3] = 'I'; - command[4] = 'N'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make the PLAIN text sequence. - /// - /// The reader to perform the operation on. - /// true if the PLAIN text sequence could be made, false if not. - public bool TryMakePlainText(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'P'; - command[1] = 'L'; - command[2] = 'A'; - command[3] = 'I'; - command[4] = 'N'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Support proxy protocol version 1 header for use with HAProxy. - /// Documented at http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt - /// - /// The reader to perform the operation on. - /// The PROXY command that is defined within the token enumerator. - /// The error that indicates why the command could not be made. - /// Returns true if a command could be made, false if not. - public bool TryMakeProxy(ref TokenReader reader, out SmtpCommand command, out SmtpResponse errorResponse) - { - // ABNF - // proxy = "PROXY" space ( unknown-proxy | tcp4-proxy | tcp6-proxy ) - // unknown-proxy = "UNKNOWN" - // tcp4-proxy = "TCP4" space ipv4-address-literal space ipv4-address-literal space ip-port-number space ip-port-number - // tcp6-proxy = "TCP6" space ipv6-address-literal space ipv6-address-literal space ip-port-number space ip-port-number - // space = " " - // ip-port = wnum - // wnum = 1*5DIGIT ; in the range of 0-65535 - - command = null; - errorResponse = null; - - if (reader.TryMake(TryMakeProxy) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - //if (TryMake(TryMakeUnknownProxy, out command)) - //{ - // return true; - //} - - //if (TryMake(TryMakeTcp4Proxy, out command)) - //{ - // return true; - //} - - //return TryMakeTcp6Proxy(out command); - - throw new NotImplementedException(); - } - - /// - /// Try to make the PROXY text sequence. - /// - /// The reader to perform the operation on. - /// true if the PROXY text sequence could be made, false if not. - public bool TryMakeProxy(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[4]; - command[0] = 'P'; - command[1] = 'R'; - command[2] = 'O'; - command[3] = 'X'; - command[4] = 'Y'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make the end of sequence. - /// - /// The reader to perform the operation on. - /// true if the end was made, false if not. - public bool TryMakeEnd(ref TokenReader reader) - { - reader.Skip(TokenKind.Space); - - return reader.Take() == default; - } - - /// - /// Try to make a reverse path. - /// - /// The reader to perform the operation on. - /// The mailbox that was made. - /// true if the reverse path was made, false if not. - /// "]]> - public bool TryMakeReversePath(ref TokenReader reader, out IMailbox mailbox) - { - if (reader.TryMake(TryMakePath, out mailbox)) - { - return true; - } - - if (TryMakeEmptyPath(ref reader)) - { - mailbox = Mailbox.Empty; - return true; - } - - return false; - } - - /// - /// Try to make an empty path. - /// - /// The reader to perform the operation on. - /// true if the empty path was made, false if not. - /// "]]> - public bool TryMakeEmptyPath(ref TokenReader reader) - { - if (reader.Take().Kind != TokenKind.LessThan) - { - return false; - } - - // not valid according to the spec but some senders do it - reader.Skip(TokenKind.Space); - - return reader.Take().Kind == TokenKind.GreaterThan; - } - - /// - /// Try to make a path. - /// - /// The reader to perform the operation on. - /// The mailbox that was made. - /// true if the path was made, false if not. - /// "]]> - public bool TryMakePath(ref TokenReader reader, out IMailbox mailbox) - { - mailbox = null; - - if (reader.Take().Kind != TokenKind.LessThan) - { - return false; - } - - // Note, the at-domain-list must be matched, but also must be ignored - // http://tools.ietf.org/html/rfc5321#appendix-C - if (reader.TryMake(TryMakeAtDomainList)) - { - // if the @domain list was matched then it needs to be followed by a colon - if (reader.Take().Kind != TokenKind.Colon) - { - return false; - } - } - - if (TryMakeMailbox(ref reader, out mailbox) == false) - { - return false; - } - - return reader.Take().Kind == TokenKind.GreaterThan; - } - - /// - /// Try to make an @domain list. - /// - /// The reader to perform the operation on. - /// true if the @domain list was made, false if not. - /// - public bool TryMakeAtDomainList(ref TokenReader reader) - { - if (TryMakeAtDomain(ref reader) == false) - { - return false; - } - - while (reader.Peek().Kind == TokenKind.Comma) - { - reader.Take(); - - if (TryMakeAtDomain(ref reader) == false) - { - return false; - } - } - - return true; - } - - /// - /// Try to make an @domain. - /// - /// The reader to perform the operation on. - /// true if the @domain was made, false if not. - /// - public bool TryMakeAtDomain(ref TokenReader reader) - { - if (reader.Take().Kind != TokenKind.At) - { - return false; - } - - return TryMakeDomain(ref reader); - } - - /// - /// Try to make a mailbox. - /// - /// The reader to perform the operation on. - /// The mailbox that was made. - /// true if the mailbox was made, false if not. - /// - public bool TryMakeMailbox(ref TokenReader reader, out IMailbox mailbox) - { - mailbox = null; - - if (reader.TryMake(TryMakeLocalPart, out var localpart) == false) - { - return false; - } - - if (reader.Take().Kind != TokenKind.At) - { - return false; - } - - if (reader.TryMake(TryMakeDomain, out var domain)) - { - mailbox = new Mailbox(StringUtil.Create(localpart), StringUtil.Create(domain)); - return true; - } - - if (reader.TryMake(TryMakeAddressLiteral, out var address)) - { - mailbox = new Mailbox(StringUtil.Create(localpart), StringUtil.Create(address)); - return true; - } - - return false; - } - - /// - /// Try to make a domain name. - /// - /// The reader to perform the operation on. - /// true if the domain name was made, false if not. - /// - public bool TryMakeDomain(ref TokenReader reader) - { - if (TryMakeSubdomain(ref reader) == false) - { - return false; - } - - while (reader.Peek().Kind == TokenKind.Period) - { - reader.Take(); - - if (TryMakeSubdomain(ref reader) == false) - { - return false; - } - } - - return true; - } - - /// - /// Try to make a subdomain name. - /// - /// The reader to perform the operation on. - /// true if the subdomain name was made, false if not. - /// - public bool TryMakeSubdomain(ref TokenReader reader) - { - if (TryMakeTextOrNumber(ref reader) == false) - { - return false; - } - - // this is optional - reader.TryMake(TryMakeTextOrNumberOrHyphenString); - - return true; - } - - /// - /// Try to make a address. - /// - /// The reader to perform the operation on. - /// true if the address was made, false if not. - /// - public bool TryMakeAddressLiteral(ref TokenReader reader) - { - if (reader.Take().Kind != TokenKind.LeftBracket) - { - return false; - } - - reader.Skip(TokenKind.Space); - - if (reader.TryMake(TryMakeIPv4AddressLiteral) == false && reader.TryMake(TryMakeIPv6AddressLiteral) == false) - { - return false; - } - - reader.Skip(TokenKind.Space); - - return reader.Take().Kind == TokenKind.RightBracket; - } - - /// - /// Try to make an IPv4 address literal. - /// - /// The reader to perform the operation on. - /// true if the address was made, false if not. - /// - public bool TryMakeIPv4AddressLiteral(ref TokenReader reader) - { - if (reader.TryMake(TryMakeSnum) == false) - { - return false; - } - - for (var i = 0; i < 3; i++) - { - if (reader.Take().Kind != TokenKind.Period) - { - return false; - } - - if (reader.TryMake(TryMakeSnum) == false) - { - return false; - } - } - - return true; - } - - /// - /// Try to make an Snum (number in the range of 0-255). - /// - /// The reader to perform the operation on. - /// true if the snum was made, false if not. - /// - public bool TryMakeSnum(ref TokenReader reader) - { - if (reader.TryMake(TryMakeNumber, out var number) == false) - { - return false; - } - - return int.TryParse(StringUtil.Create(number), out var snum) && snum >= 0 && snum <= 255; - } - - /// - /// Try to extract IPv6 address. https://tools.ietf.org/html/rfc4291 section 2.2 used for specification. - /// This method expects the address to have the IPv6: prefix. - /// - /// The reader to perform the operation on. - /// true if a valid Ipv6 address can be extracted. - public bool TryMakeIPv6AddressLiteral(ref TokenReader reader) - { - if (TryMakeIPv6(ref reader) == false) - { - return false; - } - - return TryMakeIPv6Address(ref reader); - } - - /// - /// Try to make Ip version from ip version tag which is a formatted text IPv[Version]: - /// - /// The reader to perform the operation on. - /// true if ip version tag can be extracted. - public bool TryMakeIPv6(ref TokenReader reader) - { - if (TryMakeIPv(ref reader) == false) - { - return false; - } - - var token = reader.Take(); - - if (token.Kind != TokenKind.Number || token.Text.Length > 1) - { - return false; - } - - return token.Text[0] == '6'; - } - - /// - /// Try to make the IPv text sequence. - /// - /// The reader to perform the operation on. - /// true if IPv text sequence can be made. - public bool TryMakeIPv(ref TokenReader reader) - { - if (reader.TryMake(TryMakeText, out var text)) - { - Span command = stackalloc char[3]; - command[0] = 'I'; - command[1] = 'P'; - command[2] = 'v'; - - return text.CaseInsensitiveStringEquals(ref command); - } - - return false; - } - - /// - /// Try to make an IPv6 address. - /// - /// The reader to perform the operation on. - /// true if the address was made, false if not. - /// - public bool TryMakeIPv6Address(ref TokenReader reader) - { - return reader.TryMake(TryMakeIPv6AddressRule1) - || reader.TryMake(TryMakeIPv6AddressRule2) - || reader.TryMake(TryMakeIPv6AddressRule3) - || reader.TryMake(TryMakeIPv6AddressRule4) - || reader.TryMake(TryMakeIPv6AddressRule5) - || reader.TryMake(TryMakeIPv6AddressRule6) - || reader.TryMake(TryMakeIPv6AddressRule7) - || reader.TryMake(TryMakeIPv6AddressRule8) - || reader.TryMake(TryMakeIPv6AddressRule9); - } - - public bool TryMakeIPv6AddressRule1(ref TokenReader reader) - { - // 6( h16 ":" ) ls32 - return TryMakeIPv6HexPostamble(ref reader, 6); - } - - bool TryMakeIPv6AddressRule2(ref TokenReader reader) - { - // "::" 5( h16 ":" ) ls32 - if (reader.Take().Kind != TokenKind.Colon || reader.Take().Kind != TokenKind.Colon) - { - return false; - } - - return TryMakeIPv6HexPostamble(ref reader, 5); - } - - bool TryMakeIPv6AddressRule3(ref TokenReader reader) - { - // [ h16 ] "::" 4( h16 ":" ) ls32 - if (TryMakeIPv6HexPreamble(ref reader, 1) == false) - { - return false; - } - - return TryMakeIPv6HexPostamble(ref reader, 4); - } - - bool TryMakeIPv6AddressRule4(ref TokenReader reader) - { - // [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - if (TryMakeIPv6HexPreamble(ref reader, 2) == false) - { - return false; - } - - return TryMakeIPv6HexPostamble(ref reader, 3); - } - - bool TryMakeIPv6AddressRule5(ref TokenReader reader) - { - // [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - if (TryMakeIPv6HexPreamble(ref reader, 3) == false) - { - return false; - } - - return TryMakeIPv6HexPostamble(ref reader, 2); - } - - bool TryMakeIPv6AddressRule6(ref TokenReader reader) - { - // [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - if (TryMakeIPv6HexPreamble(ref reader, 4) == false) - { - return false; - } - - return TryMakeIPv6HexPostamble(ref reader, 1); - } - - bool TryMakeIPv6AddressRule7(ref TokenReader reader) - { - // [ *4( h16 ":" ) h16 ] "::" ls32 - if (TryMakeIPv6HexPreamble(ref reader, 5) == false) - { - return false; - } - - return TryMakeIPv6Ls32(ref reader); - } - - bool TryMakeIPv6AddressRule8(ref TokenReader reader) - { - // [ *5( h16 ":" ) h16 ] "::" h16 - if (TryMakeIPv6HexPreamble(ref reader, 6) == false) - { - return false; - } - - return TryMake16BitHex(ref reader); - } - - bool TryMakeIPv6AddressRule9(ref TokenReader reader) - { - // [ *6( h16 ":" ) h16 ] "::" - return TryMakeIPv6HexPreamble(ref reader, 7); - } - - bool TryMakeIPv6HexPreamble(ref TokenReader reader, int maximum) - { - for (var i = 0; i < maximum; i++) - { - if (reader.TryMake(TryMakeTerminal)) - { - return true; - } - - if (i > 0) - { - if (reader.Take().Kind != TokenKind.Colon) - { - return false; - } - } - - if (TryMake16BitHex(ref reader) == false) - { - return false; - } - } - - return reader.TryMake(TryMakeTerminal); - - static bool TryMakeTerminal(ref TokenReader reader) - { - return reader.Take().Kind == TokenKind.Colon && reader.Take().Kind == TokenKind.Colon; - } - } - - bool TryMakeIPv6HexPostamble(ref TokenReader reader, int count) - { - while (count-- > 0) - { - if (TryMake16BitHex(ref reader) == false) - { - return false; - } - - if (reader.Take().Kind != TokenKind.Colon) - { - return false; - } - } - - return TryMakeIPv6Ls32(ref reader); - } - - bool TryMakeIPv6Ls32(ref TokenReader reader) - { - if (reader.TryMake(TryMakeIPv4AddressLiteral)) - { - return true; - } - - if (TryMake16BitHex(ref reader) == false) - { - return false; - } - - if (reader.Take().Kind != TokenKind.Colon) - { - return false; - } - - return TryMake16BitHex(ref reader); - } - - /// - /// Try to make 16 bit hex number. - /// - /// The token reader to perform the operation on. - /// true if valid hex number can be extracted. - public bool TryMake16BitHex(ref TokenReader reader) - { - var hexLength = 0L; - - var token = reader.Peek(); - while ((token.Kind == TokenKind.Text || token.Kind == TokenKind.Number) && hexLength < 4) - { - if (token.Kind == TokenKind.Text && IsHex(ref token) == false) - { - return false; - } - - hexLength += reader.Take().Text.Length; - - token = reader.Peek(); - } - - return hexLength > 0 && hexLength <= 4; - - static bool IsHex(ref Token token) - { - var span = token.Text; - - return span.IsHex(); - } - } - - /// - /// Try to make a text/number/hyphen string. - /// - /// The reader to perform the operatio on. - /// true if a text, number or hyphen was made, false if not. - /// - public bool TryMakeTextOrNumberOrHyphenString(ref TokenReader reader) - { - var token = reader.Peek(); - - if (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token.Kind == TokenKind.Hyphen) - { - reader.Skip(kind => kind == TokenKind.Text || kind == TokenKind.Number || kind == TokenKind.Hyphen); - return true; - } - - return false; - } - - /// - /// Try to make a text or number - /// - /// The reader to perform the operatio on. - /// true if the text or number was made, false if not. - /// - public bool TryMakeTextOrNumber(ref TokenReader reader) - { - var token = reader.Peek(); - - if (token.Kind == TokenKind.Text) - { - return TryMakeText(ref reader); - } - - if (token.Kind == TokenKind.Number) - { - return TryMakeNumber(ref reader); - } - - return false; - } - - /// - /// Try to make the local part of the path. - /// - /// The reader to perform the operatio on. - /// true if the local part was made, false if not. - /// - public bool TryMakeLocalPart(ref TokenReader reader) - { - if (reader.TryMake(TryMakeDotString)) - { - return true; - } - - return TryMakeQuotedString(ref reader); - } - - /// - /// Try to make a dot-string from the tokens. - /// - /// The reader to perform the operation on. - /// true if the dot-string was made, false if not. - /// - public bool TryMakeDotString(ref TokenReader reader) - { - if (TryMakeAtom(ref reader) == false) - { - return false; - } - - while (reader.Peek().Kind == TokenKind.Period) - { - reader.Take(); - - if (TryMakeAtom(ref reader) == false) - { - return false; - } - } - - return true; - } - - /// - /// Try to make a quoted-string from the tokens. - /// - /// The reader to perform the operation on. - /// true if the quoted-string was made, false if not. - /// - public bool TryMakeQuotedString(ref TokenReader reader) - { - if (reader.Take().Kind != TokenKind.Quote) - { - return false; - } - - while (reader.Peek().Kind != TokenKind.Quote) - { - if (TryMakeQContentSmtp(ref reader) == false) - { - return false; - } - } - - return reader.Take().Kind == TokenKind.Quote; - } - - /// - /// Try to make a QcontentSMTP from the tokens. - /// - /// The reader to perform the operation on. - /// true if the quoted content was made, false if not. - /// - public bool TryMakeQContentSmtp(ref TokenReader reader) - { - if (TryMakeQTextSmtp(ref reader)) - { - return true; - } - - return TryMakeQuotedPairSmtp(ref reader); - } - - /// - /// Try to make a QTextSMTP from the tokens. - /// - /// The reader to perform the operation on. - /// true if the quoted text was made, false if not. - /// - public bool TryMakeQTextSmtp(ref TokenReader reader) - { - switch (reader.Peek().Kind) - { - case TokenKind.Text: - return TryMakeText(ref reader); - - case TokenKind.Number: - return TryMakeNumber(ref reader); - - default: - var token = reader.Take(); - switch ((char)token.Text[0]) - { - case ' ': - case '!': - case '#': - case '$': - case '%': - case '&': - case '\'': - case '(': - case ')': - case '*': - case '+': - case ',': - case '-': - case '.': - case '/': - case ':': - case ';': - case '<': - case '=': - case '>': - case '?': - case '@': - case '[': - case ']': - case '^': - case '_': - case '`': - case '{': - case '|': - case '}': - case '~': - return true; - } - break; - } - - return false; - } - - /// - /// Try to make a quoted pair from the tokens. - /// - /// The reader to perform the operation on. - /// true if the quoted pair was made, false if not. - /// - public bool TryMakeQuotedPairSmtp(ref TokenReader reader) - { - if (reader.Take().Kind != TokenKind.Backslash) - { - return false; - } - - return TryMakeText(ref reader); - } - - /// - /// Try to make an "Atom" from the tokens. - /// - /// The reader to perform the operation on. - /// true if the atom was made, false if not. - /// - public bool TryMakeAtom(ref TokenReader reader) - { - var count = 0; - - while (reader.TryMake(TryMakeAtext)) - { - count++; - } - - return count >= 1; - } - - /// - /// Try to make an "Atext" from the tokens. - /// - /// The reader to perform the operation on. - /// true if the atext was made, false if not. - /// - public bool TryMakeAtext(ref TokenReader reader) - { - switch (reader.Peek().Kind) - { - case TokenKind.Text: - return TryMakeText(ref reader); - - case TokenKind.Number: - return TryMakeNumber(ref reader); - - default: - var token = reader.Take(); - switch ((char)token.Text[0]) - { - case '!': - case '#': - case '%': - case '&': - case '\'': - case '*': - case '-': - case '/': - case '?': - case '_': - case '{': - case '}': - case '$': - case '+': - case '=': - case '^': - case '`': - case '|': - case '~': - return true; - } - break; - } - - return false; - } - - /// - /// Try to make an Mail-Parameters from the tokens. - /// - /// The reader to perform the operation on. - /// The mail parameters that were made. - /// true if the mail parameters can be made, false if not. - /// - public bool TryMakeMailParameters(ref TokenReader reader, out IReadOnlyDictionary parameters) - { - Dictionary dictionary = null; - - while (reader.Peek() != default) - { - if (reader.TryMake(TryMakeEsmtpParameter, out ReadOnlySequence keyword, out ReadOnlySequence value) == false) - { - parameters = null; - return false; - } - - dictionary ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - dictionary.Add(StringUtil.Create(keyword), StringUtil.Create(value)); - - reader.Skip(TokenKind.Space); - } - - parameters = dictionary; - return parameters?.Count > 0; - } - - /// - /// Try to make an Esmtp-Parameter from the tokens. - /// - /// The reader to perform the operation on. - /// The keyword that was made. - /// The value that was made. - /// true if the esmtp-parameter can be made, false if not. - /// - public bool TryMakeEsmtpParameter(ref TokenReader reader, out ReadOnlySequence keyword, out ReadOnlySequence value) - { - value = default; - - if (reader.TryMake(TryMakeEsmtpKeyword, out keyword) == false) - { - return false; - } - - if (reader.Peek().Kind == TokenKind.None || reader.Peek().Kind == TokenKind.Space) - { - return true; - } - - if (reader.Take().Kind != TokenKind.Equal) - { - return false; - } - - return reader.TryMake(TryMakeEsmtpValue, out value); - } - - /// - /// Try to make an Esmtp-Keyword from the tokens. - /// - /// The reader to perform the operation on. - /// true if the esmtp-keyword can be made, false if not. - /// - public bool TryMakeEsmtpKeyword(ref TokenReader reader) - { - var token = reader.Take(); - if (token.Kind != TokenKind.Text && token.Kind != TokenKind.Number) - { - return false; - } - - token = reader.Peek(); - while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token.Kind == TokenKind.Hyphen) - { - reader.Take(); - token = reader.Peek(); - } - - return true; - } - - /// - /// Try to make an Esmtp-Value from the tokens. - /// - /// The reader to perform the operation on. - /// true if the esmtp-value can be made, false if not. - /// - public bool TryMakeEsmtpValue(ref TokenReader reader) - { - var token = reader.Take(); - if (token.Kind == TokenKind.None || IsValid(ref token) == false) - { - return false; - } - - token = reader.Peek(); - while (token.Kind != TokenKind.None && IsValid(ref token)) - { - reader.Take(); - - token = reader.Peek(); - } - - return true; - - static bool IsValid(ref Token token) - { - var span = token.Text; - - for (var i = 0; i < span.Length; i++) - { - if ((span[i] < 33 || span[i] > 60) && (span[i] < 62 || span[i] > 127)) - { - return false; - } - } - - return true; - } - } - - /// - /// Try to make a base64 encoded string. - /// - /// The reader to perform the operation on. - /// true if the base64 encoded string can be made, false if not. - /// - public bool TryMakeBase64(ref TokenReader reader) - { - if (TryMakeBase64Text(ref reader) == false) - { - return false; - } - - if (reader.Peek().Kind == TokenKind.Equal) - { - reader.Take(); - } - - if (reader.Peek().Kind == TokenKind.Equal) - { - reader.Take(); - } - - return true; - } - - /// - /// Try to make a base64 encoded string. - /// - /// The reader to perform the operation on. - /// true if the base64 encoded string can be made, false if not. - /// - public bool TryMakeBase64Text(ref TokenReader reader) - { - var count = 0; - - while (reader.TryMake(TryMakeBase64Chars)) - { - count++; - } - - return count > 0; - } - - /// - /// Try to make the allowable characters in a base64 encoded string. - /// - /// The reader to perform the operation on. - /// true if the base64-chars can be made, false if not. - /// - public bool TryMakeBase64Chars(ref TokenReader reader) - { - var token = reader.Take(); - - switch (token.Kind) - { - case TokenKind.Text: - case TokenKind.Number: - case TokenKind.Slash: - case TokenKind.Plus: - return true; - } - - return false; - } - - /// - /// Try to make a text sequence. - /// - /// The reader to perform the operation on. - /// true if a text sequence could be made, false if not. - public bool TryMakeText(ref TokenReader reader) - { - if (reader.Peek().Kind == TokenKind.Text) - { - reader.Skip(TokenKind.Text); - return true; - } - - return false; - } - - /// - /// Try to make a number sequence. - /// - /// The reader to perform the operation on. - /// true if a number sequence could be made, false if not. - public bool TryMakeNumber(ref TokenReader reader) - { - if (reader.Peek().Kind == TokenKind.Number) - { - reader.Skip(TokenKind.Number); - return true; - } - - return false; - } - } -} \ No newline at end of file diff --git a/Src/SmtpServer/Protocol/SmtpParserOld.cs b/Src/SmtpServer/Protocol/SmtpParserOld.cs new file mode 100644 index 0000000..6b46527 --- /dev/null +++ b/Src/SmtpServer/Protocol/SmtpParserOld.cs @@ -0,0 +1,1618 @@ +//using System; +//using System.Collections.Generic; +//using System.Globalization; +//using System.Linq; +//using System.Net; +//using SmtpServer.Mail; +//using SmtpServer.Text; + +//namespace SmtpServer.Protocol +//{ +// /// +// /// This class is responsible for parsing the SMTP command arguments according to the ANBF described in +// /// the RFC http://tools.ietf.org/html/rfc5321#section-4.1.2 +// /// +// public class SmtpParser : TokenParser +// { +// #region Tokens + +// static class Tokens +// { +// // ReSharper disable InconsistentNaming +// internal static readonly Token Hyphen = Token.Create('-'); +// internal static readonly Token Colon = Token.Create(':'); +// internal static readonly Token LessThan = Token.Create('<'); +// internal static readonly Token GreaterThan = Token.Create('>'); +// internal static readonly Token Comma = Token.Create(','); +// internal static readonly Token At = Token.Create('@'); +// internal static readonly Token Period = Token.Create('.'); +// internal static readonly Token LeftBracket = Token.Create('['); +// internal static readonly Token RightBracket = Token.Create(']'); +// internal static readonly Token Quote = Token.Create('"'); +// internal static readonly Token Equal = Token.Create('='); +// internal static readonly Token BackSlash = Token.Create('\\'); + +// internal static class Text +// { +// internal static readonly Token From = Token.Create("FROM"); +// internal static readonly Token To = Token.Create("TO"); +// internal static readonly Token IpVersionTag = Token.Create("IPv"); +// internal static readonly Token Unknown = Token.Create("UNKNOWN"); +// internal static readonly Token Tcp = Token.Create("TCP"); +// } + +// internal static class Numbers +// { +// internal static readonly Token Four = Token.Create(TokenKind.Number, "4"); +// internal static readonly Token Six = Token.Create(TokenKind.Number, "6"); +// } +// // ReSharper restore InconsistentNaming +// } + +// #endregion + +// readonly ISmtpServerOptions _options; + +// /// +// /// Constructor. +// /// +// /// The SMTP server options. +// /// The token enumerator to handle the incoming tokens. +// public SmtpParser(ISmtpServerOptions options, ITokenEnumerator enumerator) : base(enumerator) +// { +// _options = options; +// } + +// /// +// /// Make a QUIT command. +// /// +// /// The QUIT command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeQuit(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); + +// if (TryMakeEnd() == false) +// { +// _options.Logger.LogVerbose("QUIT command can not have parameters."); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new QuitCommand(_options); +// return true; +// } + +// /// +// /// Make an NOOP command from the given enumerator. +// /// +// /// The NOOP command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeNoop(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); + +// if (TryMakeEnd() == false) +// { +// _options.Logger.LogVerbose("NOOP command can not have parameters."); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new NoopCommand(_options); +// return true; +// } + +// /// +// /// Make an RSET command from the given enumerator. +// /// +// /// The RSET command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeRset(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); + +// if (TryMakeEnd() == false) +// { +// _options.Logger.LogVerbose("RSET command can not have parameters."); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new RsetCommand(_options); +// return true; +// } + +// /// +// /// Make a HELO command from the given enumerator. +// /// +// /// The HELO command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeDomain(out var domain)) +// { +// command = new HeloCommand(_options, domain); +// return true; +// } + +// // according to RFC5321 the HELO command should only accept the Domain +// // and not the address literal, however some mail clients will send the +// // address literal and there is no harm in accepting it +// if (TryMakeAddressLiteral(out var address)) +// { +// command = new HeloCommand(_options, address); +// return true; +// } + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// /// +// /// Make an EHLO command from the given enumerator. +// /// +// /// The EHLO command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeEhlo(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeDomain(out var domain)) +// { +// command = new EhloCommand(_options, domain); +// return true; +// } + +// if (TryMakeAddressLiteral(out var address)) +// { +// command = new EhloCommand(_options, address); +// return true; +// } + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// /// +// /// Make a MAIL command from the given enumerator. +// /// +// /// The MAIL command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeMail(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (Enumerator.Take() != Tokens.Text.From || Enumerator.Take() != Tokens.Colon) +// { +// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); +// return false; +// } + +// // according to the spec, whitespace isnt allowed here but most servers send it +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeReversePath(out var mailbox) == false) +// { +// _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); + +// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); +// return false; +// } + +// Enumerator.Skip(TokenKind.Space); + +// // match the optional (ESMTP) parameters +// if (TryMakeMailParameters(out var parameters) == false) +// { +// parameters = new Dictionary(); +// } + +// command = new MailCommand(_options, mailbox, parameters); +// return true; +// } + +// /// +// /// Make a RCTP command from the given enumerator. +// /// +// /// The RCTP command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeRcpt(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (Enumerator.Take() != Tokens.Text.To || Enumerator.Take() != Tokens.Colon) +// { +// errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); +// return false; +// } + +// // according to the spec, whitespace isnt allowed here anyway +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakePath(out var mailbox) == false) +// { +// _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// // TODO: support optional service extension parameters here + +// command = new RcptCommand(_options, mailbox); +// return true; +// } + +// /// +// /// Support proxy protocol version 1 header for use with HAProxy. +// /// Documented at http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt +// /// +// /// The PROXY command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeProxy(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// // ABNF +// // proxy = "PROXY" space ( unknown-proxy | tcp4-proxy | tcp6-proxy ) +// // unknown-proxy = "UNKNOWN" +// // tcp4-proxy = "TCP4" space ipv4-address-literal space ipv4-address-literal space ip-port-number space ip-port-number +// // tcp6-proxy = "TCP6" space ipv6-address-literal space ipv6-address-literal space ip-port-number space ip-port-number +// // space = " " +// // ip-port = wnum +// // wnum = 1*5DIGIT ; in the range of 0-65535 + +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (TryMake(TryMakeUnknownProxy, out command)) +// { +// return true; +// } + +// if (TryMake(TryMakeTcp4Proxy, out command)) +// { +// return true; +// } + +// return TryMakeTcp6Proxy(out command); +// } + +// /// +// /// Attempt to make the Unknown Proxy command. +// /// +// /// The command that was made. +// /// true if the command was made, false if not. +// bool TryMakeUnknownProxy(out SmtpCommand command) +// { +// command = new ProxyCommand(_options); + +// return Enumerator.Take() == Tokens.Text.Unknown; +// } + +// bool TryMakeTcp4Proxy(out SmtpCommand command) +// { +// command = null; + +// if (Enumerator.Take() != Tokens.Text.Tcp) +// { +// return false; +// } + +// if (Enumerator.Take() != Tokens.Numbers.Four) +// { +// return false; +// } + +// return TryMakeProxyAddresses(TryMakeIPv4AddressLiteral, out command); +// } + +// bool TryMakeTcp6Proxy(out SmtpCommand command) +// { +// command = null; + +// if (Enumerator.Take() != Tokens.Text.Tcp) +// { +// return false; +// } + +// if (Enumerator.Take() != Tokens.Numbers.Six) +// { +// return false; +// } + +// return TryMakeProxyAddresses(TryMakeIPv6Address, out command); +// } + +// bool TryMakeProxyAddresses(TryMakeDelegate tryMakeAddressDelegate, out SmtpCommand command) +// { +// command = null; + +// Enumerator.Skip(TokenKind.Space); + +// if (tryMakeAddressDelegate(out var sourceAddress) == false) +// { +// return false; +// } + +// Enumerator.Skip(TokenKind.Space); + +// if (tryMakeAddressDelegate(out var destinationAddress) == false) +// { +// return false; +// } + +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeWnum(out var sourcePort) == false) +// { +// return false; +// } + +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeWnum(out var destinationPort) == false) +// { +// return false; +// } + +// command = new ProxyCommand(_options, new IPEndPoint(IPAddress.Parse(sourceAddress), sourcePort), new IPEndPoint(IPAddress.Parse(destinationAddress), destinationPort)); +// return true; +// } + +// bool TryMakeWnum(out int wnum) +// { +// wnum = default; + +// var token = Enumerator.Take(); + +// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out wnum)) +// { +// return wnum >= 0 && wnum <= 65535; +// } + +// return false; +// } + +// /// +// /// Make a DATA command from the given enumerator. +// /// +// /// The DATA command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeData(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeEnd() == false) +// { +// _options.Logger.LogVerbose("DATA command can not have parameters."); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new DataCommand(_options); +// return true; +// } + +// /// +// /// Make an STARTTLS command from the given enumerator. +// /// +// /// The STARTTLS command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeStartTls(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (TryMakeEnd() == false) +// { +// _options.Logger.LogVerbose("STARTTLS command can not have parameters."); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new StartTlsCommand(_options); +// return true; +// } + +// /// +// /// Make an AUTH command from the given enumerator. +// /// +// /// The AUTH command that is defined within the token enumerator. +// /// The error that indicates why the command could not be made. +// /// Returns true if a command could be made, false if not. +// public bool TryMakeAuth(out SmtpCommand command, out SmtpResponse errorResponse) +// { +// command = null; +// errorResponse = null; + +// Enumerator.Take(); +// Enumerator.Skip(TokenKind.Space); + +// if (Enum.TryParse(Enumerator.Take().Text, true, out AuthenticationMethod method) == false) +// { +// _options.Logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// Enumerator.Take(); + +// string parameter = null; +// if (TryMake(TryMakeEnd) == false && TryMakeBase64(out parameter) == false) +// { +// _options.Logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); + +// errorResponse = SmtpResponse.SyntaxError; +// return false; +// } + +// command = new AuthCommand(_options, method, parameter); +// return true; +// } + +// /// +// /// Try to make a reverse path. +// /// +// /// The reverse path that was made, or undefined if it was not made. +// /// true if the reverse path was made, false if not. +// /// "]]> +// public bool TryMakeReversePath(out IMailbox mailbox) +// { +// if (TryMake(TryMakePath, out mailbox)) +// { +// return true; +// } + +// if (Enumerator.Take() != Tokens.LessThan) +// { +// return false; +// } + +// // not valid according to the spec but some senders do it +// Enumerator.Skip(TokenKind.Space); + +// if (Enumerator.Take() != Tokens.GreaterThan) +// { +// return false; +// } + +// mailbox = Mailbox.Empty; + +// return true; +// } + +// /// +// /// Try to make a path. +// /// +// /// The path that was made, or undefined if it was not made. +// /// true if the path was made, false if not. +// /// "]]> +// public bool TryMakePath(out IMailbox mailbox) +// { +// mailbox = Mailbox.Empty; + +// if (Enumerator.Take() != Tokens.LessThan) +// { +// return false; +// } + +// // Note, the at-domain-list must be matched, but also must be ignored +// // http://tools.ietf.org/html/rfc5321#appendix-C +// if (TryMake(TryMakeAtDomainList, out string atDomainList)) +// { +// // if the @domain list was matched then it needs to be followed by a colon +// if (Enumerator.Take() != Tokens.Colon) +// { +// return false; +// } +// } + +// if (TryMake(TryMakeMailbox, out mailbox) == false) +// { +// return false; +// } + +// return Enumerator.Take() == Tokens.GreaterThan; +// } + +// /// +// /// Try to make an @domain list. +// /// +// /// The @domain list that was made, or undefined if it was not made. +// /// true if the @domain list was made, false if not. +// /// +// public bool TryMakeAtDomainList(out string atDomainList) +// { +// if (TryMake(TryMakeAtDomain, out atDomainList) == false) +// { +// return false; +// } + +// // match the optional list +// while (Enumerator.Peek() == Tokens.Comma) +// { +// Enumerator.Take(); + +// if (TryMake(TryMakeAtDomain, out string atDomain) == false) +// { +// return false; +// } + +// atDomainList += $",{atDomain}"; +// } + +// return true; +// } + +// /// +// /// Try to make an @domain. +// /// +// /// The @domain that was made, or undefined if it was not made. +// /// true if the @domain was made, false if not. +// /// +// public bool TryMakeAtDomain(out string atDomain) +// { +// atDomain = null; + +// if (Enumerator.Take() != Tokens.At) +// { +// return false; +// } + +// if (TryMake(TryMakeDomain, out string domain) == false) +// { +// return false; +// } + +// atDomain = $"@{domain}"; + +// return true; +// } + +// /// +// /// Try to make a mailbox. +// /// +// /// The mailbox that was made, or undefined if it was not made. +// /// true if the mailbox was made, false if not. +// /// +// public bool TryMakeMailbox(out IMailbox mailbox) +// { +// mailbox = Mailbox.Empty; + +// if (TryMake(TryMakeLocalPart, out string localpart) == false) +// { +// return false; +// } + +// if (Enumerator.Take() != Tokens.At) +// { +// return false; +// } + +// if (TryMake(TryMakeDomain, out string domain)) +// { +// mailbox = new Mailbox(localpart, domain); +// return true; +// } + +// if (TryMake(TryMakeAddressLiteral, out string address)) +// { +// mailbox = new Mailbox(localpart, address); +// return true; +// } + +// return false; +// } + +// /// +// /// Try to make a domain name. +// /// +// /// The domain name that was made, or undefined if it was not made. +// /// true if the domain name was made, false if not. +// /// +// public bool TryMakeDomain(out string domain) +// { +// if (TryMake(TryMakeSubdomain, out domain) == false) +// { +// return false; +// } + +// while (Enumerator.Peek() == Tokens.Period) +// { +// Enumerator.Take(); + +// if (TryMake(TryMakeSubdomain, out string subdomain) == false) +// { +// return false; +// } + +// domain += string.Concat(".", subdomain); +// } + +// return true; +// } + +// /// +// /// Try to make a subdomain name. +// /// +// /// The subdomain name that was made, or undefined if it was not made. +// /// true if the subdomain name was made, false if not. +// /// +// public bool TryMakeSubdomain(out string subdomain) +// { +// if (TryMake(TryMakeTextOrNumber, out subdomain) == false) +// { +// return false; +// } + +// if (TryMake(TryMakeTextOrNumberOrHyphenString, out string letterNumberHyphen) == false) +// { +// return subdomain != null; +// } + +// subdomain += letterNumberHyphen; + +// return true; +// } + +// /// +// /// Try to make a address. +// /// +// /// The address that was made, or undefined if it was not made. +// /// true if the address was made, false if not. +// /// +// public bool TryMakeAddressLiteral(out string address) +// { +// address = null; + +// if (Enumerator.Take() != Tokens.LeftBracket) +// { +// return false; +// } + +// // skip any whitespace +// Enumerator.Skip(TokenKind.Space); + +// if (TryMake(TryMakeIPv4AddressLiteral, out address) == false && TryMake(TryMakeIPv6AddressLiteral, out address) == false) +// { +// return false; +// } + +// // skip any whitespace +// Enumerator.Skip(TokenKind.Space); + +// if (Enumerator.Take() != Tokens.RightBracket) +// { +// return false; +// } + +// return address != null; +// } + +// /// +// /// Try to make an IPv4 address literal. +// /// +// /// The address that was made, or undefined if it was not made. +// /// true if the address was made, false if not. +// /// +// public bool TryMakeIPv4AddressLiteral(out string address) +// { +// address = null; + +// if (TryMake(TryMakeSnum, out int snum) == false) +// { +// return false; +// } + +// address = snum.ToString(CultureInfo.InvariantCulture); + +// for (var i = 0; i < 3; i++) +// { +// if (Enumerator.Take() != Tokens.Period) +// { +// return false; +// } + +// if (TryMake(TryMakeSnum, out snum) == false) +// { +// return false; +// } + +// address = string.Concat(address, '.', snum); +// } + +// return true; +// } + +// /// +// /// Try to make an Snum (number in the range of 0-255). +// /// +// /// The snum that was made, or undefined if it was not made. +// /// true if the snum was made, false if not. +// /// +// public bool TryMakeSnum(out int snum) +// { +// snum = default; + +// var token = Enumerator.Take(); + +// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out snum)) +// { +// return snum >= 0 && snum <= 255; +// } + +// return false; +// } + +// /// +// /// Try to make Ip version from ip version tag which is a formatted text IPv[Version]: +// /// +// /// IP version. IPv6 is supported atm. +// /// true if ip version tag can be extracted. +// public bool TryMakeIpVersion(out int version) +// { +// version = default; + +// if (Enumerator.Take() != Tokens.Text.IpVersionTag) +// { +// return false; +// } + +// var token = Enumerator.Take(); + +// if (token.Kind == TokenKind.Number && int.TryParse(token.Text, out var v)) +// { +// version = v; +// return Enumerator.Take() == Tokens.Colon; +// } + +// return false; +// } + +// /// +// /// Try to make 16 bit hex number. +// /// +// /// Extracted hex number. +// /// true if valid hex number can be extracted. +// public bool TryMake16BitHex(out string hex) +// { +// hex = string.Empty; + +// var token = Enumerator.Peek(); +// while ((token.Kind == TokenKind.Text || token.Kind == TokenKind.Number) && hex.Length < 4) +// { +// if (token.Kind == TokenKind.Text && IsHex(token.Text) == false) +// { +// return false; +// } + +// hex = string.Concat(hex, token.Text); + +// Enumerator.Take(); +// token = Enumerator.Peek(); +// } + +// return hex.Length > 0 && hex.Length <= 4; + +// bool IsHex(string text) +// { +// return text.ToUpperInvariant().All(c => c >= 'A' && c <= 'F'); +// } +// } + +// /// +// /// Try to extract IPv6 address. https://tools.ietf.org/html/rfc4291 section 2.2 used for specification. +// /// This method expects the address to have the IPv6: prefix. +// /// +// /// Extracted Ipv6 address. +// /// true if a valid Ipv6 address can be extracted. +// public bool TryMakeIPv6AddressLiteral(out string address) +// { +// address = null; + +// if (TryMake(TryMakeIpVersion, out int ipVersion) == false || ipVersion != 6) +// { +// return false; +// } + +// return TryMakeIPv6Address(out address); +// } + +// /// +// /// Try to make an IPv6 address. +// /// +// /// The address that was made, or undefined if it was not made. +// /// true if the address was made, false if not. +// /// +// public bool TryMakeIPv6Address(out string address) +// { +// return TryMake(TryMakeIPv6AddressRule1, out address) +// || TryMake(TryMakeIPv6AddressRule2, out address) +// || TryMake(TryMakeIPv6AddressRule3, out address) +// || TryMake(TryMakeIPv6AddressRule4, out address) +// || TryMake(TryMakeIPv6AddressRule5, out address) +// || TryMake(TryMakeIPv6AddressRule6, out address) +// || TryMake(TryMakeIPv6AddressRule7, out address) +// || TryMake(TryMakeIPv6AddressRule8, out address) +// || TryMake(TryMakeIPv6AddressRule9, out address); +// } + +// bool TryMakeIPv6AddressRule1(out string address) +// { +// // 6( h16 ":" ) ls32 +// return TryMakeIPv6HexPostamble(6, out address); +// } + +// bool TryMakeIPv6AddressRule2(out string address) +// { +// address = null; + +// // "::" 5( h16 ":" ) ls32 +// if (Enumerator.Take() != Tokens.Colon || Enumerator.Take() != Tokens.Colon) +// { +// return false; +// } + +// if (TryMakeIPv6HexPostamble(5, out var hexPostamble) == false) +// { +// return false; +// } + +// address = "::" + hexPostamble; +// return true; +// } + +// bool TryMakeIPv6AddressRule3(out string address) +// { +// // [ h16 ] "::" 4( h16 ":" ) ls32 +// address = null; + +// if (TryMakeIPv6HexPreamble(1, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMakeIPv6HexPostamble(4, out var hexPostamble) == false) +// { +// return false; +// } + +// address = hexPreamble + hexPostamble; +// return true; +// } + +// bool TryMakeIPv6AddressRule4(out string address) +// { +// // [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 +// address = null; + +// if (TryMakeIPv6HexPreamble(2, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMakeIPv6HexPostamble(3, out var hexPostamble) == false) +// { +// return false; +// } + +// address = hexPreamble + hexPostamble; +// return true; +// } + +// bool TryMakeIPv6AddressRule5(out string address) +// { +// // [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 +// address = null; + +// if (TryMakeIPv6HexPreamble(3, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMakeIPv6HexPostamble(2, out var hexPostamble) == false) +// { +// return false; +// } + +// address = hexPreamble + hexPostamble; +// return true; +// } + +// bool TryMakeIPv6AddressRule6(out string address) +// { +// // [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 +// address = null; + +// if (TryMakeIPv6HexPreamble(4, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMakeIPv6HexPostamble(1, out var hexPostamble) == false) +// { +// return false; +// } + +// address = hexPreamble + hexPostamble; +// return true; +// } + +// bool TryMakeIPv6AddressRule7(out string address) +// { +// // [ *4( h16 ":" ) h16 ] "::" ls32 +// address = null; + +// if (TryMakeIPv6HexPreamble(5, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMakeIPv6Ls32(out var ls32) == false) +// { +// return false; +// } + +// address = hexPreamble + ls32; +// return true; +// } + +// bool TryMakeIPv6AddressRule8(out string address) +// { +// // [ *5( h16 ":" ) h16 ] "::" h16 +// address = null; + +// if (TryMakeIPv6HexPreamble(6, out var hexPreamble) == false) +// { +// return false; +// } + +// if (TryMake16BitHex(out var hex) == false) +// { +// return false; +// } + +// address = hexPreamble + hex; +// return true; +// } + +// bool TryMakeIPv6AddressRule9(out string address) +// { +// // [ *6( h16 ":" ) h16 ] "::" +// return TryMakeIPv6HexPreamble(7, out address); +// } + +// bool TryMakeIPv6HexPreamble(int maximum, out string hexPreamble) +// { +// hexPreamble = null; + +// for (var i = 0; i < maximum; i++) +// { +// if (TryMake(TryMakeTerminal)) +// { +// hexPreamble += "::"; +// return true; +// } + +// if (i > 0) +// { +// if (Enumerator.Take() != Tokens.Colon) +// { +// return false; +// } + +// hexPreamble += ":"; +// } + +// if (TryMake16BitHex(out var hex) == false) +// { +// return false; +// } + +// hexPreamble += hex; +// } + +// hexPreamble += "::"; + +// return TryMake(TryMakeTerminal); + +// bool TryMakeTerminal() +// { +// return Enumerator.Take() == Tokens.Colon && Enumerator.Take() == Tokens.Colon; +// } +// } + +// bool TryMakeIPv6HexPostamble(int count, out string hexPostamble) +// { +// hexPostamble = null; + +// while (count-- > 0) +// { +// if (TryMake16BitHex(out var hex) == false) +// { +// return false; +// } + +// hexPostamble += hex; + +// if (Enumerator.Take() != Tokens.Colon) +// { +// return false; +// } + +// hexPostamble += ":"; +// } + +// if (TryMakeIPv6Ls32(out var ls32) == false) +// { +// return false; +// } + +// hexPostamble += ls32; +// return true; +// } + +// bool TryMakeIPv6Ls32(out string address) +// { +// if (TryMake(TryMakeIPv4AddressLiteral, out address)) +// { +// return true; +// } + +// if (TryMake16BitHex(out var hex1) == false) +// { +// return false; +// } + +// if (Enumerator.Take() != Tokens.Colon) +// { +// return false; +// } + +// if (TryMake16BitHex(out var hex2) == false) +// { +// return false; +// } + +// address = hex1 + ":" + hex2; +// return true; +// } + +// /// +// /// Try to make a text/number/hyphen string. +// /// +// /// The text, number, or hyphen that was matched, or undefined if it was not matched. +// /// true if a text, number or hyphen was made, false if not. +// /// +// public bool TryMakeTextOrNumberOrHyphenString(out string textOrNumberOrHyphenString) +// { +// textOrNumberOrHyphenString = null; + +// var token = Enumerator.Peek(); +// while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token == Tokens.Hyphen) +// { +// textOrNumberOrHyphenString += Enumerator.Take().Text; + +// token = Enumerator.Peek(); +// } + +// // can not end with a hyphen +// return textOrNumberOrHyphenString != null && token != Tokens.Hyphen; +// } + +// /// +// /// Try to make a text or number +// /// +// /// The text or number that was made, or undefined if it was not made. +// /// true if the text or number was made, false if not. +// /// +// public bool TryMakeTextOrNumber(out string textOrNumber) +// { +// var token = Enumerator.Take(); + +// textOrNumber = token.Text; + +// return token.Kind == TokenKind.Text || token.Kind == TokenKind.Number; +// } + +// /// +// /// Try to make the local part of the path. +// /// +// /// The local part that was made, or undefined if it was not made. +// /// true if the local part was made, false if not. +// /// +// public bool TryMakeLocalPart(out string localPart) +// { +// if (TryMake(TryMakeDotString, out localPart)) +// { +// return true; +// } + +// return TryMakeQuotedString(out localPart); +// } + +// /// +// /// Try to make a dot-string from the tokens. +// /// +// /// The dot-string that was made, or undefined if it was not made. +// /// true if the dot-string was made, false if not. +// /// +// public bool TryMakeDotString(out string dotString) +// { +// if (TryMake(TryMakeAtom, out dotString) == false) +// { +// return false; +// } + +// while (Enumerator.Peek() == Tokens.Period) +// { +// // skip the punctuation +// Enumerator.Take(); + +// if (TryMake(TryMakeAtom, out string atom) == false) +// { +// return true; +// } + +// dotString += string.Concat(".", atom); +// } + +// return true; +// } + +// /// +// /// Try to make a quoted-string from the tokens. +// /// +// /// The quoted-string that was made, or undefined if it was not made. +// /// true if the quoted-string was made, false if not. +// /// +// public bool TryMakeQuotedString(out string quotedString) +// { +// quotedString = null; + +// if (Enumerator.Take() != Tokens.Quote) +// { +// return false; +// } + +// while (Enumerator.Peek() != Tokens.Quote) +// { +// if (TryMakeQContentSmtp(out var text) == false) +// { +// return false; +// } + +// quotedString += text; +// } + +// return Enumerator.Take() == Tokens.Quote; +// } + +// /// +// /// Try to make a QcontentSMTP from the tokens. +// /// +// /// The text that was made. +// /// true if the quoted content was made, false if not. +// /// +// public bool TryMakeQContentSmtp(out string text) +// { +// if (TryMake(TryMakeQTextSmtp, out text)) +// { +// return true; +// } + +// return TryMakeQuotedPairSmtp(out text); +// } + +// /// +// /// Try to make a QTextSMTP from the tokens. +// /// +// /// The text that was made. +// /// true if the quoted text was made, false if not. +// /// +// public bool TryMakeQTextSmtp(out string text) +// { +// text = null; + +// var token = Enumerator.Take(); +// switch (token.Kind) +// { +// case TokenKind.Text: +// case TokenKind.Number: +// text += token.Text; +// return true; + +// case TokenKind.Space: +// case TokenKind.Other: +// switch (token.Text[0]) +// { +// case ' ': +// case '!': +// case '#': +// case '$': +// case '%': +// case '&': +// case '\'': +// case '(': +// case ')': +// case '*': +// case '+': +// case ',': +// case '-': +// case '.': +// case '/': +// case ':': +// case ';': +// case '<': +// case '=': +// case '>': +// case '?': +// case '@': +// case '[': +// case ']': +// case '^': +// case '_': +// case '`': +// case '{': +// case '|': +// case '}': +// case '~': +// text += token.Text[0]; +// return true; +// } + +// return false; +// } + +// return false; +// } + +// /// +// /// Try to make a quoted pair from the tokens. +// /// +// /// The text that was made. +// /// true if the quoted pair was made, false if not. +// /// +// public bool TryMakeQuotedPairSmtp(out string text) +// { +// text = null; + +// if (Enumerator.Take() != Tokens.BackSlash) +// { +// return false; +// } + +// text += Enumerator.Take().Text; + +// return true; +// } + +// /// +// /// Try to make an "Atom" from the tokens. +// /// +// /// The atom that was made, or undefined if it was not made. +// /// true if the atom was made, false if not. +// /// +// public bool TryMakeAtom(out string atom) +// { +// atom = null; + +// while (TryMake(TryMakeAtext, out string atext)) +// { +// atom += atext; +// } + +// return atom != null; +// } + +// /// +// /// Try to make an "Atext" from the tokens. +// /// +// /// The atext that was made, or undefined if it was not made. +// /// true if the atext was made, false if not. +// /// +// public bool TryMakeAtext(out string atext) +// { +// atext = null; + +// var token = Enumerator.Take(); +// switch (token.Kind) +// { +// case TokenKind.Text: +// case TokenKind.Number: +// atext = token.Text; +// return true; + +// case TokenKind.Other: +// switch (token.Text[0]) +// { +// case '!': +// case '#': +// case '%': +// case '&': +// case '\'': +// case '*': +// case '-': +// case '/': +// case '?': +// case '_': +// case '{': +// case '}': +// case '$': +// case '+': +// case '=': +// case '^': +// case '`': +// case '|': +// case '~': +// atext += token.Text[0]; +// return true; +// } + +// break; +// } + +// return false; +// } + +// /// +// /// Try to make an Mail-Parameters from the tokens. +// /// +// /// The mail parameters that were made. +// /// true if the mail parameters can be made, false if not. +// /// +// public bool TryMakeMailParameters(out IReadOnlyDictionary parameters) +// { +// var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + +// while (Enumerator.Peek().Kind != TokenKind.None) +// { +// if (TryMake(TryMakeEsmtpParameter, out KeyValuePair parameter) == false) +// { +// parameters = null; +// return false; +// } + +// dictionary.Add(parameter.Key, parameter.Value); +// Enumerator.Skip(TokenKind.Space); +// } + +// parameters = dictionary; +// return parameters.Count > 0; +// } + +// /// +// /// Try to make an Esmtp-Parameter from the tokens. +// /// +// /// The esmtp-parameter that was made. +// /// true if the esmtp-parameter can be made, false if not. +// /// +// public bool TryMakeEsmtpParameter(out KeyValuePair parameter) +// { +// parameter = default; + +// if (TryMake(TryMakeEsmtpKeyword, out string keyword) == false) +// { +// return false; +// } + +// if (Enumerator.Peek().Kind == TokenKind.None || Enumerator.Peek().Kind == TokenKind.Space) +// { +// parameter = new KeyValuePair(keyword, null); +// return true; +// } + +// if (Enumerator.Peek() != Tokens.Equal) +// { +// return false; +// } + +// Enumerator.Take(); + +// if (TryMake(TryMakeEsmtpValue, out string value) == false) +// { +// return false; +// } + +// parameter = new KeyValuePair(keyword, value); + +// return true; +// } + +// /// +// /// Try to make an Esmtp-Keyword from the tokens. +// /// +// /// The esmtp-keyword that was made. +// /// true if the esmtp-keyword can be made, false if not. +// /// +// public bool TryMakeEsmtpKeyword(out string keyword) +// { +// keyword = null; + +// var token = Enumerator.Peek(); +// while (token.Kind == TokenKind.Text || token.Kind == TokenKind.Number || token == Tokens.Hyphen) +// { +// keyword += Enumerator.Take().Text; + +// token = Enumerator.Peek(); +// } + +// return keyword != null; +// } + +// /// +// /// Try to make an Esmtp-Value from the tokens. +// /// +// /// The esmtp-value that was made. +// /// true if the esmtp-value can be made, false if not. +// /// +// public bool TryMakeEsmtpValue(out string value) +// { +// value = null; + +// var token = Enumerator.Peek(); +// while (token.Text.Length > 0 && token.Text.ToCharArray().All(ch => (ch >= 33 && ch <= 60) || (ch >= 62 && ch <= 127))) +// { +// value += Enumerator.Take().Text; + +// token = Enumerator.Peek(); +// } + +// return value != null; +// } + +// /// +// /// Try to make a base64 encoded string. +// /// +// /// The base64 encoded string that were found. +// /// true if the base64 encoded string can be made, false if not. +// /// +// public bool TryMakeBase64(out string base64) +// { +// if (TryMakeBase64Text(out base64) == false) +// { +// return false; +// } + +// if (Enumerator.Peek() == Tokens.Equal) +// { +// base64 += Enumerator.Take().Text; +// } + +// if (Enumerator.Peek() == Tokens.Equal) +// { +// base64 += Enumerator.Take().Text; +// } + +// // because the TryMakeBase64Chars method matches tokens, each TextValue token could make +// // up several Base64 encoded "bytes" so we ensure that we have a length divisible by 4 +// return base64 != null +// && base64.Length % 4 == 0 +// && new[] {TokenKind.None, TokenKind.Space, TokenKind.NewLine}.Contains(Enumerator.Peek().Kind); +// } + +// /// +// /// Try to make a base64 encoded string. +// /// +// /// The base64 encoded string that were found. +// /// true if the base64 encoded string can be made, false if not. +// /// +// bool TryMakeBase64Text(out string base64) +// { +// base64 = null; + +// while (TryMake(TryMakeBase64Chars, out string base64Chars)) +// { +// base64 += base64Chars; +// } + +// return true; +// } + +// /// +// /// Try to make the allowable characters in a base64 encoded string. +// /// +// /// The base64 characters that were found. +// /// true if the base64-chars can be made, false if not. +// /// +// bool TryMakeBase64Chars(out string base64Chars) +// { +// base64Chars = null; + +// var token = Enumerator.Take(); +// switch (token.Kind) +// { +// case TokenKind.Text: +// case TokenKind.Number: +// base64Chars = token.Text; +// return true; + +// case TokenKind.Other: +// switch (token.Text[0]) +// { +// case '/': +// case '+': +// base64Chars = token.Text; +// return true; +// } + +// break; +// } + +// return false; +// } + +// /// +// /// Attempt to make the end of the line. +// /// +// /// true if the end of the line could be made, false if not. +// bool TryMakeEnd() +// { +// Enumerator.Skip(TokenKind.Space); + +// return Enumerator.Take() == Token.None; +// } + +// /// +// /// Returns the complete tokenized text. +// /// +// /// The complete tokenized text. +// string CompleteTokenizedText() +// { +// return string.Concat(Enumerator.Tokens.Select(token => token.Text)); +// } +// } +//} \ No newline at end of file diff --git a/Src/SmtpServer/Storage/CompositeMailboxFilter.cs b/Src/SmtpServer/Storage/CompositeMailboxFilter.cs index 872bda8..64fea31 100644 --- a/Src/SmtpServer/Storage/CompositeMailboxFilter.cs +++ b/Src/SmtpServer/Storage/CompositeMailboxFilter.cs @@ -54,7 +54,7 @@ public async Task CanDeliverToAsync( ISessionContext context, IMailbox to, IMailbox @from, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { if (_filters == null || _filters.Any() == false) { diff --git a/Src/SmtpServer/Storage/DelegatingMessageStore.cs b/Src/SmtpServer/Storage/DelegatingMessageStore.cs deleted file mode 100644 index ad4f017..0000000 --- a/Src/SmtpServer/Storage/DelegatingMessageStore.cs +++ /dev/null @@ -1,73 +0,0 @@ -//using System; -//using SmtpServer.Protocol; -//using System.Threading; -//using System.Threading.Tasks; - -//namespace SmtpServer.Storage -//{ -// public sealed class DelegatingMessageStore : MessageStore -// { -// readonly Func _delegate; - -// /// -// /// Constructor. -// /// -// /// The delegate to execute. -// public DelegatingMessageStore(Action @delegate) : this(Wrap(@delegate)) { } - -// /// -// /// Constructor. -// /// -// /// The delegate to execute. -// public DelegatingMessageStore(Func @delegate) : this(Wrap(@delegate)) { } - -// /// -// /// Constructor. -// /// -// /// The delegate to execute. -// public DelegatingMessageStore(Func @delegate) -// { -// _delegate = @delegate; -// } - -// /// -// /// Wrap the delegate into a function that is compatible with the signature. -// /// -// /// The delegate to wrap. -// /// The function that is compatible with the main signature. -// static Func Wrap(Action @delegate) -// { -// return (context, transaction) => -// { -// @delegate(transaction); - -// return SmtpResponse.Ok; -// }; -// } - -// /// -// /// Wrap the delegate into a function that is compatible with the signature. -// /// -// /// The delegate to wrap. -// /// The function that is compatible with the main signature. -// static Func Wrap(Func @delegate) -// { -// return (context, transaction) => @delegate(transaction); -// } - -// /// -// /// Save the given message to the underlying storage system. -// /// -// /// The session context. -// /// The SMTP message transaction to store. -// /// The cancellation token. -// /// The response code to return that indicates the result of the message being saved. -// public override Task SaveAsync( -// ISessionContext context, -// IMessageTransaction transaction, -// CancellationToken cancellationToken) -// { -// return Task.FromResult(_delegate(context, transaction)); -// } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Storage/DisposableContainer.cs b/Src/SmtpServer/Storage/DisposableContainer.cs index fd5bfbe..852b533 100644 --- a/Src/SmtpServer/Storage/DisposableContainer.cs +++ b/Src/SmtpServer/Storage/DisposableContainer.cs @@ -18,9 +18,9 @@ internal DisposableContainer(TInstance instance) /// public void Dispose() { - if (Instance is IDisposable) + if (Instance is IDisposable disposable) { - ((IDisposable)Instance).Dispose(); + disposable.Dispose(); } } diff --git a/Src/SmtpServer/Text/ByteArrayTokenReader.cs b/Src/SmtpServer/Text/ByteArrayTokenReader.cs deleted file mode 100644 index 3976c77..0000000 --- a/Src/SmtpServer/Text/ByteArrayTokenReader.cs +++ /dev/null @@ -1,184 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; - -//namespace SmtpServer.Text -//{ -// internal sealed class ByteArrayTokenReader : TokenReader -// { -// readonly IReadOnlyList> _segments; -// int _index = 0; -// int _position = 0; - -// /// -// /// Constructor. -// /// -// /// The list of array segments to read from. -// internal ByteArrayTokenReader(IReadOnlyList> segments) -// { -// _segments = segments; -// } - -// /// -// /// Reads the next token. -// /// -// /// The next token that was read. -// public override Token NextToken() -// { -// if (EnsureDataIsAvailable() == false) -// { -// return Token.None; -// } - -// if (Token.IsText(Current)) -// { -// return TextToken(); -// } - -// if (Token.IsNumber(Current)) -// { -// return NumberToken(); -// } - -// if (Token.IsCR(Current)) -// { -// return NewLineToken(); -// } - -// return OtherToken(Current); -// } - -// /// -// /// Creates a single character token that represents the given character. -// /// -// /// The character to create the token for. -// /// The token that represents the given character. -// Token OtherToken(byte value) -// { -// _position++; - -// if (Token.IsWhiteSpace(value)) -// { -// return Token.Create(TokenKind.Space, value); -// } - -// return Token.Create(TokenKind.Other, value); -// } - -// /// -// /// Returns a TextValue token from the current position. -// /// -// /// The text token that was found at the current position. -// Token TextToken() -// { -// return Token.Create(Consume(Token.IsText)); -// } - -// /// -// /// Returns a Number token from the current position. -// /// -// /// The number token that was found at the current position. -// Token NumberToken() -// { -// return Token.Create(TokenKind.Number, Consume(Token.IsNumber)); -// } - -// /// -// /// Returns a New Line token from the current position. -// /// -// /// The new line token that was found at the current position. -// Token NewLineToken() -// { -// var state = 0; -// var text = Consume(b => -// { -// switch (state) -// { -// case 0: -// state = b == 13 ? 1 : 0; -// return state == 1; - -// case 1: -// state = b == 10 ? 2 : 0; -// return state == 2; -// } -// return false; -// }); - -// return Token.Create(state == 2 ? TokenKind.NewLine : TokenKind.Space, text); -// } - -// /// -// /// Returns a continuous segment of characters matching the predicate. -// /// -// /// The predicate to apply to the characters for the continuous segment. -// /// The text that defines a continuous segment of characters that have matched the predicate. -// string Consume(Func predicate) -// { -// return String.Concat(ConsumeIterator(predicate).Select(segment => Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count))); -// } - -// /// -// /// Returns a continuous segment of characters matching the predicate. -// /// -// /// The predicate to apply to the characters for the continuous segment. -// /// The array segment that defines a continuous segment of characters that have matched the predicate. -// IEnumerable> ConsumeIterator(Func predicate) -// { -// var @continue = true; -// while (EnsureDataIsAvailable() && @continue) -// { -// if (TryConsume(predicate, out ArraySegment segment) == false) -// { -// yield break; -// } - -// yield return segment; - -// @continue = _position >= _segments[_index].Count; -// } -// } - -// /// -// /// Try to consume from the current segment. -// /// -// /// The predicate to apply to the characters in the segment -// /// The segment that was matched. -// /// true if a segment was consumed, false if not. -// bool TryConsume(Func predicate, out ArraySegment segment) -// { -// var current = _segments[_index]; -// var start = _position; - -// while (_index < _segments.Count && _position < current.Count && predicate(Current)) -// { -// _position++; -// } - -// segment = new ArraySegment(current.Array, current.Offset + start, _position - start); - -// return segment.Count > 0; -// } - -// /// -// /// Ensure that data is available for the operation. -// /// -// /// true if there is data available, false if not. -// bool EnsureDataIsAvailable() -// { -// if (_index < _segments.Count && _position >= _segments[_index].Count) -// { -// _index++; -// _position = 0; -// } - -// return _index < _segments.Count; -// } - -// /// -// /// Returns the current value for the reader. -// /// -// public byte Current => _segments[_index].Array[_segments[_index].Offset + _position]; -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Text/ITokenEnumerator.cs b/Src/SmtpServer/Text/ITokenEnumerator.cs deleted file mode 100644 index f5286f4..0000000 --- a/Src/SmtpServer/Text/ITokenEnumerator.cs +++ /dev/null @@ -1,72 +0,0 @@ -//using System; -//using System.Collections.Generic; - -//namespace SmtpServer.Text -//{ -// public interface ITokenEnumerator -// { -// /// -// /// Peek at the next token. -// /// -// /// The token at the given number of tokens past the current index, or Token.None if no token exists. -// Token Peek(); - -// /// -// /// Take the next token. -// /// -// /// The last token that was consumed. -// Token Take(); - -// /// -// /// Create a checkpoint that will ensure the tokens are kept in the buffer from this point forward. -// /// -// /// A disposable instance that is used to release the checkpoint. -// ITokenEnumeratorCheckpoint Checkpoint(); - -// /// -// /// The complete list of tokens. -// /// -// IReadOnlyList Tokens { get; } - -// /// -// /// Returns the current position of the enumerator. -// /// -// int Position { get; } -// } - -// public static class TokenEnumeratorExtensions -// { -// /// -// /// Skips tokens in the stream while the given predicate is true. -// /// -// /// The enumerator to perform the operation on. -// /// The predicate to use for evaluating whether or not to consume a token. -// public static void Skip(this ITokenEnumerator enumerator, Func predicate) -// { -// if (enumerator == null) -// { -// throw new ArgumentNullException(nameof(enumerator)); -// } - -// while (predicate(enumerator.Peek())) -// { -// enumerator.Take(); -// } -// } - -// /// -// /// Skips tokens in the stream while the token kind is the same as the supplied kind. -// /// -// /// The enumerator to perform the operation on. -// /// The token kind to test against to determine whether the token should be skipped. -// public static void Skip(this ITokenEnumerator enumerator, TokenKind kind) -// { -// if (enumerator == null) -// { -// throw new ArgumentNullException(nameof(enumerator)); -// } - -// Skip(enumerator, t => t.Kind == kind); -// } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Text/ITokenEnumeratorCheckpoint.cs b/Src/SmtpServer/Text/ITokenEnumeratorCheckpoint.cs deleted file mode 100644 index 10eac62..0000000 --- a/Src/SmtpServer/Text/ITokenEnumeratorCheckpoint.cs +++ /dev/null @@ -1,12 +0,0 @@ -//using System; - -//namespace SmtpServer.Text -//{ -// public interface ITokenEnumeratorCheckpoint : IDisposable -// { -// /// -// /// Rollback to the checkpoint; -// /// -// void Rollback(); -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Text/StringUtil.cs b/Src/SmtpServer/Text/StringUtil.cs index 806963d..2ff71f9 100644 --- a/Src/SmtpServer/Text/StringUtil.cs +++ b/Src/SmtpServer/Text/StringUtil.cs @@ -1,25 +1,31 @@ using System; using System.Buffers; +using System.Text; namespace SmtpServer.Text { internal static class StringUtil { - internal static unsafe string Create(ReadOnlySequence sequence) + internal static string Create(ReadOnlySequence sequence) { - Span buffer = stackalloc char[(int)sequence.Length]; + return Create(sequence, Encoding.ASCII); + } + internal static unsafe string Create(ReadOnlySequence sequence, Encoding encoding) + { if (sequence.IsSingleSegment) { var span = sequence.First.Span; - for (var i = 0; i < span.Length; i++) + fixed (byte* ptr = span) { - buffer[i] = (char)span[i]; + return Encoding.ASCII.GetString(ptr, span.Length); } } else { + Span buffer = stackalloc byte[(int)sequence.Length]; + var i = 0; var position = sequence.GetPosition(0); @@ -28,14 +34,14 @@ internal static unsafe string Create(ReadOnlySequence sequence) var span = memory.Span; for (var j = 0; j < span.Length; i++, j++) { - buffer[i] = (char)span[j]; + buffer[i] = span[j]; } } - } - fixed (char* ptr = buffer) - { - return new string(ptr); + fixed (byte* ptr = buffer) + { + return Encoding.ASCII.GetString(ptr, buffer.Length); + } } } } diff --git a/Src/SmtpServer/Text/TokenEnumerator.cs b/Src/SmtpServer/Text/TokenEnumerator.cs deleted file mode 100644 index e003c86..0000000 --- a/Src/SmtpServer/Text/TokenEnumerator.cs +++ /dev/null @@ -1,103 +0,0 @@ -//using System.Collections.Generic; - -//namespace SmtpServer.Text -//{ -// public sealed class TokenEnumerator : ITokenEnumerator -// { -// int _index = -1; - -// /// -// /// Constructor. -// /// -// /// The token reader to read the tokens from. -// public TokenEnumerator(TokenReader tokenReader) : this(tokenReader.ToList()) { } - -// /// -// /// Constructor. -// /// -// /// The list of tokens that the enumerator is working with. -// public TokenEnumerator(IReadOnlyList tokens) -// { -// Tokens = tokens; -// } - -// /// -// /// Peek at the next token. -// /// -// /// The token at the given number of tokens past the current index, or Token.None if no token exists. -// public Token Peek() -// { -// return At(_index + 1); -// } - -// /// -// /// Take the next token. -// /// -// /// The last token that was consumed. -// public Token Take() -// { -// return At(++_index); -// } - -// /// -// /// Returns the token at the given index. -// /// -// /// The last token that was consumed. -// Token At(int index) -// { -// return index < Tokens.Count ? Tokens[index] : Token.None; -// } - -// /// -// /// Create a checkpoint that will ensure the tokens are kept in the buffer from this point forward. -// /// -// /// A disposable instance that is used to release the checkpoint. -// public ITokenEnumeratorCheckpoint Checkpoint() -// { -// return new TokenEnumeratorCheckpoint(this); -// } - -// /// -// /// The complete list of tokens. -// /// -// public IReadOnlyList Tokens { get; } - -// /// -// /// Returns the current position of the enumerator. -// /// -// public int Position => _index; - -// #region TokenEnumeratorCheckpoint - -// class TokenEnumeratorCheckpoint : ITokenEnumeratorCheckpoint -// { -// readonly TokenEnumerator _enumerator; -// readonly int _index; - -// /// -// /// Constructor. -// /// -// /// The enumerator that is being checkpointed. -// public TokenEnumeratorCheckpoint(TokenEnumerator enumerator) -// { -// _enumerator = enumerator; -// _index = enumerator._index; -// } - -// /// -// /// Rollback to the checkpoint; -// /// -// public void Rollback() -// { -// _enumerator._index = _index; -// } - -// /// -// /// Release the checkpoint. -// /// -// public void Dispose() { } -// } - -// #endregion -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Text/TokenParser.cs b/Src/SmtpServer/Text/TokenParser.cs deleted file mode 100644 index f7592ef..0000000 --- a/Src/SmtpServer/Text/TokenParser.cs +++ /dev/null @@ -1,103 +0,0 @@ -//namespace SmtpServer.Text -//{ -// public abstract class TokenParser -// { -// /// -// /// Delegate for the TryMake function. -// /// -// /// true if the make operation was a success, false if not. -// protected delegate bool TryMakeDelegate(); - -// /// -// /// Delegate for the TryMake function to allow for "out" parameters. -// /// -// /// The type of the out parameter. -// /// The out parameter that was found during the make operation. -// /// true if the make operation found a parameter, false if not. -// protected delegate bool TryMakeDelegate(out TOut found); - -// /// -// /// Delegate for the TryMake function to allow for "out" parameters. -// /// -// /// The type of the in parameter. -// /// The type of the out parameter. -// /// The input parameter for the function. -// /// The out parameter that was found during the make operation. -// /// true if the make operation found a parameter, false if not. -// protected delegate bool TryMakeDelegate(TIn parameter, out TOut found); - -// /// -// /// Constructor. -// /// -// /// The token enumerator to handle the incoming tokens. -// protected TokenParser(ITokenEnumerator enumerator) -// { -// Enumerator = enumerator; -// } - -// /// -// /// Try to make a callback in a transactional way. -// /// -// /// The callback to perform the match. -// /// true if the match could be made, false if not. -// protected bool TryMake(TryMakeDelegate @delegate) -// { -// var checkpoint = Enumerator.Checkpoint(); - -// if (@delegate() == false) -// { -// checkpoint.Rollback(); -// return false; -// } - -// checkpoint.Dispose(); -// return true; -// } - -// /// -// /// Try to make a callback in a transactional way. -// /// -// /// The callback to perform the match. -// /// The parameter that was returned from the matching function. -// /// true if the match could be made, false if not. -// protected bool TryMake(TryMakeDelegate @delegate, out TOut found) -// { -// var checkpoint = Enumerator.Checkpoint(); - -// if (@delegate(out found) == false) -// { -// checkpoint.Rollback(); -// return false; -// } - -// checkpoint.Dispose(); -// return true; -// } - -// /// -// /// Try to make a callback in a transactional way. -// /// -// /// The callback to perform the match. -// /// The input parameter. -// /// The parameter that was returned from the matching function. -// /// true if the match could be made, false if not. -// protected bool TryMake(TryMakeDelegate @delegate, TIn parameter, out TOut found) -// { -// var checkpoint = Enumerator.Checkpoint(); - -// if (@delegate(parameter, out found) == false) -// { -// checkpoint.Rollback(); -// return false; -// } - -// checkpoint.Dispose(); -// return true; -// } - -// /// -// /// Returns the enumerator to handle the incoming tokens. -// /// -// protected ITokenEnumerator Enumerator { get; } -// } -//} \ No newline at end of file diff --git a/Src/SmtpServer/Text/TokenReader.txt b/Src/SmtpServer/Text/TokenReader.txt deleted file mode 100644 index 7d4cb5d..0000000 --- a/Src/SmtpServer/Text/TokenReader.txt +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Buffers; - -namespace SmtpServer.Text -{ - public ref struct TokenReader - { - readonly ReadOnlySequence _buffer; - Token _peek; - bool _hasPeeked; - SequencePosition _position; - ReadOnlySpan _currentSpan; - - /// - /// Constructor. - /// - /// The buffer to read from. - public TokenReader(ReadOnlySequence buffer) - { - _buffer = buffer; - _position = buffer.GetPosition(0); - _currentSpan = buffer.First.Span; - _peek = default; - _hasPeeked = false; - } - - /// - /// Peek at the next token in the sequence. - /// - /// The next token in the sequence. - public Token Peek() - { - if (_hasPeeked == false) - { - _peek = ConsumeToken(); - _hasPeeked = true; - } - - return _peek; - } - - /// - /// Take the next token from the sequence. - /// - /// The next token from the sequence. - public Token Take() - { - if (_hasPeeked) - { - _hasPeeked = false; - return _peek; - } - - return ConsumeToken(); - } - - /// - /// Consume the next token from the buffer. - /// - /// The token that was read and consumed from the buffer. - Token ConsumeToken() - { - var token = ReadToken(); - - _position = _buffer.GetPosition(token.Text.Length, _position); - - return token; - } - - /// - /// Read a token from the current position in the sequence. - /// - /// The token that was read from the sequence. - Token ReadToken() - { - if (_position.Equals(_buffer.End) || _buffer.TryGet(ref _position, out var memory, false) == false) - { - return default; - } - - var span = memory.Span; - switch (span[0]) - { - case { } ch when Token.IsText(ch): - return new Token(TokenKind.Text, ReadWhile(ref span, Token.IsText)); - - case { } ch when Token.IsNumber(ch): - return new Token(TokenKind.Number, ReadWhile(ref span, Token.IsNumber)); - - case { } ch when Token.IsWhiteSpace(ch): - return new Token(TokenKind.Space, ReadOne(ref span)); - } - - return new Token(TokenKind.Other, ReadOne(ref span)); - } - - static ReadOnlySpan ReadWhile(ref ReadOnlySpan span, Func predicate) - { - var count = 0; - while (count < span.Length && predicate(span[count])) - { - count++; - } - - return span.Slice(0, count); - } - - static ReadOnlySpan ReadOne(ref ReadOnlySpan span) - { - return span.Slice(0, 1); - } - - //public long Checkpoint() - //{ - // return _reader.Consumed; - //} - - //public void Restore(long checkpoint) - //{ - // var count = _reader.Consumed - checkpoint; - - // if (count > 0) - // { - // _reader.Rewind(count); - // _hasPeeked = false; - // } - //} - - //public ReadOnlySequence Slice(long from, long to) - //{ - // return _reader.Sequence.Slice(from, to - from); - //} - } -} \ No newline at end of file diff --git a/Src/SmtpServer/Text/TokenReaderOld.cs b/Src/SmtpServer/Text/TokenReaderOld.cs deleted file mode 100644 index 8672cca..0000000 --- a/Src/SmtpServer/Text/TokenReaderOld.cs +++ /dev/null @@ -1,49 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; - -//namespace SmtpServer.Text -//{ -// public abstract class TokenReader -// { -// /// -// /// Reads the next token. -// /// -// /// The next token that was read. -// public abstract Token NextToken(); -// } - -// public static class TokenReaderExtensions -// { -// /// -// /// Returns the complete list of tokens from the token reader. -// /// -// /// The reader to return the list of tokens from. -// /// The list of tokens from the token reader. -// public static IReadOnlyList ToList(this TokenReader reader) -// { -// if (reader == null) -// { -// throw new ArgumentNullException(nameof(reader)); -// } - -// return ToEnumerable(reader).ToList(); -// } - -// /// -// /// Returns the complete list of tokens from the token reader. -// /// -// /// The reader to return the list of tokens from. -// /// The list of tokens from the token reader. -// static IEnumerable ToEnumerable(TokenReader reader) -// { -// Token token; -// while ((token = reader.NextToken()) != Token.None) -// { -// yield return token; -// } - -// yield return token; -// } -// } -//} \ No newline at end of file