by Antonio Miguel Ferreira
This chapter describes the creation of a Web application that deals with multiple Internet protocols. It covers the basics of building such an application and gives you some ideas for enhancements that could be used on your own multi-protocol based applications.
The application described here is called WebPOP, a Web-based mail reader.
Careful planning of a multi-protocol application should be finished before you start to write the program code. If you take some time to think about the application you want to write, you could save many hours of work. Here is a checklist you can follow to write your own multi-protocol applications:
Web servers and Web browsers exchange information using the HyperText Transfer Protocol (HTTP), but several Internet services are based on other well-known protocols such as SMTP (mail), NNTP (news), FTP (file transfer), and so on. Fortunately, there are ways to exchange information between different servers and to present it all under the same Web-based interface. The integration of several protocols in one application requires a careful design process from the programmer and, most important of all, a good knowledge of the protocols and languages used. A common characteristic of multi-protocol Web-based applications is the use of the CGI (Common Gateway Interface) specifications. Unless an application uses a proprietary Application Programming Interface (API), it probably uses the CGI because it is the standard way of communication between a Web server and a custom application.
CGI applications can be written in virtually any computer language, such as Perl and C; it is just a matter of using their communication possibilities (using the sockets library, for example, available for all major platforms) in order to write an application that talks with different servers and outputs the results in HTML format. This is the idea behind multi-protocol applications based on the Web: Talk with other applications; treat the information they provide; and present the results in HTML.
One of the advantages of using CGI applications is that you don't have to worry about making a version for every known platform. Because the program is executed in the server-side, and because the output is in standard format (HTML, plain text, and so on), CGI is a good choice for creating multi-platform applications.
To illustrate the use of an integrated application with multiple protocols, presented here is a CGI program called WebPOP (POP stands for Post Office Protocol, the most common Internet protocol for mailbox contents retrieval). It is a very simple and practical mail-reading program that gets user input and presents results formatted in HTML so that the user's browser can display it correctly.
WebPOP was created to fill the need to offer Internet users an easy-to-use mail program. Internet and the World Wide Web are attracting a lot of new users, most of whom are not computer experts-some aren't even computer literate. These users need a very simple mail program that they can use to process a few messages per day. The use of a standard Web browser, along with WebPOP on the server-side, eliminates the need for a special purpose (platform-dependent) mail reader. These are generally good mail readers, but they frequently offer more possibilities than many newcomers need or want.
On the client side (the user side), the only piece of software needed to use such an application is a common Web browser. On the server side, the Web server must comply with the CGI specification, as do all well-known Web servers today.
WebPOP is a CGI application written in Perl (version 4.036), a very useful scripting language commonly used for CGI development. The complete source code of this application is listed at the end of the chapter. If Perl does not come with your operating system, you should consider installing it. To try it out, take a look at the Web site whose address is
http://www.perl.com
NOTE |
You might need the help of the Webmaster of your site if you do not have direct access to the cgi-bin directory (in which you put programs and scripts that will be executed in the Web). Also, it is important to know the exact locations (paths) of the finger, grep, and smail applications as well as the name of your mail server. See Listing 35.1 for more details. |
An entity is a program executing a particular function; it is independent from other software components on the system. There are five entities that participate in the functioning of the application presented in this chapter: the CGI application itself (WebPOP), the user's Web browser, the Web server, the POP3 server, and the SMTP server. See Figure 35.1 for an illustration of the participating entities.
Figure 35.1 : The entities and protocols of the integrated application.
In order to provide the functionality expected from a mail reader, your application must be able to use multiple protocols:
At the same time, the Web browser and server communicate with each other using the standard HTTP protocol. The integration of all these protocols is done via CGI, the standard specification that permits the dialog between the Web server and the application. Finally, WebPOP formats the results in HTML and sends them to the Web server that is responsible for forwarding it to the user's Web browser. The protocols are also illustrated in Figure 35.1.
A mail program must offer some core functions-it must be able to
The following list shows the functions in detail:
user authentication | A mailbox contains messages that are private to a given user. The mail program must confirm the identity of a user before retrieving that user's messages. The authentication function is the process of asking the user her username and password, and then verifying the information provided against a list of authorized users. |
check mailbox contents | When the user wants to read incoming mail, the mail program checks the electronic mailbox and presents a list of the messages sent to that user. |
read a mail message | When the user wants to read a particular message, the mail program displays it on the screen. |
send a mail message | When the user wants to send a message to someone, either a reply or a new message, the mail program forwards it to its destination. |
Having defined the entities, protocols, and core functions that the integrated application must offer, take a look at the overall functioning. The CGI application must receive orders from the Web server (transmitted by the Web browser, because it is the only entity taking initiatives) and must communicate with the SMTP and POP3 servers. The results obtained are sent back to the Web server and finally to the Web browser in HTML format.
In addition to operating under normal circumstances (consulting a mailbox or sending messages), the application must also operate in abnormal situations, such as facing an unknown user, receiving a bad password or, more critically, dealing with a communications error. In fact, you must try to make the application as robust as possible because it will probably be used by many people at the same time, day and night. Special care should be taken to predict every possible execution sequence of a Web application, under both normal and abnormal circumstances. If possible, test the application using different browsers, too, and check to see that everything runs as expected.
See Figure 35.2 for an illustration of the functioning of the application.
Figure 35.2 : The functioning of the application.
Here is a description of the steps described in Figure 35.2:
The user of the CGI application must provide information concerning himself or his mailbox. This is entered in the data fields of HTML forms; and when completed, the form is sent (submitted) to the application. The form is then decoded by a function of the cgi-lib.pl library, called ReadParse. It takes a reference to a hash table (you can think of a hash table as an array in which elements are related two by two, the key and the key's corresponding value) as an argument and retrieves the hash table in which each form field name is a key. The value of a field is the value associated with the hash key.
In the application, the hash table that stores the form fields data is called input. Every form is sent to the Web server with the POST method, which means the function ReadParse must read data from standard input (instead of reading the environment variable QUERY_STRING, if the method GET were used). This function is one of the general CGI application functions found in the useful cgi-lib.pl, which is included in this book's CD-ROM.
The following is an example of a form using the SendNewMessage function:
$fromaddr = $input{'login'}.'@'.$mailserver; print <<EOM; <HTML> <HEAD> <TITLE>New message</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <FORM ACTION="$url" METHOD=POST> <P> <CENTER> <B>De:</B> $fromaddr<P> <B>Para (email):</B> <INPUT VALUE="$oldTo" NAME=to SIZE=50><P> <B>Assunto:</B> <INPUT VALUE="$oldSubject" NAME=subject SIZE=50><P> <INPUT TYPE=image SRC="$pathButtonSend" NAME=send BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image SRC="$pathButtonReturnMailbox" NAME=return BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <P> <TEXTAREA ROWS=18 COLS=70 NAME=body>$body</TEXTAREA> </CENTER> <INPUT TYPE="hidden" VALUE="$input{'login'}" NAME=login> <INPUT TYPE="hidden" VALUE="$input{'password'}" NAME=password> </FORM> </BODY> </HTML>
When a Web browser requests a document from a Web server, a connection (using the HTTP-over TCP-protocol) must be established between the two parties. After the required page is sent to the browser, the connection and any information concerning this connection are lost.
But, each time the user asks the CGI script to perform an action (such as deleting a message from the electronic mailbox), there is data that the CGI script must know, such as the user's login name or password; otherwise, it cannot identify the correct mailbox.
Due to the design of HTTP protocol described earlier (which is transaction-oriented), a Web server is stateless. WebPOP is also stateless, which means that no user information is kept by the application (on a separate file or database) during the interval between connections. If you want to store information between two consecutive requests from the same user, some sort of keep-alive mechanism needs to be provided between the browser and the server in order to keep a connection as long as needed. You can also use the functionality provided by cookies, which are special bits of data constructed by the server that the browser keeps and sends to the server at each request).
It is up to the browser, or to the documents transferred between client and server, to keep any relevant user information. To save data, I have chosen to use hidden form fields (invisible to the user) on an HTML page. The form itself is sent to the CGI script with the POST method, read from stdin (stdin stands for standard input, and represents the "place" from which input data is obtained), and then decoded in order for all relevant data to be obtained. This way, you can transparently keep every important data field on the user's side; it will be sent to WebPOP each time there is a request on behalf of the user.
Output from the CGI script is always in HTML format so that the browser can display it correctly. WebPOP is capable of producing a limited number of HTML pages. In normal functioning, one of the following pages is displayed on the browser:
Figure 35.3 : WebPOP's initial page.
Additionally, the following are some status pages that report successful operation or errors:
Before receiving and displaying a page, the browser must identify its contents. The page contents are identified by the CGI application, which sends the MIME-type header Content-Type: text/html, followed by two newline characters \n before any HTML tags.
Having seen how the browser, server, and CGI application communicate, now look at the use of the protocols POP3 and SMTP. The finger protocol is also used by the application in order to find the user's name to include it in the From field of outgoing messages.
The Post Office Protocol version 3 (RFC1725) was created to permit mail clients (who are not permanently connected to the Internet) to access remote mailboxes in an easy and useful fashion. The application uses some of the most important commands that a POP3 server understands:
The POP3 server listens, by default, to port 110. A client must establish a TCP connection with it in order to start a dialog using these commands. Each command issued to the POP3 server must be followed by a CRLF (Carriage Return and Line Feed) pair (\r\n).
Look at an example of a connection (message request) between WebPOP and the POP3 server (POPGetMessage function):
print remoteHost "USER $input{'login'}\r\n"; &POPReplyOK; print remoteHost "PASS $input{'password'}\r\n"; &POPReplyOK; print remoteHost "LIST\r\n"; if (&POPReplyOK) { $nbMessages = &POPCountMessages(); if ($messageN <= $nbMessages) { print remoteHost "RETR $messageN\r\n"; &POPGetMessageHeader; &POPGetMessageBody; } else { &ShowSimpleForm($errorOutMessageTitle, $errorOutMessageBody); } } # else there's something wrong because there are no messages print remoteHost "QUIT\r\n"; &POPReplyOK;
The function that retrieves and presents the message list is POPListMessageBox, similar to the preceding function, but using the LIST command. Besides talking with the POP3 server, this function has to deal with the formatting of the messages list box. There were several choices in the presentation of the list of mail messages:
For your application, this final choice was taken in order to limit the message list to a pre-defined size. Otherwise, lots of messages in a mailbox would produce an HTML page too big to fit on a computer screen.
See Figure 35.4 for an example of the resulting page.
Figure 35.4 : The contents of a mailbox.
Mail is sent using the Simple Mail Transfer Protocol-SMTP (RFC821) server, which will forward any mail to the destination specified in the mail header. An SMTP server is somewhat more complicated than a POP3 server because it offers a lot more functionality; but, in your application, you use it indirectly in order to simplify functioning. In fact, mail is sent by invocation of either sendmail or smail, two well-known UNIX mail-handling systems. The function SendMessage in your application is responsible for the mailer invocation:
$destaddr = $input{'to'}; $fromaddr = $input{'login'}.'@'.$mailserver; $subject = $input{'subject'}; $body = $input{'body'}; if ($destaddr eq '' || $fromaddr eq '' || $subject eq '' || $body eq '') { &ShowSimpleForm($noFieldsTitle, $noFieldsBody); } else { $userName=&GetUserName($input{'login'}); open(MAIL,"| $sendmail \"$destaddr\""); print MAIL <<EOM; From: $fromaddr ($userName) To: $destaddr Reply-To: $fromaddr Subject: $subject X-Mail-Program: WebPOP - Web/Mail interface $body EOM close(MAIL);
As you might have noticed, the message sent to the SMTP server is composed of To, From, Subject, Reply-To, and custom X-Mail-Program fields and a message body. The mail-handling system understands those fields (in particular the To field) and forwards the message.
The preceding GetUserName function executes the finger program with the From address as the argument and parses the results in order to search the complete user's name. It would also be possible to communicate with the SMTP server through port 25 and issue the VRFY command, but I found the use of finger simpler (although it might not work on systems that refuse finger connections).
Installing WebPOP is a relatively simple task.
http://www.esoterica.pt/cgi-bin/webpop.pl
The file install.txt on the CD-ROM explains the installation process. There is one part of the listing that contains hard-coded values, generally found in socket.ph or socket.h.
$AF_INET = 2; # Internet protocols, specified in "socket.h" $SOCK_STREAM = 1; # Semantic (sequenced, two-way, reliable) $PROTOCOL = 0; # Protocol used to support the semantic chosen (TCP)
If you have problems or just want to improve portability of the application, you should consider including socket.ph in you Perl script (using the require directive).
WebPOP is an interpreted application that depends on the Perl interpreter of your system (either version 4 or version 5). As with every interpreted application, it is somewhat slower than it could be if it were executed using a binary format (precompiled). If you have a fast and resource-full system or do not plan to have many people using your application, this kind of functioning is just fine. But if you have either a surcharged server or plan to have many people using your application, you should probably consider porting the program to a language that can be compiled, such as C. That would limit the performance hit of the application on your system.
The CGI application presented in this chapter is a simple one and is intended to be a practical example. Several improvements could be made to it-in fact, most of the possible improvements listed here will probably appear in a future version of WebPOP. Anyway, feel free to change the code to suit your own needs or expectations. Here are some ideas:
You can also find Listing 35.1 on the CD-ROM included with this book.
Listing 35.1. WEBPOP.pl-A simple HTML to POP3-mail interface.
#!/usr/bin/perl ########################################################################### # webpop.pl 1.3sp - A simple HTML to POP3-mail interface # # # # How does it work? # # This program acts as a WWW to POP3 interface. It gets user login and # # password from a HTML form (form A) and then returns the user mail list, # # from the POP3 server. This list is another HTML form (form B), so that # # the user can pick a message to read, reply to, or delete. If the user # # asks to read a message, another request is sent to the POP3 server, # # now returning a HTML form (form C) with the message body. When in # # form B, the user can request to delete or reply to a message, too. # # That's it, no more bells or whistles. # # # # Todo list: # # - Support MIME / Attachments mail # # - Save message options # # - Support for URLs on messages # # # # Antonio Ferreira # # amcf@esoterica.pt # # # # January 1996 # ########################################################################### require '/usr/lib/cgi-lib.pl'; # The useful cgi-lib ######################### Variables ######################## ##### HTML Text and Buttons ##### $formATitle = 'WebPOP - Electronic mail'; # Title of form A (input form) $formBTitle = 'Mailbox of'; # Title of form B (list of messages) $formBHeading = 'Mailbox of'; # Heading of form B preceding user's login $formCTitle = 'Message'; # Title of form C (message) $formErrorTitle = 'Error'; # Title of error forms $error1 = 'Error 1: gethostbyname'; # Body of form reporting error on gethostbyname function $error2 = 'Error 2: socket'; # Body of form reporting error on socket function $error3 = 'Error 3: connect'; # Body of form reporting error on connect function $userInfoTitle = 'Unknown user info'; # Title of form reporting missing user info (login, password) $userInfoBody = 'You must enter your login and password!'; # Body of form reporting missing user info (login, password) $wrongLoginPassword = 'Wrong login or password!'; # Body of form reporting bad login or password $noMailBody = 'Sorry, you have no mail...'; # Body of form reporting no mail $errorOutMessageTitle = 'Invalid message number'; # Title of form reporting invalid message number $errorOutMessageBody = 'The requested message does not exist!'; # Body of form reporting invalid message number $noFieldsTitle = 'Uncompleted form'; # Title of form reporting 'missing fields on message' $noFieldsBody = 'You must fill in the form!'; # Body of form reporting incomplete form $deletedMessageTitle = 'Deleted message'; # Title of form reporting deleted message $deletedMessageBody = 'The message was deleted!'; # Body of form reporting deleted message $sentMessageTitle = 'Message sent'; # Title of form reporting sent message $sentMessageBody = 'Your message was sent!'; # Body of form reporting sent message $fromLine = 'From'; # String introducing "From" field on a form $toLine = 'To'; # String introducing "To" field on a form $subjectLine = 'Subject'; # String introducing "Subject" field on a form $dateLine = 'Date'; # String introducing "Date" field on a form $pathBackground = '/bg/esot_bg.gif'; # Path to background GIF $pathLogo = '/Imagens/webpop/webpop.gif'; # Path to webpop logo GIF $pathButtonHelp ='/Imagens/webpop/help.gif'; # Path to button Help GIF $pathButtonOpenMailbox ='/Imagens/webpop/open.gif'; # Path to button OpenMailbox GIF $pathButtonReturnMailbox = '/Imagens/webpop/return.gif'; # Path to button ReturnToMailBox GIF $pathButtonSendNewMessage = '/Imagens/webpop/sendn.gif'; # Path to SendNewMessage button GIF $pathButtonSend = '/Imagens/webpop/send.gif'; # Path to Send button GIF $pathButtonReadMessage = '/Imagens/webpop/read.gif'; # Path to ReadMessage button GIF $pathButtonReplyMessage = '/Imagens/webpop/reply.gif'; # Path to ReplyMessage button GIF $pathButtonDeleteMessage = '/Imagens/webpop/delete.gif'; # Path to DeleteMessage button GIF $lwidth = 197; # Width of logo $lheight = 68; # Height of logo $bwidth = 70; # Width of buttons $bheight = 37; # Height of buttons ##### Paths, binaries and system specific information ##### $url = 'http://www.esoterica.pt/cgi-bin/webpop.pl'; # webpop URL $sendmail = '/usr/bin/smail'; # Path and parameters for the mailer #$sendmail = '/usr/bin/sendmail -t -n'; # Path and parameters for the mailer $mailserver = 'mail.esoterica.pt'; # Complete default POP3 mail server hostname $POP3_Port = 110; # Port which the POP3 server is listening to $grep = '/usr/bin/grep'; # Path for the grep program $finger = '/usr/bin/finger'; # Path for the finger client program $AF_INET = 2; # Internet protocols, specified in "socket.h" $SOCK_STREAM = 1; # Semantic (sequenced, two-way, reliable) $PROTOCOL = 0; # Protocol used to support the semantic chosen (TCP) $template = 'S n a4 x8'; # Template used to pack lists of values ########################## Start of Main Program ########################## &ReadParse(*input); # cgi-lib, constructs list of key=value form data print &PrintHeader(); # cgi-lib, prints header "Content-type: text/html\n\n" if (&MethGet()) { # GET was used, so... &InitialForm(); # ... retrieve the initial form } else { # POST was used, so process POP3 query $messageN = $input{'messageN'}; if ((!$input{'login'} || !$input{'password'}) && (!$input{'help.x'})) { # No info given &ShowSimpleForm($userInfoTitle, $userInfoBody); } else { if ((defined $input{'open.x'}) || (defined $input{'return.x'})) { # User asked to list messages (needs: login, password) &ShowMessageList(); } elsif (defined $input{'read.x'}) { # User asked to read a message (needs: login, password, msgnumber) &ShowMessage(); } elsif (defined $input{'sendn.x'}) { # User asked to send a new message (needs: login, password) &SendNewMessage(); } elsif (defined $input{'reply.x'}) { # User asked to reply to a message (needs: login, password, msgnumber) &ReplyMessage(); } elsif (defined $input{'send.x'}) { # User asked to send to a message (needs: login, password) &SendMessage(); } elsif (defined $input{'delete.x'}) { # User asked to delete a message (needs: login, password, msgnumber) &DeleteMessage(); } elsif (defined $input{'help.x'}) { # User asked for help &HelpUser(); } } } exit(0); ########################## End of Main Program ########################## #################### Start of subroutines definitions ################### ##### Shows the initial form asking for user's login and password ##### sub InitialForm { print <<EOM; <HTML> <HEAD> <TITLE>$formATitle</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <P ALIGN=center><IMG SRC="$pathLogo" BORDER=0 WIDTH=$lwidth HEIGHT=$lheight></P> <P> <FORM ACTION="$url" METHOD=POST> <CENTER> <PRE> <B> Login</B>: <INPUT VALUE="$input{'login'}" NAME=login SIZE=12><BR> <B>Password</B>: <INPUT VALUE="$input{'password'}" TYPE="password" NAME=password SIZE=12> </PRE> </CENTER> <P> <CENTER> <INPUT TYPE=image SRC="$pathButtonOpenMailbox" NAME=open BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image SRC="$pathButtonHelp" NAME=help BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> </CENTER> </FORM> </BODY> </HTML> EOM } ######################################################################### # Subroutines for option "open", that is, ShowMessageList # ######################################################################### ##### Gets and displays the list of mail messages ##### sub ShowMessageList { if (($name, $aliases, $addrtype, $len, $remote_addr) = gethostbyname($mailserver)) { if (socket(remoteHost, $AF_INET, $SOCK_STREAM, $PROTOCOL)) { $remoteStruct = pack ($template, $AF_INET, $POP3_Port, $remote_addr); if (connect(remoteHost, $remoteStruct)) { &POPListMessageBox(); } else { &ShowSimpleForm($formErrorTitle, $error3); } close(remoteHost); } else { &ShowSimpleForm($formErrorTitle, $error2); } } else { &ShowSimpleForm($formErrorTitle, $error1); } } ##### Talks with POP server, gets and prints the list of messages ##### sub POPListMessageBox { local($nbMessages); select(remoteHost); $| = 1; select(STDOUT); $| = 1; print <<EOM; <HTML> <HEAD> <TITLE>$formBTitle $input{'login'}</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <H1 ALIGN=center>$formBHeading <I>$input{'login'}</I></H1> <FORM ACTION="$url" METHOD=POST> <P> EOM $nbMessages = 0; $_ = <remoteHost>; print remoteHost "USER $input{'login'}\r\n"; if (&POPReplyOK) { print remoteHost "PASS $input{'password'}\r\n"; if (&POPReplyOK) { print remoteHost "LIST\r\n"; if (&POPReplyOK) { # If 0 messages POP returns -ERR, so it skips this if $nbMessages=&POPCountMessages(); if ($nbMessages > 0) { # If POP replied +OK after LIST, but there are no messages print "<CENTER>You have $nbMessages message"; # User can have one or more messages ($nbMessages>1) ? print "s" : print ""; print "!<P>"; if ($nbMessages>10) { print "<SELECT NAME=\"messageN\" SIZE=10>"; } else { print "<SELECT NAME=\"messageN\" SIZE=$nbMessages>"; } $messageN = 0; while ($messageN < $nbMessages) { $messageN = $messageN +1; print remoteHost "TOP $messageN 50\r\n"; # 50 lines should be enough to get the headers &POPGetMessageHeaders(); } } else { print "$noMailBody"; } } else { print "$noMailBody"; } } else { print "$wrongLoginPassword"; $nbMessages = -1; } } else { print "$wrongLoginPassword"; $nbMessages = -1; } print remoteHost "QUIT\r\n"; &POPReplyOK; if ($nbMessages > 0) { print "</SELECT>"; } if ($nbMessages > -1 ) { print <<EOM; <P> <INPUT TYPE=image SRC="$pathButtonSendNewMessage" NAME=sendn BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> EOM } if ($nbMessages > 0) { # Only if there are messages print <<EOM; <INPUT TYPE=image ALIGN=top SRC="$pathButtonReadMessage" NAME=read BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image ALIGN=top SRC="$pathButtonReplyMessage" NAME=reply BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image ALIGN=top SRC="$pathButtonDeleteMessage" NAME=delete BORDER=0 WIDTH=$bwidth HEIGHT=$bheight><BR> EOM } print <<EOM; <INPUT TYPE="hidden" NAME=login VALUE=$input{'login'}> <INPUT TYPE="hidden" NAME=password VALUE=$input{'password'}> </CENTER> </FORM></BODY></HTML> EOM } ##### Gets headers (From, Date, ...) from message sent by POP server ##### sub POPGetMessageHeaders { local($from, $date, $subject, $replyto); $_ = <remoteHost>; while ($_ !~ /^\.\r\n/) { # Seeks . in one line to end if ($_ =~ /^From: (.+)$/) { # Seeks "From: ..." ($from) = /^From: (.+)$/; ($from) =~ s/</</g; # Replace <'s ($from) =~ s/>/>/g; # Replace >'s ($from) =~ s/\r//; # Replace carriage return's ($from) =~ s/\n//; # Replace newline's $_ = $from; ($replyto) = /<(.+)>/; if ($replyto eq '') { $replyto=$from; } } elsif ($_ =~ /^Subject: (.+)$/) { ($subject) = /^Subject: (.+)$/; ($subject) =~ s/</</g; # Replace <'s ($subject) =~ s/>/>/g; # Replace >'s ($subject) =~ s/\r//; # Replace carriage return's ($subject) =~ s/\n//; # Replace newline's } elsif ($_ =~ /^Date: (.+) .+$/) { ($date) = /^Date: (.+) .+$/; } $_ = <remoteHost>; } # Fixed size list items $from = substr($from,0,40); $_ = $date; ($date) = /.* ([0-9]+ [a-zA-Z]+ [0-9]+) .*$/; $date = substr($date,0,11); $subject = substr($subject,0,24); $selected = ($messageN==1 ? "SELECTED" : ""); print "<OPTION VALUE=$messageN $selected>$from <> $subject <> $date\n"; } ##### Counts the number of messages available from the POP3 server ##### sub POPCountMessages { local($nbMessages); $nbMessages = 0; while (<remoteHost> !~ /^\.\r\n/) { $nbMessages = $nbMessages + 1; } return $nbMessages; } ######################################################################### # Subroutines for option "read", that is, ShowMessage # ######################################################################### ##### Gets and displays the a given message ##### sub ShowMessage { if (($name, $aliases, $addrtype, $len, $remote_addr) = gethostbyname($mailserver)) { if (socket(remoteHost, $AF_INET, $SOCK_STREAM, $PROTOCOL)) { $remoteStruct = pack ($template, $AF_INET, $POP3_Port, $remote_addr); if (connect(remoteHost, $remoteStruct)) { &POPGetMessage(); $messageBody = $body; $body =~ s/</</g; $body =~ s/>/>/g; $body =~ s/"/"/g; print "<HTML>"; print $messageHeader.$body; print <<EOM; </PRE> <HR> <FORM ACTION="$url" METHOD=POST> <CENTER> <INPUT TYPE=image SRC="$pathButtonReturnMailbox" NAME=return BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image SRC="$pathButtonReplyMessage" NAME=reply BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image SRC="$pathButtonDeleteMessage" NAME=delete BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> </CENTER> <INPUT TYPE="hidden" VALUE="$input{'login'}" NAME=login> <INPUT TYPE="hidden" VALUE="$input{'password'}" NAME=password> <INPUT TYPE="hidden" VALUE=$messageN NAME=messageN> </FORM> </BODY> </HTML> EOM } else { &ShowSimpleForm($formErrorTitle, $error3); } close(remoteHost); } else { &ShowSimpleForm($formErrorTitle, $error2); } } else { &ShowSimpleForm($formErrorTitle, $error1); } } ##### Asks POP server to retrieve a message ##### sub POPGetMessage { select(remoteHost); $| = 1; select(STDOUT); $| = 1; $_ = <remoteHost>; print remoteHost "USER $input{'login'}\r\n"; &POPReplyOK; # This time, no need to check login or password... print remoteHost "PASS $input{'password'}\r\n"; &POPReplyOK; # ... because it was WEBPOP that sent it! print remoteHost "LIST\r\n"; if (&POPReplyOK) { $nbMessages = &POPCountMessages(); if ($messageN <= $nbMessages) { print remoteHost "RETR $messageN\r\n"; &POPGetMessageHeader; &POPGetMessageBody; } else { &ShowSimpleForm($errorOutMessageTitle, $errorOutMessageBody); } } # else there's something wrong because there are no messages print remoteHost "QUIT\r\n"; &POPReplyOK; } ##### Gets the Header of the message sent by the POP server ##### sub POPGetMessageHeader { local($from, $subject, $date, $replyto); do { $_ = <remoteHost>; if ($_ =~ /^From: (.+)$/) { ($from) = /^From: (.+)$/; ($from) =~ s/</</g; ($from) =~ s/>/>/g; ($from) =~ s/\r//; ($from) =~ s/\n//; } elsif ($_ =~ /^Subject: (.+)$/) { ($subject) = /^Subject: (.+)$/; ($subject) =~ s/</</g; ($subject) =~ s/>/>/g; ($subject) =~ s/\r//; ($subject) =~ s/\n//; } elsif ($_ =~ /^Date: (.+)$/) { ($date) = /^Date: (.+) .+$/; ($date) =~ s/</</g; ($date) =~ s/>/>/g; ($date) =~ s/\r//; ($date) =~ s/\n//; } } until ($_ =~ /^\r\n/); $_ = $from; ($replyto) = /.*<(.+)>.*/; if ($replyto eq '') { $replyto = $from; } $messageHeader = "<HEAD><TITLE>$subject</TITLE> <BODY BACKGROUND=\"$pathBackground\"></HEAD> <BODY> <H2>$fromLine: $from</H2> <H3>$subjectLine: $subject<BR> $dateLine: $date</H3><HR><PRE>"; $oldTo = $from; $oldSubject = "Re: ".$subject; } ##### Gets the Body of the message sent by the POP server ##### sub POPGetMessageBody { do { $_ = <remoteHost>; $body = $body.$_; } until ($_ =~ /^\.\r\n/); } ######################################################################### # Subroutines for option "sendnew", that is, SendNewMessage # ######################################################################### ##### Asks for data (header+body) and then sends the message ##### sub SendNewMessage { $fromaddr = $input{'login'}.'@'.$mailserver; print <<EOM; <HTML> <HEAD> <TITLE>New message</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <FORM ACTION="$url" METHOD=POST> <P> <CENTER> <B>De:</B> $fromaddr<P> <B>Para (email):</B> <INPUT VALUE="$oldTo" NAME=to SIZE=50><P> <B>Assunto:</B> <INPUT VALUE="$oldSubject" NAME=subject SIZE=50><P> <INPUT TYPE=image SRC="$pathButtonSend" NAME=send BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <INPUT TYPE=image SRC="$pathButtonReturnMailbox" NAME=return BORDER=0 WIDTH=$bwidth HEIGHT=$bheight> <P> <TEXTAREA ROWS=18 COLS=70 NAME=body>$body</TEXTAREA> </CENTER> <INPUT TYPE="hidden" VALUE="$input{'login'}" NAME=login> <INPUT TYPE="hidden" VALUE="$input{'password'}" NAME=password> </FORM> </BODY> </HTML> EOM } ##### Uses the mailer defined to send a message ##### sub SendMessage { $destaddr = $input{'to'}; $fromaddr = $input{'login'}.'@'.$mailserver; $subject = $input{'subject'}; $body = $input{'body'}; if ($destaddr eq '' || $fromaddr eq '' || $subject eq '' || $body eq '') { &ShowSimpleForm($noFieldsTitle, $noFieldsBody); } else { $userName=&GetUserName($input{'login'}); open(MAIL,"| $sendmail \"$destaddr\""); print MAIL <<EOM; From: $fromaddr ($userName) To: $destaddr Reply-To: $fromaddr Subject: $subject X-Mail-Program: WebPOP - Web/Mail interface $body EOM close(MAIL); print <<EOM; <HTML> <HEAD> <TITLE>$sentMessageTitle</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <H1 ALIGN=center>$sentMessageBody</H1> <P> <B>$fromLine: </B> $fromaddr<BR> <B>$toLine: </B> $destaddr<BR> <B>$subjectLine: </B> $subject <P> <FORM ACTION="$url" METHOD=POST> <CENTER> <INPUT TYPE=image SRC="$pathButtonReturnMailbox" NAME=return BORDER=0 WIDTH=$bwidth HEIGHT=$bheight><BR> </CENTER> <INPUT TYPE="hidden" VALUE="$input{'login'}" NAME=login> <INPUT TYPE="hidden" VALUE="$input{'password'}" NAME=password> </FORM> </BODY> </HTML> EOM } } ######################################################################### # Subroutines for option "reply", that is, ReplyMessage # ######################################################################### ##### Reads from POP server, presents reply-form and sends the message ##### sub ReplyMessage { if (($name, $aliases, $addrtype, $len, $remote_addr) = gethostbyname($mailserver)) { if (socket(remoteHost, $AF_INET, $SOCK_STREAM, $PROTOCOL)) { $remoteStruct = pack ($template, $AF_INET, $POP3_Port, $remote_addr); if (connect(remoteHost, $remoteStruct)) { &POPGetMessage(); $body = "> ".$body; ($body) =~ s/\n/\n> /g; } else { &ShowSimpleForm($formErrorTitle, $error3); } close(remoteHost); } else { &ShowSimpleForm($formErrorTitle, $error2); } } else { &ShowSimpleForm($formErrorTitle, $error1); } &SendNewMessage(); } ######################################################################### # Subroutines for option "delete", that is, DeleteMessage # ######################################################################### ##### Deletes a message ##### sub DeleteMessage { if (($name, $aliases, $addrtype, $len, $remote_addr) = gethostbyname($mailserver)) { if (socket(remoteHost, $AF_INET, $SOCK_STREAM, $PROTOCOL)) { $remoteStruct = pack ($template, $AF_INET, $POP3_Port, $remote_addr); if (connect(remoteHost, $remoteStruct)) { &POPDeleteMessage(); } else { &ShowSimpleForm($formErrorTitle, $error3); } close(remoteHost); } else { &ShowSimpleForm($formErrorTitle, $error2); } } else { &ShowSimpleForm($formErrorTitle, $error1); } } ##### Deletes a message using the POP server ##### sub POPDeleteMessage { select(remoteHost); $| = 1; select(STDOUT); $| = 1; $_ = <remoteHost>; print remoteHost "USER $input{'login'}\r\n"; &POPReplyOK; print remoteHost "PASS $input{'password'}\r\n"; &POPReplyOK; print remoteHost "LIST\r\n"; if (&POPReplyOK) { $nbMessages = &POPCountMessages(); if ($messageN <= $nbMessages) { print remoteHost "DELE $messageN\r\n"; print <<EOM; <HTML> <HEAD> <TITLE>$deletedMessageTitle</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <H2 ALIGN=center>$deletedMessageBody</H2> <P> <FORM ACTION="$url" METHOD=POST> <CENTER> <INPUT TYPE=image SRC="$pathButtonReturnMailbox" NAME=return BORDER=0 WIDTH=$bwidth HEIGHT=$bheight><BR> </CENTER> <INPUT TYPE="hidden" VALUE="$input{'login'}" NAME=login> <INPUT TYPE="hidden" VALUE="$input{'password'}" NAME=password> </FORM> </BODY> </HTML> EOM } else { &ShowSimpleForm($errorOutMessageTitle, $errorOutMessageBody); } } # else there's something wrong because there are no messages print remoteHost "QUIT\r\n"; &POPReplyOK; } ######################################################################### # General subroutines # ######################################################################### ##### True if POP reply's '+OK', false otherwise ##### sub POPReplyOK { $_ = <remoteHost>; if ($_ =~ /^\+OK/) { return 1; } else { return 0; } } ##### Shows a simple form with a title a short message ##### sub ShowSimpleForm { print <<EOM; <HTML> <HEAD> <TITLE>$_[0]</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <H2>$_[1]</H2> </BODY> </HTML> EOM } ##### Gets user's name from finger information, using login as the key ##### sub GetUserName { local($tmp); $login = $_[0]; $_ = '$finger $login@$mailserver | $grep \"Name:\"'; ($tmp) = /.*Name: (.+).*$/; return $tmp; } ##### Adds or removes charaters from a string in order to have a fixed length ##### sub fixedLength { if (length($_[0])<$_[1]) { foreach $num (1 .. ($_[1]-length($_[0]))) { $_[0] = $_[0]." "; } } elsif (length($_[0])>$_[1]) { $_[0] = substr($_[0],0,$_[1]); } return $_[0]; } ##### Shows help page ##### sub HelpUser { print <<EOM; <HTML> <HEAD> <TITLE>WebPOP - Help</TITLE> <BODY BACKGROUND="$pathBackground"> </HEAD> <BODY> <H1 ALIGN=center>WebPOP<BR><I>Help </I></H1> <P> Welcome to WebPOP, the web application that helps you manage your electronic mail, in a pratical and simple manner! The author of this application, <A HREF="http://www.esoterica.pt/amcf/">Ant-nio Ferreira</A>, would like to know <A HREF="mailto:amcf@esoterica.pt">your opinion and suggestions</A> on WebPOP. <P> About WebPOP: <UL> <LI><B>What is WebPOP?</B> <BR> It is an application that helps you manage your electronic mailbox from a World-Wide Web page, which is an interface certainly familiar to you. Besides reading your mail, you can also send messages to other people on the Internet and/or delete old messages from your mailbox. <P> <LI><B>How does it work?</B> <BR> The initial screen asks for your identification (login and password). Only that way you can have access to your mailbox! <BR> After entering the identification, you are given the list of messages from your electronic mailbox. 3 of the 4 buttons at the bottom of the list permit you to <B>Read</B>, <B>Reply</B>, or <B>Delete</B> the selected message. If you wish to send a new message, you must click on the first button, titled <B>Send new</B>. <BR> Finally, when you are reading a message, 2 additional buttons permit you to <B>Reply</B> to that message (send a message with the same subject to the person who wrote to you before) or <B>Delete</B> the message (permanently erase it from your mailbox). If you wish to save a copy of a particular message, you must use the option <B>Save as...</B> (or an equivalent one) from your browser's File menu. The browser is the program you are using to access the World-Wide Web. <P> <LI><B>Can I save my messages on my personal computer and organize my mail?</B> <BR> Yes. You can create, in your personal computer's hard disk, some directories (folders), each one dedicated to a person or subject. You should have your own mail organization strategy. Those folders can all be put inside a main folder called <B>MAIL</B> or <B>WEBPOP</B>, for example. Then, when you are reading a message from your browser's window, you can choose the option <B>Save as...</B> and save the message in your hard disk, inside the desired folder and with a suggestive name. After saving the message, you can delete it from your mailbox... it is very important to do so every once in a while (or every time you save a message) otherwise your mailbox will grow forever, and accessing it will become progressively slower. <P> <LI><B>Is there any way to attach a file to a message?</B> <BR> Not really. The only way is to use the options <B>Cut</B> and <B>Paste</B> from your operating system's Edit menu. This only works if your file is not too big and is composed only of simple (ASCII) text. <P> <LI><B>If I send a message with WWW links (URL's) in the text, will it be possible to click on them so that the browser executes the action indicated?</B> <BR> Not yet. This is a feature that will probably appear with the next version of WebPOP. It will offer you the possibility to click on a URL inside a given message, just as you would on a link inside an ordinary HTML page. Support for MIME types (so that messages can be composed with different file formats) is also a future feature. </UL> <P> <A HREF="$url">Return to the initial form</A> </BODY> </HTML> EOM }
In this chapter you created an application that enables users to read their mail through Web pages. This application has served as an example of the principles and characteristics of multi-protocol applications. Not only have you created what can be a very useful application, but you have also gone through all the steps in the creation process of such applications and have acquired the know-how needed to proceed with the development of other applications.