Back to index

d-push  2.0
maildir.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   maildir.php
00004 * Project   :   Z-Push
00005 * Descr     :   This backend is based on
00006 *               'BackendDiff' which handles the
00007 *               intricacies of generating
00008 *               differentials from static
00009 *               snapshots. This means that the
00010 *               implementation here needs no
00011 *               state information, and can simply
00012 *               return the current state of the
00013 *               messages. The diffbackend will
00014 *               then compare the current state
00015 *               to the known last state of the PDA
00016 *               and generate change increments
00017 *               from that.
00018 *
00019 * Created   :   01.10.2007
00020 *
00021 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00022 *
00023 * This program is free software: you can redistribute it and/or modify
00024 * it under the terms of the GNU Affero General Public License, version 3,
00025 * as published by the Free Software Foundation with the following additional
00026 * term according to sec. 7:
00027 *
00028 * According to sec. 7 of the GNU Affero General Public License, version 3,
00029 * the terms of the AGPL are supplemented with the following terms:
00030 *
00031 * "Zarafa" is a registered trademark of Zarafa B.V.
00032 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00033 * The licensing of the Program under the AGPL does not imply a trademark license.
00034 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00035 *
00036 * However, if you propagate an unmodified version of the Program you are
00037 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00038 * Furthermore you may use our trademarks where it is necessary to indicate
00039 * the intended purpose of a product or service provided you use it in accordance
00040 * with honest practices in industrial or commercial matters.
00041 * If you want to propagate modified versions of the Program under the name "Z-Push",
00042 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00043 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00044 *
00045 * This program is distributed in the hope that it will be useful,
00046 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00047 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00048 * GNU Affero General Public License for more details.
00049 *
00050 * You should have received a copy of the GNU Affero General Public License
00051 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00052 *
00053 * Consult LICENSE file for details
00054 ************************************************/
00055 
00056 include_once('lib/default/diffbackend/diffbackend.php');
00057 include_once('include/mimeDecode.php');
00058 require_once('include/z_RFC822.php');
00059 
00060 class BackendMaildir extends BackendDiff {
00078     public function Logon($username, $domain, $password) {
00079         return true;
00080     }
00081 
00088     public function Logoff() {
00089         return true;
00090     }
00091 
00102     public function SendMail($sm) {
00103         return false;
00104     }
00105 
00112     public function GetWasteBasket() {
00113         return false;
00114     }
00115 
00128     public function GetAttachmentData($attname) {
00129         list($id, $part) = explode(":", $attname);
00130 
00131         $fn = $this->findMessage($id);
00132         if ($fn == false)
00133             throw new StatusException(sprintf("BackendMaildir->GetAttachmentData('%s'): Error, requested message/attachment can not be found", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
00134 
00135         // Parse e-mail
00136         $rfc822 = file_get_contents($this->getPath() . "/$fn");
00137 
00138         $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
00139 
00140         include_once('include/stringstreamwrapper.php');
00141         $attachment = new SyncItemOperationsAttachment();
00142         $attachment->data = StringStreamWrapper::Open($message->parts[$part]->body);
00143         if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary))
00144             $attachment->contenttype = $message->parts[$part]->ctype_primary .'/'.$message->parts[$part]->ctype_secondary;
00145 
00146         return $attachment;
00147     }
00148 
00161     public function GetFolderList() {
00162         $folders = array();
00163 
00164         $inbox = array();
00165         $inbox["id"] = "root";
00166         $inbox["parent"] = "0";
00167         $inbox["mod"] = "Inbox";
00168 
00169         $folders[]=$inbox;
00170 
00171         $sub = array();
00172         $sub["id"] = "sub";
00173         $sub["parent"] = "root";
00174         $sub["mod"] = "Sub";
00175 
00176 //        $folders[]=$sub;
00177 
00178         return $folders;
00179     }
00180 
00189     public function GetFolder($id) {
00190         if($id == "root") {
00191             $inbox = new SyncFolder();
00192 
00193             $inbox->serverid = $id;
00194             $inbox->parentid = "0"; // Root
00195             $inbox->displayname = "Inbox";
00196             $inbox->type = SYNC_FOLDER_TYPE_INBOX;
00197 
00198             return $inbox;
00199         } else if($id == "sub") {
00200             $inbox = new SyncFolder();
00201             $inbox->serverid = $id;
00202             $inbox->parentid = "root";
00203             $inbox->displayname = "Sub";
00204             $inbox->type = SYNC_FOLDER_TYPE_OTHER;
00205 
00206             return $inbox;
00207         } else {
00208             return false;
00209         }
00210     }
00211 
00212 
00221     public function StatFolder($id) {
00222         $folder = $this->GetFolder($id);
00223 
00224         $stat = array();
00225         $stat["id"] = $id;
00226         $stat["parent"] = $folder->parentid;
00227         $stat["mod"] = $folder->displayname;
00228 
00229         return $stat;
00230     }
00231 
00232 
00247     public function ChangeFolder($folderid, $oldid, $displayname, $type){
00248         return false;
00249     }
00250 
00262     public function DeleteFolder($id, $parentid){
00263         return false;
00264     }
00265 
00275     public function GetMessageList($folderid, $cutoffdate) {
00276         $this->moveNewToCur();
00277 
00278         if($folderid != "root")
00279             return false;
00280 
00281         // return stats of all messages in a dir. We can do this faster than
00282         // just calling statMessage() on each message; We still need fstat()
00283         // information though, so listing 10000 messages is going to be
00284         // rather slow (depending on filesystem, etc)
00285 
00286         // we also have to filter by the specified cutoffdate so only the
00287         // last X days are retrieved. Normally, this would mean that we'd
00288         // have to open each message, get the Received: header, and check
00289         // whether that is in the filter range. Because this is much too slow, we
00290         // are depending on the creation date of the message instead, which should
00291         // normally be just about the same, unless you just did some kind of import.
00292 
00293         $messages = array();
00294         $dirname = $this->getPath();
00295 
00296         $dir = opendir($dirname);
00297 
00298         if(!$dir)
00299             return false;
00300 
00301         while($entry = readdir($dir)) {
00302             if($entry{0} == ".")
00303                 continue;
00304 
00305             $message = array();
00306 
00307             $stat = stat("$dirname/$entry");
00308 
00309             if($stat["mtime"] < $cutoffdate) {
00310                 // message is out of range for curoffdate, ignore it
00311                 continue;
00312             }
00313 
00314             $message["mod"] = $stat["mtime"];
00315 
00316             $matches = array();
00317 
00318             // Flags according to http://cr.yp.to/proto/maildir.html (pretty authoritative - qmail author's website)
00319             if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$entry,$matches))
00320                 continue;
00321             $message["id"] = $matches[1];
00322             $message["flags"] = 0;
00323 
00324             if(strpos($matches[2],"S") !== false) {
00325                 $message["flags"] |= 1; // 'seen' aka 'read' is the only flag we want to know about
00326             }
00327 
00328             array_push($messages, $message);
00329         }
00330 
00331         return $messages;
00332     }
00333 
00344     public function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) {
00345         if($folderid != 'root')
00346             return false;
00347 
00348         $fn = $this->findMessage($id);
00349 
00350         // Get flags, etc
00351         $stat = $this->StatMessage($folderid, $id);
00352 
00353         // Parse e-mail
00354         $rfc822 = file_get_contents($this->getPath() . "/" . $fn);
00355 
00356         $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
00357 
00358         $output = new SyncMail();
00359 
00360         $output->body = str_replace("\n", "\r\n", $this->getBody($message));
00361         $output->bodysize = strlen($output->body);
00362         $output->bodytruncated = 0; // We don't implement truncation in this backend
00363         $output->datereceived = $this->parseReceivedDate($message->headers["received"][0]);
00364         $output->messageclass = "IPM.Note";
00365         $output->subject = $message->headers["subject"];
00366         $output->read = $stat["flags"];
00367         $output->from = $message->headers["from"];
00368 
00369         $Mail_RFC822 = new Mail_RFC822();
00370         $toaddr = $ccaddr = $replytoaddr = array();
00371         if(isset($message->headers["to"]))
00372             $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]);
00373         if(isset($message->headers["cc"]))
00374             $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]);
00375         if(isset($message->headers["reply_to"]))
00376             $replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]);
00377 
00378         $output->to = array();
00379         $output->cc = array();
00380         $output->reply_to = array();
00381         foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) {
00382             foreach($addrlist as $addr) {
00383                 $address = $addr->mailbox . "@" . $addr->host;
00384                 $name = $addr->personal;
00385 
00386                 if (!isset($output->displayto) && $name != "")
00387                     $output->displayto = $name;
00388 
00389                 if($name == "" || $name == $address)
00390                     $fulladdr = w2u($address);
00391                 else {
00392                     if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
00393                         $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">";
00394                     }
00395                     else {
00396                         $fulladdr = w2u($name) ." <" . w2u($address) . ">";
00397                     }
00398                 }
00399 
00400                 array_push($output->$type, $fulladdr);
00401             }
00402         }
00403 
00404         // convert mime-importance to AS-importance
00405         if (isset($message->headers["x-priority"])) {
00406             $mimeImportance =  preg_replace("/\D+/", "", $message->headers["x-priority"]);
00407             if ($mimeImportance > 3)
00408                 $output->importance = 0;
00409             if ($mimeImportance == 3)
00410                 $output->importance = 1;
00411             if ($mimeImportance < 3)
00412                 $output->importance = 2;
00413         }
00414 
00415         // Attachments are only searched in the top-level part
00416         $n = 0;
00417         if(isset($message->parts)) {
00418             foreach($message->parts as $part) {
00419                 if($part->ctype_primary == "application") {
00420                     $attachment = new SyncAttachment();
00421                     $attachment->attsize = strlen($part->body);
00422 
00423                     if(isset($part->d_parameters['filename']))
00424                         $attname = $part->d_parameters['filename'];
00425                     else if(isset($part->ctype_parameters['name']))
00426                         $attname = $part->ctype_parameters['name'];
00427                     else if(isset($part->headers['content-description']))
00428                         $attname = $part->headers['content-description'];
00429                     else $attname = "unknown attachment";
00430 
00431                     $attachment->displayname = $attname;
00432                     $attachment->attname = $id . ":" . $n;
00433                     $attachment->attmethod = 1;
00434                     $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : "";
00435 
00436                     array_push($output->attachments, $attachment);
00437                 }
00438                 $n++;
00439             }
00440         }
00441 
00442         return $output;
00443     }
00444 
00454     public function StatMessage($folderid, $id) {
00455         $dirname = $this->getPath();
00456         $fn = $this->findMessage($id);
00457         if(!$fn)
00458             return false;
00459 
00460         $stat = stat("$dirname/$fn");
00461 
00462         $entry = array();
00463         $entry["id"] = $id;
00464         $entry["flags"] = 0;
00465 
00466         if(strpos($fn,"S"))
00467             $entry["flags"] |= 1;
00468         $entry["mod"] = $stat["mtime"];
00469 
00470         return $entry;
00471     }
00472 
00485     public function ChangeMessage($folderid, $id, $message) {
00486         return false;
00487     }
00488 
00500     public function SetReadFlag($folderid, $id, $flags) {
00501         if($folderid != 'root')
00502             return false;
00503 
00504         $fn = $this->findMessage($id);
00505 
00506         if(!$fn)
00507             return true; // message may have been deleted
00508 
00509         if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$fn,$matches))
00510             return false;
00511 
00512         // remove 'seen' (S) flag
00513         if(!$flags) {
00514             $newflags = str_replace("S","",$matches[2]);
00515         } else {
00516             // make sure we don't double add the 'S' flag
00517             $newflags = str_replace("S","",$matches[2]) . "S";
00518         }
00519 
00520         $newfn = $matches[1] . ":2," . $newflags;
00521         // rename if required
00522         if($fn != $newfn)
00523             rename($this->getPath() ."/$fn", $this->getPath() . "/$newfn");
00524 
00525         return true;
00526     }
00527 
00538     public function DeleteMessage($folderid, $id) {
00539         if($folderid != 'root')
00540             return false;
00541 
00542         $fn = $this->findMessage($id);
00543 
00544         if(!$fn)
00545             return true; // success because message has been deleted already
00546 
00547         if(!unlink($this->getPath() . "/$fn")) {
00548             return true; // success - message may have been deleted in the mean time (since findMessage)
00549         }
00550 
00551         return true;
00552     }
00553 
00566     public function MoveMessage($folderid, $id, $newfolderid) {
00567         return false;
00568     }
00569 
00570 
00583     private function findMessage($id) {
00584         // We could use 'this->folderid' for path info but we currently
00585         // only support a single INBOX. We also have to use a glob '*'
00586         // because we don't know the flags of the message we're looking for.
00587 
00588         $dirname = $this->getPath();
00589         $dir = opendir($dirname);
00590 
00591         while($entry = readdir($dir)) {
00592             if(strpos($entry,$id) === 0)
00593                 return $entry;
00594         }
00595         return false; // not found
00596     }
00597 
00606     private function getBody($message) {
00607         $body = "";
00608         $htmlbody = "";
00609 
00610         $this->getBodyRecursive($message, "plain", $body);
00611 
00612         if(!isset($body) || $body === "") {
00613             $this->getBodyRecursive($message, "html", $body);
00614             // remove css-style tags
00615             $body = preg_replace("/<style.*?<\/style>/is", "", $body);
00616             // remove all other html
00617             $body = strip_tags($body);
00618         }
00619 
00620         return $body;
00621     }
00622 
00634     private function getBodyRecursive($message, $subtype, &$body) {
00635         if(!isset($message->ctype_primary)) return;
00636         if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
00637             $body .= $message->body;
00638 
00639         if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
00640             foreach($message->parts as $part) {
00641                 if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment"))  {
00642                     $this->getBodyRecursive($part, $subtype, $body);
00643                 }
00644             }
00645         }
00646     }
00647 
00656     private function parseReceivedDate($received) {
00657         $pos = strpos($received, ";");
00658         if(!$pos)
00659             return false;
00660 
00661         $datestr = substr($received, $pos+1);
00662         $datestr = ltrim($datestr);
00663 
00664         return strtotime($datestr);
00665     }
00666 
00673     private function moveNewToCur() {
00674         $newdirname = MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/new";
00675 
00676         $newdir = opendir($newdirname);
00677 
00678         while($newentry = readdir($newdir)) {
00679             if($newentry{0} == ".")
00680                 continue;
00681 
00682             // link/unlink == move. This is the way to move the message according to cr.yp.to
00683             link($newdirname . "/" . $newentry, $this->getPath() . "/" . $newentry . ":2,");
00684             unlink($newdirname . "/" . $newentry);
00685         }
00686     }
00687 
00694     private function getPath() {
00695         return MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/cur";
00696     }
00697 }
00698 
00699 ?>