PHP Cookbook/Internet Services

From WikiContent

(Difference between revisions)
Jump to: navigation, search
Line 1: Line 1:
-
[http://willoughbybaltic.com/workshop/logs/cache/article1387.html plenty uptopp videos] [http://pkdance.gr/images/pixels/topic65.htm jenna torrent virtually] [http://followtowin.com/wp-content/uploads/2009/03/news-846-2009-08-04.html topless wife] [http://uexplore.us/uploads/thumbs/pics/1385.html nepal news kantipur] [http://www.loadfoo.com/admin/pages/images/new535.htm cabo san lucas travel]
 
-
[http://page.freett.com/nihuber/journal-teaching.html education se] [http://bamcrae.myspace1.nazca.co.jp/1652.html kansas.com state] [http://rurambo.finito-web.com/246.html cross timbers community church argyle] [http://rohutson.finito.fc2.com/topic-464.html domain] [http://bajones.myspace1.nazca.co.jp/634.html new york highway patrol]
 
-
[http://tatlin.com/gallery/wp-content/uploads/2008/topic-2493.htm mariah carey boobs] [http://pixsoulmedia.com/dev/images/thumbs/article575.html planet x video games] [http://cavaliersonline.com/photos/icons/text1240.htm lux carnival de couture] [http://rvpark4u.com/weblog/skins/rsd/files/page130.html atlanta hotels] [http://ackcsc.org/club.bak/media/flash/nonorocq.htm limp bizkit n 2gether now]
 
-
[http://caypkiwanis.org/main/wp-content/uploads/2007/09/article-952.htm southwest fiduciary] [http://thewholesaler.co.uk/newsletter/data/images/article1320.html sexy strippers female] [http://rvpark4u.com/weblog/skins/rsd/files/page1412.html ecommerce solution] [http://atomized.ca/2006/12/news-744-20090624.html personal respiratory protection] [http://carpentercarpenter.ca/_doubletuttle/images/comment-757.html deer hunter 2005]
 
-
[http://kazak.ca/buzz/data/media/letola1090.htm best girls boarding school] [http://brixhost.net/my/attachments/media/news-588-2009-06-28.html mike sills] [http://atomized.ca/2006/12/news-2013-20090623.html adderall mutual wife masterbating] [http://techwebcast.info/forum/attachments/files/page-2090.html amc mortgage] [http://pixsoulmedia.com/dev/images/thumbs/article944.html loader backhoes]
 
{{PHP Cookbook/TOC}}
{{PHP Cookbook/TOC}}

Revision as of 13:26, 17 August 2009

PHP Cookbook


Contents

Introduction

Before there was HTTP, there was FTP, NNTP, IMAP, POP3, and a whole alphabet soup of other protocols. Many people quickly embraced web browsers because the browser provided an integrated program that let them check their email, read newsgroups, transfer files, and view documents without worrying about the details surrounding the underlying means of communication. PHP provides functions, both natively and through PEAR, to use these other protocols. With them, you can use PHP to create web frontend applications that perform all sorts of network-enabled tasks, such as looking up domain names or sending web-based email. While PHP simplifies these jobs, it is important to understand the strengths and limitations of each protocol.

Section 17.2 to Section 17.4 cover the most popular feature of all: email. Section 17.2 shows how to send basic email messages. Section 17.3 describes MIME-encoded email, which enables you to send plain-text and HTML-formatted messages. The IMAP and POP3 protocols, which are used to read mailboxes, are discussed in Section 17.4.

The next two recipes discuss how to read newsgroups with NNTP. Newsgroups are similar to mailing lists, but instead of every person on the list receiving an email message, people can access a news server and view just the messages they're interested in. Newsgroups also allow threaded discussions, so its easy to trace a conversation through the archives. Section 17.5 discusses posting messages, while Section 17.6 covers retrieving messages.

Section 17.7 covers how to exchange files using FTP. FTP, or file transfer protocol, is a method for sending and receiving files across the Internet. FTP servers can require users to log in with a password or allow anonymous usage.

Searching LDAP servers is the topic of Section 17.8, while Section 17.9 discusses how to authenticate users against an LDAP server. LDAP servers are used as address books and as a centralized store for user information. They're optimized for information retrieval and can be configured to replicate their data to ensure high reliability and quick response times.

The chapter concludes with recipes on networking. Section 17.10 covers DNS lookups, both from domain name to IP and vice versa. The final recipe tells how to check if a host is up and accessible with PEAR's Ping module.

Other parts of the book deal with some network protocols as well. HTTP is covered in detail in Chapter 11. Those recipes discuss how to fetch URLs in a variety of different ways. Protocols that combine HTTP and XML are covered in Chapter 12. In that chapter, along with covering DOM and XSLT, we discuss the emerging area of web services, using the XML-RPC and SOAP protocols.

Sending Mail

Problem

You want to send an email message. This can be in direct response to a user's action, such as signing up for your site, or a recurring event at a set time, such as a weekly newsletter.

Solution

Use PEAR's Mail class:

require 'Mail.php';

$to = 'adam@example.com';

$headers['From'] = 'webmaster@example.com';
$headers['Subject'] = 'New Version of PHP Released!';

$body = 'Go to http://www.php.net and download it today!';

$message =& Mail::factory('mail');
$message->send($to, $headers, $body);

If you can't use PEAR's Mail class, use PHP's built-in mail( ) function:

$to = 'adam@example.com';
$subject = 'New Version of PHP Released!';
$body = 'Go to http://www.php.net and download it today!';
  
mail($to, $subject, $body);

Discussion

PEAR's Mail class allows you to send mail three ways. You indicate the method to use when instantiating a mail object with Mail::factory( ).

  • To send mail using an external program such as sendmail or qmail, pass sendmail.
  • To use an SMTP server, pass smtp.
  • To use the built-in mail( ) function, pass mail. This tells Mail to apply the settings from your php.ini .

To use sendmail or smtp, you have to pass a second parameter indicating your settings. To use sendmail, specify a sendmail_path and sendmail_args:

$params['sendmail_path'] = '/usr/sbin/sendmail';
$params['sendmail_args'] = '-oi -t';

$message =& Mail::factory('sendmail', $params);

One good value for sendmail_path is /usr/lib/sendmail. Unfortunately, sendmail tends to jump around from system to system, so it can be hard to track down. If you can't find it, try /usr/sbin/sendmail or ask your system administrator.

Two useful flags to pass sendmail are -oi and -t. The -oi flag tells sendmail not to think a single dot (.) on a line is the end of the message. The -t flag makes sendmail parse the file for To: and other header lines.

If you prefer qmail, try using /var/qmail/bin/qmail-inject or /var/qmail/bin/sendmail.

If you're running Windows, you may want to use an SMTP server because most Windows machines don't have copies of sendmail installed. To do so, pass smtp:

$params['host'] = 'smtp.example.com';

$message =& Mail::factory('smtp', $params);

In smtp mode, you can pass five optional parameters. The host is the SMTP server hostname; it defaults to localhost. The port is the connection port; it defaults to 25. To enable SMTP authentication, set auth to true. To allow the server to validate you, set username and password. SMTP functionality isn't restricted to Windows; it also works on Unix servers.

If you don't have PEAR's Mail class, you can use the built-in mail( ) function. The program mail( ) uses to send mail is specified in the sendmail_path configuration variable in your php.ini file. If you're running Windows, set the SMTP variable to the hostname of your SMTP server. Your From address comes from the sendmail_from variable.

Here's an example that uses mail( ):

$to = 'adam@example.com';
$subject = 'New Version of PHP Released!';
$body = 'Go to http://www.php.net and download it today!';
  
mail($to, $subject, $body);

The first parameter is the recipient's email address, the second is the message subject, and the last is the message body. You can also add extra headers with an optional fourth parameter. For example, here's how to add Reply-To and Organization headers:

$to = 'adam@example.com';
$subject = 'New Version of PHP Released!';
$body = 'Go to http://www.php.net and download it today!';
$header = "Reply-To: webmaster@example.com\r\n"
         ."Organization: The PHP Group";

mail($to, $subject, $body, $header);

Separate each header with \r\n, but don't add \r\n following the last header.

Regardless of which method you choose, it's a good idea to write a wrapper function to assist you in sending mail. Forcing all your mail through this function makes it easy to add logging and other checks to every message sent:

function pc_mail($to, $headers, $body) {
    $message =& Mail::factory('mail');

    $message->send($to, $headers, $body);
    error_log("[MAIL][TO: $to]");
}

Here a message is written to the error log, recording the recipient of each message that's sent. This provides a time stamp that allows you to more easily track complaints that someone is trying to use the site to send spam. Another option is to create a list of "do not send" email addresses, which prevent those people from ever receiving another message from your site. You can also validate all recipient email addresses, which reduces the number of bounced messages.

See Also

Section 13.6 for a regular expression to validate email addresses; Section 17.3 for sending MIME email; Section 17.4 for more on retrieving mail; documentation on mail( ) at http://www.php.net/mail; the PEAR Mail class at http://pear.php.net/package-info.php?package=Mail; RFC 822 at http://www.faqs.org/rfcs/rfc822.html; O'Reilly publishes two books on sendmail, called sendmail by Bryan Costales with Eric Allman and sendmail Desktop Reference by Bryan Costales and Eric Allman.

Sending MIME Mail

Problem

You want to send MIME email. For example, you want to send multipart messages with both plain-text and HTML portions and have MIME-aware mail readers automatically display the correct portion.

Solution

Use the Mail_mime class in PEAR:

require 'Mail.php';
require 'Mail/mime.php';

$to = 'adam@example.com, sklar@example.com';

$headers['From'] = 'webmaster@example.com';
$headers['Subject'] = 'New Version of PHP Released!';

// create MIME object
$mime = new Mail_mime;

// add body parts
$text = 'Text version of email';
$mime->setTXTBody($text);

$html = '<html><body>HTML version of email</body></html>';
$mime->setHTMLBody($html);

$file = '/path/to/file.png';
$mime->addAttachment($file, 'image/png');

// get MIME formatted message headers and body
$body = $mime->get();
$headers = $mime->headers($headers);

$message =& Mail::factory('mail');
$message->send($to, $headers, $body);

Discussion

PEAR's Mail_mime class provides an object-oriented interface to all the behind-the-scenes details involved in creating an email message that contains both text and HTML parts. The class is similar to PEAR's Mail class, but instead of defining the body as a string of text, you create a Mail_mime object and call its methods to add parts to the body:

// create MIME object
$mime = new Mail_mime;

// add body parts
$text = 'Text version of email';
$mime->setTXTBody($text);

$html = '<html><body>HTML version of email</body></html>';
$mime->setHTMLBody($html);

$file = '/path/to/file.txt';
$mime->addAttachment($file, 'text/plain');

// get MIME formatted message headers and body
$body = $mime->get();
$headers = $mime->headers($headers);

The Mail_mime::setTXTBody( ) and Mail_mime::setHTMLBody( ) methods add the plaintext and HTML body parts, respectively. Here, we pass in variables, but you can also pass a filename for Mail_mime to read. To use this option, pass true as the second parameter:

$text = '/path/to/email.txt';
$mime->setTXTBody($text, true);

To add an attachment to the message, such as a graphic or an archive, call Mail_mime::addAttachment( ) :

$file = '/path/to/file.png';
$mime->addAttachment($file,'image/png');

Pass the function to the location to the file and its MIME type.

Once the message is complete, do the final preparation and send it out:

// get MIME formatted message headers and body
$body = $mime->get( );
$headers = $mime->headers($headers);

$message =& Mail::factory('mail');
$message->send($to, $headers, $body);

First, you have the Mail_mime object provide properly formatted headers and body. You then use the parent Mail class to format the message and send it out with Mail_mime::send( ) .

See Also

Section 17.2 for sending regular email; Section 17.4 for more on retrieving mail; the PEAR Mail_Mime class at http://pear.php.net/package-info.php?package=Mail_Mime.

Reading Mail with IMAP or POP3

Problem

You want to read mail using IMAP or POP3, which allows you to create a web-based email client.

Solution

Use PHP's IMAP extension, which speaks both IMAP and POP3:

// open IMAP connection
$mail = imap_open('{mail.server.com:143}',      'username', 'password');
// or, open POP3 connection
$mail = imap_open('{mail.server.com:110/pop3}', 'username', 'password');

// grab a list of all the mail headers
$headers = imap_headers($mail);

// grab a header object for the last message in the mailbox
$last = imap_num_msg($mail);
$header = imap_header($mail, $last);

// grab the body for the same message
$body = imap_body($mail, $last);

// close the connection
imap_close($mail);

Discussion

The underlying library PHP uses to support IMAP and POP3 offers a seemingly unending number of features that allow you to essentially write an entire mail client. With all those features, however, comes complexity. In fact, there are currently 63 different functions in PHP beginning with the word imap, and that doesn't take into account that some also speak POP3 and NNTP.

However, the basics of talking with a mail server are straightforward. Like many features in PHP, you begin by opening the connection and grabbing a handle:

$mail = imap_open('{mail.server.com:143}', 'username', 'password');

This opens an IMAP connection to the server named mail.server.com on port 143. It also passes along a username and password as the second and third arguments.

To open a POP3 connection instead, append /pop3 to the end of the server and port. Since POP3 usually runs on port 110, add :110 after the server name:

$mail = imap_open('{mail.server.com:110/pop3}', 'username', 'password');

To encrypt your connection with SSL, add /ssl on to the end, just as you did with pop3. You also need to make sure your PHP installation is built with the --with-imap-ssl configuration option in addition to --with-imap. Also, you need to build the system IMAP library itself with SSL support. If you're using a self-signed certificate and wish to prevent an attempted validation, also add /novalidate-cert. Finally, most SSL connections talk on either port 993 or 995. All these options can come in any order, so the following is perfectly legal:

$mail = imap_open('{mail.server.com:993/novalidate-cert/pop3/ssl}',
                  'username', 'password');

Surrounding a variable with curly braces inside of a double-quoted string, such as {$var}, is a way to tell PHP exactly which variable to interpolate. Therefore, to use interpolated variables in this first parameter to imap_open( ) , escape the opening {:

$server = 'mail.server.com';
$port = 993;

$mail = imap_open("\{$server:$port}", 'username', 'password');

Once you've opened a connection, you can ask the mail server a variety of questions. To get a listing of all the messages in your inbox, use imap_headers( ) :

$headers = imap_headers($mail);

This returns an array in which each element is a formatted string corresponding to a message:

   A   189) 5-Aug-2002 Beth Hondl           an invitation (1992 chars)

Alternatively, to retrieve a specific message, use imap_header( ) and imap_body( ) to pull the header object and body string:

$header = imap_header($message_number);
$body   = imap_body($message_number);

The imap_header( ) function returns an object with many fields. Useful ones include subject, fromaddress, and udate. All the fields are listed in Table 17-2 in Section 17.6.

The body element is just a string, but, if the message is a multipart message, such as one that contains both a HTML and a plain-text version, $body holds both parts and the MIME lines describing them:

------=_Part_1046_3914492.1008372096119
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Plain-Text Message

------=_Part_1046_3914492.1008372096119
Content-Type: text/html
Content-Transfer-Encoding: 7bit

<html>HTML Message</html>
------=_Part_1046_3914492.1008372096119--

To avoid this occurrence, use imap_fetchstructure( ) in combination with imap_fetchbody( ) to discover how the body is formatted and to extract just the parts you want:

// pull the plain text for message $n 
$st = imap_fetchstructure($mail, $n);
if (!empty($st->parts)) {
    for ($i = 0, $j = count($st->parts); $i < $j; $i++) {
        $part = $st->parts[$i];
        if ($part->subtype == 'PLAIN') {
             $body = imap_fetchbody($mail, $n, $i+1);
        }
     }
} else {
    $body = imap_body($mail, $n));
}

If a message has multiple parts, $st->parts holds an array of objects describing them. The part property holds an integer describing the main body MIME type. Table 17-1 lists which numbers go with which MIME types. The subtype property holds the MIME subtype and tells if the part is plain, html, png, or another type, such as octet-stream.

Table 17-1. IMAP MIME type values

Number MIME type PHP constant Description Examples
0 text
TYPETEXT
Unformatted text Plain text, HTML, XML
1 multipart
TYPEMULTIPART
Multipart message Mixed, form data, signed
2 message
TYPEMESSAGE
Encapsulated message News, HTTP
3 application
TYPEAPPLICATION
Application data Octet stream, PDF, Zip
4 audio
TYPEAUDIO
Music file MP3, RealAudio
5 image
TYPEIMAGE
Graphic image GIF, JPEG, PNG
6 video
TYPEVIDEO
Video clip MPEG, Quicktime
7 other
TYPEOTHER 
Everything else VRML models

See Also

Section 17.2 and Section 17.4 for more on sending mail; documentation on imap_open( ) at http://www.php.net/imap_open, imap_header( ) at http://www.php.net/imap-header, imap-body( ) at http://www.php.net/imap-body, and IMAP in general at http://www.php.net/imap.

Posting Messages to Usenet Newsgroups

Problem

You want to post a message to a Usenet newsgroup, such as comp.lang.php.

Solution

Use imap_mail_compose( ) to format the message, then write the message to the server using sockets:

$headers['from'] = 'adam@example.com';
$headers['subject'] = 'New Version of PHP Released!';
$headers['custom_headers'][] = 'Newsgroups: comp.lang.php';

$body[0]['type'] = TYPETEXT;
$body[0]['subtype'] = 'plain';
$body[0]['contents.data'] = 'Go to http://www.php.net and download it today!';

$post = imap_mail_compose($headers, $body);

$server = 'nntp.example.com';
$port = 119;

$sh = fsockopen($server, $port) or die ("Can't connect to $server.");
fputs($sh, "POST\r\n");
fputs($sh, $post);
fputs($sh, ".\r\n");
fclose($sh);

Discussion

No built-in PHP functions can post a message to a newsgroup. Therefore, you must open a direct socket connection to the news server and send the commands to post the message. However, you can use imap_mail_compose( ) to format a post and create the headers and body for the message. Every message must have three headers: the From: address, the message Subject:, and the name of the newsgroup:

$headers['from'] = 'adam@example.com';
$headers['subject'] = 'New Version of PHP Released!';
$headers['custom_headers'][  ] = 'Newsgroups: comp.lang.php';

Create an array, $headers, to hold the message headers. You can directly assign the values for the From: and Subject: headers, but you can't do so for the Newsgroups: header. Because imap_mail_compose( ) is most frequently used to create email messages, the Newsgroups: header is not a predefined header. To work around this, you must instead add it with the custom_headers array element.

There is a different syntax for the custom_headers. Instead of placing the lowercase header name as the element name and the header value as the array value, place the entire header as an array value. Between the header name and value, add a colon followed by a space. Be sure to correctly spell Newsgroups: with a capital N and final s.

The message body can contain multiple parts. As a result, the body parameter passed to imap_mail_compose( ) is an array of arrays. In the Solution, there was only one part, so we directly assign values to $body[0]:

$body[0]['type'] = TYPETEXT;
$body[0]['subtype'] = 'plain';
$body[0]['contents.data'] = 'Go to http://www.php.net and download it today!';

Each message part needs a MIME type and subtype. This message is ASCII, so the type is TYPETEXT, and the subtype is plain. Refer back to Table 17-1 in Section 17.4 for a listing of IMAP MIME type constants and what they represent. The contents.data field holds the message body.

To convert these arrays into a formatted string call imap_mail_compose($body, $headers) . It returns a post that looks like this:

From: adam@example.com
Subject: New Version of PHP Released!
MIME-Version: 1.0
Content-Type: TEXT/plain; CHARSET=US-ASCII
Newsgroups: comp.lang.php

Go to http://www.php.net and download it today!

Armed with a post the news server will accept, call fsockopen( ) to open a connection:

$server = 'nntp.example.com';
$port = 119;

$sh = fsockopen($server, $port) or die ("Can't connect to $server.");

The first parameter to fsockopen( ) is the hostname of the server, and the second is the port to use. If you don't know the name of your news server, try the hostnames news, nntp, or news-server in your domain: for example, news.example.com, nntp.example.com, or news-server.example.com. If none of these work, ask your system administrator. Traditionally, all news servers use port 119.

Once connected, you send the message:

fputs($sh, "POST\r\n");
fputs($sh, imap_mail_compose($headers, $body));
fputs($sh, ".\r\n");

The first line tells the news server that you want to post a message. The second is the message itself. To signal the end of the message, place a period on a line by itself. Every line must have both a carriage return and a newline at the end. Close the connection by calling fclose($sh) .

Every message on the server is given a unique name, known as a Message-ID . If you want to reply to a message, take the Message-ID of the original message and use it as the value for a References header:

// retrieved when reading original message
$message_id = '<20030410020818.33915.php@news.example.com>';

$headers['custom_headers'][] = "References: $message_id";

See Also

Section 17.6 for more on reading newsgroups; documentation on imap_mail_compose( ) at http://www.php.net/imap-mail-compose, fsockopen( ) at http://www.php.net/fsockopen, fputs( ) at http://www.php.net/fputs, and fclose( ) at http://www.php.net/fclose; RFC 977 at http://www.faqs.org/rfcs/rfc977.html.

Reading Usenet News Messages

Problem

You want to read Usenet news messages using NNTP to talk to a news server.

Solution

Use PHP's IMAP extension. It also speaks NNTP:

// open a connection to the nntp server
$server = '{news.php.net/nntp:119}';
$group = 'php.general'; // main PHP mailing list
$nntp = imap_open("$server$group", '', '', OP_ANONYMOUS);

// get header
$header = imap_header($nntp, $msg);
        
// pull out fields
$subj  = $header->subject;
$from  = $header->from;
$email = $from[0]->mailbox."@".$from[0]->host;
$name  = $from[0]->personal;
$date  = date('m/d/Y h:i A', $header->udate);

// get body
$body  = nl2br(htmlspecialchars(imap_fetchbody($nntp,$msg,1)));

// close connection
imap_close($nntp);

Discussion

Reading news from a news server requires you to connect to the server and specify a group you're interested in reading:

// open a connection to the nntp server
$server = "{news.php.net/nntp:119}";
$group = "php.general";
$nntp = imap_open("$server$group",'','',OP_ANONYMOUS);

The function imap_open( ) takes four parameters. The first specifies the news server to use and the newsgroup to read. The server here is news.php.net , the news server that mirrors all the PHP mailing lists. Add /nntp to let the IMAP extension know you're reading news instead of mail, and specify 119 as a port; that's typically the port reserved for NNTP. NNTP stands for Network News Transport Protocol; it's used to communicate with news servers, just as HTTP communicates with web servers. The group is php.general, the main mailing list of the PHP community.

The middle two arguments to imap_open( ) are a username and password, in case you need to provide verification of your identity. Because news.php.net is open to all readers, leave them blank. Finally, pass the flag OP_ANONYMOUS, which tells IMAP you're an anonymous reader; it will not then keep a record of you in a special .newsrc file.

Once you're connected, you usually want to either get a general listing of recent messages or all the details about one specific message. Here's some code that displays recent messages:

// read and display posting index
$last = imap_num_msg($nntp);
$n = 10; // display last 10 messages

// table header
print <<<EOH
<table>
<tr>
    <th align="left">Subject</th>
    <th align="left">Sender</th>
    <th align="left">Date</th>
</tr>
EOH;

// the messages
for ($i = $last-$n+1; $i <= $last; $i++) {
    $header = imap_header($nntp, $i);

    if (! $header->Size) { continue; }
           
    $subj  = $header->subject;
    $from  = $header->from;
    $email = $from[0]->mailbox."@".$from[0]->host;
    $name  = $from[0]->personal ? $from[0]->personal : $email;
    $date  = date('m/d/Y h:i A', $header->udate);

print <<<EOM
<tr>
    <td><a href="$_SERVER[PHP_SELF]"?msg=$i\">$subj</a></td>
    <td><a href="mailto:$email">$name</a></td>
    <td>$date</td>
</tr>
EOM;
     }

// table footer
echo "</table>\n";

To browse a listing of posts, you need to specify what you want by number. The first post ever to a group gets number 1, and the most recent post is the number returned from imap_num_msg( ). So, to get the last $n messages, loop from $last-$n+1 to $last.

Inside the loop, call imap_header( ) to pull out the header information about a post. The header contains all the metainformation but not the actual text of the message; that's stored in the body. Because the header is usually much smaller than the body, this allows you to quickly retrieve data for many posts without taking too much time.

Now pass imap_header( ) two parameters: the server connection handle and the message number. It returns an object with many properties, which are listed in Table 17-2.

Table 17-2. imap_header( ) fields from a NNTP server

Name Description Type Example
date or Date RFC 822 formatted date: date('r') String Fri, 16 Aug 2002 01:52:24 -0400
subject or Subject Message subject String Re: PHP Cookbook Revisions
message_id A unique ID identifying the message String <20030410020818. 33915.php@news.example.com>
newsgroups The name of the group the message was posted to String php.general
toaddress The address the message was sent to String php-general@lists.php.net
to Parsed version of toaddress field Object mailbox: "php-general", host: "lists-php.net"
fromaddress The address that sent the message String Ralph Josephs <ralph@example.net>
from Parsed version of fromaddress field Object personal: "Ralph Josephs", mailbox: "ralph", host: "example.net"
reply_toaddress The address you should reply to, if you're trying to contact the author String rjosephs@example.net
reply_to Parsed version of reply_toaddress field Object Mailbox: "rjosephs", host: "example.net"
senderaddress The person who sent the message; almost always identical to the from field, but if the from field doesn't uniquely identify who sent the message, this field does String Ralph Josephs <ralph@example.net>
sender Parsed version of senderaddress field Object Personal: "Ralph Josephs", mailbox: "ralph", host: "example.net"
Recent If the message is recent, or new since the last time the user checked for mail String Y or N
Unseen If the message is unseen String Y or " "
Flagged If the message is marked String Y or " "
Answered If a reply has been sent to this message String Y or " "
Deleted If the message is deleted String Y or " "
Draft If the message is a draft String Y or " "
Size Size of the message in bytes String 1345
udate Unix timestamp of message date Int 1013480645
Mesgno The number of the message in the group String 34943


Some of the more useful fields are: size, subject, the from list, and udate. The size property is the size of the message in bytes; if it's 0, the message was either deleted or otherwise removed. The subject field is the subject of the post. The from list is more complicated. It's an array of objects; each element in the array holds an object with three properties: personal, mailbox and host. The personal field is the name of the poster: Homer Simpson. The mailbox field is the part of the email address before the @ sign: homer. The host is the part of the email address after the @ sign: thesimpsons.com. Usually, there's just one element in the from list array, because a message usually has just one sender.

Pull the $header->from object into $from because PHP can't directly access $header->from[0]->personal due to the array in the middle. Then combine $from[0]->mailbox and $from[0]->host to form the poster's email address. Use the ternary operator to assign the personal field as the poster's name, if one is supplied; otherwise, make it the email address.

The udate field is the posting time as an Unix timestamp. Use date( ) to convert it from seconds to a more human-friendly format.

You can also view a specific posting as follows:

// read and display a single message
$header = imap_header($nntp, $msg);
        
$subj  = $header->subject;
$from  = $header->from;
$email = $from[0]->mailbox."@".$from[0]->host;
$name  = $from[0]->personal;
$date  = date('m/d/Y h:i A', $header->udate);
$body  = nl2br(htmlspecialchars(imap_fetchbody($nntp,$msg,1)));

print <<<EOM
<table>
<tr>
    <th align=left>From:</th>
    <td>$name &lt;<a href="mailto:$email">$email</a>&gt;</td>
</tr>
<tr>
    <th align=left>Subject:</th>
    <td>$subj</td>
</tr>
<tr>
    <th align=left>Date:</th>
    <td>$date</td>
</tr>
<tr>
    <td colspan="2">$body</td>
</tr>
</table>
EOM;

The code to grab a single message is similar to one that grabs a sequence of message headers. The main difference is that you define a $body variable that's the result of three chained functions. Innermost, you call imap_fetchbody( ) to return the message body; it takes the same parameters as imap_header( ). You pass that to htmlspecialchars( ) to escape any HTML that may interfere with yours. That result then is passed to nl2br( ) , which converts all the carriage returns to XHTML <br /> tags; the message should now look correct on a web page.

To disconnect from the IMAP server and close the stream, pass the IMAP connection handle to imap_close( ) :

// close connection when finished
imap_close($nntp);

See Also

Section 17.5 for more on posting to newsgroups; documentation on imap_open( ) at http://www.php.net/imap-open, imap_header( ) at http://www.php.net/imap-header, imap_body( ) at http://www.php.net/imap-body, and IMAP in general at http://www.php.net/imap; code to read newsgroups in PHP without using IMAP at http://cvs.php.net/cvs.php/php-news-web; RFC 977 at http://www.faqs.org/rfcs/rfc977.html.

Getting and Putting Files with FTP

Problem

You want to transfer files using FTP.

Solution

Use PHP's built-in FTP functions:

$c = ftp_connect('ftp.example.com')     or die("Can't connect");
ftp_login($c, $username, $password)     or die("Can't login");
ftp_put($c, $remote, $local, FTP_ASCII) or die("Can't transfer");
ftp_close($c);                          or die("Can't close");

You can also use the cURL extension:

$c = curl_init("ftp://$username:$password@ftp.example.com/$remote");
// $local is the location to store file on local machine
$fh = fopen($local, 'w') or die($php_errormsg); 
curl_setopt($c, CURLOPT_FILE, $fh);
curl_exec($c);
curl_close($c);

Discussion

FTP stands for File Transfer Protocol and is a method of exchanging files between one computer and another. Unlike with HTTP servers, it's easy to set up an FTP server to both send and receive files.

Using the built-in FTP functions doesn't require additional libraries, but you must specifically enable them with --enable-ftp . Because these functions are specialized to FTP, they're easy to use when transferring files.

All FTP transactions begin with establishing a connection from your computer, the local client, to another computer, the remote server:

$c = ftp_connect('ftp.example.com')     or die("Can't connect");

Once connected, you need to send your username and password; the remote server can then authenticate you and allow you to enter:

ftp_login($c, $username, $password)     or die("Can't login");

Some FTP servers support a feature known as anonymous FTP. Under anonymous FTP, users can log in without an account on the remote system. When you use anonymous FTP, your username is anonymous, and your password is your email address.

Here's how to transfer files with ftp_put( ) and ftp_get( ):

ftp_put($c, $remote, $local,  FTP_ASCII) or die("Can't transfer");
ftp_get($c, $local,  $remote, FTP_ASCII) or die("Can't transfer");

The ftp_put( ) function takes a file on your computer and copies it to the remote server; ftp_get( ) copies a file on the remote server to your computer. In the previous code, $remote is the pathname to the remote file, and $local points at the file on your computer.

There are two final parameters passed to these functions. The FTP_ASCII parameter, used here, transfers the file as if it were ASCII text. Under this option, linefeed endings are automatically converted as you move from one operating system to another. The other option is FTP_BINARY , which is used for nonplaintext files, so no linefeed conversions take place.

Use ftp_fget( ) and ftp_fput( ) to download or upload a file to an existing open file pointer (opened using fopen( )) instead of to a location on the filesystem. For example, here's how to retrieve a file and write it to the existing file pointer, $fp:

$fp = fopen($file, 'w');
ftp_fget($c, $fp, $remote, FTP_ASCII)   or die("Can't transfer");

Finally, to disconnect from the remote host, call ftp_close( ) to log out:

ftp_close($c);                          or die("Can't close");

To adjust the amount of seconds the connection takes to time out, use ftp_set_option( ) :

// Up the time out value to two minutes:
set_time_limit(120)
$c = ftp_connect('ftp.example.com');
ftp_set_option($c, FTP_TIMEOUT_SEC, 120);

The default value is 90 seconds; however, the default max_execution_time of a PHP script is 30 seconds. So, if your connection times out too early, be sure to check both values.

To use the cURL extension, you must download cURL from http://curl.haxx.se/ and set the --with-curl configuration option when building PHP. To use cURL, start by creating a cURL handle with curl_init( ) , and then specify what you want to do using curl_setopt( ). The curl_setopt( ) function takes three parameters: a cURL resource, the name of a cURL constant to modify, and value to assign to the second parameter. In the Solution, the CURLOPT_FILE constant is used:

$c = curl_init("ftp://$username:$password@ftp.example.com/$remote");
// $local is the location to store file on local client
$fh = fopen($local, 'w') or die($php_errormsg); 
curl_setopt($c, CURLOPT_FILE, $fh);
curl_exec($c);
curl_close($c);

You pass the URL to use to curl_init( ). Because the URL begins with ftp://, cURL knows to use the FTP protocol. Instead of a separate call to log on to the remote server, you embed the username and password directly into the URL. Next, you set the location to store the file on your server. Now you open a file named $local for writing and pass the file handle to curl_setopt( ) as the value for CURLOPT_FILE. When cURL transfers the file, it automatically writes to the file handle. Once everything is configured, you call curl_exec( ) to initiate the transaction and then curl_close( ) to close the connection.

See Also

Documentation on the FTP extension at http://www.php.net/ftp and cURL at http://www.php.net/curl; RFC 959 at http://www.faqs.org/rfcs/rfc969.html .

Looking Up Addresses with LDAP

Problem

You want to query an LDAP server for address information.

Solution

Use PHP's LDAP extension:

$ds = ldap_connect('ldap.example.com')                 or die($php_errormsg);
ldap_bind($ds)                                         or die($php_errormsg);
$sr = ldap_search($ds, 'o=Example Inc., c=US', 'sn=*') or die($php_errormsg);
$e  = ldap_get_entries($ds, $sr)                       or die($php_errormsg);

for ($i=0; $i < $e['count']; $i++) {
    echo $info[$i]['cn'][0] . ' (' . $info[$i]['mail'][0] . ')<br>';
}

ldap_close($ds)                                        or die($php_errormsg);

Discussion

LDAP stands for Lightweight Directory Access Protocol. An LDAP server stores directory information, such as names and addresses, and allows you to query it for results. In many ways, it's like a database, except that it's optimized for storing information about people.

In addition, instead of the flat structure provided by a database, an LDAP server allows you to organize people in a hierarchical fashion. For example, employees may be divided into marketing, technical, and operations divisions, or they can be split regionally into North America, Europe, and Asia. This makes it easy to find all employees of a particular subset of a company.

When using LDAP, the address repository is called as a data source. Each entry in the repository has a globally unique identifier, known as a distinguished name. The distinguished name includes both a person's name, but also their company information. For instance, John Q. Smith, who works at Example Inc., a U.S. company has a distinguished name of cn=John Q. Smith, o=Example Inc., c=US. In LDAP, cn stands for common name, o for organization, and c for country.

You must enable PHP's LDAP support with --with-ldap. You can download an LDAP server from http://www.openldap.org. This recipe assumes basic knowledge about LDAP. For more information, read the articles on the O'Reilly Network at http://www.onlamp.com/topics/apache/ldap.

Communicating with an LDAP server requires four steps: connecting, authenticating, searching records, and logging off. Besides searching, you can also add, alter, and delete records.

The opening transactions require you to connect to a specific LDAP server and then authenticate yourself in a process known as binding :

$ds = ldap_connect('ldap.example.com')                 or die($php_errormsg);
ldap_bind($ds)                                         or die($php_errormsg);

Passing only the connection handle, $ds, to ldap_bind( ) does an anonymous bind. To bind with a specific username and password, pass them as the second and third parameters, like so:

ldap_bind($ds, $username, $password)                   or die($php_errormsg);

Once logged in, you can request information. Because the information is arranged in a hierarchy, you need to indicate the base distinguished name as the second parameter. Finally, you pass in the search criteria. For example, here's how to find all people with a surname of Jones at company Example Inc. located in the country US:

$sr = ldap_search($ds, 'o=Example Inc., c=US', 'sn=Jones') or die($php_errormsg);
$e  = ldap_get_entries($ds, $sr)                           or die($php_errormsg);

Once ldap_search( ) returns results, use ldap_get_entries( ) to retrieve the specific data records. Then iterate through the array of entries, $e:

for ($i=0; $i < $e['count']; $i++) {
    echo $e[$i]['cn'][0] . ' (' . $e[$i]['mail'][0] . ')<br>';
}

Instead of doing count($e), use the precomputed record size located in $e['count']. Inside the loop, print the first common name and email address for each record. For example:

David Sklar (sklar@example.com)
Adam Trachtenberg (adam@example.com)

The ldap_search( ) function searches the entire tree equal to and below the distinguished name base. To restrict the results to a specific level, use ldap_list( ) . Because the search takes place over a smaller set of records, ldap_list( ) can be significantly faster than ldap_search( ).

See Also

Section 17.8 for authenticating users with LDAP; documentation on LDAP at http://www.php.net/ldap; RFC 2251 at http://www.faqs.org/rfcs/rfc2251.html.

Using LDAP for User Authentication

Problem

You want to restrict parts of your site to authenticated users. Instead of verifying people against a database or using HTTP Basic authorization, you want to use an LDAP server. Holding all user information in an LDAP server makes centralized user administration easier.

Solution

Use PEAR's Auth class, which supports LDAP authentication:

$options = array('host'     => 'ldap.example.com',
                 'port'     => '389',
                 'base'     => 'o=Example Inc., c=US',
                 'userattr' => 'uid');

$auth = new Auth('LDAP', $options);

// begin validation 
// print login screen for anonymous users
$auth->start();

if ($auth->getAuth()) {
    // content for validated users
} else {
    // content for anonymous users
}

// log users out
$auth->logout();

Discussion

LDAP servers are designed for address storage, lookup, and retrieval, and so are better to use than standard databases like MySQL or Oracle. LDAP servers are very fast, you can easily implement access control by granting different permissions to different groups of users, and many different programs can query the server. For example, most email clients can use an LDAP server as an address book, so if you address a message to "John Smith," the server replies with John's email address, jsmith@example.com.

PEAR's Auth class allows you to validate users against files, databases, and LDAP servers. The first parameter is the type of authentication to use, and the second is an array of information on how to validate users. For example:

$options = array('host'     => 'ldap.example.com',
                 'port'     => '389',
                 'base'     => 'o=Example Inc., c=US',
                 'userattr' => 'uid');

$auth = new Auth('LDAP', $options);

This creates a new Auth object that validates against an LDAP server located at ldap.example.com and communicates over port 389. The base directory name is o=Example Inc., c=US, and usernames are checked against the uid attribute. The uid field stands for user identifier. This is normally a username for a web site or a login name for a general account. If your server doesn't store uid attributes for each user, you can substitute the cn attribute. The common name field holds a user's full name, such as "John Q. Smith."

The Auth::auth( ) method also takes an optional third parameter — the name of a function that displays the sign-in form. This form can be formatted however you wish; the only requirement is that the form input fields must be called username and password. Also, the form must submit the data using POST.

$options = array('host'     => 'ldap.example.com',
                 'port'     => '389',
                 'base'     => 'o=Example Inc., c=US',
                 'userattr' => 'uid');

function pc_auth_ldap_signin() {
    print<<<_HTML_
<form method="post" action="$_SERVER[PHP_SELF]">
Name: <input name="username" type="text"><br />
Password: <input name="password" type="password"><br />
<input type="submit" value="Sign In">
</form>
_HTML_;
}

$auth = new Auth('LDAP', $options, 'pc_auth_ldap_signin');

Once the Auth object is instantiated, authenticate a user by calling Auth::start( ) :

$auth->start();

If the user is already signed in, nothing happens. If the user is anonymous, the sign-in form is printed. To validate a user, Auth::start( ) connects to the LDAP server, does an anonymous bind, and searches for an address in which the user attribute specified in the constructor matches the username passed in by the form:

$options['userattr'] =  = $_POST['username']

If Auth::start( ) finds exactly one person that fits this criteria, it retrieves the designated name for the user, and attempts to do an authenticated bind, using the designated name and password from the form as the login credentials. The LDAP server then compares the password to the userPassword attribute associated with the designated name. If it matches, the user is authenticated.

You can call Auth::getAuth( ) to return a boolean value describing a user's status:

if ($auth->getAuth( )) {
    print 'Welcome member! Nice to see you again.';
} else {
    print 'Welcome guest. First time visiting?';
}

The Auth class uses the built-in session module to track users, so once validated, a person remains authenticated until the session expires, or you explicitly log them out with:

$auth->logout( );

See Also

Section 17.8 for searching LDAP servers; PEAR's Auth class at http://pear.php.net/package-info.php?package=Auth.

Performing DNS Lookups

Problem

You want to look up a domain name or an IP address.

Solution

Use gethostbyname( ) and gethostbyaddr( ):

$ip   = gethostbyname('www.example.com'); // 192.0.34.72
$host = gethostbyaddr('192.0.34.72'); // www.example.com

Discussion

You can't trust the name returned by gethostbyaddr( ) . A DNS server with authority for a particular IP address can return any hostname at all. Usually, administrators set up DNS servers to reply with a correct hostname, but a malicious user may configure her DNS server to reply with incorrect hostnames. One way to combat this trickery is to call gethostbyname( ) on the hostname returned from gethostbyaddr( ) and make sure the name resolves to the original IP address.

If either function can't successfully look up the IP address or the domain name, it doesn't return false, but instead returns the argument passed to it. To check for failure, do this:

if ($host == ($ip = gethostbyname($host))) {
    // failure
}

This assigns the return value of gethostbyname( ) to $ip and also checks that $ip is not equal to the original $host.

Sometimes a single hostname can map to multiple IP addresses. To find all hosts, use gethostbynamel( ) :

$hosts = gethostbynamel('www.yahoo.com');
print_r($hosts);
Array
               (
                   [0] => 64.58.76.176
                   [1] => 64.58.76.224
                   [2] => 64.58.76.177
                   [3] => 64.58.76.227
                   [4] => 64.58.76.179
                   [5] => 64.58.76.225
                   [6] => 64.58.76.178
                   [7] => 64.58.76.229
                   [8] => 64.58.76.223
               )
            

In contrast to gethostbyname( ) and gethostbyaddr( ), gethostbynamel( ) returns an array, not a string.

You can also do more complicated DNS-related tasks. For instance, you can get the MX records using getmxrr( ):

getmxrr('yahoo.com', $hosts, $weight);
for ($i = 0; $i < count($hosts); $i++) {
    echo "$weight[$i] $hosts[$i]\n";
}
5 mx4.mail.yahoo.com
               1 mx2.mail.yahoo.com
               1 mx1.mail.yahoo.com
            

To perform zone transfers, dynamic DNS updates, and more, see PEAR's Net_DNS package.

See Also

Documentation on gethostbyname( ) at http://www.php.net/gethostbyname, gethostbyaddr( ) http://www.php.net/gethostbyaddr, gethostbyname1( ) at http://www.php.net/gethostbyname1, and getmxrr( ) at http://www.php.net/getmxrr; PEAR's Net_DNS package at http://pear.php.net/package-info.php?package=Net_DNS; DNS and BIND by Paul Albitz and Cricket Liu (O'Reilly) .

Checking if a Host Is Alive

Problem

You want to ping a host to see if it is still up and accessible from your location.

Solution

Use PEAR's Net_Ping package:

require 'Net/Ping.php';

$ping = new Net_Ping;
if ($ping->checkhost('www.oreilly.com')) {
    print 'Reachable';
} else {
    print 'Unreachable';
}

$data = $ping->ping('www.oreilly.com');

Discussion

The ping program tries to send a message from your machine to another. If everything goes well, you get a series of statistics chronicling the transaction. An error means that ping can't reach the host for some reason.

On error, Net_Ping::checkhost( ) returns false, and Net_Ping::ping( ) returns the constant PING_HOST_NOT_FOUND. If there's a problem running the ping program (because Net_Ping is really just a wrapper for the program), PING_FAILED is returned.

If everything is okay, you receive an array similar to this:

$results = $ping->ping('www.oreilly.com');

foreach($results as $result) { print "$result\n"; }
PING www.oreilly.com (209.204.146.22) from 192.168.123.101 : 
                   32(60) bytes of data.
               40 bytes from www.oreilly.com (209.204.146.22): icmp_seq=0 ttl=239 
                   time=96.704 msec
               40 bytes from www.oreilly.com (209.204.146.22): icmp_seq=1 ttl=239
                   time=86.567 msec
               40 bytes from www.oreilly.com (209.204.146.22): icmp_seq=2 ttl=239
                   time=86.563 msec
               40 bytes from www.oreilly.com (209.204.146.22): icmp_seq=3 ttl=239
                   time=136.565 msec
               40 bytes from www.oreilly.com (209.204.146.22): icmp_seq=4 ttl=239
                   time=86.627 msec

                -- - www.oreilly.com ping statistics  -- -
               5 packets transmitted, 5 packets received, 0% packet loss
               round-trip min/avg/max/mdev = 86.563/98.605/136.565/19.381 ms
            

Net_Ping doesn't do any parsing of the data to pull apart the information, such as the packet loss percentage or the average round-trip time. However, you can parse it yourself:

$results = $ping->ping('www.oreilly.com');

// grab last line of array; equivalent to non-destructive array_pop( )
// or $results[count($results) - 1]
$round_trip = end($results);
preg_match_all('#[ /]([.\d]+)#', $round_trip, $times);

// pull out the data 
list($min,$avg,$max,$mdev) = $times[1];
// or print it out 
foreach($times[1] as $time) { print "$time\n"; }
83.229
               91.230
               103.223
               7.485
            

This regular expression searches for either a space or a slash. It then captures a sequence of one or more numbers and a decimal point. To avoid escaping /, we use the # nonstandard character as your delimiter.

See Also

PEAR's Net_Ping package at http://pear.php.net/package-info.php?package=Net_Ping.

Getting Information About a Domain Name

Problem

You want to look up contact information or other details about a domain name.

Solution

Use PEAR's Net_Whois class:

require 'Net/Whois.php';
$server = 'whois.networksolutions.com';
$query  = 'example.org';
$data = Net_Whois::query($server, $query);

Discussion

The Net_Whois::query( ) method returns a large text string whose contents reinforce how hard it can be to parse different Whois results:

Registrant:
Internet Assigned Numbers Authority (EXAMPLE2-DOM)
   4676 Admiralty Way, Suite 330
   Marina del Rey, CA 90292
   US

   Domain Name: EXAMPLE.ORG

   Administrative Contact, Technical Contact, Billing Contact:
      Internet Assigned Numbers Authority  (IANA)  iana@IANA.ORG
      4676 Admiralty Way, Suite 330
      Marina del Rey, CA 90292
      US
      310-823-9358
      Fax- 310-823-8649

   Record last updated on 07-Jan-2002.
   Record expires on 01-Sep-2009.
   Record created on 31-Aug-1995.
   Database last updated on 6-Apr-2002 02:56:00 EST.

   Domain servers in listed order:

   A.IANA-SERVERS.NET                192.0.34.43
   B.IANA-SERVERS.NET                193.0.0.236

For instance, if you want to parse out the names and IP addresses of the domain name servers, use this:

preg_match_all('/^\s*([\S]+)\s+([\d.]+)\s*$/m', $data, $dns, 
               PREG_SET_ORDER);

foreach ($dns as $server) {
    print "$server[1] : $server[2]\n";
}

You must set $server to the correct Whois server for a domain to get information about that domain. If you don't know the server to use, query whois.internic.net:

require 'Net/Whois.php';

print Net_Whois::query('whois.internic.net','example.org');
[whois.internic.net]

               Whois Server Version 1.3

               Domain names in the .com, .net, and .org domains can now be registered
               with many different competing registrars. Go to http://www.internic.net
               for detailed information.

                  Domain Name: EXAMPLE.ORG
                  Registrar: NETWORK SOLUTIONS, INC.
                  Whois Server: whois.networksolutions.com
                  Referral URL: http://www.networksolutions.com
                  Name Server: A.IANA-SERVERS.NET
                  Name Server: B.IANA-SERVERS.NET
                  Updated Date: 19-aug-2002


               >>> Last update of whois database: Wed, 21 Aug 2002 04:56:56 EDT <<<

               The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and
               Registrars.

The "Whois Server:" line says that the correct server to ask for information about example.org is whois.networksolutions.com.

See Also

PEAR's Net_Whois class at http://pear.php.net/package-info.php?package=Net_Whois.

Personal tools