/lib/horde/framework/Horde/Mime/Part.php
PHP | 2528 lines | 1390 code | 286 blank | 852 comment | 224 complexity | caee7b98b9a202c510bf9de7715d2f15 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (LGPL). If you
- * did not receive this file, see http://www.horde.org/licenses/lgpl21.
- *
- * @category Horde
- * @copyright 1999-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Mime
- */
- /**
- * Object-oriented representation of a MIME part (RFC 2045-2049).
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @copyright 1999-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Mime
- */
- class Horde_Mime_Part
- implements ArrayAccess, Countable, RecursiveIterator, Serializable
- {
- /* Serialized version. */
- const VERSION = 2;
- /* The character(s) used internally for EOLs. */
- const EOL = "\n";
- /* The character string designated by RFC 2045 to designate EOLs in MIME
- * messages. */
- const RFC_EOL = "\r\n";
- /* The default encoding. */
- const DEFAULT_ENCODING = 'binary';
- /* Constants indicating the valid transfer encoding allowed. */
- const ENCODE_7BIT = 1;
- const ENCODE_8BIT = 2;
- const ENCODE_BINARY = 4;
- /* MIME nesting limit. */
- const NESTING_LIMIT = 100;
- /* Status mask value: Need to reindex the current part. */
- const STATUS_REINDEX = 1;
- /* Status mask value: This is the base MIME part. */
- const STATUS_BASEPART = 2;
- /**
- * The default charset to use when parsing text parts with no charset
- * information.
- *
- * @todo Make this a non-static property or pass as parameter to static
- * methods in Horde 6.
- *
- * @var string
- */
- public static $defaultCharset = 'us-ascii';
- /**
- * The memory limit for use with the PHP temp stream.
- *
- * @var integer
- */
- public static $memoryLimit = 2097152;
- /**
- * Parent object. Value only accurate when iterating.
- *
- * @since 2.8.0
- *
- * @var Horde_Mime_Part
- */
- public $parent = null;
- /**
- * Default value for this Part's size.
- *
- * @var integer
- */
- protected $_bytes;
- /**
- * The body of the part. Always stored in binary format.
- *
- * @var resource
- */
- protected $_contents;
- /**
- * The sequence to use as EOL for this part.
- *
- * The default is currently to output the EOL sequence internally as
- * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
- * To be RFC complaint, the full <CR><LF> EOL combination should be used
- * when sending a message.
- *
- * @var string
- */
- protected $_eol = self::EOL;
- /**
- * The MIME headers for this part.
- *
- * @var Horde_Mime_Headers
- */
- protected $_headers;
- /**
- * The charset to output the headers in.
- *
- * @var string
- */
- protected $_hdrCharset = null;
- /**
- * Metadata.
- *
- * @var array
- */
- protected $_metadata = array();
- /**
- * The MIME ID of this part.
- *
- * @var string
- */
- protected $_mimeid = null;
- /**
- * The subparts of this part.
- *
- * @var array
- */
- protected $_parts = array();
- /**
- * Status mask for this part.
- *
- * @var integer
- */
- protected $_status = 0;
- /**
- * Temporary array.
- *
- * @var array
- */
- protected $_temp = array();
- /**
- * The desired transfer encoding of this part.
- *
- * @var string
- */
- protected $_transferEncoding = self::DEFAULT_ENCODING;
- /**
- * Flag to detect if a message failed to send at least once.
- *
- * @var boolean
- */
- protected $_failed = false;
- /**
- * Constructor.
- */
- public function __construct()
- {
- $this->_headers = new Horde_Mime_Headers();
- /* Mandatory MIME headers. */
- $this->_headers->addHeaderOb(
- new Horde_Mime_Headers_ContentParam_ContentDisposition(null, '')
- );
- $ct = Horde_Mime_Headers_ContentParam_ContentType::create();
- $ct['charset'] = self::$defaultCharset;
- $this->_headers->addHeaderOb($ct);
- }
- /**
- * Function to run on clone.
- */
- public function __clone()
- {
- foreach ($this->_parts as $k => $v) {
- $this->_parts[$k] = clone $v;
- }
- $this->_headers = clone $this->_headers;
- if (!empty($this->_contents)) {
- $this->_contents = $this->_writeStream($this->_contents);
- }
- }
- /**
- * Set the content-disposition of this part.
- *
- * @param string $disposition The content-disposition to set ('inline',
- * 'attachment', or an empty value).
- */
- public function setDisposition($disposition = null)
- {
- $this->_headers['content-disposition']->setContentParamValue(
- strval($disposition)
- );
- }
- /**
- * Get the content-disposition of this part.
- *
- * @return string The part's content-disposition. An empty string means
- * no desired disposition has been set for this part.
- */
- public function getDisposition()
- {
- return $this->_headers['content-disposition']->value;
- }
- /**
- * Add a disposition parameter to this part.
- *
- * @param string $label The disposition parameter label.
- * @param string $data The disposition parameter data. If null, removes
- * the parameter (@since 2.8.0).
- */
- public function setDispositionParameter($label, $data)
- {
- $cd = $this->_headers['content-disposition'];
- if (is_null($data)) {
- unset($cd[$label]);
- } elseif (strlen($data)) {
- $cd[$label] = $data;
- if (strcasecmp($label, 'size') === 0) {
- // RFC 2183 [2.7] - size parameter
- $this->_bytes = $cd[$label];
- } elseif ((strcasecmp($label, 'filename') === 0) &&
- !strlen($cd->value)) {
- /* Set part to attachment if not already explicitly set to
- * 'inline'. */
- $cd->setContentParamValue('attachment');
- }
- }
- }
- /**
- * Get a disposition parameter from this part.
- *
- * @param string $label The disposition parameter label.
- *
- * @return string The data requested.
- * Returns null if $label is not set.
- */
- public function getDispositionParameter($label)
- {
- $cd = $this->_headers['content-disposition'];
- return $cd[$label];
- }
- /**
- * Get all parameters from the Content-Disposition header.
- *
- * @return array An array of all the parameters
- * Returns the empty array if no parameters set.
- */
- public function getAllDispositionParameters()
- {
- return $this->_headers['content-disposition']->params;
- }
- /**
- * Set the name of this part.
- *
- * @param string $name The name to set.
- */
- public function setName($name)
- {
- $this->setDispositionParameter('filename', $name);
- $this->setContentTypeParameter('name', $name);
- }
- /**
- * Get the name of this part.
- *
- * @param boolean $default If the name parameter doesn't exist, should we
- * use the default name from the description
- * parameter?
- *
- * @return string The name of the part.
- */
- public function getName($default = false)
- {
- if (!($name = $this->getDispositionParameter('filename')) &&
- !($name = $this->getContentTypeParameter('name')) &&
- $default) {
- $name = preg_replace('|\W|', '_', $this->getDescription(false));
- }
- return $name;
- }
- /**
- * Set the body contents of this part.
- *
- * @param mixed $contents The part body. Either a string or a stream
- * resource, or an array containing both.
- * @param array $options Additional options:
- * - encoding: (string) The encoding of $contents.
- * DEFAULT: Current transfer encoding value.
- * - usestream: (boolean) If $contents is a stream, should we directly
- * use that stream?
- * DEFAULT: $contents copied to a new stream.
- */
- public function setContents($contents, $options = array())
- {
- if (is_resource($contents) && ($contents === $this->_contents)) {
- return;
- }
- if (empty($options['encoding'])) {
- $options['encoding'] = $this->_transferEncoding;
- }
- $fp = (empty($options['usestream']) || !is_resource($contents))
- ? $this->_writeStream($contents)
- : $contents;
- /* Properly close the existing stream. */
- $this->clearContents();
- $this->setTransferEncoding($options['encoding']);
- $this->_contents = $this->_transferDecode($fp, $options['encoding']);
- }
- /**
- * Add to the body contents of this part.
- *
- * @param mixed $contents The part body. Either a string or a stream
- * resource, or an array containing both.
- * - encoding: (string) The encoding of $contents.
- * DEFAULT: Current transfer encoding value.
- * - usestream: (boolean) If $contents is a stream, should we directly
- * use that stream?
- * DEFAULT: $contents copied to a new stream.
- */
- public function appendContents($contents, $options = array())
- {
- if (empty($this->_contents)) {
- $this->setContents($contents, $options);
- } else {
- $fp = (empty($options['usestream']) || !is_resource($contents))
- ? $this->_writeStream($contents)
- : $contents;
- $this->_writeStream((empty($options['encoding']) || ($options['encoding'] == $this->_transferEncoding)) ? $fp : $this->_transferDecode($fp, $options['encoding']), array('fp' => $this->_contents));
- unset($this->_temp['sendTransferEncoding']);
- }
- }
- /**
- * Clears the body contents of this part.
- */
- public function clearContents()
- {
- if (!empty($this->_contents)) {
- fclose($this->_contents);
- $this->_contents = null;
- unset($this->_temp['sendTransferEncoding']);
- }
- }
- /**
- * Return the body of the part.
- *
- * @param array $options Additional options:
- * - canonical: (boolean) Returns the contents in strict RFC 822 &
- * 2045 output - namely, all newlines end with the
- * canonical <CR><LF> sequence.
- * DEFAULT: No
- * - stream: (boolean) Return the body as a stream resource.
- * DEFAULT: No
- *
- * @return mixed The body text (string) of the part, null if there is no
- * contents, and a stream resource if 'stream' is true.
- */
- public function getContents($options = array())
- {
- return empty($options['canonical'])
- ? (empty($options['stream']) ? $this->_readStream($this->_contents) : $this->_contents)
- : $this->replaceEOL($this->_contents, self::RFC_EOL, !empty($options['stream']));
- }
- /**
- * Decodes the contents of the part to binary encoding.
- *
- * @param resource $fp A stream containing the data to decode.
- * @param string $encoding The original file encoding.
- *
- * @return resource A new file resource with the decoded data.
- */
- protected function _transferDecode($fp, $encoding)
- {
- /* If the contents are empty, return now. */
- fseek($fp, 0, SEEK_END);
- if (ftell($fp)) {
- switch ($encoding) {
- case 'base64':
- try {
- return $this->_writeStream($fp, array(
- 'error' => true,
- 'filter' => array(
- 'convert.base64-decode' => array()
- )
- ));
- } catch (ErrorException $e) {}
- rewind($fp);
- return $this->_writeStream(base64_decode(stream_get_contents($fp)));
- case 'quoted-printable':
- try {
- return $this->_writeStream($fp, array(
- 'error' => true,
- 'filter' => array(
- 'convert.quoted-printable-decode' => array()
- )
- ));
- } catch (ErrorException $e) {}
- // Workaround for Horde Bug #8747
- rewind($fp);
- return $this->_writeStream(quoted_printable_decode(stream_get_contents($fp)));
- case 'uuencode':
- case 'x-uuencode':
- case 'x-uue':
- /* Support for uuencoded encoding - although not required by
- * RFCs, some mailers may still encode this way. */
- $res = Horde_Mime::uudecode($this->_readStream($fp));
- return $this->_writeStream($res[0]['data']);
- }
- }
- return $fp;
- }
- /**
- * Encodes the contents of the part as necessary for transport.
- *
- * @param resource $fp A stream containing the data to encode.
- * @param string $encoding The encoding to use.
- *
- * @return resource A new file resource with the encoded data.
- */
- protected function _transferEncode($fp, $encoding)
- {
- $this->_temp['transferEncodeClose'] = true;
- switch ($encoding) {
- case 'base64':
- /* Base64 Encoding: See RFC 2045, section 6.8 */
- return $this->_writeStream($fp, array(
- 'filter' => array(
- 'convert.base64-encode' => array(
- 'line-break-chars' => $this->getEOL(),
- 'line-length' => 76
- )
- )
- ));
- case 'quoted-printable':
- // PHP Bug 65776 - Must normalize the EOL characters.
- stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
- $stream = new Horde_Stream_Existing(array(
- 'stream' => $fp
- ));
- $stream->stream = $this->_writeStream($stream->stream, array(
- 'filter' => array(
- 'horde_eol' => array('eol' => $stream->getEOL()
- )
- )));
- /* Quoted-Printable Encoding: See RFC 2045, section 6.7 */
- return $this->_writeStream($fp, array(
- 'filter' => array(
- 'convert.quoted-printable-encode' => array_filter(array(
- 'line-break-chars' => $stream->getEOL(),
- 'line-length' => 76
- ))
- )
- ));
- default:
- $this->_temp['transferEncodeClose'] = false;
- return $fp;
- }
- }
- /**
- * Set the MIME type of this part.
- *
- * @param string $type The MIME type to set (ex.: text/plain).
- */
- public function setType($type)
- {
- /* RFC 2045: Any entity with unrecognized encoding must be treated
- * as if it has a Content-Type of "application/octet-stream"
- * regardless of what the Content-Type field actually says. */
- if (!is_null($this->_transferEncoding)) {
- $this->_headers['content-type']->setContentParamValue($type);
- }
- }
- /**
- * Get the full MIME Content-Type of this part.
- *
- * @param boolean $charset Append character set information to the end
- * of the content type if this is a text/* part?
- *`
- * @return string The MIME type of this part.
- */
- public function getType($charset = false)
- {
- $ct = $this->_headers['content-type'];
- return $charset
- ? $ct->type_charset
- : $ct->value;
- }
- /**
- * If the subtype of a MIME part is unrecognized by an application, the
- * default type should be used instead (See RFC 2046). This method
- * returns the default subtype for a particular primary MIME type.
- *
- * @return string The default MIME type of this part (ex.: text/plain).
- */
- public function getDefaultType()
- {
- switch ($this->getPrimaryType()) {
- case 'text':
- /* RFC 2046 (4.1.4): text parts default to text/plain. */
- return 'text/plain';
- case 'multipart':
- /* RFC 2046 (5.1.3): multipart parts default to multipart/mixed. */
- return 'multipart/mixed';
- default:
- /* RFC 2046 (4.2, 4.3, 4.4, 4.5.3, 5.2.4): all others default to
- application/octet-stream. */
- return 'application/octet-stream';
- }
- }
- /**
- * Get the primary type of this part.
- *
- * @return string The primary MIME type of this part.
- */
- public function getPrimaryType()
- {
- return $this->_headers['content-type']->ptype;
- }
- /**
- * Get the subtype of this part.
- *
- * @return string The MIME subtype of this part.
- */
- public function getSubType()
- {
- return $this->_headers['content-type']->stype;
- }
- /**
- * Set the character set of this part.
- *
- * @param string $charset The character set of this part.
- */
- public function setCharset($charset)
- {
- $this->setContentTypeParameter('charset', $charset);
- }
- /**
- * Get the character set to use for this part.
- *
- * @return string The character set of this part (lowercase). Returns
- * null if there is no character set.
- */
- public function getCharset()
- {
- return $this->getContentTypeParameter('charset')
- ?: (($this->getPrimaryType() === 'text') ? 'us-ascii' : null);
- }
- /**
- * Set the character set to use when outputting MIME headers.
- *
- * @param string $charset The character set.
- */
- public function setHeaderCharset($charset)
- {
- $this->_hdrCharset = $charset;
- }
- /**
- * Get the character set to use when outputting MIME headers.
- *
- * @return string The character set. If no preferred character set has
- * been set, returns null.
- */
- public function getHeaderCharset()
- {
- return is_null($this->_hdrCharset)
- ? $this->getCharset()
- : $this->_hdrCharset;
- }
- /**
- * Set the language(s) of this part.
- *
- * @param mixed $lang A language string, or an array of language
- * strings.
- */
- public function setLanguage($lang)
- {
- $this->_headers->addHeaderOb(
- new Horde_Mime_Headers_ContentLanguage('', $lang)
- );
- }
- /**
- * Get the language(s) of this part.
- *
- * @param array The list of languages.
- */
- public function getLanguage()
- {
- return $this->_headers['content-language']->langs;
- }
- /**
- * Set the content duration of the data contained in this part (see RFC
- * 3803).
- *
- * @param integer $duration The duration of the data, in seconds. If
- * null, clears the duration information.
- */
- public function setDuration($duration)
- {
- if (is_null($duration)) {
- unset($this->_headers['content-duration']);
- } else {
- if (!($hdr = $this->_headers['content-duration'])) {
- $hdr = new Horde_Mime_Headers_Element_Single(
- 'Content-Duration',
- ''
- );
- $this->_headers->addHeaderOb($hdr);
- }
- $hdr->setValue($duration);
- }
- }
- /**
- * Get the content duration of the data contained in this part (see RFC
- * 3803).
- *
- * @return integer The duration of the data, in seconds. Returns null if
- * there is no duration information.
- */
- public function getDuration()
- {
- return ($hdr = $this->_headers['content-duration'])
- ? intval($hdr->value)
- : null;
- }
- /**
- * Set the description of this part.
- *
- * @param string $description The description of this part. If null,
- * deletes the description (@since 2.8.0).
- */
- public function setDescription($description)
- {
- if (is_null($description)) {
- unset($this->_headers['content-description']);
- } else {
- if (!($hdr = $this->_headers['content-description'])) {
- $hdr = new Horde_Mime_Headers_ContentDescription(null, '');
- $this->_headers->addHeaderOb($hdr);
- }
- $hdr->setValue($description);
- }
- }
- /**
- * Get the description of this part.
- *
- * @param boolean $default If the description parameter doesn't exist,
- * should we use the name of the part?
- *
- * @return string The description of this part.
- */
- public function getDescription($default = false)
- {
- if (($ob = $this->_headers['content-description']) &&
- strlen($ob->value)) {
- return $ob->value;
- }
- return $default
- ? $this->getName()
- : '';
- }
- /**
- * Set the transfer encoding to use for this part.
- *
- * Only needed in the following circumstances:
- * 1.) Indicate what the transfer encoding is if the data has not yet been
- * set in the object (can only be set if there presently are not
- * any contents).
- * 2.) Force the encoding to a certain type on a toString() call (if
- * 'send' is true).
- *
- * @param string $encoding The transfer encoding to use.
- * @param array $options Additional options:
- * - send: (boolean) If true, use $encoding as the sending encoding.
- * DEFAULT: $encoding is used to change the base encoding.
- */
- public function setTransferEncoding($encoding, $options = array())
- {
- if (empty($encoding) ||
- (empty($options['send']) && !empty($this->_contents))) {
- return;
- }
- switch ($encoding = Horde_String::lower($encoding)) {
- case '7bit':
- case '8bit':
- case 'base64':
- case 'binary':
- case 'quoted-printable':
- // Non-RFC types, but old mailers may still use
- case 'uuencode':
- case 'x-uuencode':
- case 'x-uue':
- if (empty($options['send'])) {
- $this->_transferEncoding = $encoding;
- } else {
- $this->_temp['sendEncoding'] = $encoding;
- }
- break;
- default:
- if (empty($options['send'])) {
- /* RFC 2045: Any entity with unrecognized encoding must be
- * treated as if it has a Content-Type of
- * "application/octet-stream" regardless of what the
- * Content-Type field actually says. */
- $this->setType('application/octet-stream');
- $this->_transferEncoding = null;
- }
- break;
- }
- }
- /**
- * Get a list of all MIME subparts.
- *
- * @return array An array of the Horde_Mime_Part subparts.
- */
- public function getParts()
- {
- return $this->_parts;
- }
- /**
- * Add/remove a content type parameter to this part.
- *
- * @param string $label The content-type parameter label.
- * @param string $data The content-type parameter data. If null, removes
- * the parameter (@since 2.8.0).
- */
- public function setContentTypeParameter($label, $data)
- {
- $ct = $this->_headers['content-type'];
- if (is_null($data)) {
- unset($ct[$label]);
- } elseif (strlen($data)) {
- $ct[$label] = $data;
- }
- }
- /**
- * Get a content type parameter from this part.
- *
- * @param string $label The content type parameter label.
- *
- * @return string The data requested.
- * Returns null if $label is not set.
- */
- public function getContentTypeParameter($label)
- {
- $ct = $this->_headers['content-type'];
- return $ct[$label];
- }
- /**
- * Get all parameters from the Content-Type header.
- *
- * @return array An array of all the parameters
- * Returns the empty array if no parameters set.
- */
- public function getAllContentTypeParameters()
- {
- return $this->_headers['content-type']->params;
- }
- /**
- * Sets a new string to use for EOLs.
- *
- * @param string $eol The string to use for EOLs.
- */
- public function setEOL($eol)
- {
- $this->_eol = $eol;
- }
- /**
- * Get the string to use for EOLs.
- *
- * @return string The string to use for EOLs.
- */
- public function getEOL()
- {
- return $this->_eol;
- }
- /**
- * Returns a Horde_Mime_Header object containing all MIME headers needed
- * for the part.
- *
- * @param array $options Additional options:
- * - encode: (integer) A mask of allowable encodings.
- * DEFAULT: Auto-determined
- * - headers: (Horde_Mime_Headers) The object to add the MIME headers
- * to.
- * DEFAULT: Add headers to a new object
- *
- * @return Horde_Mime_Headers A Horde_Mime_Headers object.
- */
- public function addMimeHeaders($options = array())
- {
- if (empty($options['headers'])) {
- $headers = new Horde_Mime_Headers();
- } else {
- $headers = $options['headers'];
- $headers->removeHeader('Content-Disposition');
- $headers->removeHeader('Content-Transfer-Encoding');
- }
- /* Add the mandatory Content-Type header. */
- $ct = $this->_headers['content-type'];
- $headers->addHeaderOb($ct);
- /* Add the language(s), if set. (RFC 3282 [2]) */
- if ($hdr = $this->_headers['content-language']) {
- $headers->addHeaderOb($hdr);
- }
- /* Get the description, if any. */
- if ($hdr = $this->_headers['content-description']) {
- $headers->addHeaderOb($hdr);
- }
- /* Set the duration, if it exists. (RFC 3803) */
- if ($hdr = $this->_headers['content-duration']) {
- $headers->addHeaderOb($hdr);
- }
- /* Per RFC 2046[4], this MUST appear in the base message headers. */
- if ($this->_status & self::STATUS_BASEPART) {
- $headers->addHeaderOb(Horde_Mime_Headers_MimeVersion::create());
- }
- /* message/* parts require no additional header information. */
- if ($ct->ptype === 'message') {
- return $headers;
- }
- /* RFC 2183 [2] indicates that default is no requested disposition -
- * the receiving MUA is responsible for display choice. */
- $cd = $this->_headers['content-disposition'];
- if (!$cd->isDefault()) {
- $headers->addHeaderOb($cd);
- }
- /* Add transfer encoding information. RFC 2045 [6.1] indicates that
- * default is 7bit. No need to send the header in this case. */
- $cte = new Horde_Mime_Headers_ContentTransferEncoding(
- null,
- $this->_getTransferEncoding(
- empty($options['encode']) ? null : $options['encode']
- )
- );
- if (!$cte->isDefault()) {
- $headers->addHeaderOb($cte);
- }
- /* Add content ID information. */
- if ($hdr = $this->_headers['content-id']) {
- $headers->addHeaderOb($hdr);
- }
- return $headers;
- }
- /**
- * Return the entire part in MIME format.
- *
- * @param array $options Additional options:
- * - canonical: (boolean) Returns the encoded part in strict RFC 822 &
- * 2045 output - namely, all newlines end with the
- * canonical <CR><LF> sequence.
- * DEFAULT: false
- * - defserver: (string) The default server to use when creating the
- * header string.
- * DEFAULT: none
- * - encode: (integer) A mask of allowable encodings.
- * DEFAULT: self::ENCODE_7BIT
- * - headers: (mixed) Include the MIME headers? If true, create a new
- * headers object. If a Horde_Mime_Headers object, add MIME
- * headers to this object. If a string, use the string
- * verbatim.
- * DEFAULT: true
- * - id: (string) Return only this MIME ID part.
- * DEFAULT: Returns the base part.
- * - stream: (boolean) Return a stream resource.
- * DEFAULT: false
- *
- * @return mixed The MIME string (returned as a resource if $stream is
- * true).
- */
- public function toString($options = array())
- {
- $eol = $this->getEOL();
- $isbase = true;
- $oldbaseptr = null;
- $parts = $parts_close = array();
- if (isset($options['id'])) {
- $id = $options['id'];
- if (!($part = $this[$id])) {
- return $part;
- }
- unset($options['id']);
- $contents = $part->toString($options);
- $prev_id = Horde_Mime::mimeIdArithmetic($id, 'up', array('norfc822' => true));
- $prev_part = ($prev_id == $this->getMimeId())
- ? $this
- : $this[$prev_id];
- if (!$prev_part) {
- return $contents;
- }
- $boundary = trim($this->getContentTypeParameter('boundary'), '"');
- $parts = array(
- $eol . '--' . $boundary . $eol,
- $contents
- );
- if (!isset($this[Horde_Mime::mimeIdArithmetic($id, 'next')])) {
- $parts[] = $eol . '--' . $boundary . '--' . $eol;
- }
- } else {
- if ($isbase = empty($options['_notbase'])) {
- $headers = !empty($options['headers'])
- ? $options['headers']
- : false;
- if (empty($options['encode'])) {
- $options['encode'] = null;
- }
- if (empty($options['defserver'])) {
- $options['defserver'] = null;
- }
- $options['headers'] = true;
- $options['_notbase'] = true;
- } else {
- $headers = true;
- $oldbaseptr = &$options['_baseptr'];
- }
- $this->_temp['toString'] = '';
- $options['_baseptr'] = &$this->_temp['toString'];
- /* Any information about a message is embedded in the message
- * contents themself. Simply output the contents of the part
- * directly and return. */
- $ptype = $this->getPrimaryType();
- if ($ptype == 'message') {
- $parts[] = $this->_contents;
- } else {
- if (!empty($this->_contents)) {
- $encoding = $this->_getTransferEncoding($options['encode']);
- switch ($encoding) {
- case '8bit':
- if (empty($options['_baseptr'])) {
- $options['_baseptr'] = '8bit';
- }
- break;
- case 'binary':
- $options['_baseptr'] = 'binary';
- break;
- }
- $parts[] = $this->_transferEncode($this->_contents, $encoding);
- /* If not using $this->_contents, we can close the stream
- * when finished. */
- if ($this->_temp['transferEncodeClose']) {
- $parts_close[] = end($parts);
- }
- }
- /* Deal with multipart messages. */
- if ($ptype == 'multipart') {
- if (empty($this->_contents)) {
- $parts[] = 'This message is in MIME format.' . $eol;
- }
- $boundary = trim($this->getContentTypeParameter('boundary'), '"');
- /* If base part is multipart/digest, children should not
- * have content-type (automatically treated as
- * message/rfc822; RFC 2046 [5.1.5]). */
- if ($this->getSubType() === 'digest') {
- $options['is_digest'] = true;
- }
- foreach ($this as $part) {
- $parts[] = $eol . '--' . $boundary . $eol;
- $tmp = $part->toString($options);
- if ($part->getEOL() != $eol) {
- $tmp = $this->replaceEOL($tmp, $eol, !empty($options['stream']));
- }
- if (!empty($options['stream'])) {
- $parts_close[] = $tmp;
- }
- $parts[] = $tmp;
- }
- $parts[] = $eol . '--' . $boundary . '--' . $eol;
- }
- }
- if (is_string($headers)) {
- array_unshift($parts, $headers);
- } elseif ($headers) {
- $hdr_ob = $this->addMimeHeaders(array(
- 'encode' => $options['encode'],
- 'headers' => ($headers === true) ? null : $headers
- ));
- if (!$isbase && !empty($options['is_digest'])) {
- unset($hdr_ob['content-type']);
- }
- if (!empty($this->_temp['toString'])) {
- $hdr_ob->addHeader(
- 'Content-Transfer-Encoding',
- $this->_temp['toString']
- );
- }
- array_unshift($parts, $hdr_ob->toString(array(
- 'canonical' => ($eol == self::RFC_EOL),
- 'charset' => $this->getHeaderCharset(),
- 'defserver' => $options['defserver']
- )));
- }
- }
- $newfp = $this->_writeStream($parts);
- array_map('fclose', $parts_close);
- if (!is_null($oldbaseptr)) {
- switch ($this->_temp['toString']) {
- case '8bit':
- if (empty($oldbaseptr)) {
- $oldbaseptr = '8bit';
- }
- break;
- case 'binary':
- $oldbaseptr = 'binary';
- break;
- }
- }
- if ($isbase && !empty($options['canonical'])) {
- return $this->replaceEOL($newfp, self::RFC_EOL, !empty($options['stream']));
- }
- return empty($options['stream'])
- ? $this->_readStream($newfp)
- : $newfp;
- }
- /**
- * Get the transfer encoding for the part based on the user requested
- * transfer encoding and the current contents of the part.
- *
- * @param integer $encode A mask of allowable encodings.
- *
- * @return string The transfer-encoding of this part.
- */
- protected function _getTransferEncoding($encode = self::ENCODE_7BIT)
- {
- if (!empty($this->_temp['sendEncoding'])) {
- return $this->_temp['sendEncoding'];
- } elseif (!empty($this->_temp['sendTransferEncoding'][$encode])) {
- return $this->_temp['sendTransferEncoding'][$encode];
- }
- if (empty($this->_contents)) {
- $encoding = '7bit';
- } else {
- switch ($this->getPrimaryType()) {
- case 'message':
- case 'multipart':
- /* RFC 2046 [5.2.1] - message/rfc822 messages only allow 7bit,
- * 8bit, and binary encodings. If the current encoding is
- * either base64 or q-p, switch it to 8bit instead.
- * RFC 2046 [5.2.2, 5.2.3, 5.2.4] - All other messages
- * only allow 7bit encodings.
- *
- * TODO: What if message contains 8bit characters and we are
- * in strict 7bit mode? Not sure there is anything we can do
- * in that situation, especially for message/rfc822 parts.
- *
- * These encoding will be figured out later (via toString()).
- * They are limited to 7bit, 8bit, and binary. Default to
- * '7bit' per RFCs. */
- $default_8bit = 'base64';
- $encoding = '7bit';
- break;
- case 'text':
- $default_8bit = 'quoted-printable';
- $encoding = '7bit';
- break;
- default:
- $default_8bit = 'base64';
- /* If transfer encoding has changed from the default, use that
- * value. */
- $encoding = ($this->_transferEncoding == self::DEFAULT_ENCODING)
- ? 'base64'
- : $this->_transferEncoding;
- break;
- }
- switch ($encoding) {
- case 'base64':
- case 'binary':
- break;
- default:
- $encoding = $this->_scanStream($this->_contents);
- break;
- }
- switch ($encoding) {
- case 'base64':
- case 'binary':
- /* If the text is longer than 998 characters between
- * linebreaks, use quoted-printable encoding to ensure the
- * text will not be chopped (i.e. by sendmail if being
- * sent as mail text). */
- $encoding = $default_8bit;
- break;
- case '8bit':
- $encoding = (($encode & self::ENCODE_8BIT) || ($encode & self::ENCODE_BINARY))
- ? '8bit'
- : $default_8bit;
- break;
- }
- }
- $this->_temp['sendTransferEncoding'][$encode] = $encoding;
- return $encoding;
- }
- /**
- * Replace newlines in this part's contents with those specified by either
- * the given newline sequence or the part's current EOL setting.
- *
- * @param mixed $text The text to replace. Either a string or a
- * stream resource. If a stream, and returning
- * a string, will close the stream when done.
- * @param string $eol The EOL sequence to use. If not present, uses
- * the part's current EOL setting.
- * @param boolean $stream If true, returns a stream resource.
- *
- * @return string The text with the newlines replaced by the desired
- * newline sequence (returned as a stream resource if
- * $stream is true).
- */
- public function replaceEOL($text, $eol = null, $stream = false)
- {
- if (is_null($eol)) {
- $eol = $this->getEOL();
- }
- stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
- $fp = $this->_writeStream($text, array(
- 'filter' => array(
- 'horde_eol' => array('eol' => $eol)
- )
- ));
- return $stream ? $fp : $this->_readStream($fp, true);
- }
- /**
- * Determine the size of this MIME part and its child members.
- *
- * @todo Remove $approx parameter.
- *
- * @param boolean $approx If true, determines an approximate size for
- * parts consisting of base64 encoded data.
- *
- * @return integer Size of the part, in bytes.
- */
- public function getBytes($approx = false)
- {
- if ($this->getPrimaryType() == 'multipart') {
- if (isset($this->_bytes)) {
- return $this->_bytes;
- }
- $bytes = 0;
- foreach ($this as $part) {
- $bytes += $part->getBytes($approx);
- }
- return $bytes;
- }
- if ($this->_contents) {
- fseek($this->_contents, 0, SEEK_END);
- $bytes = ftell($this->_contents);
- } else {
- $bytes = $this->_bytes;
- /* Base64 transfer encoding is approx. 33% larger than original
- * data size (RFC 2045 [6.8]). */
- if ($approx && ($this->_transferEncoding == 'base64')) {
- $bytes *= 0.75;
- }
- }
- return intval($bytes);
- }
- /**
- * Explicitly set the size (in bytes) of this part. This value will only
- * be returned (via getBytes()) if there are no contents currently set.
- *
- * This function is useful for setting the size of the part when the
- * contents of the part are not fully loaded (i.e. creating a
- * Horde_Mime_Part object from IMAP header information without loading the
- * data of the part).
- *
- * @param integer $bytes The size of this part in bytes.
- */
- public function setBytes($bytes)
- {
- /* Consider 'size' disposition parameter to be the canonical size.
- * Only set bytes if that value doesn't exist. */
- if (!$this->getDispositionParameter('size')) {
- $this->setDispositionParameter('size', $bytes);
- }
- }
- /**
- * Output the size of this MIME part in KB.
- *
- * @todo Remove $approx parameter.
- *
- * @param boolean $approx If true, determines an approximate size for
- * parts consisting of base64 encoded data.
- *
- * @return string Size of the part in KB.
- */
- public function getSize($approx = false)
- {
- if (!($bytes = $this->getBytes($approx))) {
- return 0;
- }
- $localeinfo = Horde_Nls::getLocaleInfo();
- // TODO: Workaround broken number_format() prior to PHP 5.4.0.
- return str_replace(
- array('X', 'Y'),
- array($localeinfo['decimal_point'], $localeinfo['thousands_sep']),
- number_format(ceil($bytes / 1024), 0, 'X', 'Y')
- );
- }
- /**
- * Sets the Content-ID header for this part.
- *
- * @param string $cid Use this CID (if not already set). Else, generate
- * a random CID.
- *
- * @return string The Content-ID for this part.
- */
- public function setContentId($cid = null)
- {
- if (!is_null($id = $this->getContentId())) {
- return $id;
- }
- $this->_headers->addHeaderOb(
- is_null($cid)
- ? Horde_Mime_Headers_ContentId::create()
- : new Horde_Mime_Headers_ContentId(null, $cid)
- );
- return $this->getContentId();
- }
- /**
- * Returns the Content-ID for this part.
- *
- * @return string The Content-ID for this part (null if not set).
- */
- public function getContentId()
- {
- return ($hdr = $this->_headers['content-id'])
- ? trim($hdr->value, '<>')
- : null;
- }
- /**
- * Alter the MIME ID of this part.
- *
- * @param string $mimeid The MIME ID.
- */
- public function setMimeId($mimeid)
- {
- $this->_mimeid = $mimeid;
- }
- /**
- * Returns the MIME ID of this part.
- *
- * @return string The MIME ID.
- */
- public function getMimeId()
- {
- return $this->_mimeid;
- }
- /**
- * Build the MIME IDs for this part and all subparts.
- *
- * @param string $id The ID of this part.
- * @param boolean $rfc822 Is this a message/rfc822 part?
- */
- public function buildMimeIds($id = null, $rfc822 = false)
- {
- $this->_status &= ~self::STATUS_REINDEX;
- if (is_null($id)) {
- $rfc822 = true;
- $id = '';
- }
- if ($rfc822) {
- if (empty($this->_parts) &&
- ($this->getPrimaryType() != 'multipart')) {
- $this->setMimeId($id . '1');
- } else {
- if (empty($id) && ($this->getType() == 'message/rfc822')) {
- $this->setMimeId('1.0');
- } else {
- $this->setMimeId($id . '0');
- }
- $i = 1;
- foreach ($this as $val) {
- $val->buildMimeIds($id . ($i++));
- }
- }
- } else {
- $this->setMimeId($id);
- $id = $id
- ? ((substr($id, -2) === '.0') ? substr($id, 0, -1) : ($id . '.'))
- : '';
- if (count($this)) {
- if ($this->getType() == 'message/rfc822') {
- $this->rewind();
- $this->current()->buildMimeIds($id, true);
- } else {
- $i = 1;
- foreach ($this as $val) {
- $val->buildMimeIds($id . ($i++));
- }
- }
- }
- }
- }
- /**
- * Is this the base MIME part?
- *
- * @param boolean $base True if this is the base MIME part.
- */
- public function isBasePart($base)
- {
- if (empty($base)) {
- $this->_status &= ~self::STATUS_BASEPART;
- } else {
- $this->_status |= self::STATUS_BASEPART;
- }
- }
- /**
- * Determines if this MIME part is an attachment for display purposes.
- *
- * @since Horde_Mime 2.10.0
- *
- * @return boolean True if this part should be considered an attachment.
- */
- public function isAttachment()
- {
- $type = $this->getType();
- switch ($type) {
- case 'application/ms-tnef':
- case 'application/pgp-keys':
- case 'application/vnd.ms-tnef':
- return false;
- }
- if ($this->parent) {
- switch ($this->parent->getType()) {
- case 'multipart/encrypted':
- switch ($type) {
- case 'application/octet-stream':
- return false;
- }
- break;
- case 'multipart/signed':
- switch ($type) {
- case 'application/pgp-signature':
- case 'application/pkcs7-signature':
- case 'application/x-pkcs7-signature':
- return false;
- }
- break;
- }
- }
- switch ($this->getDisposition()) {
- case 'attachment':
- return true;
- }
- switch ($this->getPrimaryType()) {
- case 'application':
- if (strlen($this->getName())) {
- return true;
- }
- break;
- case 'audio':
- case 'video':
- return true;
- case 'multipart':
- return false;
- }
- return false;
- }
- /**
- * Set a piece of metadata on this object.
- *
- * @param string $key The metadata key.
- * @param mixed $data The metadata. If null, clears the key.
- */
- public function setMetadata($key, $data = null)
- {
- if (is_null($data)) {
- unset($this->_metadata[$key]);
- } else {
- $this->_metadata[$key] = $data;
- }
- }
- /**
- * Retrieves metadata from this object.
- *
- * @param string $key The metadata key.
- *
- * @return mixed The metadata, or null if it doesn't exist.
- */
- public function getMetadata($key)
- {
- return isset($this->_metadata[$key])
- ? $this->_metadata[$key]
- : null;
- }
- /**
- * Sends this message.
- *
- * @param string $email The address list to send to.
- * @param Horde_Mime_Headers $headers The Horde_Mime_Headers object
- * holding this message's headers.
- * @param Horde_Mail_Transport $mailer A Horde_Mail_Transport object.
- * @param array $opts Additional options:
- * <pre>
- * - broken_rfc2231: (boolean) Attempt to work around non-RFC
- * 2231-compliant MUAs by generating both a RFC
- * 2047-like parameter name and also the correct RFC
- * 2231 parameter (@since 2.5.0).
- * DEFAULT: false
- * - encode: (integer) The encoding to use. A mask of self::ENCODE_*
- * values.
- * DEFAULT: Auto-determined based on transport driver.
- * </pre>
- *
- * @throws Horde_Mime_Exception
- * @throws InvalidArgumentException
- */
- public function send($email, $headers, Horde_Mail_Transport $mailer,
- array $opts = array())
- {
- $old_status = $this->_status;
- $this->isBasePart(true);
- /* Does the SMTP backend support 8BITMIME (RFC 1652)? */
- $canonical = true;
- $encode = self::ENCODE_7BIT;
- if (isset($opts['encode'])) {
- /* Always allow 7bit encoding. */
- $encode |= $opts['encode'];
- } elseif ($mailer instanceof Horde_Mail_Transport_Smtp) {
- try {
- $smtp_ext = $mailer->getSMTPObject()->getServiceExtensions();
- if (isset($smtp_ext['8BITMIME'])) {
- $encode |= self::ENCODE_8BIT;
- }
- } catch (Horde_Mail_Exception $e) {}
- $canonical = false;
- } elseif ($mailer instanceof Horde_Mail_Transport_Smtphorde) {
- try {
- if ($mailer->getSMTPOb…
Large files files are truncated, but you can click here to view the full file