+ All Categories
Home > Documents > [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

[Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Date post: 23-Oct-2015
Category:
Upload: eminemb
View: 15 times
Download: 1 times
Share this document with a friend
29
By icarus This article copyright Melonfire 2000-2002. All rights reserved.
Transcript
Page 1: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

By icarus

This article copyright Melonfire 2000−2002. All rights reserved.

Page 2: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Table of ContentsThe Plan...............................................................................................................................................................1

A Picture Is Worth A Thousand Words...........................................................................................................2

The Way Things Work.......................................................................................................................................5

Structure And Syntax.........................................................................................................................................7

Getting Attached...............................................................................................................................................11

Room With A View...........................................................................................................................................15

Getting Down.....................................................................................................................................................24

Miles To Go.......................................................................................................................................................27

Building A PHP−Based Mail Client (part 2)

i

Page 3: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

The PlanIn the first part of this article, I demonstrated the basics of PHP's IMAP functions, using them to connect to aPOP3 server and retrieve a list of messages from it. I also explained how to extract important messageheaders, showed you how to delete selected messages from the server, and gave you a crash course in PHP'ssession management functions.

While I have a fairly clear idea of the requirements for this application, there's still an important hole in myoverall plan − I have not yet written any code to handle message attachments. This is holding up myimplementation, as a number of scripts in the application will require this capability. And so, my first tasktoday must be to understand how message attachments work, and write a few generic functions that alloweasy manipulation of these attachments.

In case you don't already have the source code for the application described in this case study, you candownload it here.

Let's get started!

The Plan 1

Page 4: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

A Picture Is Worth A Thousand WordsThe best way to understand how message attachments work is with an example. Consider the following emailmessage, which contains no attachments,

Return−Path: <[email protected]>Received: from localdomain (unknown [205.89.123.112])by mail.domain.com (Postfix) with ESMTP id B0AYBHA41for <[email protected]>; Thu, 6 Dec 2001 21:05:51 +0530(IST)Message−Id: <[email protected]>Date: Thu, 06 Dec 2001 21:09:43 +0500To: John Doe <[email protected]>From: Jane Doe <[email protected]>Subject: PhotosMime−Version: 1.0Content−Type: text/plain; charset="us−ascii"

Do you still have those vacation photos with you? Send me acopy if you do.

− Jane

and then contrast it with this one, which does.

Return−Path: <[email protected]>Received: from localdomain (unknown [205.89.123.112])by mail.domain.com (Postfix) with ESMTP id B0AYBHA41for <[email protected]>; Thu, 6 Dec 2001 21:05:51 +0530(IST)Message−Id: <3.0.3.32.20011207082225.006e927c@localhost>Date: Fri, 07 Dec 2001 08:22:25 +0500To: Jane Doe <[email protected]>From: John Doe <[email protected]>Subject: Re: PhotosMime−Version: 1.0Content−Type: multipart/mixed;boundary="=====================_1007675545==_"

−−=====================_1007675545==_Content−Type: text/plain; charset="us−ascii"

Hi Jane,

Find photos attached.

A Picture Is Worth A Thou... 2

Page 5: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

John−−=====================_1007675545==_Content−Type: application/zip; name="photos1.zip";x−mac−type="705A4950"; x−mac−creator="705A4950"Content−Transfer−Encoding: base64Content−Disposition: attachment; filename="photos1.zip"

UEsDBBQAAAAIABythCvu1mnpugIAALsIAAALAAAAY29tcG9zZS5waHC1lltP2zAUx59Xqd/−− snip −−AK6LAAAAAA==−−=====================_1007675545==_Content−Type: application/zip; name="photos2.zip";x−mac−type="705A4950"; x−mac−creator="705A4950"Content−Transfer−Encoding: base64Content−Disposition: attachment; filename="photos2.zip"

UEsDBBQAAAAIAACRDSux3m3PFwIAABsIAAAXAAAAc29hcGxpYi5zb2FwaW50ZXJvcC5waHC−− snip −−cFBLBQYAAAAACwALALcCAABRfQAAAAA=−−=====================_1007675545==_−−

As you can see, the message containing attachments differs from the plaintext message in a couple ofimportant ways.

1. Since it consists of different parts (one for the body and one for each attachment), it's Content−Type:header specifies the message type as "multipart/mixed".

Content−Type: multipart/mixed;

2. The various message sections are demarcated by a boundary "marker"; this marker is unique to eachmessage, and is defined in the main message header so that MIME−compliant mail clients can distinguishbetween the various message parts.

Content−Type: multipart/mixed;boundary="=====================_1007675545==_"

If you look at the example above, you'll see that, as specified in the message header, the boundary marker"=====================_1007675545==_" is used as a separator between the different parts of themessage.

3. Since the SMTP protocol cannot handle binary data, binary attachments − in this case, zip archives − mustbe encoded into a text representation using one of a number of different encoding methods. This information,together with information on the attachment type and original filename, appears in the header for each

Building A PHP−Based Mail Client (part 2)

A Picture Is Worth A Thou... 3

Page 6: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

message section.

Content−Type: application/zip; name="photos2.zip";x−mac−type="705A4950"; x−mac−creator="705A4950"Content−Transfer−Encoding: base64Content−Disposition: attachment; filename="photos2.zip"

This provides the mail client with all the information it needs to decode and display the attachment. It can usethe Content−Transfer−Encoding: header to determine how best to decode the text block back into binary data,the filename to determine what the resulting file should be called, and the Content−Disposition: header todecide whether the binary data should be displayed as an attachment or within the message body.

Note that this discussion is restricted to MIME attachments only. Other standards to handle mail attachmentsdo exist. Take a look at http://www.faqs.org/rfcs/rfc2045.html for the MIME specification.

Building A PHP−Based Mail Client (part 2)

A Picture Is Worth A Thou... 4

Page 7: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

The Way Things WorkAfter a little research, it quickly becomes apparent that a standard process flow can be developed forattachment handling, both while sending and reading mail. Here's my first draft of the process:

When reading email,

1. Check the message's Content−Type: header and count the number of message parts.

If the Content−Type: header indicates that the message is in plaintext, you can skip ahead to the last step. Ifit's anything but "text/plain", it's a fair bet that you'll have to parse it further.

An alternative here would be to parse the message body and count how many parts it's divided into − if it'stwo or more, you can again expect to have to parse it further.

2. Parse the message body.

If the message is a multipart message − for example, the type "multipart/mixed" − it is necessary to parse thevarious sections of the message to determine the attachment attributes.

As explained previously, every message section includes a header providing information on the data enclosedwithin that section. This data is used by the mail client to figure out how to handle that particular section.

3. Display the message section(s).

Once the client has sufficient information on the number and type of attachments, the final step is to displayeach section. Plaintext sections can be displayed as is; encoded sections may need to be decoded and thendisplayed. If the client supports HTML−encoded mail − as most Web−based clients do − it may need to jumpthrough a few additional hoops to decode and render embedded HTML markup.

It's important to note that different mail clients handle multipart messages differently. Some clients displayeach section as is (raw text) without processing it further; others use the encoding information present in thesection header to decode and display each section separately. Some clients display each message section as anattachment, while others use the data type of each section to decide whether the decoded section should bedisplayed inline (within the message body) or as an attachment.

What about sending email? Well, that's also fairly simple − all you need to do is build a messageincrementally, with each part representing an attachment and a boundary marker separating the various parts.

1. Build the message headers.

When sending email, the first step is to build the message headers. Some of these headers are built in responseto user input − for example, the subject line and recipient addresses − while others must be added by the clientitself. Typically, if one or more message attachments exist, the client will need to alter the Content−Type: ofthe message and generate a boundary marker to demarcate the various message sections.

2. Add the message body and attachments.

The Way Things Work 5

Page 8: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Once the headers have been built, the next step is to add the message text. If attachments exist, they must beencoded into plaintext and appended to the message body, with the message's unique boundary markerseparating the various sections from each other.

3. Send the message.

With the headers and body both ready, the final task is to actually connect to a mail server and send the mailout. Some programming languages (like PHP) offer high−level constructs to perform this function; othersrequire you to use low−level constructs to open up a socket connection to the server and send the messageusing socket communication techniques.

Building A PHP−Based Mail Client (part 2)

The Way Things Work 6

Page 9: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Structure And SyntaxUsing this process flow as reference, let's now begin implementing the code for the mail client.

A quick look at the PHP manual reveals that PHP provides a number of useful functions to assist in thisprocess:

1. imap_fetchstructure() − read and return information on message structure

2. imap_header() − return an object containing header elements

3. imap_body() − retrieve the complete message body

4. imap_fetchbody() − retrieve a specific section of the body

Of these, you've already seen the imap_fetchstructure() function in action − using a mailbox handle andmessage number as arguments, this function reads the message body and returns an object containinginformation on the message size, message body and MIME parts within it.

This object exposes a "parts" property, which is itself an array; the elements of this array are objectsrepresenting the various sections of the message. Therefore, it's possible to identify whether or not a messagecontains multiple parts, and obtain information on those parts, simply by iterating through this array.

Each of the objects in the "parts" array provides information on the corresponding message section − theencoding, the size, the type and subtype, the filename and so on. A complete list of the properties exposed byeach of these objects is available in the PHP manual athttp://www.php.net/manual/en/function.imap−fetchstructure.php − you should look at it before proceedingfurther.

Using this information, it's possible to write a couple of simple functions that build on imap_fetchstructure()to deliver customized information about message sections:

<?// define some constants// message types$type = array("text", "multipart", "message", "application","audio","image", "video", "other");// message encodings$encoding = array("7bit", "8bit", "binary", "base64","quoted−printable","other");

// parse message bodyfunction parse($structure){global $type;

Structure And Syntax 7

Page 10: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

global $encoding;

// create an array to hold message sections$ret = array();

// split structure into parts$parts = $structure−>parts;

for($x=0; $x<sizeof($parts); $x++){$ret[$x]["pid"] = ($x+1);

$this = $parts[$x];

// default to textif ($this−>type == "") { $this−>type = 0; }$ret[$x]["type"] = $type[$this−>type] . "/" .strtolower($this−>subtype);

// default to 7bitif ($this−>encoding == "") { $this−>encoding = 0; }$ret[$x]["encoding"] = $encoding[$this−>encoding];

$ret[$x]["size"] = strtolower($this−>bytes);

$ret[$x]["disposition"] = strtolower($this−>disposition);

if (strtolower($this−>disposition) == "attachment"){$params = $this−>dparameters;foreach ($params as $p){if($p−>attribute == "FILENAME"){$ret[$x]["name"] = $p−>value;break;}}}}

return $ret;}?>

The parse() function accepts an object, as returned by imap_fetchstructure(), and parses it to produce an arrayholding some very specific information on each message part.

Building A PHP−Based Mail Client (part 2)

Structure And Syntax 8

Page 11: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

First, parse() creates an empty array named $ret, which will ultimately contain as many elements as there aremessage parts.

<?// create an array to hold message sections$ret = array();?>

Every element of $ret will itself be an associative array, with keys representing the part number, part type,encoding, disposition, size and filename.

Next, parse() creates an array named $parts, to hold the object array returned by the "parts" property of theimap_fetchstructure() object.

<?// split structure into parts$parts = $structure−>parts;?>

parse() then iterates through $parts, adding each element to $ret and creating keys to represent thecharacteristics of each part found.

<?for($x=0; $x<sizeof($parts); $x++){$ret[$x]["pid"] = ($x+1);

$ret[$x]["type"] = $type[$this−>type] . "/" .strtolower($this−>subtype);

$ret[$x]["encoding"] = $encoding[$this−>encoding];

$ret[$x]["size"] = strtolower($this−>bytes);

$ret[$x]["disposition"] = strtolower($this−>disposition);

// some parts snipped for brevity}?>

The IMAP specification (available at http://www.faqs.org/rfcs/rfc2060.html) defines a number, or part ID, forevery part of a multipart message, starting from 1 (which is usually the message text itself); my array definesthis part ID via the "pid" key, and I'm recording it at this stage itself because I'm sure to need it when handling

Building A PHP−Based Mail Client (part 2)

Structure And Syntax 9

Page 12: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

messages containing more than one attachment.

The type and encoding of each part are stored as integers by imap_fetchstructure(); these correspond toelements of the $type and $encoding arrays respectively. If you look at the complete function definitionabove, you'll see that these arrays are defined outside the function, and imported into it with the "global"keyword.

Here's an example of the array returned by parse():

Array([0] => Array([pid] => 1[type] => text/plain[encoding] => 7bit[size] => 41[disposition] =>)

[1] => Array([pid] => 2[type] => application/zip[encoding] => base64[size] => 50634[disposition] => attachment[name] => photos1.zip)

[2] => Array([pid] => 3[type] => application/zip[encoding] => base64[size] => 44882[disposition] => attachment[name] => photos2.zip)

)

As you can see, this is a fairly clear representation of the various sections that make up a MIME message.

Building A PHP−Based Mail Client (part 2)

Structure And Syntax 10

Page 13: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Getting AttachedI've also written another function to extract the attachments only from the array returned by parse() − take alook at the get_attachments() function:

<?// iterate through object returned by parse()// create a new array holding information only on messageattachmentsfunction get_attachments($arr){for($x=0; $x<sizeof($arr); $x++){if($arr[$x]["disposition"] == "attachment"){$ret[] = $arr[$x];}}return $ret;}?>

Let's now backtrack a little and add this code to the message listing, "list.php", where it will be used toidentify which messages have attachments (so that an attachment icon can be displayed next to thosemessages).

Here's the revised version of that script:

<?php

// list.php − display message list

// includesinclude("functions.php");

// session checksession_start();if (!session_is_registered("SESSION")){header("Location: error.php?ec=2");exit;}

// open mailbox$inbox = @imap_open ("{". $SESSION_MAIL_HOST . "/pop3:110}",

Getting Attached 11

Page 14: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

$SESSION_USER_NAME, $SESSION_USER_PASS) or header("Location:error.php?ec=3");

// get number of messages$total = imap_num_msg($inbox);

?><html><head></head><body bgcolor="White">

<?// page header?>

<table width="100%" border="0" cellspacing="3"cellpadding="5"><!−− command buttons − snipped −−></table>

<?if ($total > 0){?><table width="100%" border="0" cellspacing="0"cellpadding="5"><form action="delete.php" method="post"><!−− message info columns −−><tr><td width="5%"><font size="−1">&nbsp;</font></td><td width="5%"><font size="−1">&nbsp;</font></td><td width="15%"><font face="Verdana"size="−1"><b>Date</b></font></td><td width="20%"><font face="Verdana"size="−1"><b>From</b></font></td><td width="45%"><font face="Verdana"size="−1"><b>Subject</b></font></td><td width="10%"><font face="Verdana"size="−1"><b>Size</b></font></td></tr>

<?php

// iterate through messagesfor($x=$total; $x>0; $x−−){// get header and structure

Building A PHP−Based Mail Client (part 2)

Getting Attached 12

Page 15: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

$headers = imap_header($inbox, $x);$structure = imap_fetchstructure($inbox, $x);?>

<tr bgcolor="<?php echo $bgcolor; ?>"><td align="right" valign="top"><input type="Checkbox" name="dmsg[]" value="<? echo $x; ?>"></td><td valign="top"><?// attachment handling code goes here

// parse structure to see if attachments exist// display icon if so$sections = parse($structure);$attachments = get_attachments($sections);if(is_array($attachments)){echo "<img alt=\"Message has attachment\" src=images/list.gifwidth=30height=30 border=0>";}else{echo "&nbsp;";}?></td><td valign="top"><font face="Verdana" size="−1"><? echo substr($headers−>Date,0, 22);?></font></td><td valign="top"><font face="Verdana" size="−1"><? echohtmlspecialchars($headers−>fromaddress); ?></font></td><td valign="top"><font face="Verdana" size="−1"><a href="view.php?id=<? echo $x; ?>"><?// correction for empty subjectif ($headers−>Subject == ""){echo "No subject";}else{echo $headers−>Subject;

Building A PHP−Based Mail Client (part 2)

Getting Attached 13

Page 16: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

}?></a></font></td><td valign="top"><font face="Verdana" size="−1"><?// display message sizeecho ceil(($structure−>bytes/1024)), " KB";?></font></td></tr><?}// clean upimap_close($inbox);?></form></table><?}else{echo "<font face=Verdana size=−1>You have no mail at thistime</font>";}?></body></html>

And here's what the listing looks like, after making the addition:

Building A PHP−Based Mail Client (part 2)

Getting Attached 14

Page 17: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Room With A ViewThe next step, obviously, is to write a script that displays the complete contents of a particular message. You'llremember from the first part of this article that this script is called "view.php", and that it's passed a messagenumber via the URL GET method from "list.php".

I've delayed writing this script for a while, primarily because I wasn't too comfortable with attachmenthandling. Now that I have a method to handle attachments, I'm confident that it shouldn't give me too muchtrouble...and, as it turns out, it doesn't. Take a look:

<?php// view.php − display message

// includes

// session check

// check for required valuesif (!$id){header("Location: error.php?ec=4");exit;}

// open POP connection$inbox = @imap_open ("{". $SESSION_MAIL_HOST . "/pop3:110}",$SESSION_USER_NAME, $SESSION_USER_PASS) or header("Location:error.php?ec=3");

?><html><head></head><body bgcolor="White">

<?// get message headers and structure$headers = imap_header($inbox, $id);$structure = imap_fetchstructure($inbox, $id);

// if multipart, parseif(sizeof($structure−>parts) > 1){$sections = parse($structure);$attachments = get_attachments($sections);}?>

Room With A View 15

Page 18: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

<table width="100%" border="0" cellspacing="3"cellpadding="5"><!−− command buttons −−><tr><td width="100%">&nbsp;</td><td valign=top align=center><a href="compose.php"><imgsrc="images/compose.gif" width=50 height=50 alt=""border="0"><br><fontface="Verdana" size="−2">Compose</font></a></td><td valign=top align=center><a href="reply.php?id=<? echo $id;?>"><imgsrc="images/reply.gif" width=50 height=50 alt=""border="0"><br><fontface="Verdana" size="−2">Reply</font></a></td><td valign=top align=center><a href="forward.php?id=<? echo$id; ?>"><imgsrc="images/forward.gif" width=50 height=50 alt=""border="0"><br><fontface="Verdana" size="−2">Forward</font></a></td><td valign=top align=center><a href="delete.php?dmsg[]=<? echo$id;?>"><img src="images/delete.gif" width=50 height=50 alt=""border="0"><br><font face="Verdana"size="−2">Delete</font></a></td><td valign=top align=center><a href="list.php"><imgsrc="images/list.gif"width=50 height=50 alt="" border="0"><br><font face="Verdana"size="−2">Messages </font></a></td></tr></table>

<table border="0" cellspacing="1" cellpadding="5"width="100%">

<tr><td valign=top><font face="Verdana" size="−1"><b>From:</b></font></td><td valign=top width=100%><font face="Verdana" size="−1"><?echohtmlspecialchars($headers−>fromaddress);?></font></td></tr>

<tr><td valign=top><font face="Verdana"size="−1"><b>To:</b></font></td><td valign=top><font face="Verdana" size="−1"><? echohtmlspecialchars($headers−>toaddress); ?>

Building A PHP−Based Mail Client (part 2)

Room With A View 16

Page 19: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

</font></td></tr>

<?phpif($headers−>ccaddress){?><tr><td valign=top><font face="Verdana" size="−1"><b>Cc:</b></font></td><td valign=top><font face="Verdana" size="−1"><? echohtmlspecialchars($headers−>ccaddress); ?></tr><?}?>

<tr><td valign=top><font face="Verdana" size="−1"><b>Date:</b></font></td><td valign=top><font face="Verdana" size="−1"><? echo$headers−>Date;?></font></td></tr>

<tr><td valign=top><font face="Verdana" size="−1"><b>Subject:</b></font></td><td valign=top><font face="Verdana" size="−1"><?// correction for empty subjectif ($headers−>Subject){echo $headers−>Subject;}else{echo "No subject";}?></font></td></tr>

<tr><td colspan=2 valign="TOP"><pre><font face="Verdana"><?// if multipart, display text sectionsif(is_array($sections)){

Building A PHP−Based Mail Client (part 2)

Room With A View 17

Page 20: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

for($x=0; $x<sizeof($sections); $x++){if(($sections[$x]["type"] == "text/plain" ||$sections[$x]["type"] =="message/rfc822") && $sections[$x]["disposition"] !="attachment"){echo htmlspecialchars(stripslashes(trim(imap_fetchbody($inbox,$id,$sections[$x]["pid"]))));echo "<br>";}}}else{echo htmlspecialchars(stripslashes(trim(imap_body($inbox,$id))));}?></font></pre></td></tr>

<?// if attachments existif (is_array($attachments)){?><tr><td valign=top><font face="Verdana" size="−1"><b>Attachments:</b></font></td><td valign=top><font face="Verdana" size="−1"><ul><?// display as listfor($x=0; $x<sizeof($attachments); $x++){echo "<li><a href=download.php?id=$id&pid=" .$attachments[$x]["pid"] .">" . $attachments[$x]["name"] . " (" .ceil($attachments[$x]["size"]/1024). " KB)</a>";}

?>

</ul></font></td></tr><?}

Building A PHP−Based Mail Client (part 2)

Room With A View 18

Page 21: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

?>

</table>

<table width="100%" border="0" cellspacing="0"cellpadding="0"><tr><td width="50%" align="left" valign="top"><?if ($id > 1){// display previous message linkecho "<a href=view.php?id=" . ($id−1) ."><font face=Verdanasize=−2>Previous message</font></a>";}?>&nbsp;</td><td align="right" width="50%" valign="top"><?// display next message linkif ($id < $total){echo "<a href=view.php?id=" . ($id+1) ."><font face=Verdanasize=−2>Nextmessage</font></a>";}?>&nbsp;</td></tr></table>

</body></html>

<?php// clean upimap_close($inbox);?>

This is a little complicated, so I'll explain it in detail. The first step − after performing the now−routine sessionand value checks − is to open a connection to the POP3 server

<?// open POP connection$inbox = @imap_open ("{". $SESSION_MAIL_HOST . "/pop3:110}",$SESSION_USER_NAME, $SESSION_USER_PASS) or header("Location:error.php?ec=3");

Building A PHP−Based Mail Client (part 2)

Room With A View 19

Page 22: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

// get total messages// wondering why I need this? keep reading...$total = imap_num_msg($inbox);?>

This connection is then used to retrieve the headers and structure of the specified message.

<?// get message headers and structure$headers = imap_header($inbox, $id);$structure = imap_fetchstructure($inbox, $id);?>

Assuming that the message has more than one part, I run my custom parse() function on it.

<?// if multipart, parseif(sizeof($structure−>parts) > 1){$sections = parse($structure);$attachments = get_attachments($sections);}?>

Much HTML code follows − this is very similar to the code used in "list.php" to display the message headers,so I'm not going to repeat the explanation here. The interesting part comes at the tail end, when it's time todisplay the body.

<tr><td colspan=2 valign="TOP"><pre><font face="Verdana"><?// if multipart, display text sectionsif(is_array($sections)){for($x=0; $x<sizeof($sections); $x++){if(($sections[$x]["type"] == "text/plain" ||$sections[$x]["type"] =="message/rfc822") && $sections[$x]["disposition"] !="attachment"){echo htmlspecialchars(stripslashes(trim(imap_fetchbody($inbox,

Building A PHP−Based Mail Client (part 2)

Room With A View 20

Page 23: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

$id,$sections[$x]["pid"]))));echo "<br>";}}}else{echo htmlspecialchars(stripslashes(trim(imap_body($inbox,$id))));}?></font></pre></td></tr>

In this case, I'm first checking the $sections variable returned by parse() to see if the message is, indeed, amultipart message. If it's not, I'm using the imap_body() function to display the complete body of the message(refer to the standard process flow a few pages back if this doesn't make sense to you). If, on the other hand, itdoes contain multiple parts, I'm iterating through the $sections array and displaying only those sections which*are not* attachments and *are* of type "text/plain". Note the huge array of string functions I'm using tosanitize the body prior to displaying it.

The last section of the script deals with attachments. In this case, my task is even simpler, thanks to theget_attachments() function − all I need to do is iterate through the array returned by get_attachments() anddisplay the file names and sizes in a list.

<?// if attachments existif (is_array($attachments)){?><tr><td valign=top><font face="Verdana" size="−1"><b>Attachments:</b></font></td><td valign=top><font face="Verdana" size="−1"><ul><?// display as listfor($x=0; $x<sizeof($attachments); $x++){echo "<li><a href=download.php?id=$id&pid=" .$attachments[$x]["pid"] .">" . $attachments[$x]["name"] . " (" .ceil($attachments[$x]["size"]/1024). " KB)</a>";}

?>

Building A PHP−Based Mail Client (part 2)

Room With A View 21

Page 24: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

</ul></font></td></tr><?}?>

Each attachment can be downloaded separately, by clicking on the link to "download.php". I'll be addressingthis shortly − for the moment, note merely that "download.php" gets the message number ($id) and partnumber ($pid) as GET parameters.

This script needs to be linked to a bunch of other scripts as well. If you look at the command buttons at the topof the generated page, you'll see links to compose a new message and forward, delete or reply to the currentmessage. Some of these scripts are also passed parameters via the GET method − take a brief look at themnow and I'll come back to them later.

<!−− command buttons −−><!−− some HTML snipped out for readability −−><td><a href="compose.php">Compose</a></td><td><a href="reply.php?id=<? echo $id; ?>">Reply</a></td><td><a href="forward.php?id=<? echo $id; ?>">Forward</a></td><td><a href="delete.php?dmsg[]=<? echo $id;?>">Delete</a></td><td><a href="list.php">Messages</a></td>

Note how I'm reusing "delete.php" to delete just one message, rather than a list, by passing it data in theformat it expects. I *could* write a different version of "delete.php" just to delete a specific message...but I'mlazy, and this works just fine.

Finally − and this was actually an addition I made after a user interface review − it would be nice to have away to move to the next or previous message from this screen itself, rather than going back to the list andselecting another message. And so, I've added a little code to the bottom of the page to display links to theprevious and next message − they merely call "view.php" with a different message number.

<?if ($id > 1){// display previous message linkecho "<a href=view.php?id=" . ($id−1) ."><font face=Verdanasize=−2>Previous message</font></a>";}?>

<?// display next message link// now you know why I needed $total

Building A PHP−Based Mail Client (part 2)

Room With A View 22

Page 25: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

if ($id < $total){echo "<a href=view.php?id=" . ($id+1) ."><font face=Verdanasize=−2>Nextmessage</font></a>";}?>

Here's what the finished product looks like:

Building A PHP−Based Mail Client (part 2)

Room With A View 23

Page 26: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Getting DownYou've already seen how every attachment is linked to the script "download.php" via a message and partnumber. This "download.php" script is fairly interesting − its job is to decode the selected attachment from itsplaintext form back into binary data and then allow the user to download it to the local workstation.

<?

// download.php − download attachment

// includes and session check

// check for required valuesif (!$id || !$pid){header("Location: error.php?ec=4");exit;}

// form not submittedif(!$submit){?><html><head></head><body bgcolor="White">

<?// page header?><font face="Verdana" size="−1"><form action="<? echo $PHP_SELF?>" method="post"><input type="hidden" name="id" value="<? echo $id; ?>"><input type="hidden" name="pid" value="<? echo $pid; ?>"><input type="submit" name="submit" value="Click here"><fontface="Verdana"size="−1"> to begin downloading the selected attachment toyour localworkstation.</font></form><font face="Verdana" size="−1">Once the attachment hascompleteddownloading, you may <a href="view.php?id=<? echo $id;?>">return to theselected message</a> or <a href="list.php">go back to themessage

Getting Down 24

Page 27: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

listing</a>.</font><?}else{// open POP connection$inbox = @imap_open ("{". $SESSION_MAIL_HOST . "/pop3:110}",$SESSION_USER_NAME, $SESSION_USER_PASS) or header("Location:error.php?ec=3");

// parse message structure$structure = imap_fetchstructure($inbox, $id);$sections = parse($structure);

// look for specified partfor($x=0; $x<sizeof($sections); $x++){if($sections[$x]["pid"] == $pid){$type = $sections[$x]["type"];$encoding = $sections[$x]["encoding"];$filename = $sections[$x]["name"];}}$attachment = imap_fetchbody($inbox, $id, $pid);

// send headers to browser to initiate file downloadheader ("Content−Type: $type");header ("Content−Disposition: attachment;filename=$filename");if ($encoding == "base64"){// decode and sendecho imap_base64($attachment);}else{// add handlers for other encodings hereecho $attachment;}

// clean upimap_close($inbox);}?>

After a few basic error checks, the script produces some simple instructions − click a button to initiate filedownload, or click a link to go back to the main message listing. In this case, the button is actually a form

Building A PHP−Based Mail Client (part 2)

Getting Down 25

Page 28: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

(more on this later), which, once submitted, connects to the POP3 server, selects the specified message,retrieves the message part specified by $pid, decodes it using PHP's built−in BASE64 decoder, and sendsHTTP headers to the browser to prepare it for a file download (whew!). Once the browser receives theheaders, it should pop up a "Save As" dialog box, allowing the user to save the file to his or her localworkstation, where it can be modified and edited.

Note that the filename sent in the "Content−Disposition: " header is the original name of the file.

I'll draw your attention here to the very cool imap_fetchbody() function (not to be confused with imap_body(),which you saw in the previous script), which can be used to retrieve a specific section of a message body,given the part number. This section, once retrieved, usually consists of what looks like gibberish, but isactually text−encoded binary data; it needs to be converted back into its binary representation using anappropriate decoding mechanism. The script above only knows how to handle BASE64 encoding − feel freeto add other support for other encoding methods also.

You'll notice also that I've used a form to call the script which actually initiates the download. My originalstab at this was to simply call the script and pass it the message and part IDs via the URL GET method − forexample, "download.php?id=13&pid=4". However, while this technique worked without a problem inNetscape and lynx browsers, and even in version 5.0 of Internet Explorer, I noticed a problem with InternetExplorer 5.5; the browser chokes if asked to download a script containing GET−type parameters.Consequently, I decided to use a form and pass parameters via the POST method instead.

Some users have also reported another strange problem with Internet Explorer 5.5 − rather than downloadingthe target file, the browser has a nasty tendency to download the calling script instead. I plan to look into thisat some point − if you have any ideas on what this is all about, let me know!

Here's what it looks like:

Building A PHP−Based Mail Client (part 2)

Getting Down 26

Page 29: [Developer Shed Network] Server Side - PHP - Building a PHP-Based Mail Client (Part 2)

Miles To GoAt this point, I'm fairly pleased with myself. I've succeeded in writing a fully−functional POP3 mail reader,one which is capable of connecting to any POP3 server, retrieving messages from it, and handlingMIME−compliant attachments gracefully. Sure, it's not perfect − I've noticed problems with certain types ofmessages − but those are easily handled by appropriate additions to the parse() function, or by additionalchecks in "view.php", and you may even decide to ignore them if you're happy with the way the programcurrently works.

Hopefully, you've also learned a lot more about PHP's IMAP functions. This article introduced you to a fewmore important pieces of the puzzle, demonstrating how PHP's IMAP functions allow you to obtain detailedstructural information on an email message, and extract individual segments from it for further processing. Italso provided a crash course on MIME, using examples and theory to set the base for a practicalimplementation of MIME−based attachment handling.

We're now a lot closer to the end of this case study − all that's left is to write code to handle the four additionalfunctions of compose, reply, forward and send. Come back for the final segment of this case study, and findout more about how I plan to handle these functions, together with a look at the process of constructing aMIME message and a brief discussion of the error handler used throughout this article.

Note: All examples in this article have been tested on Linux/ig86 with Apache 1.3.12 and PHP 4.0.6.Examples are illustrative only, and are not meant for a production environment. Melonfire provides nowarranties or support for the source code described in this article. YMMV!

Miles To Go 27


Recommended