From 30b30226e6569f13e444cdcb513cd2bfc24318d7 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Thu, 04 Nov 2010 10:03:26 -0400
Subject: [PATCH] - Add possibility to force mailbox selection. There're situations where we're invoking STATUS (for all messages count) and SELECT later for other operations. If we call SELECT first, the STATUS will be not needed.
---
program/lib/Mail/mimePart.php | 1289 ++++++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 1,076 insertions(+), 213 deletions(-)
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
index b429b90..55ea7a6 100644
--- a/program/lib/Mail/mimePart.php
+++ b/program/lib/Mail/mimePart.php
@@ -1,197 +1,273 @@
<?php
-// +-----------------------------------------------------------------------+
-// | Copyright (c) 2002-2003 Richard Heyes |
-// | All rights reserved. |
-// | |
-// | Redistribution and use in source and binary forms, with or without |
-// | modification, are permitted provided that the following conditions |
-// | are met: |
-// | |
-// | o Redistributions of source code must retain the above copyright |
-// | notice, this list of conditions and the following disclaimer. |
-// | o Redistributions in binary form must reproduce the above copyright |
-// | notice, this list of conditions and the following disclaimer in the |
-// | documentation and/or other materials provided with the distribution.|
-// | o The names of the authors may not be used to endorse or promote |
-// | products derived from this software without specific prior written |
-// | permission. |
-// | |
-// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
-// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
-// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
-// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
-// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
-// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
-// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
-// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// | |
-// +-----------------------------------------------------------------------+
-// | Author: Richard Heyes <richard@phpguru.org> |
-// +-----------------------------------------------------------------------+
+/**
+ * The Mail_mimePart class is used to create MIME E-mail messages
+ *
+ * This class enables you to manipulate and build a mime email
+ * from the ground up. The Mail_Mime class is a userfriendly api
+ * to this class for people who aren't interested in the internals
+ * of mime mail.
+ * This class however allows full control over the email.
+ *
+ * Compatible with PHP versions 4 and 5
+ *
+ * LICENSE: This LICENSE is in the BSD license style.
+ * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
+ * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * - Neither the name of the authors, nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category Mail
+ * @package Mail_Mime
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Cipriano Groenendal <cipri@php.net>
+ * @author Sean Coates <sean@php.net>
+ * @author Aleksander Machniak <alec@php.net>
+ * @copyright 2003-2006 PEAR <pear-group@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/Mail_mime
+ */
+
/**
-*
-* Raw mime encoding class
-*
-* What is it?
-* This class enables you to manipulate and build
-* a mime email from the ground up.
-*
-* Why use this instead of mime.php?
-* mime.php is a userfriendly api to this class for
-* people who aren't interested in the internals of
-* mime mail. This class however allows full control
-* over the email.
-*
-* Eg.
-*
-* // Since multipart/mixed has no real body, (the body is
-* // the subpart), we set the body argument to blank.
-*
-* $params['content_type'] = 'multipart/mixed';
-* $email = new Mail_mimePart('', $params);
-*
-* // Here we add a text part to the multipart we have
-* // already. Assume $body contains plain text.
-*
-* $params['content_type'] = 'text/plain';
-* $params['encoding'] = '7bit';
-* $text = $email->addSubPart($body, $params);
-*
-* // Now add an attachment. Assume $attach is
-* the contents of the attachment
-*
-* $params['content_type'] = 'application/zip';
-* $params['encoding'] = 'base64';
-* $params['disposition'] = 'attachment';
-* $params['dfilename'] = 'example.zip';
-* $attach =& $email->addSubPart($body, $params);
-*
-* // Now build the email. Note that the encode
-* // function returns an associative array containing two
-* // elements, body and headers. You will need to add extra
-* // headers, (eg. Mime-Version) before sending.
-*
-* $email = $message->encode();
-* $email['headers'][] = 'Mime-Version: 1.0';
-*
-*
-* Further examples are available at http://www.phpguru.org
-*
-* TODO:
-* - Set encode() to return the $obj->encoded if encode()
-* has already been run. Unless a flag is passed to specifically
-* re-build the message.
-*
-* @author Richard Heyes <richard@phpguru.org>
-* @version $Revision$
-* @package Mail
-*/
-
-class Mail_mimePart {
-
- /**
+ * The Mail_mimePart class is used to create MIME E-mail messages
+ *
+ * This class enables you to manipulate and build a mime email
+ * from the ground up. The Mail_Mime class is a userfriendly api
+ * to this class for people who aren't interested in the internals
+ * of mime mail.
+ * This class however allows full control over the email.
+ *
+ * @category Mail
+ * @package Mail_Mime
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Cipriano Groenendal <cipri@php.net>
+ * @author Sean Coates <sean@php.net>
+ * @author Aleksander Machniak <alec@php.net>
+ * @copyright 2003-2006 PEAR <pear-group@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Mail_mime
+ */
+class Mail_mimePart
+{
+ /**
* The encoding type of this part
+ *
* @var string
+ * @access private
*/
var $_encoding;
- /**
+ /**
* An array of subparts
+ *
* @var array
+ * @access private
*/
var $_subparts;
- /**
+ /**
* The output of this part after being built
+ *
* @var string
+ * @access private
*/
var $_encoded;
- /**
+ /**
* Headers for this part
+ *
* @var array
+ * @access private
*/
var $_headers;
- /**
+ /**
* The body of this part (not encoded)
+ *
* @var string
+ * @access private
*/
var $_body;
/**
- * Constructor.
- *
- * Sets up the object.
- *
- * @param $body - The body of the mime part if any.
- * @param $params - An associative array of parameters:
- * content_type - The content type for this part eg multipart/mixed
- * encoding - The encoding to use, 7bit, 8bit, base64, or quoted-printable
- * cid - Content ID to apply
- * disposition - Content disposition, inline or attachment
- * dfilename - Optional filename parameter for content disposition
- * description - Content description
- * charset - Character set to use
- * @access public
- */
+ * The location of file with body of this part (not encoded)
+ *
+ * @var string
+ * @access private
+ */
+ var $_body_file;
+
+ /**
+ * The end-of-line sequence
+ *
+ * @var string
+ * @access private
+ */
+ var $_eol = "\r\n";
+
+ /**
+ * Constructor.
+ *
+ * Sets up the object.
+ *
+ * @param string $body The body of the mime part if any.
+ * @param array $params An associative array of optional parameters:
+ * content_type - The content type for this part eg multipart/mixed
+ * encoding - The encoding to use, 7bit, 8bit,
+ * base64, or quoted-printable
+ * cid - Content ID to apply
+ * disposition - Content disposition, inline or attachment
+ * dfilename - Filename parameter for content disposition
+ * description - Content description
+ * charset - Character set to use
+ * name_encoding - Encoding for attachment name (Content-Type)
+ * By default filenames are encoded using RFC2231
+ * Here you can set RFC2047 encoding (quoted-printable
+ * or base64) instead
+ * filename_encoding - Encoding for attachment filename (Content-Disposition)
+ * See 'name_encoding'
+ * eol - End of line sequence. Default: "\r\n"
+ * body_file - Location of file with part's body (instead of $body)
+ *
+ * @access public
+ */
function Mail_mimePart($body = '', $params = array())
{
- if (!defined('MAIL_MIMEPART_CRLF')) {
- define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE);
+ if (!empty($params['eol'])) {
+ $this->_eol = $params['eol'];
+ } else if (defined('MAIL_MIMEPART_CRLF')) { // backward-copat.
+ $this->_eol = MAIL_MIMEPART_CRLF;
}
+ $c_type = array();
+ $c_disp = array();
foreach ($params as $key => $value) {
switch ($key) {
- case 'content_type':
- $headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : '');
- break;
+ case 'content_type':
+ $c_type['type'] = $value;
+ break;
- case 'encoding':
- $this->_encoding = $value;
- $headers['Content-Transfer-Encoding'] = $value;
- break;
+ case 'encoding':
+ $this->_encoding = $value;
+ $headers['Content-Transfer-Encoding'] = $value;
+ break;
- case 'cid':
- $headers['Content-ID'] = '<' . $value . '>';
- break;
+ case 'cid':
+ $headers['Content-ID'] = '<' . $value . '>';
+ break;
- case 'disposition':
- $headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : '');
- break;
+ case 'disposition':
+ $c_disp['disp'] = $value;
+ break;
- case 'dfilename':
- if (isset($headers['Content-Disposition'])) {
- $headers['Content-Disposition'] .= '; filename="' . $value . '"';
- } else {
- $dfilename = $value;
- }
- break;
+ case 'dfilename':
+ $c_disp['filename'] = $value;
+ $c_type['name'] = $value;
+ break;
- case 'description':
- $headers['Content-Description'] = $value;
- break;
+ case 'description':
+ $headers['Content-Description'] = $value;
+ break;
- case 'charset':
- if (isset($headers['Content-Type'])) {
- $headers['Content-Type'] .= '; charset="' . $value . '"';
- } else {
- $charset = $value;
- }
- break;
+ case 'charset':
+ $c_type['charset'] = $value;
+ $c_disp['charset'] = $value;
+ break;
+
+ case 'language':
+ $c_type['language'] = $value;
+ $c_disp['language'] = $value;
+ break;
+
+ case 'location':
+ $headers['Content-Location'] = $value;
+ break;
+
+ case 'body_file':
+ $this->_body_file = $value;
+ break;
}
}
// Default content-type
- if (!isset($headers['Content-Type'])) {
- $headers['Content-Type'] = 'text/plain';
+ if (empty($c_type['type'])) {
+ $c_type['type'] = 'text/plain';
}
- //Default encoding
+ // Content-Type
+ if (!empty($c_type['type'])) {
+ $headers['Content-Type'] = $c_type['type'];
+ if (!empty($c_type['charset'])) {
+ $charset = "charset={$c_type['charset']}";
+ // place charset parameter in the same line, if possible
+ if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
+ $headers['Content-Type'] .= '; ';
+ } else {
+ $headers['Content-Type'] .= ';' . $this->_eol . ' ';
+ }
+ $headers['Content-Type'] .= $charset;
+ }
+ if (!empty($c_type['name'])) {
+ $headers['Content-Type'] .= ';' . $this->_eol;
+ $headers['Content-Type'] .= $this->_buildHeaderParam(
+ 'name', $c_type['name'],
+ isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
+ isset($c_type['language']) ? $c_type['language'] : null,
+ isset($params['name_encoding']) ? $params['name_encoding'] : null
+ );
+ }
+ }
+
+ // Content-Disposition
+ if (!empty($c_disp['disp'])) {
+ $headers['Content-Disposition'] = $c_disp['disp'];
+ if (!empty($c_disp['filename'])) {
+ $headers['Content-Disposition'] .= ';' . $this->_eol;
+ $headers['Content-Disposition'] .= $this->_buildHeaderParam(
+ 'filename', $c_disp['filename'],
+ isset($c_disp['charset']) ? $c_disp['charset'] : 'US-ASCII',
+ isset($c_disp['language']) ? $c_disp['language'] : null,
+ isset($params['filename_encoding']) ? $params['filename_encoding'] : null
+ );
+ }
+ }
+
+ if (!empty($headers['Content-Description'])) {
+ $headers['Content-Description'] = $this->encodeHeader(
+ 'Content-Description', $headers['Content-Description'],
+ isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
+ isset($params['name_encoding']) ? $params['name_encoding'] : 'quoted-printable',
+ $this->_eol
+ );
+ }
+
+ // Default encoding
if (!isset($this->_encoding)) {
$this->_encoding = '7bit';
}
@@ -203,41 +279,60 @@
}
/**
- * encode()
- *
* Encodes and returns the email. Also stores
* it in the encoded member variable
*
+ * @param string $boundary Pre-defined boundary string
+ *
* @return An associative array containing two elements,
* body and headers. The headers element is itself
- * an indexed array.
+ * an indexed array. On error returns PEAR error object.
* @access public
*/
- function encode()
+ function encode($boundary=null)
{
$encoded =& $this->_encoded;
- if (!empty($this->_subparts)) {
- srand((double)microtime()*1000000);
- $boundary = '=_' . md5(rand() . microtime());
- $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"';
+ if (count($this->_subparts)) {
+ $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
+ $eol = $this->_eol;
- // Add body parts to $subparts
+ $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
+
+ $encoded['body'] = '';
+
for ($i = 0; $i < count($this->_subparts); $i++) {
- $headers = array();
+ $encoded['body'] .= '--' . $boundary . $eol;
$tmp = $this->_subparts[$i]->encode();
- foreach ($tmp['headers'] as $key => $value) {
- $headers[] = $key . ': ' . $value;
+ if (PEAR::isError($tmp)) {
+ return $tmp;
}
- $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'];
+ foreach ($tmp['headers'] as $key => $value) {
+ $encoded['body'] .= $key . ': ' . $value . $eol;
+ }
+ $encoded['body'] .= $eol . $tmp['body'] . $eol;
}
- $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF .
- implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) .
- '--' . $boundary.'--' . MAIL_MIMEPART_CRLF;
+ $encoded['body'] .= '--' . $boundary . '--' . $eol;
+ } else if ($this->_body) {
+ $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding);
+ } else if ($this->_body_file) {
+ // Temporarily reset magic_quotes_runtime for file reads and writes
+ if ($magic_quote_setting = get_magic_quotes_runtime()) {
+ @ini_set('magic_quotes_runtime', 0);
+ }
+ $body = $this->_getEncodedDataFromFile($this->_body_file, $this->_encoding);
+ if ($magic_quote_setting) {
+ @ini_set('magic_quotes_runtime', $magic_quote_setting);
+ }
+
+ if (PEAR::isError($body)) {
+ return $body;
+ }
+ $encoded['body'] = $body;
} else {
- $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF;
+ $encoded['body'] = '';
}
// Add headers to $encoded
@@ -247,105 +342,873 @@
}
/**
- * &addSubPart()
+ * Encodes and saves the email into file. File must exist.
+ * Data will be appended to the file.
*
+ * @param string $filename Output file location
+ * @param string $boundary Pre-defined boundary string
+ * @param boolean $skip_head True if you don't want to save headers
+ *
+ * @return array An associative array containing message headers
+ * or PEAR error object
+ * @access public
+ * @since 1.6.0
+ */
+ function encodeToFile($filename, $boundary=null, $skip_head=false)
+ {
+ if (file_exists($filename) && !is_writable($filename)) {
+ $err = PEAR::raiseError('File is not writeable: ' . $filename);
+ return $err;
+ }
+
+ if (!($fh = fopen($filename, 'ab'))) {
+ $err = PEAR::raiseError('Unable to open file: ' . $filename);
+ return $err;
+ }
+
+ // Temporarily reset magic_quotes_runtime for file reads and writes
+ if ($magic_quote_setting = get_magic_quotes_runtime()) {
+ @ini_set('magic_quotes_runtime', 0);
+ }
+
+ $res = $this->_encodePartToFile($fh, $boundary, $skip_head);
+
+ fclose($fh);
+
+ if ($magic_quote_setting) {
+ @ini_set('magic_quotes_runtime', $magic_quote_setting);
+ }
+
+ return PEAR::isError($res) ? $res : $this->_headers;
+ }
+
+ /**
+ * Encodes given email part into file
+ *
+ * @param string $fh Output file handle
+ * @param string $boundary Pre-defined boundary string
+ * @param boolean $skip_head True if you don't want to save headers
+ *
+ * @return array True on sucess or PEAR error object
+ * @access private
+ */
+ function _encodePartToFile($fh, $boundary=null, $skip_head=false)
+ {
+ $eol = $this->_eol;
+
+ if (count($this->_subparts)) {
+ $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
+ $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
+ }
+
+ if (!$skip_head) {
+ foreach ($this->_headers as $key => $value) {
+ fwrite($fh, $key . ': ' . $value . $eol);
+ }
+ $f_eol = $eol;
+ } else {
+ $f_eol = '';
+ }
+
+ if (count($this->_subparts)) {
+ for ($i = 0; $i < count($this->_subparts); $i++) {
+ fwrite($fh, $f_eol . '--' . $boundary . $eol);
+ $res = $this->_subparts[$i]->_encodePartToFile($fh);
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+ $f_eol = $eol;
+ }
+
+ fwrite($fh, $eol . '--' . $boundary . '--' . $eol);
+
+ } else if ($this->_body) {
+ fwrite($fh, $f_eol . $this->_getEncodedData($this->_body, $this->_encoding));
+ } else if ($this->_body_file) {
+ fwrite($fh, $f_eol);
+ $res = $this->_getEncodedDataFromFile(
+ $this->_body_file, $this->_encoding, $fh
+ );
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Adds a subpart to current mime part and returns
* a reference to it
*
- * @param $body The body of the subpart, if any.
- * @param $params The parameters for the subpart, same
- * as the $params argument for constructor.
- * @return A reference to the part you just added. It is
- * crucial if using multipart/* in your subparts that
- * you use =& in your script when calling this function,
- * otherwise you will not be able to add further subparts.
+ * @param string $body The body of the subpart, if any.
+ * @param array $params The parameters for the subpart, same
+ * as the $params argument for constructor.
+ *
+ * @return Mail_mimePart A reference to the part you just added. It is
+ * crucial if using multipart/* in your subparts that
+ * you use =& in your script when calling this function,
+ * otherwise you will not be able to add further subparts.
* @access public
*/
- function &addSubPart($body, $params)
+ function &addSubpart($body, $params)
{
$this->_subparts[] = new Mail_mimePart($body, $params);
return $this->_subparts[count($this->_subparts) - 1];
}
/**
- * _getEncodedData()
- *
* Returns encoded data based upon encoding passed to it
*
- * @param $data The data to encode.
- * @param $encoding The encoding type to use, 7bit, base64,
- * or quoted-printable.
+ * @param string $data The data to encode.
+ * @param string $encoding The encoding type to use, 7bit, base64,
+ * or quoted-printable.
+ *
+ * @return string
* @access private
*/
function _getEncodedData($data, $encoding)
{
switch ($encoding) {
- case '8bit':
- case '7bit':
- return $data;
- break;
+ case 'quoted-printable':
+ return $this->_quotedPrintableEncode($data);
+ break;
- case 'quoted-printable':
- return $this->_quotedPrintableEncode($data);
- break;
+ case 'base64':
+ return rtrim(chunk_split(base64_encode($data), 76, $this->_eol));
+ break;
- case 'base64':
- return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF));
- break;
-
- default:
- return $data;
+ case '8bit':
+ case '7bit':
+ default:
+ return $data;
}
}
/**
- * quoteadPrintableEncode()
+ * Returns encoded data based upon encoding passed to it
*
+ * @param string $filename Data file location
+ * @param string $encoding The encoding type to use, 7bit, base64,
+ * or quoted-printable.
+ * @param resource $fh Output file handle. If set, data will be
+ * stored into it instead of returning it
+ *
+ * @return string Encoded data or PEAR error object
+ * @access private
+ */
+ function _getEncodedDataFromFile($filename, $encoding, $fh=null)
+ {
+ if (!is_readable($filename)) {
+ $err = PEAR::raiseError('Unable to read file: ' . $filename);
+ return $err;
+ }
+
+ if (!($fd = fopen($filename, 'rb'))) {
+ $err = PEAR::raiseError('Could not open file: ' . $filename);
+ return $err;
+ }
+
+ $data = '';
+
+ switch ($encoding) {
+ case 'quoted-printable':
+ while (!feof($fd)) {
+ $buffer = $this->_quotedPrintableEncode(fgets($fd));
+ if ($fh) {
+ fwrite($fh, $buffer);
+ } else {
+ $data .= $buffer;
+ }
+ }
+ break;
+
+ case 'base64':
+ while (!feof($fd)) {
+ // Should read in a multiple of 57 bytes so that
+ // the output is 76 bytes per line. Don't use big chunks
+ // because base64 encoding is memory expensive
+ $buffer = fread($fd, 57 * 9198); // ca. 0.5 MB
+ $buffer = base64_encode($buffer);
+ $buffer = chunk_split($buffer, 76, $this->_eol);
+ if (feof($fd)) {
+ $buffer = rtrim($buffer);
+ }
+
+ if ($fh) {
+ fwrite($fh, $buffer);
+ } else {
+ $data .= $buffer;
+ }
+ }
+ break;
+
+ case '8bit':
+ case '7bit':
+ default:
+ while (!feof($fd)) {
+ $buffer = fread($fd, 1048576); // 1 MB
+ if ($fh) {
+ fwrite($fh, $buffer);
+ } else {
+ $data .= $buffer;
+ }
+ }
+ }
+
+ fclose($fd);
+
+ if (!$fh) {
+ return $data;
+ }
+ }
+
+ /**
* Encodes data to quoted-printable standard.
*
- * @param $input The data to encode
- * @param $line_max Optional max line length. Should
- * not be more than 76 chars
+ * @param string $input The data to encode
+ * @param int $line_max Optional max line length. Should
+ * not be more than 76 chars
+ *
+ * @return string Encoded data
*
* @access private
*/
function _quotedPrintableEncode($input , $line_max = 76)
{
+ $eol = $this->_eol;
+ /*
+ // imap_8bit() is extremely fast, but doesn't handle properly some characters
+ if (function_exists('imap_8bit') && $line_max == 76) {
+ $input = preg_replace('/\r?\n/', "\r\n", $input);
+ $input = imap_8bit($input);
+ if ($eol != "\r\n") {
+ $input = str_replace("\r\n", $eol, $input);
+ }
+ return $input;
+ }
+ */
$lines = preg_split("/\r?\n/", $input);
- $eol = MAIL_MIMEPART_CRLF;
$escape = '=';
$output = '';
- while(list(, $line) = each($lines)){
-
- $linlen = strlen($line);
+ while (list($idx, $line) = each($lines)) {
$newline = '';
+ $i = 0;
- for ($i = 0; $i < $linlen; $i++) {
- $char = substr($line, $i, 1);
+ while (isset($line[$i])) {
+ $char = $line[$i];
$dec = ord($char);
+ $i++;
- if (($dec == 32) AND ($i == ($linlen - 1))){ // convert space at eol only
+ if (($dec == 32) && (!isset($line[$i]))) {
+ // convert space at eol only
$char = '=20';
-
- } elseif(($dec == 9) AND ($i == ($linlen - 1))) { // convert tab at eol only
- $char = '=09';
- } elseif($dec == 9) {
- ; // Do nothing if a tab.
- } elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) {
- $char = $escape . strtoupper(sprintf('%02s', dechex($dec)));
+ } elseif ($dec == 9 && isset($line[$i])) {
+ ; // Do nothing if a TAB is not on eol
+ } elseif (($dec == 61) || ($dec < 32) || ($dec > 126)) {
+ $char = $escape . sprintf('%02X', $dec);
+ } elseif (($dec == 46) && (($newline == '')
+ || ((strlen($newline) + strlen("=2E")) >= $line_max))
+ ) {
+ // Bug #9722: convert full-stop at bol,
+ // some Windows servers need this, won't break anything (cipri)
+ // Bug #11731: full-stop at bol also needs to be encoded
+ // if this line would push us over the line_max limit.
+ $char = '=2E';
}
- if ((strlen($newline) + strlen($char)) >= $line_max) { // MAIL_MIMEPART_CRLF is not counted
- $output .= $newline . $escape . $eol; // soft line break; " =\r\n" is okay
+ // Note, when changing this line, also change the ($dec == 46)
+ // check line, as it mimics this line due to Bug #11731
+ // EOL is not counted
+ if ((strlen($newline) + strlen($char)) >= $line_max) {
+ // soft line break; " =\r\n" is okay
+ $output .= $newline . $escape . $eol;
$newline = '';
}
$newline .= $char;
} // end of for
$output .= $newline . $eol;
+ unset($lines[$idx]);
}
- $output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf
+ // Don't want last crlf
+ $output = substr($output, 0, -1 * strlen($eol));
return $output;
}
+
+ /**
+ * Encodes the paramater of a header.
+ *
+ * @param string $name The name of the header-parameter
+ * @param string $value The value of the paramter
+ * @param string $charset The characterset of $value
+ * @param string $language The language used in $value
+ * @param string $encoding Parameter encoding. If not set, parameter value
+ * is encoded according to RFC2231
+ * @param int $maxLength The maximum length of a line. Defauls to 75
+ *
+ * @return string
+ *
+ * @access private
+ */
+ function _buildHeaderParam($name, $value, $charset=null, $language=null,
+ $encoding=null, $maxLength=75
+ ) {
+ // RFC 2045:
+ // value needs encoding if contains non-ASCII chars or is longer than 78 chars
+ if (!preg_match('#[^\x20-\x7E]#', $value)) {
+ $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
+ . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
+ if (!preg_match($token_regexp, $value)) {
+ // token
+ if (strlen($name) + strlen($value) + 3 <= $maxLength) {
+ return " {$name}={$value}";
+ }
+ } else {
+ // quoted-string
+ $quoted = addcslashes($value, '\\"');
+ if (strlen($name) + strlen($quoted) + 5 <= $maxLength) {
+ return " {$name}=\"{$quoted}\"";
+ }
+ }
+ }
+
+ // RFC2047: use quoted-printable/base64 encoding
+ if ($encoding == 'quoted-printable' || $encoding == 'base64') {
+ return $this->_buildRFC2047Param($name, $value, $charset, $encoding);
+ }
+
+ // RFC2231:
+ $encValue = preg_replace_callback(
+ '/([^\x21,\x23,\x24,\x26,\x2B,\x2D,\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])/',
+ array($this, '_encodeReplaceCallback'), $value
+ );
+ $value = "$charset'$language'$encValue";
+
+ $header = " {$name}*={$value}";
+ if (strlen($header) <= $maxLength) {
+ return $header;
+ }
+
+ $preLength = strlen(" {$name}*0*=");
+ $maxLength = max(16, $maxLength - $preLength - 3);
+ $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|";
+
+ $headers = array();
+ $headCount = 0;
+ while ($value) {
+ $matches = array();
+ $found = preg_match($maxLengthReg, $value, $matches);
+ if ($found) {
+ $headers[] = " {$name}*{$headCount}*={$matches[0]}";
+ $value = substr($value, strlen($matches[0]));
+ } else {
+ $headers[] = " {$name}*{$headCount}*={$value}";
+ $value = '';
+ }
+ $headCount++;
+ }
+
+ $headers = implode(';' . $this->_eol, $headers);
+ return $headers;
+ }
+
+ /**
+ * Encodes header parameter as per RFC2047 if needed
+ *
+ * @param string $name The parameter name
+ * @param string $value The parameter value
+ * @param string $charset The parameter charset
+ * @param string $encoding Encoding type (quoted-printable or base64)
+ * @param int $maxLength Encoded parameter max length. Default: 76
+ *
+ * @return string Parameter line
+ * @access private
+ */
+ function _buildRFC2047Param($name, $value, $charset,
+ $encoding='quoted-printable', $maxLength=76
+ ) {
+ // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in
+ // parameter of a MIME Content-Type or Content-Disposition field",
+ // but... it's supported by many clients/servers
+ $quoted = '';
+
+ if ($encoding == 'base64') {
+ $value = base64_encode($value);
+ $prefix = '=?' . $charset . '?B?';
+ $suffix = '?=';
+
+ // 2 x SPACE, 2 x '"', '=', ';'
+ $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
+ $len = $add_len + strlen($value);
+
+ while ($len > $maxLength) {
+ // We can cut base64-encoded string every 4 characters
+ $real_len = floor(($maxLength - $add_len) / 4) * 4;
+ $_quote = substr($value, 0, $real_len);
+ $value = substr($value, $real_len);
+
+ $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
+ $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
+ $len = strlen($value) + $add_len;
+ }
+ $quoted .= $prefix . $value . $suffix;
+
+ } else {
+ // quoted-printable
+ $value = $this->encodeQP($value);
+ $prefix = '=?' . $charset . '?Q?';
+ $suffix = '?=';
+
+ // 2 x SPACE, 2 x '"', '=', ';'
+ $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
+ $len = $add_len + strlen($value);
+
+ while ($len > $maxLength) {
+ $length = $maxLength - $add_len;
+ // don't break any encoded letters
+ if (preg_match("/^(.{0,$length}[^\=][^\=])/", $value, $matches)) {
+ $_quote = $matches[1];
+ }
+
+ $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
+ $value = substr($value, strlen($_quote));
+ $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
+ $len = strlen($value) + $add_len;
+ }
+
+ $quoted .= $prefix . $value . $suffix;
+ }
+
+ return " {$name}=\"{$quoted}\"";
+ }
+
+ /**
+ * Encodes a header as per RFC2047
+ *
+ * @param string $name The header name
+ * @param string $value The header data to encode
+ * @param string $charset Character set name
+ * @param string $encoding Encoding name (base64 or quoted-printable)
+ * @param string $eol End-of-line sequence. Default: "\r\n"
+ *
+ * @return string Encoded header data (without a name)
+ * @access public
+ * @since 1.6.1
+ */
+ function encodeHeader($name, $value, $charset='ISO-8859-1',
+ $encoding='quoted-printable', $eol="\r\n"
+ ) {
+ // Structured headers
+ $comma_headers = array(
+ 'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
+ 'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
+ 'resent-sender', 'resent-reply-to',
+ 'return-receipt-to', 'disposition-notification-to',
+ );
+ $other_headers = array(
+ 'references', 'in-reply-to', 'message-id', 'resent-message-id',
+ );
+
+ $name = strtolower($name);
+
+ if (in_array($name, $comma_headers)) {
+ $separator = ',';
+ } else if (in_array($name, $other_headers)) {
+ $separator = ' ';
+ }
+
+ if (!$charset) {
+ $charset = 'ISO-8859-1';
+ }
+
+ // Structured header (make sure addr-spec inside is not encoded)
+ if (!empty($separator)) {
+ $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
+ $value = '';
+
+ foreach ($parts as $part) {
+ $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part);
+ $part = trim($part);
+
+ if (!$part) {
+ continue;
+ }
+ if ($value) {
+ $value .= $separator==',' ? $separator.' ' : ' ';
+ } else {
+ $value = $name . ': ';
+ }
+
+ // let's find phrase (name) and/or addr-spec
+ if (preg_match('/^<\S+@\S+>$/', $part)) {
+ $value .= $part;
+ } else if (preg_match('/^\S+@\S+$/', $part)) {
+ // address without brackets and without name
+ $value .= $part;
+ } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
+ // address with name (handle name)
+ $address = $matches[0];
+ $word = str_replace($address, '', $part);
+ $word = trim($word);
+ // check if phrase requires quoting
+ if ($word) {
+ // non-ASCII: require encoding
+ if (preg_match('#([\x80-\xFF]){1}#', $word)) {
+ if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
+ // de-quote quoted-string, encoding changes
+ // string to atom
+ $search = array("\\\"", "\\\\");
+ $replace = array("\"", "\\");
+ $word = str_replace($search, $replace, $word);
+ $word = substr($word, 1, -1);
+ }
+ // find length of last line
+ if (($pos = strrpos($value, $eol)) !== false) {
+ $last_len = strlen($value) - $pos;
+ } else {
+ $last_len = strlen($value);
+ }
+ $word = Mail_mimePart::encodeHeaderValue(
+ $word, $charset, $encoding, $last_len, $eol
+ );
+ } else if (($word[0] != '"' || $word[strlen($word)-1] != '"')
+ && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word)
+ ) {
+ // ASCII: quote string if needed
+ $word = '"'.addcslashes($word, '\\"').'"';
+ }
+ }
+ $value .= $word.' '.$address;
+ } else {
+ // addr-spec not found, don't encode (?)
+ $value .= $part;
+ }
+
+ // RFC2822 recommends 78 characters limit, use 76 from RFC2047
+ $value = wordwrap($value, 76, $eol . ' ');
+ }
+
+ // remove header name prefix (there could be EOL too)
+ $value = preg_replace(
+ '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value
+ );
+
+ } else {
+ // Unstructured header
+ // non-ASCII: require encoding
+ if (preg_match('#([\x80-\xFF]){1}#', $value)) {
+ if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
+ // de-quote quoted-string, encoding changes
+ // string to atom
+ $search = array("\\\"", "\\\\");
+ $replace = array("\"", "\\");
+ $value = str_replace($search, $replace, $value);
+ $value = substr($value, 1, -1);
+ }
+ $value = Mail_mimePart::encodeHeaderValue(
+ $value, $charset, $encoding, strlen($name) + 2, $eol
+ );
+ } else if (strlen($name.': '.$value) > 78) {
+ // ASCII: check if header line isn't too long and use folding
+ $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value);
+ $tmp = wordwrap($name.': '.$value, 78, $eol . ' ');
+ $value = preg_replace('/^'.$name.':\s*/', '', $tmp);
+ // hard limit 998 (RFC2822)
+ $value = wordwrap($value, 998, $eol . ' ', true);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Explode quoted string
+ *
+ * @param string $delimiter Delimiter expression string for preg_match()
+ * @param string $string Input string
+ *
+ * @return array String tokens array
+ * @access private
+ */
+ function _explodeQuotedString($delimiter, $string)
+ {
+ $result = array();
+ $strlen = strlen($string);
+
+ for ($q=$p=$i=0; $i < $strlen; $i++) {
+ if ($string[$i] == "\""
+ && (empty($string[$i-1]) || $string[$i-1] != "\\")
+ ) {
+ $q = $q ? false : true;
+ } else if (!$q && preg_match("/$delimiter/", $string[$i])) {
+ $result[] = substr($string, $p, $i - $p);
+ $p = $i + 1;
+ }
+ }
+
+ $result[] = substr($string, $p);
+ return $result;
+ }
+
+ /**
+ * Encodes a header value as per RFC2047
+ *
+ * @param string $value The header data to encode
+ * @param string $charset Character set name
+ * @param string $encoding Encoding name (base64 or quoted-printable)
+ * @param int $prefix_len Prefix length. Default: 0
+ * @param string $eol End-of-line sequence. Default: "\r\n"
+ *
+ * @return string Encoded header data
+ * @access public
+ * @since 1.6.1
+ */
+ function encodeHeaderValue($value, $charset, $encoding, $prefix_len=0, $eol="\r\n")
+ {
+ // #17311: Use multibyte aware method (requires mbstring extension)
+ if ($result = Mail_mimePart::encodeMB($value, $charset, $encoding, $prefix_len, $eol)) {
+ return $result;
+ }
+
+ // Generate the header using the specified params and dynamicly
+ // determine the maximum length of such strings.
+ // 75 is the value specified in the RFC.
+ $encoding = $encoding == 'base64' ? 'B' : 'Q';
+ $prefix = '=?' . $charset . '?' . $encoding .'?';
+ $suffix = '?=';
+ $maxLength = 75 - strlen($prefix . $suffix);
+ $maxLength1stLine = $maxLength - $prefix_len;
+
+ if ($encoding == 'B') {
+ // Base64 encode the entire string
+ $value = base64_encode($value);
+
+ // We can cut base64 every 4 characters, so the real max
+ // we can get must be rounded down.
+ $maxLength = $maxLength - ($maxLength % 4);
+ $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4);
+
+ $cutpoint = $maxLength1stLine;
+ $output = '';
+
+ while ($value) {
+ // Split translated string at every $maxLength
+ $part = substr($value, 0, $cutpoint);
+ $value = substr($value, $cutpoint);
+ $cutpoint = $maxLength;
+ // RFC 2047 specifies that any split header should
+ // be seperated by a CRLF SPACE.
+ if ($output) {
+ $output .= $eol . ' ';
+ }
+ $output .= $prefix . $part . $suffix;
+ }
+ $value = $output;
+ } else {
+ // quoted-printable encoding has been selected
+ $value = Mail_mimePart::encodeQP($value);
+
+ // This regexp will break QP-encoded text at every $maxLength
+ // but will not break any encoded letters.
+ $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
+ $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";
+
+ if (strlen($value) > $maxLength1stLine) {
+ // Begin with the regexp for the first line.
+ $reg = $reg1st;
+ $output = '';
+ while ($value) {
+ // Split translated string at every $maxLength
+ // But make sure not to break any translated chars.
+ $found = preg_match($reg, $value, $matches);
+
+ // After this first line, we need to use a different
+ // regexp for the first line.
+ $reg = $reg2nd;
+
+ // Save the found part and encapsulate it in the
+ // prefix & suffix. Then remove the part from the
+ // $value_out variable.
+ if ($found) {
+ $part = $matches[0];
+ $len = strlen($matches[0]);
+ $value = substr($value, $len);
+ } else {
+ $part = $value;
+ $value = '';
+ }
+
+ // RFC 2047 specifies that any split header should
+ // be seperated by a CRLF SPACE
+ if ($output) {
+ $output .= $eol . ' ';
+ }
+ $output .= $prefix . $part . $suffix;
+ }
+ $value = $output;
+ } else {
+ $value = $prefix . $value . $suffix;
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Encodes the given string using quoted-printable
+ *
+ * @param string $str String to encode
+ *
+ * @return string Encoded string
+ * @access public
+ * @since 1.6.0
+ */
+ function encodeQP($str)
+ {
+ // Bug #17226 RFC 2047 restricts some characters
+ // if the word is inside a phrase, permitted chars are only:
+ // ASCII letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
+
+ // "=", "_", "?" must be encoded
+ $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
+ $str = preg_replace_callback(
+ $regexp, array('Mail_mimePart', '_qpReplaceCallback'), $str
+ );
+
+ return str_replace(' ', '_', $str);
+ }
+
+ /**
+ * Encodes the given string using base64 or quoted-printable.
+ * This method makes sure that encoded-word represents an integral
+ * number of characters as per RFC2047.
+ *
+ * @param string $str String to encode
+ * @param string $charset Character set name
+ * @param string $encoding Encoding name (base64 or quoted-printable)
+ * @param int $prefix_len Prefix length. Default: 0
+ * @param string $eol End-of-line sequence. Default: "\r\n"
+ *
+ * @return string Encoded string
+ * @access public
+ * @since 1.8.0
+ */
+ function encodeMB($str, $charset, $encoding, $prefix_len=0, $eol="\r\n")
+ {
+ if (!function_exists('mb_substr') || !function_exists('mb_strlen')) {
+ return;
+ }
+
+ $encoding = $encoding == 'base64' ? 'B' : 'Q';
+ // 75 is the value specified in the RFC
+ $prefix = '=?' . $charset . '?'.$encoding.'?';
+ $suffix = '?=';
+ $maxLength = 75 - strlen($prefix . $suffix);
+
+ // A multi-octet character may not be split across adjacent encoded-words
+ // So, we'll loop over each character
+ // mb_stlen() with wrong charset will generate a warning here and return null
+ $length = mb_strlen($str, $charset);
+ $result = '';
+ $line_length = $prefix_len;
+
+ if ($encoding == 'B') {
+ // base64
+ $start = 0;
+ $prev = '';
+
+ for ($i=1; $i<=$length; $i++) {
+ // See #17311
+ $chunk = mb_substr($str, $start, $i-$start, $charset);
+ $chunk = base64_encode($chunk);
+ $chunk_len = strlen($chunk);
+
+ if ($line_length + $chunk_len == $maxLength || $i == $length) {
+ if ($result) {
+ $result .= "\n";
+ }
+ $result .= $chunk;
+ $line_length = 0;
+ $start = $i;
+ } else if ($line_length + $chunk_len > $maxLength) {
+ if ($result) {
+ $result .= "\n";
+ }
+ if ($prev) {
+ $result .= $prev;
+ }
+ $line_length = 0;
+ $start = $i - 1;
+ } else {
+ $prev = $chunk;
+ }
+ }
+ } else {
+ // quoted-printable
+ // see encodeQP()
+ $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
+
+ for ($i=0; $i<=$length; $i++) {
+ $char = mb_substr($str, $i, 1, $charset);
+ // RFC recommends underline (instead of =20) in place of the space
+ // that's one of the reasons why we're not using iconv_mime_encode()
+ if ($char == ' ') {
+ $char = '_';
+ $char_len = 1;
+ } else {
+ $char = preg_replace_callback(
+ $regexp, array('Mail_mimePart', '_qpReplaceCallback'), $char
+ );
+ $char_len = strlen($char);
+ }
+
+ if ($line_length + $char_len > $maxLength) {
+ if ($result) {
+ $result .= "\n";
+ }
+ $line_length = 0;
+ }
+
+ $result .= $char;
+ $line_length += $char_len;
+ }
+ }
+
+ if ($result) {
+ $result = $prefix
+ .str_replace("\n", $suffix.$eol.' '.$prefix, $result).$suffix;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Callback function to replace extended characters (\x80-xFF) with their
+ * ASCII values (RFC2047: quoted-printable)
+ *
+ * @param array $matches Preg_replace's matches array
+ *
+ * @return string Encoded character string
+ * @access private
+ */
+ function _qpReplaceCallback($matches)
+ {
+ return sprintf('=%02X', ord($matches[1]));
+ }
+
+ /**
+ * Callback function to replace extended characters (\x80-xFF) with their
+ * ASCII values (RFC2231)
+ *
+ * @param array $matches Preg_replace's matches array
+ *
+ * @return string Encoded character string
+ * @access private
+ */
+ function _encodeReplaceCallback($matches)
+ {
+ return sprintf('%%%02X', ord($matches[1]));
+ }
+
} // End of class
-?>
--
Gitblit v1.9.1