Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MailLibrary/ContactList.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
class ContactList implements Iterator, Countable
{
/** @var Contact[] */
protected $contacts;
protected $contacts = [];

protected $builtContacts;
protected $builtContacts = [];

public function addContact($mailbox = NULL, $host = NULL, $personal = NULL, $adl = NULL)
{
Expand Down
49 changes: 35 additions & 14 deletions MailLibrary/Drivers/ImapDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use greeny\MailLibrary\Mailbox;
use greeny\MailLibrary\Structures\IStructure;
use greeny\MailLibrary\Structures\ImapStructure;
use Nette\Utils\Strings;
use greeny\MailLibrary\Mail;
use DateTime;

Expand Down Expand Up @@ -227,18 +228,21 @@ public function checkFilter($key, $value = NULL) {
public function getHeaders($mailId)
{
$raw = imap_fetchheader($this->resource, $mailId, FT_UID);
$lines = explode("\n", $raw);
$lines = explode("\n", Strings::fixEncoding($raw));
$headers = array();
$lastHeader = NULL;

// normalize headers
foreach($lines as $line) {
if(mb_substr($line, 0, 1, 'UTF-8') === " ") {
$headers[$lastHeader] .= $line;
$firstCharacter = mb_substr($line, 0, 1, 'UTF-8'); // todo: correct assumption that string must be UTF-8 encoded?
if(preg_match('/[\pZ\pC]/u', $firstCharacter) === 1) { // search for UTF-8 whitespaces
$headers[$lastHeader] .= " " . Strings::trim($line);
} else {
$parts = explode(':', $line);
$name = $parts[0];
$name = Strings::trim($parts[0]);
unset($parts[0]);

$headers[$name] = implode(':', $parts);
$headers[$name] = Strings::trim(implode(':', $parts));
$lastHeader = $name;
}
}
Expand All @@ -254,7 +258,7 @@ public function getHeaders($mailId)
$text = '';
foreach($decoded as $part) {
if($part->charset !== 'UTF-8' && $part->charset !== 'default') {
$text .= mb_convert_encoding($part->text, 'UTF-8', $part->charset);
$text .= @mb_convert_encoding($part->text, 'UTF-8', $part->charset); // todo: handle this more properly
} else {
$text .= $part->text;
}
Expand Down Expand Up @@ -297,22 +301,39 @@ public function getStructure($mailId, Mailbox $mailbox)
* Gets part of body
*
* @param int $mailId
* @param array $data
* @param array $data requires id and encoding keys
* @return string
* @throws \greeny\MailLibrary\DriverException
*/
public function getBody($mailId, array $data)
{
$body = array();
foreach($data as $part) {
$data = ($part['id'] == 0) ? imap_body($this->resource, $mailId, FT_UID | FT_PEEK) : imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK);
$encoding = $part['encoding'];
if($encoding === ImapStructure::ENCODING_BASE64) {
$data = base64_decode($data);
} else if($encoding === ImapStructure::ENCODING_QUOTED_PRINTABLE) {
$data = quoted_printable_decode($data);
assert(is_array($part));
$dataMessage = ($part['id'] === 0) ? @imap_body($this->resource, $mailId, FT_UID | FT_PEEK) : @imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK);
if($dataMessage === FALSE) {
throw new DriverException("Cannot read given message part - " . error_get_last()["message"]);
}

// when there is no encoding of mime part available
if(!isset($part['encoding'])) {
$decodedMessage = $dataMessage;

} elseif($part['encoding'] === ImapStructure::ENCODING_BASE64) {
$decodedMessage = base64_decode($dataMessage);
if($decodedMessage === FALSE) {
throw new DriverException('Malformed mime-part: cannot decode base64 mime-part');
}

} elseif($part['encoding'] === ImapStructure::ENCODING_QUOTED_PRINTABLE) {
$decodedMessage = quoted_printable_decode($dataMessage);

} else {
throw new DriverException("Mime-part reading error: unknown encoding ({$part['encoding']})");

}

$body[] = $data;
$body[] = $decodedMessage;
}
return implode('\n\n', $body);
}
Expand Down
77 changes: 63 additions & 14 deletions MailLibrary/Mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace greeny\MailLibrary;

use greeny\MailLibrary\Structures\IStructure;
use Nette\Utils\Strings;

class Mail {
const ANSWERED = 'ANSWERED';
const BCC = 'BCC';
Expand Down Expand Up @@ -39,10 +42,10 @@ class Mail {
const ORDER_CC = SORTCC;
const ORDER_SIZE = SORTSIZE;

/** @var \greeny\MailLibrary\Connection */
/** @var Connection */
protected $connection;

/** @var \greeny\MailLibrary\Mailbox */
/** @var Mailbox */
protected $mailbox;

/** @var int */
Expand All @@ -51,7 +54,7 @@ class Mail {
/** @var array */
protected $headers = NULL;

/** @var \greeny\MailLibrary\Structures\IStructure */
/** @var IStructure */
protected $structure = NULL;

/** @var array */
Expand All @@ -78,18 +81,27 @@ public function __construct(Connection $connection, Mailbox $mailbox, $id)
public function __isset($name)
{
$this->headers !== NULL || $this->initializeHeaders();
return isset($this->headers[$this->formatHeaderName($name)]);
$key = $this->normalizeHeaderName($this->lowerCamelCaseToHeaderName($name));
return isset($this->headers[$key]);
}

/**
* Header getter
*
* @param string $name
* @return mixed
* @deprecated
*/
public function __get($name)
{
return $this->getHeader($name);
\trigger_error(\E_USER_DEPRECATED, 'use array access with execat header name instead');
return $this->getHeader(
$this->normalizeHeaderName($this->lowerCamelCaseToHeaderName($name))
);
}

public function __set($name, $value) {
throw new \Exception('Mail headers are read-only.');
}

/**
Expand Down Expand Up @@ -124,7 +136,12 @@ public function getHeaders()
public function getHeader($name)
{
$this->headers !== NULL || $this->initializeHeaders();
return $this->headers[$this->formatHeaderName($name)];
$index = $this->normalizeHeaderName($name);
if(isset($this->headers[$index])) {
return $this->headers[$index];
}

return NULL;
}

/**
Expand All @@ -135,9 +152,9 @@ public function getSender() {
if($from) {
$contacts = $from->getContactsObjects();
return (count($contacts) ? $contacts[0] : NULL);
} else {
return NULL;
}

return NULL;
}

/**
Expand Down Expand Up @@ -176,6 +193,15 @@ public function getAttachments()
return $this->structure->getAttachments();
}

/**
* @return \greeny\MailLibrary\MimePart[]
*/
public function getMimeParts()
{
$this->structure !== NULL || $this->initializeStructure();
return $this->structure->getMimeParts();
}

/**
* @return array
*/
Expand Down Expand Up @@ -230,7 +256,7 @@ protected function initializeHeaders()
$this->headers = array();
$this->connection->getDriver()->switchMailbox($this->mailbox->getName());
foreach($this->connection->getDriver()->getHeaders($this->id) as $key => $value) {
$this->headers[$this->formatHeaderName($key)] = $value;
$this->headers[$this->normalizeHeaderName($key)] = $value;
}
}

Expand All @@ -240,22 +266,45 @@ protected function initializeStructure()
$this->structure = $this->connection->getDriver()->getStructure($this->id, $this->mailbox);
}

/**
* @internal
* @return IStructure
*/
public function getStructure() {
$this->structure !== NULL || $this->initializeStructure();
return $this->structure;
}

protected function initializeFlags()
{
$this->connection->getDriver()->switchMailbox($this->mailbox->getName());
$this->flags = $this->connection->getDriver()->getFlags($this->id);
}

/**
* Formats header name (X-Received-From => xReceivedFrom)
* Formats header name (X-Received-From => x-recieved-from)
*
* @param string $name
* @param string $name Header name (with dashes, valid UTF-8 string)
* @return string
*/
protected function formatHeaderName($name)
protected function normalizeHeaderName($name)
{
return lcfirst(preg_replace_callback("~-.~", function($matches){
return Strings::normalize(Strings::lower($name));
}

/**
* Converts camel cased name to normalized header name (xReceivedFrom => x-recieved-from)
*
* @param string $camelCasedName
* @return string name with dashes
*/
protected function lowerCamelCaseToHeaderName($camelCasedName) {
// todo: test this
// todo: use something like this instead http://stackoverflow.com/a/1993772
$dashedName = lcfirst(preg_replace_callback("~-.~", function($matches){
return ucfirst(substr($matches[0], 1));
}, $name));
}, $camelCasedName));

return $this->normalizeHeaderName($dashedName);
}
}
82 changes: 82 additions & 0 deletions MailLibrary/MimePart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* This file is part of the imap-mail-list-extractor.
* Copyright (c) 2017 Grifart spol. s r.o. (https://grifart.cz)
*/

namespace greeny\MailLibrary;

use greeny\MailLibrary\Drivers\IDriver;

class MimePart
{
/** @var string */
private $partId;

/** @var string */
private $mimeType;

/** @var string */
private $name;

/** @var string */
private $encoding;

/** @var IDriver */
private $driver;

/** @var string */
private $mailId;

public function __construct(IDriver $driver, $mailId, $partId, $mimeType, $name, $encoding)
{
$this->partId = $partId;
$this->mailId = $mailId;
$this->mimeType = $mimeType;
$this->name = $name;
$this->encoding = $encoding;
$this->driver = $driver;
}

/**
* @return string
*/
public function getPartId()
{
return $this->partId;
}

/**
* @return string
*/
public function getMimeType()
{
return $this->mimeType;
}

/**
* @return string
*/
public function getName()
{
return $this->name;
}

/**
* @return string
*/
public function getEncoding()
{
return $this->encoding;
}

public function getContent()
{
// todo: this automatically decodes content type if supported, support for getting RAW content?
return $this->driver->getBody($this->mailId, [[
'id' => $this->partId,
'encoding' => $this->encoding,
]]);
}

}
8 changes: 7 additions & 1 deletion MailLibrary/Structures/IStructure.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace greeny\MailLibrary\Structures;

use greeny\MailLibrary\Attachment;
use greeny\MailLibrary\MimePart;

interface IStructure {
/**
Expand All @@ -27,4 +28,9 @@ function getTextBody();
* @return Attachment[]
*/
function getAttachments();
}

/**
* @return MimePart[]
*/
function getMimeParts();
}
Loading