16.3. The ReceivingMailServer

 <  Day Day Up  >  

The ReceivingMailServer creates a ConnectionHandler for every SendingMailServer that connects to it. The ConnectionHandler directs the overall flow of the command/response operation. Commands are both used as the event that determines the next state and processed . We separate these actions by having both a state processor and a command processor. The state processor ensures that the commands are presented in a valid order, according to the state table (Figure 16-2). The command processor interprets the command, which for the most part involves updating the MailDTO , to be discussed shortly. The overall view of ConnectionHandler appears in Figure 16-3.

Figure 16-3. ConnectionHandler communication diagram

The ConnectionHandler class looks like this:

 class ConnectionHandler         Connection connection         ReceivedMailExaminer received_mail_examiner         StateProcessor state_processor         MailDTO mail_dto         MailReport mail_report         handle(  )         send_greeting(  )         Command receive_command(  )         write_mail(  )         process_command(Command command) 

The Connection class represents the connection to the SendingMailServer . It looks like this:

 class Connection         CommonString read_line(  ) throws ConnectionException         write_line(CommonString line) throws ConnectionException         drop(  ) 

Both read_line( ) and write_line( ) tHRow ConnectionException if an error occurred when sending or receiving characters . This is an unrecoverable error, for which the only appropriate action is to terminate the connection ("Decide on a Strategy to Deal With Deviations and Errors").

The receive_command( ) method of ConnectionHandler receives a line from the connection and converts it into a Command . The Command class looks like this:

 class Command         CommonString remainder_of_command_line         abstract Response process(MailDTO received_mail_dto,             MailReport received_mail_report,             Connection connection) 

A class derived from Command represents each command. We will examine the process() method for each command shortly.

The StateProcessor ensures that the sequence of commands is in accordance with the state table shown in Figure 16-2 ("See What Condition Your Condition Is In"). The class looks like this:

 class StateProcessor         Count number_of_unknown_commands         ReceivedState current_state         StateProcessor(  )    // initializes current_state to AWAITING_HELO         Response process(Command current_command) 

The spam policy (i.e., whether to reject an email message because it is probably spam) is dictated by the ReceivedMailExaminer , to be described shortly ("Separate Policy from Implementation").

Example 16-1 shows the overall flow for the handle( ) method of ConnectionHandler .

Example 16-1. ConnectionHandler overall flow
 handle(  )         {         try             {             state_processor = new StateProcessor(  );             received_mail_examiner = MailExaminer.get_instance(  );             mail_dto = new MailDTO(  );             mail_report = new MailReport(  );             send_greeting(  );             while (true)                 {                 Command current_command = receive_command(  )                 Response response = process_command(current_command);                 if (response.mail_complete && response.result == REQUEST_OK)                     {                     write_mail(  );                     }                 if (response.reset_dto)                     {                     mail_dto = new MailDTO(  );                     mail_report = new MailReport(  );                     }                 connection.write_line(response.to_string(  ));                 if (response.terminate_connection)                     {                     connection.drop(  );                     break;                     }                 }             }         catch(ConnectionException exception)             {             // Log the broken connection             }         } 

The handle( ) method begins by calling send_greeting( ) to send the desired greeting. It creates a new StateProcessor whose initial state is AWAITING_HELO . Then it receives commands and processes them in a loop. In a reverse of the normal policy/implementation split ("Separate Policy from Implementation"), the called methods determine what to do and the handle( ) method executes those decisions. If the state processor, the command processor, or the mail examiner determines that the connection should be terminated , it is dropped and the loop exits.

If mail input is complete ( mail_complete is set by the DATA command), handle( ) calls write_mail( ) to write the message and the examination report to the ReceivingMailQueue (described later in this chapter ) . A "Received:"header line is added to the message that specifies the ReceivingMailServer domain name and the time of receipt. If response.reset_dto is set, handle( ) initializes the mail_dto and mail_report to unfilled objects.

16.3.1. The Response

The Response class represents what a MailExaminer , the Command processor, or the StateProcessor suggests should be reported back to the SendingMailServer . This class has the following structure:

 enumeration Result {REQUEST_OK, MAILBOX_UNAVAILABLE, ... }     class Response         Result result         CommonString explanation_text         boolean terminate_connection         boolean reset_dto         boolean mail_complete         Response(SymbolicReturn symbolic_return, CommonString text_to_return)         Response combine(Response other)             // Creates a Response with the most severe result.         CommonString to_string(  ) 

The response sent to the SendingMailServer for a command has the following syntax:

    numeric_value_string explanation_text_string    

An example of a response is:

502 Command not implemented

The meanings of numeric_value_string are defined in RFC 2821. The explanation_text_string is free-form. The Response attribute result is turned into the numeric_value_string . The Result enumeration separates the representation of the return value from the meaning of the return value ("Never Let a Constant Slip into Code"). The Response attribute explanation_text is the text to send back as the explanation_text_string .

In the standard SMTP, the ReceivingMailServer should never terminate a connection until the SendingMailServer issues a QUIT command. However, in the case of spammers, we might want to terminate a connection to prevent wasting our processing power. Any method determining that the SendMailServer is a spammer can set the terminate_connection attribute.

16.3.2. The Process Method

The pseudocode for the process_command method of ConnectionHandler looks like the code shown in Example 16-2.

Example 16-2. ConnectionHandler process_command method
 Response process_command(Command current_command)         {         Response response;         response = state_processor.process(current_command);         if (response.result == REQUEST_OK)             {             response = current_command.process(mail_dto,                 connection);             if (response.result == REQUEST_OK)                 received_mail_examiner.process(mail_dto,                 mail_report);             }         return response;         } 

First, a command is checked to see if it is valid according to the state processor. Then the appropriate command process method is invoked to check the data supplied with the command and to update the MailDTO , which is described in the next section. If the command is valid, the received_mail_examiner checks the mail for spam.

16.3.3. MailDTO

The MailDTO class represents the logical information contained in an email message. [*] The format of a mail message for SMTP is described in RFC 2822 (http://www.ietf.org/rfc/rfc2822.txt). MailDTO separates the external physical representation of an email message from its logical representation ("To Text or Not to Text"). Each command alters the MailDTO . Example 16-3 shows what the class looks like.

[*] DTO, you might recall, stands for Data Transfer Object.

Example 16-3. MailDTO
 class MailDTO         Greeting the_greeting         Envelope the_envelope         MessageData the_message_data         Message the_message         Status the_status 

The individual parts of MailDTO use the following abstract data types (ADTs) ("When You're Abstract Be Abstract All the Way"). Each ADT represents a logical type. The classes with String in their name have appropriate validation methods to ensure that the corresponding objects have the correct format.

 class DomainNameString     class EmailAddressString     class MimeTypeString     class HeaderString     class IPAddress     class EmailAddress         EmailString identity         EmailString domain     class MimeType         MimeTypeString content_type         MimeTypeString content_subtype     enumeration HeaderCategory {FROM, TO, SUBJECT,... X_FIELD} 

These ADTs are used in the following classes that declare the attributes of the MailDTO of Example 16-3:

 class Greeting         IPAddress sending_ip_address         DomainNameString hello_host_name     class Envelope         EmailAddress sender         EmailAddress [] recipients     class HeaderField         HeaderCategory category         HeaderString name         CommonString value     class BodyPart         HeaderField [] header_fields         Byte [] data     class Message         HeaderField [] header_fields         BodyPart [] body_parts     class MessageData         CommonString [] raw_data 

An email message consists of a header, which contains HeaderField s with name/value pairs and a body. The lines that constitute the HeaderField s are separated from the body by a blank line. There are standard HeaderField names and nonstandard field names. Standard field names will have the category set to the appropriate value of HeaderCategory . Nonstandard names will have the category set to X_FIELD , which represents that those fields should begin with the letter X , as stated in RFC 2822.

HeaderField could represent a base class with each standard field having a corresponding derived class. However, there are no significant behavioral differences between the headers, so inheritance is not employed ("Avoid Premature Inheritance"). The category attribute was created to help with simple validity checking of the fields (e.g., no more than one "From:" field.). If validity checking of the individual HeaderField s were implemented, this would be an appropriate place to employ inheritance.

The body can consist of a single part (e.g., just a text message) or multiple parts (e.g., a message with attachments). Each BodyPart can have its own header fields. One field describes the MimeType of the part. A MimeType consists of a content_type and a content_subtype , such as "text/html" . Each BodyPart can be examined to see if it contains a virus or other nefarious data. [*]

[*] In a complex message, BodyPart s can contain BodyPart s (in a hierarchical pattern). The representation has been simplified for this example.

The MessageParser class parses text one line at a time and fills in the Message part of the MailDTO . This class could be reused by other message processing programs:

 class MessageParser         parse_line(MailDTO mail_dto, CommonString line) 

The raw_data attribute of MessageData contains the message as delivered by the sender. The attribute is included in the MailDTO for MailExaminer s (to be defined shortly) that perform their own message parsing.

The Status class is an enumeration that indicates how much of the MailDTO contains data. The MailExaminer s use it to determine how much of the MailDTO to examine for indications of spam.:

 enumeration Status {EMPTY, GREETING, ENVELOPE_SENDER, ENVELOPE_RECIPIENT, ...} 

16.3.4. Command Processing

The process( ) methods for the individual commands alter the appropriate parts of the MailDTO :



HeloCommand

Sets the_greeting.sending_ip_address .

Sets the_greeting.hello_host_name .



MailFromCommand

Sets the_envelope.sender .



RcptToCommand

Adds the address to the_envelope.recipient s.



DataCommand

This command works differently from the other commands. It has an intermediate response and it reads multiple lines. After sending an intermediate response back to the SendingMailServer , it performs the following until it receives EndofData :

  1. Reads a line from the connection .

  2. Adds the line to the_message_data.raw_data .

  3. Converts the received line into the appropriate part of a Message by calling MessageParser.parse_line( ) .

At the end, the command sets response.mail_complete if the data was successfully received and sets response.reset_dto .

If the data exceeds MAXIMUM_SIZE_DATA , or the SendingMailServer takes too long to send the data, the command can terminate with an error.



QuitCommand

Sets response.terminate_connection .



RsetCommand

Sets response.reset_dto .

16.3.5. Alternatives

Should the handle( ) method of ConnectionHandler (Example 16-1) process received commands or should it process received lines? Almost all commands are single lines, so distinguishing between a command and a line is a matter of abstraction. The receive_command( ) method reads a line and creates a Command from it. The process_command( ) acts on that Command . The methods could have been written as receive_line( ) and process_line( ) , with one exception.

The DATA command is the exception. It needs additional lines to fulfill its operation. If handle( ) processed lines, the method would have to keep track of which commands required additional lines. It is easier to give responsibility to each Command object to process all input for a command. Thus, the Connection is passed to each Command , so additional lines can be read. Only the DATA command requires the connection.

 <  Day Day Up  >  


Prefactoring
Prefactoring: Extreme Abstraction, Extreme Separation, Extreme Readability
ISBN: 0596008740
EAN: 2147483647
Year: 2005
Pages: 175
Authors: Ken Pugh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net