public void CanMakeNoop()
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader("NOOP"));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeNoop(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is NoopCommand);
        }
        public void CanMakeQuit()
        {
            // arrange
            var enumerator = new TokenEnumerator(new Token(TokenKind.Text, "QUIT"));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeQuit(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is QuitCommand);
        }
        public void CanNotMakeHelo(string input)
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader(input));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeHelo(enumerator, out command, out errorResponse);

            // assert
            Assert.False(result);
            Assert.Null(command);
            Assert.NotNull(errorResponse);
        }
        public void CanMakeHelo()
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader("HELO abc-1-def.mail.com"));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeHelo(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is HeloCommand);
            Assert.Equal("abc-1-def.mail.com", ((HeloCommand)command).Domain);
        }
        /// <summary>
        /// Make an NOOP command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The NOOP command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeNoop(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "NOOP"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            if (enumerator.Count > 1)
            {
                _logger.LogVerbose("NOOP command can not have parameters (found {0} parameters).", enumerator.Count);

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            command = NoopCommand.Instance;
            return true;
        }
        /// <summary>
        /// Make a RCTP command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The RCTP command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeRcpt(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RCPT"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            if (enumerator.Take() != new Token(TokenKind.Text, "TO") || enumerator.Take() != new Token(TokenKind.Punctuation, ":"))
            {
                errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:");
                return false;
            }

            // according to the spec, whitespace isnt allowed here anyway
            enumerator.TakeWhile(TokenKind.Space);

            IMailbox mailbox;
            if (_parser.TryMakePath(enumerator, out mailbox) == false)
            {
                _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText());

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            // TODO: support optional service extension parameters here

            command = new RcptCommand(mailbox, _options.MailboxFilterFactory);
            return true;
        }
        /// <summary>
        /// Make a MAIL command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The MAIL command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeMail(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "MAIL"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            if (enumerator.Take() != new Token(TokenKind.Text, "FROM") || enumerator.Take() != new Token(TokenKind.Punctuation, ":"))
            {
                errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:");
                return false;
            }

            // according to the spec, whitespace isnt allowed here but most servers send it
            enumerator.TakeWhile(TokenKind.Space);

            IMailbox mailbox;
            if (_parser.TryMakeReversePath(enumerator, out mailbox) == false)
            {
                _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText());

                errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError);
                return false;
            }

            // match the optional (ESMTP) parameters
            IDictionary<string, string> parameters;
            if (_parser.TryMakeMailParameters(enumerator, out parameters) == false)
            {
                parameters = new Dictionary<string, string>();
            }

            command = new MailCommand(mailbox, parameters, _options.MailboxFilterFactory);
            return true;
        }
        /// <summary>
        /// Make an EHLO command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The EHLO command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeEhlo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "EHLO"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            string domain;
            if (_parser.TryMakeDomain(enumerator, out domain))
            {
                command = new EhloCommand(domain, _options);
                return true;
            }

            string address;
            if (_parser.TryMakeAddressLiteral(enumerator, out address))
            {
                command = new EhloCommand(address, _options);
                return true;
            }

            errorResponse = SmtpResponse.SyntaxError;
            return false;
        }
        /// <summary>
        /// Make a HELO command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The HELO command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeHelo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "HELO"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            string domain;
            if (_parser.TryMakeDomain(enumerator, out domain) == false)
            {
                _logger.LogVerbose("Could not match the domain name (Text={0}).", enumerator.AsText());

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            command = new HeloCommand(domain);
            return true;
        }
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="tokenEnumerator">The token enumerator to create the checkpoint on.</param>
 internal TokenEnumeratorCheckpoint(TokenEnumerator tokenEnumerator)
 {
     _tokenEnumerator = tokenEnumerator;
     _index = tokenEnumerator._index;
 }
        public void CanMakeMailWithBlankAddress()
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader("MAIL FROM:<   >"));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeMail(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is MailCommand);
            Assert.Null(((MailCommand)command).Address);
        }
        /// <summary>
        /// Make an AUTH command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The AUTH command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeAuth(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "AUTH"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            AuthenticationMethod method;
            if (Enum.TryParse(enumerator.Peek().Text, true, out method) == false)
            {
                _logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)");

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            enumerator.Take();

            string parameter = null;
            if (enumerator.Count > 0 && _parser.TryMakeBase64(enumerator, out parameter) == false)
            {
                _logger.LogVerbose("AUTH parameter must be a Base64 encoded string");

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            command = new AuthCommand(_options.UserAuthenticator, method, parameter);
            return true;
        }
        public void CanMakeEhlo(string input)
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader(input));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeEhlo(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is EhloCommand);
            Assert.Equal(input.Substring(5), ((EhloCommand)command).DomainOrAddress);
        }
        public void CanMakeRcpt()
        {
            // arrange
            var enumerator = new TokenEnumerator(new StringTokenReader("RCPT TO:<*****@*****.**>"));

            // act
            SmtpCommand command;
            SmtpResponse errorResponse;
            var result = _smtpCommandFactory.TryMakeRcpt(enumerator, out command, out errorResponse);

            // assert
            Assert.True(result);
            Assert.True(command is RcptCommand);
            Assert.Equal("cain.osullivan", ((RcptCommand)command).Address.User);
            Assert.Equal("gmail.com", ((RcptCommand)command).Address.Host);
        }
        /// <summary>
        /// Make a DATA command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The DATA command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeData(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "DATA"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            if (enumerator.Count > 1)
            {
                _logger.LogVerbose("DATA command can not have parameters (Tokens={0})", enumerator.Count);

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            command = new DataCommand(_options.MessageStoreFactory);
            return true;
        }
        /// <summary>
        /// Make an STARTTLS command from the given enumerator.
        /// </summary>
        /// <param name="enumerator">The enumerator to create the command from.</param>
        /// <param name="command">The STARTTLS command that is defined within the token enumerator.</param>
        /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
        /// <returns>Returns true if a command could be made, false if not.</returns>
        public bool TryMakeStartTls(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse)
        {
            Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "STARTTLS"));

            command = null;
            errorResponse = null;

            enumerator.Take();
            enumerator.TakeWhile(TokenKind.Space);

            if (enumerator.Count > 1)
            {
                _logger.LogVerbose("STARTTLS command can not have parameters (Tokens={0})", enumerator.Count);

                errorResponse = SmtpResponse.SyntaxError;
                return false;
            }

            command = new StartTlsCommand(_options.ServerCertificate);
            return true;
        }
 /// <summary>
 /// Advances the enumerator to the next command in the stream.
 /// </summary>
 /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param>
 /// <param name="command">The command that was found.</param>
 /// <param name="errorResponse">The error response that indicates why a command could not be accepted.</param>
 /// <returns>true if a valid command was found, false if not.</returns>
 public bool TryAccept(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse)
 {
     return _stateTable.TryAccept(tokenEnumerator, out command, out errorResponse);
 }
            /// <summary>
            /// Advances the enumerator to the next command in the stream.
            /// </summary>
            /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param>
            /// <param name="command">The command that is defined within the token enumerator.</param>
            /// <param name="errorResponse">The error that indicates why the command could not be made.</param>
            /// <returns>true if a valid command was found, false if not.</returns>
            public bool TryAccept(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse)
            {
                // lookup the correct action
                Tuple<State.TryMakeDelegate, SmtpState> action;
                if (_state.Actions.TryGetValue(tokenEnumerator.Peek().Text, out action) == false)
                {
                    var response = $"expected {String.Join("/", _state.Actions.Keys)}";

                    command = null;
                    errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, response);

                    return false;
                }

                if (action.Item1(tokenEnumerator, out command, out errorResponse) == false)
                {
                    return false;
                }
                
                // transition to the next state
                _state = _states[action.Item2];
                return true;
            }
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="tokenEnumerator">The token enumerator to create the checkpoint on.</param>
 internal TokenEnumeratorCheckpoint(TokenEnumerator tokenEnumerator)
 {
     _tokenEnumerator = tokenEnumerator;
     _index           = tokenEnumerator._index;
 }