techno-express/mailreader

View on GitHub
decode/MailReader.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

namespace Mail;

use \Mail_mimeDecode;
use Mail\MailParser;

/**
 * @package MailReader.php
 *
 * Receive mail and attachments with PHP
 *
 * Support: 
 * http://stuporglue.org/mailreader-php-parse-e-mail-and-save-attachments-php-version-2/
 *
 * Code:
 * https://github.com/stuporglue/mailreader
 *
 * See the README.md for the license, and other information
 */
class MailReader
{
    /**
     * Attachments Array
     *
     * @var array
     */
    private $saved_files = [];

    /**
     * Send confirmation e-mail back to sender?
     *
     * @var boolean
     */
    private $send_email = false;

    /**
     * Save e-mail message and file list to DB?
     *
     * @var boolean
     */
    private $save_msg_to_db = false;

    /**
     * A safe place for files. 
     * Malicious users could upload a php or executable file, 
     * so keep this out of your web root
     *
     * @var string
     */
    private $save_directory;

    private $allowed_mime_types = [
        'audio/wave',
        'audio/caf',
        'application/pdf',
        'application/zip',
        'application/octet-stream',
        'image/jpeg',
        'image/png',
        'image/gif',
    ];

    private $debug = false;

    private $raw = '';
    private $decoded;
    private $to;
    private $to_email;
    private $date;
    private $from;
    private $from_email;
    private $subject;
    private $plain;
    private $html;

    /**
     * @param \PDO $pdo A PDO connection to a database for saving emails 
     * @param string $save_directory A path to a directory where files will be saved
     */
    public function __construct($pdo = null, $save_directory = null)
    {
        if (!\file_exists($save_directory))
            @\mkdir($save_directory, 0770, true);

        if (!\preg_match('|\\/$|', $save_directory))
            $save_directory .= \DIRECTORY_SEPARATOR;

        $this->save_directory = $save_directory;
        $this->pdo = $pdo;
    }

    /**
     * Add additional allowed mime types to the list.
     * 
     * @param string
     */
    public function addMimeType($mime_types = '')
    {
        if (\strpos($mime_types, '/') !== false)
            \array_push($this->allowed_mime_types, $mime_types);

        return $this;
    }

    public function debugOn()
    {
        $this->debug = true;
        return $this;
    }

    public function debugOff()
    {
        $this->debug = false;
        return $this;
    }

    /**
     * Turn on sending confirmation e-mail back to sender.
     */
    public function sendOn()
    {
        $this->send_email = true;
        return $this;
    }

    /**
     * Turn off sending confirmation e-mail back to sender.
     */
    public function sendOff()
    {
        $this->send_email = false;
        return $this;
    }

    /**
     * Turn on saving e-mail message and file list to the database.
     */
    public function saveOn()
    {
        $this->save_msg_to_db = true;
        return $this;
    }

    /**
     * Turn off saving e-mail message and file list to the database.
     */
    public function saveOff()
    {
        $this->save_msg_to_db = false;
        return $this;
    }

    public function findDirectory($startingDirectory = null, $directoryNamed = 'mailPiped')
    {
        $home = (\getenv('USERPROFILE') !== false) ? \getenv('USERPROFILE') : \getenv('HOME');
        $info = ($home !== false) ? $home : \dirname(__FILE__);
        $findConfigPath = $info . \DIRECTORY_SEPARATOR;
        $save_directory = '';

        if ('\\' !== \DIRECTORY_SEPARATOR) {
            $info = \posix_getpwuid(\posix_getuid());
            $findConfigPath = $info['dir'] . \DIRECTORY_SEPARATOR;
        }
        if (!empty($startingDirectory))
            $findConfigPath = $startingDirectory . \DIRECTORY_SEPARATOR;

        if (($directoryHandle = @\opendir($findConfigPath)) == true) {
            while (($file = \readdir($directoryHandle)) !== false) {
                if (
                    \is_dir($findConfigPath . $file)
                    && (($file == '.' || $file == '..') !== true)
                    && (\strpos($file, $directoryNamed) !== false)
                ) {
                    $save_directory = $findConfigPath . $file;
                    break;
                }
            }
            \closedir($directoryHandle);
        }

        return $save_directory;
    }

    /**
     * Read an email message
     * with the given $src file to read the email from. 
     * Default is php://stdin for use as a pipe email handler
     * 
     * Will returns an associative array of files saved. 
     * The key is the file name, the value is an associative array with size and mime type as keys.
     * 
     * @param $src (optional) 
     *
     * @return array
     */
    public function readEmail($src = 'php://stdin')
    {
        // Process the e-mail from stdin
        $fd = \fopen($src, 'r');
        while (!\feof($fd)) {
            $this->raw .= \fread($fd, 1024);
        }

        $this->decoded = new MailParser($this->raw);

        $this->to = $this->decoded->getTo();
        $this->to_email = $this->decoded->getToEmail();
        $this->date = $this->decoded->getDate();
        $this->from = $this->decoded->getFrom();
        $this->from_email = $this->decoded->getFromEmail();
        $this->subject = $this->decoded->getSubject();
        $this->plain = $this->decoded->getPlain();
        $this->html = $this->decoded->getHtml();
        $this->saved_files = $this->decoded->decode($this->save_directory);

        // Put the results in the database if needed
        if ($this->save_msg_to_db && !\is_null($this->pdo)) {
            $this->saveToDb();
        }

        // Send response e-mail if needed
        if ($this->send_email && $this->from_email != "") {
            $this->sendEmail();
        }

        // Print messages
        if ($this->debug) {
            $this->debugMsg();
        }

        return $this->saved_files;
    }

    public function getMessageCount(string $email)
    {
        $dbc = $this->pdo;
        if ($dbc instanceof \PDO) {
            $messages = $dbc->prepare("SELECT count(*) FROM emails WHERE toaddr=?");
            if (!is_bool($messages)) {
                $messages->execute([$email]);
                $count = $messages->fetchColumn();
                unset($messages);
                return $count;
            }
        }

        return false;
    }

    public function getMessages(string $email, int $offset = 0, int $max = 20)
    {
        $dbc = $this->pdo;
        if ($dbc instanceof \PDO) {
            $emails = $dbc->prepare("SELECT * FROM emails WHERE toaddr=:mail LIMIT :offset, :max");
            if (!is_bool($emails)) {
                $emails->bindValue(':mail', $email);
                $emails->bindValue(':offset', $offset, \PDO::PARAM_INT);
                $emails->bindValue(':max', $max, \PDO::PARAM_INT);
                $emails->execute();

                $records = [];
                while ($record = $emails->fetchObject('Mail\Messages')) {
                    $records[] = $record;
                }

                unset($emails);
                if (\count($records) > 0)
                    return $records;

                return 0;
            }
        }

        return false;
    }

    public function getMessageAttachments(int $message_id)
    {
        $dbc = $this->pdo;
        if ($dbc instanceof \PDO) {
            $files = $dbc->prepare("SELECT * FROM files WHERE email=:id");
            if (!is_bool($files)) {
                $files->bindValue(':id', $message_id, \PDO::PARAM_INT);
                $files->execute();

                $records = [];
                while ($record = $files->fetchObject('Mail\MessageAttachments')) {
                    $records[] = $record;
                }

                unset($files);
                if (\count($records) > 0)
                    return $records;
            }
        }

        return false;
    }

    /**
     * Save the html and plain text, receiver, subject and sender of an email to the database
     * @throws \Exception if insert failure
     */
    private function saveToDb()
    {
        $insert = $this->pdo->prepare("INSERT INTO emails (user, toaddr, sender, fromaddr, date, subject, plain, html) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");

        // Replace non UTF-8 characters with their UTF-8 equivalent, or drop them
        if (!$insert->execute(array(
            \mb_convert_encoding($this->to, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->to_email, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->from, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->from_email, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->date, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->subject, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->plain, 'UTF-8', 'UTF-8'),
            \mb_convert_encoding($this->html, 'UTF-8', 'UTF-8')
        ))) {
            if ($this->debug) {
                \print_r($insert->errorInfo());
            }
            throw new \Exception("INSERT INTO emails failed!");
        }
        $email_id = $this->pdo->lastInsertId();
        unset($insert);

        foreach ($this->saved_files as $data) {
            $insertFile = $this->pdo->prepare("INSERT INTO files (email, name, path, size, mime) VALUES (:email, :name, :path, :size, :mime)");
            $insertFile->bindParam(':email', $email_id);
            $convertedFilename = \mb_convert_encoding($data['name'], 'UTF-8', 'UTF-8');
            $insertFile->bindParam(':name', $convertedFilename);
            $insertFile->bindParam(':path', $data['path']);
            $insertFile->bindParam(':size', $data['size']);
            $insertFile->bindParam(':mime', $data['mime']);
            if (!$insertFile->execute()) {
                if ($this->debug) {
                    \print_r($insertFile->errorInfo());
                }
                throw new \Exception("Insert file info failed!");
            }
        }
        unset($insertFile);
    }

    /**
     * Send the sender a response email with a summary of what was saved
     */
    private function sendEmail()
    {
        $newmsg = "Thanks! We just received the following ";
        $newmsg .= "files for storage:\n\n";
        $newmsg .= "Filename -- Size\n";
        foreach ($this->saved_files as $s) {
            $newmsg .= "{$s['name']} -- ({$s['size']}) of type {$s['mime']}\n";
        }

        $newmsg .= "\nHope everything looks right. If not,";
        $newmsg .=  "please send us an e-mail!\n";

        \mail($this->from_email, $this->subject, $newmsg);
    }

    /**
     * Print a summary of the most recent email read
     */
    private function debugMsg()
    {
        print "From : $this->from_email\n";
        print "Subject : $this->subject\n";
        print "PlainBody : $this->plain\n";
        print "HtmlBody : $this->html\n";
        print "Saved Files : \n";
        print_r($this->saved_files);
    }
}