Fortsetzung von Tutorial: Mail-Autoresponder mit PHP - Teil 1
Im zweiten Teil dieses Tutorials geht es jetzt so langsam ans Eingemachte. Schritt für Schritt möchte ich zeigen, wie ich damals an das Problem herangegangen bin, welche Komponenten verwendet werden - und welche Tests warum wie umgesetzt werden. Ergänzungen, Verbesserungsvorschläge und Rückfragen sind jederzeit gerne willkommen!
Heute beschäftigen wir uns mit dem Zerlegen der eingehenden Mail und dem Extrahieren der für uns wichtigen Parameter. Beim Zerlegen der Mail-Header setze ich auf Mail_mimeDecode aus dem PEAR Repository. Das Modul ist zwar schon ein wenig in die Jahre gekommen - selbiges gilt aber auch für das MIME-Format.
Das Zend Framework bietet noch nichts vergleichbares, die eZ Components sind zwar allemal moderner als PEAR und stellen ezcMailTools::mimeDecode zur Verfügung - allerdings würde sich vermutlich so mancher nicht gerade freuen, wenn ich ihm für ein so kleines Skript einen Schnell-Einstieg in eZ verpassen würde. Dazu vielleicht ein andermal mehr, ich habe die Components für einige Aufgaben im Einsatz und finde sie sehr gut.
Unser Skript soll per Kommandozeile aufrufbar sein, das heißt es muss erstens ausführbar (chmod +x) sein und zweitens eine korrekte Shebang-Zeile enthalten. Außerdem binden wir schon mal ein paar Bibliotheken ein:
#!/usr/bin/php
<?php
require_once('Mail.php');
require_once('Mail/mime.php');
require_once 'Mail/mimeDecode.php';
Keine Angst, mehr werden's nicht. Zwar könnte man prinzipiell die Mail-Header auch selbst parsen - aber wozu das Rad neu erfinden? Genannte Bibliotheken finden sich auf fast jedem Hosting-Server, und sonst kann man sie auch mitliefern. Wie wir an das Mail kommen folgt im nächsten Tutorial, ich arbeite hier jetzt mal mit $mail_source weiter:
$decoder = new Mail_mimeDecode($mail_source);
$structure = $decoder->decode(array(
'include_bodies' => false,
'decode_bodies' => false,
'decode_headers' => true,
));
$hdr = & $structure->headers;
if (PEAR::isError($hdr)) {
fail('Ignoring mail, unable to parse');
}
Wir gehen jetzt davon aus, dass wir in der Variable $returnPath möglicherweise schon vom Mailserver RFC5321.MailFrom erhalten haben. Falls nicht nutzen wir den Return-Path:
$sender = null;
if (isset($hdr['reply-to'])) {
$sender = extractMailAddress($hdr['reply-to']);
} elseif (isset($hdr['from'])) {
$sender = extractMailAddress($hdr['from']);
} else {
fail('Ignoring mail, no valid sender header found');
}
if (! $sender) {
fail('Ignoring mail, unknown sender');
}
$sender = strtolower($sender);
if ($returnPath === null && isset($hdr['return-path'])) {
$returnPath = extractMailAddress($hdr['return-path']);
}
if (! $returnPath) {
fail('Ignoring mail, return-path is empty');
}
list($returnPathLocal, ) = preg_split('~@~', $returnPath, 2);
Das wäre jetzt also geschafft. Die Funktion fail() wird im Fehlerfalle ins Log schreiben und das Programm sauber beenden, die Funktion extractMailAddress() liefert die erste E-Mail Adresse aus einem Konstrukt wie "Thomas Gelf" <thomas@gelf.net>.
Wir verfügen jetzt über drei sehr wichtige Variablen:
- $sender: Diese Adresse wird unsere automatische Antwort erhalten - falls wir eine senden.
- $returnPath: RFC5321.MailFrom, werden wir vor allem für eine Reihe von Checks benötigen. Wir seinen keine Antwort dorthin. Nie.
- $returnPathLocal: Der "Local-Part" des FC5321.MailFrom, also der Teil vor dem @ - wird für ein paar Checks benötigt und deshalb jetzt schon extrahiert.
Aber weiter im Code. Wir wollen jetzt wissen, wer der primäre Adressat dieser Nachricht war und wer sie eventuell noch erhalten hat:
$to_hdr = array(
'to',
'cc',
'bcc',
'resent-to',
'resent-cc',
'resent-bcc'
);
$recipients = array();
$recipient = null;
foreach ($to_hdr as $to) {
if (isset($hdr[$to])) {
foreach (extractMailAddresses($hdr[$to]) as $a) {
$a = strtolower($a);
if (isValidRecipient($a)) {
$recipients[] = $a;
if ($recipient === null) $recipient = $a;
}
}
}
}
Alle in Betracht kommenden Header werden untersucht. Die Funktion extractMailAddresses() liefert im Unterschied zu extractMailAddress() ein Array mit einer oder mehreren E-Mail-Adressen aus den jeweiligen Header-Zeilen. Die Funktion isValidRecipient() wird für uns prüfen, ob es sich hier um einen gültigen lokalen Empfänger handelt.
Dies war's auch schon für heute - der dritte Teil folgt in Kürze!
Nachtrag: Teil 3 ist online!