< 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 diagramThe 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 flowhandle( ) { 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 ResponseThe 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 MethodThe pseudocode for the process_command method of ConnectionHandler looks like the code shown in Example 16-2. Example 16-2. ConnectionHandler process_command methodResponse 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. MailDTOThe 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.
Example 16-3. MailDTOclass 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. [*]
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 ProcessingThe process( ) methods for the individual commands alter the appropriate parts of the MailDTO :
16.3.5. AlternativesShould 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 > |