Commits

"Fel...@schwarz.eu>"  committed edf6c85

added noop and quit, prepared state machine for real mail sending

  • Participants
  • Parent commits 2968eaa

Comments (0)

Files changed (2)

File pymta/processor.py

 # -*- coding: UTF-8 -*-
 
+from sets import Set
+
 from repoze.workflow.statemachine import StateMachine, StateMachineError
 
 __all__ = ['SMTPProcessor']
         self.state.add(from_state, smtp_command, to_state, handler_function)
     
     
+    def _add_noop_and_quit_transitions(self):
+        """NOOP and QUIT should be possible from everywhere so we need to add 
+        these transitions to all states configured so far."""
+        states = Set()
+        for key in self.state.states:
+            new_state = self.state.states[key]
+            state_name = new_state[0]
+            if state_name not in ['new', 'finished']:
+                states.add(state_name)
+        for state in states:
+            self._add_state(state, 'NOOP',  state)
+            self._add_state(state, 'QUIT',  'finished')
+        
+    
     def _build_state_machine(self):
         # This will implicitely declare an instance variable '_state' with the
         # initial state
         self.state = StateMachine('_state', initial_state='new')
         self._add_state('new',     'GREET', 'greeted')
         self._add_state('greeted', 'HELO',  'identify')
-        self._add_state('identify', 'QUIT',  'finished')
-        self._add_state('greeted', 'QUIT',  'finished')
-        self.valid_commands = [command for new_state, command in self.state.states]
+        self._add_state('identify', 'MAIL FROM',  'sender_known')
+        self._add_state('sender_known', 'RCPT TO',  'recipient_known')
+        self._add_state('recipient_known', 'DATA',  'identify')
+        # How to add commands?
+        self._add_noop_and_quit_transitions()
+        self.valid_commands = [command for from_state, command in self.state.states]
     
     
     def _dispatch_commands(self, from_state, to_state, smtp_command, ob):
         method. It is called after a new command was received and a valid 
         transition was found."""
         print from_state, ' -> ', to_state, ':', smtp_command
-        name_handler_method = 'smtp_%s' % smtp_command.lower()
+        name_handler_method = 'smtp_%s' % smtp_command.lower().replace(' ', '_')
         try:
             handler_method = getattr(self, name_handler_method)
         except AttributeError:
             self.state.execute(self, command)
         except StateMachineError:
             if command not in self.valid_commands:
-                self.reply(500, 'unrecognized command')
+                self.reply(500, 'unrecognized command "%s"' % smtp_command)
             else:
                 msg = 'Command "%s" is not allowed here' % smtp_command
                 allowed_transitions = self.state.transitions(self)
                 if len(allowed_transitions) > 0:
                       msg += ', expected on of %s' % allowed_transitions
-                self.reply(502, msg)
+                self.reply(503, msg)
         self._command_arguments = None
     
     
         self.reply(221, reply_text)
         self._session.close_when_done()
     
+    def smtp_noop(self):
+        self.reply(250, 'OK')
+    
     def smtp_helo(self):
         helo_string = self._command_arguments
         # We could store the helo string for a later check
         primary_hostname = self._session.primary_hostname
         self.reply(250, primary_hostname)
+    
+    def smtp_mail_from(self):
+        raise NotImplementedError
+        self.reply(250, 'NI')
+    
+    def smtp_rcpt_to(self):
+        raise NotImplementedError
+        self.reply(250, 'NI')
+    
+    def smtp_data(self):
+        raise NotImplementedError
+        self.reply(250, 'NI')
         
         
 

File tests/basic_message_sending_test.py

         self.processor = SMTPProcessor(session=self.session)
         self.processor.new_connection('127.0.0.1', 4567)
     
+    def _send(self, command, data=None):
+        number_replies_before = len(self.session.replies)
+        self.processor.handle_input(command, data)
+        self.assertEqual(number_replies_before + 1, len(self.session.replies))
+        code, reply_text = self.session.replies[-1]
+        self.assertEqual('2', str(code)[0], "%s %s" % (code, reply_text))
+    
     def _close_connection(self):
-        self.processor.handle_input('quit')
-        self.assertTrue(len(self.session.replies) >= 1)
+        self._send('quit')
         code, reply_text = self.session.replies[-1]
         self.assertTrue(221, code)
         self.assertEqual('localhost closing connection', reply_text)
         self._close_connection()
     
     def test_send_helo(self):
-        self.processor.handle_input('helo', 'foo.example.com')
+        self._send('helo', 'foo.example.com')
         self.assertEqual(2, len(self.session.replies))
         code, reply_text = self.session.replies[-1]
         self.assertEqual(250, code)
         self.assertEqual('localhost', reply_text)
         self._close_connection()
+    
+    def test_noop_does_nothing(self):
+        self._send('noop')
+        self._close_connection()
 
     def test_reject_duplicated_helo(self):
-        self.processor.handle_input('helo', 'foo.example.com')
+        self._send('helo', 'foo.example.com')
         self.processor.handle_input('helo', 'foo.example.com')
         self.assertEqual(3, len(self.session.replies))
         code, reply_text = self.session.replies[-1]
-        self.assertEqual(502, code)
+        self.assertEqual(503, code)
         expected_message = 'Command "helo" is not allowed here'
         self.assertTrue(reply_text.startswith(expected_message), reply_text)
         self._close_connection()
         self.assertEqual(2, len(self.session.replies))
         code, reply_text = self.session.replies[-1]
         self.assertEqual(500, code)
-        self.assertEqual('unrecognized command', reply_text)
+        self.assertEqual('unrecognized command "invalid"', reply_text)
         self._close_connection()
 
     def test_send_simple_mail(self):
-        self.processor.handle_input('MAIL FROM', 'foo@example.com')
-        self.processor.handle_input('RCPT TO', 'bar@example.com')
+        self._send('HELO', 'foo.example.com')
+        self._send('MAIL FROM', 'foo.example.com')
+        self._send('RCPT TO', 'bar@example.com')
         msg = 'Subject: Test\r\n\r\nJust testing...\r\n'
-        self.processor.handle_input('DATA', msg)
+        self._send('DATA', msg)
         self._close_connection()
+        raise NotImplementedError('We have to check the email')