This commit is contained in:
cutemeli
2025-12-22 10:35:30 +00:00
parent 0bfc6c8425
commit 5ce7ca2c5d
38927 changed files with 0 additions and 4594700 deletions

View File

@@ -1,7 +0,0 @@
#!/bin/bash
# Temporary workaround for cyclic dependencies - can be removed once 3.0 has been released
echo "Branch as 2.99.x in Pre-Install"
git checkout -b 2.99.x

View File

@@ -1 +0,0 @@
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)

View File

@@ -1,26 +0,0 @@
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
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 Laminas Foundation 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.

View File

@@ -1,19 +0,0 @@
# laminas-mime
## Installation
Run the following to install this library:
```bash
$ composer require laminas/laminas-mime
```
## Documentation
Browse the documentation online at https://docs.laminas.dev/laminas-mime/
## Support
- [Issues](https://github.com/laminas/laminas-mime/issues/)
- [Chat](https://laminas.dev/chat/)
- [Forum](https://discourse.laminas.dev/)

View File

@@ -1,62 +0,0 @@
{
"name": "plesk/laminas-mime",
"description": "Create and parse MIME messages and parts",
"license": "BSD-3-Clause",
"keywords": [
"laminas",
"mime"
],
"homepage": "https://laminas.dev",
"support": {
"docs": "https://docs.laminas.dev/laminas-mime/",
"issues": "https://github.com/laminas/laminas-mime/issues",
"source": "https://github.com/laminas/laminas-mime",
"rss": "https://github.com/laminas/laminas-mime/releases.atom",
"chat": "https://laminas.dev/chat",
"forum": "https://discourse.laminas.dev"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"require": {
"php": "^8.0",
"laminas/laminas-stdlib": "^3.20"
},
"require-dev": {
"laminas/laminas-coding-standard": "~2.4.0",
"plesk/laminas-mail": "2.26.0-patch1",
"phpunit/phpunit": "~9.5.25"
},
"suggest": {
"laminas/laminas-mail": "Laminas\\Mail component"
},
"autoload": {
"psr-4": {
"Laminas\\Mime\\": "src/"
}
},
"autoload-dev": {
"files": [
"test/TestAsset/Mail/Headers.php"
],
"psr-4": {
"LaminasTest\\Mime\\": "test/"
}
},
"scripts": {
"check": [
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
},
"conflict": {
"zendframework/zend-mime": "*"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,240 +0,0 @@
<?php // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
namespace Laminas\Mime;
use Laminas\Mail\Headers;
use Laminas\Stdlib\ErrorHandler;
use function count;
use function explode;
use function iconv_mime_decode;
use function preg_match;
use function preg_match_all;
use function preg_split;
use function str_replace;
use function strcasecmp;
use function strlen;
use function strpos;
use function strtok;
use function strtolower;
use function substr;
use const E_NOTICE;
use const E_WARNING;
use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
class Decode
{
/**
* Explode MIME multipart string into separate parts
*
* Parts consist of the header and the body of each MIME part.
*
* @param string $body raw body of message
* @param string $boundary boundary as found in content-type
* @return array parts with content of each part, empty if no parts found
* @throws Exception\RuntimeException
*/
public static function splitMime($body, $boundary)
{
// TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r?
$body = str_replace("\r", '', $body);
$start = 0;
$res = [];
// find every mime part limiter and cut out the
// string before it.
// the part before the first boundary string is discarded:
$p = strpos($body, '--' . $boundary . "\n", $start);
if ($p === false) {
// no parts found!
return [];
}
// position after first boundary line
$start = $p + 3 + strlen($boundary);
while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
$res[] = substr($body, $start, $p - $start);
$start = $p + 3 + strlen($boundary);
}
// no more parts, find end boundary
$p = strpos($body, '--' . $boundary . '--', $start);
if ($p === false) {
throw new Exception\RuntimeException('Not a valid Mime Message: End Missing');
}
// the remaining part also needs to be parsed:
$res[] = substr($body, $start, $p - $start);
return $res;
}
/**
* decodes a mime encoded String and returns a
* struct of parts with header and body
*
* @param string $message raw message content
* @param string $boundary boundary as found in content-type
* @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND}
* @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found
* @throws Exception\RuntimeException
*/
public static function splitMessageStruct($message, $boundary, $EOL = Mime::LINEEND)
{
$parts = static::splitMime($message, $boundary);
if (! $parts) {
return;
}
$result = [];
$headers = null; // "Declare" variable before the first usage "for reading"
$body = null; // "Declare" variable before the first usage "for reading"
foreach ($parts as $part) {
static::splitMessage($part, $headers, $body, $EOL);
$result[] = [
'header' => $headers,
'body' => $body,
];
}
return $result;
}
/**
* split a message in header and body part, if no header or an
* invalid header is found $headers is empty
*
* The charset of the returned headers depend on your iconv settings.
*
* @param string|Headers $message raw message with header and optional content
* @param Headers $headers output param, headers container
* @param string $body output param, content of message
* @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND}
* @param bool $strict enable strict mode for parsing message
* @return null
*/
public static function splitMessage($message, &$headers, &$body, $EOL = Mime::LINEEND, $strict = false)
{
if ($message instanceof Headers) {
$message = $message->toString();
}
// check for valid header at first line
$firstlinePos = strpos($message, "\n");
$firstline = $firstlinePos === false ? $message : substr($message, 0, $firstlinePos);
if (! preg_match('%^[^\s]+[^:]*:%', $firstline)) {
$headers = new Headers();
// TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r?
$body = str_replace(["\r", "\n"], ['', $EOL], $message);
return;
}
// see @Laminas-372, pops the first line off a message if it doesn't contain a header
if (! $strict) {
$parts = explode(':', $firstline, 2);
if (count($parts) !== 2) {
$message = substr($message, strpos($message, $EOL) + 1);
}
}
// @todo splitMime removes "\r" sequences, which breaks valid mime
// messages as returned by many mail servers
$headersEOL = $EOL;
// find an empty line between headers and body
// default is set new line
// @todo Maybe this is too much "magic"; we should be more strict here
if (strpos($message, $EOL . $EOL)) {
[$headers, $body] = explode($EOL . $EOL, $message, 2);
// next is the standard new line
} elseif ($EOL !== "\r\n" && strpos($message, "\r\n\r\n")) {
[$headers, $body] = explode("\r\n\r\n", $message, 2);
$headersEOL = "\r\n"; // Headers::fromString will fail with incorrect EOL
// next is the other "standard" new line
} elseif ($EOL !== "\n" && strpos($message, "\n\n")) {
[$headers, $body] = explode("\n\n", $message, 2);
$headersEOL = "\n";
// at last resort find anything that looks like a new line
} else {
ErrorHandler::start(E_NOTICE | E_WARNING);
[$headers, $body] = preg_split("%([\r\n]+)\\1%U", $message, 2);
ErrorHandler::stop();
}
$headers = Headers::fromString($headers, $headersEOL);
}
/**
* split a content type in its different parts
*
* @param string $type content-type
* @param string $wantedPart the wanted part, else an array with all parts is returned
* @return string|array wanted part or all parts as array('type' => content-type, partname => value)
*/
public static function splitContentType($type, $wantedPart = null)
{
return static::splitHeaderField($type, $wantedPart, 'type');
}
/**
* split a header field like content type in its different parts
*
* @param string $field header field
* @param string $wantedPart the wanted part, else an array with all parts is returned
* @param string $firstName key name for the first part
* @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
* @throws Exception\RuntimeException
*/
public static function splitHeaderField($field, $wantedPart = null, $firstName = '0')
{
$wantedPart = strtolower($wantedPart ?? '');
$firstName = strtolower($firstName);
// special case - a bit optimized
if ($firstName === $wantedPart) {
$field = strtok($field, ';');
return $field[0] === '"' ? substr($field, 1, -1) : $field;
}
$field = $firstName . '=' . $field;
if (! preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) {
throw new Exception\RuntimeException('not a valid header field');
}
if ($wantedPart) {
foreach ($matches[1] as $key => $name) {
if (strcasecmp($name, $wantedPart)) {
continue;
}
if ($matches[2][$key][0] !== '"') {
return $matches[2][$key];
}
return substr($matches[2][$key], 1, -1);
}
return;
}
$split = [];
foreach ($matches[1] as $key => $name) {
$name = strtolower($name);
if ($matches[2][$key][0] === '"') {
$split[$name] = substr($matches[2][$key], 1, -1);
} else {
$split[$name] = $matches[2][$key];
}
}
return $split;
}
/**
* decode a quoted printable encoded string
*
* The charset of the returned string depends on your iconv settings.
*
* @param string $string encoded string
* @return string decoded string
*/
public static function decodeQuotedPrintable($string)
{
return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
}
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Laminas\Mime\Exception;
interface ExceptionInterface
{
}

View File

@@ -1,8 +0,0 @@
<?php
namespace Laminas\Mime\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Laminas\Mime\Exception;
/**
* Exception for Laminas\Mime component.
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -1,321 +0,0 @@
<?php // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps,PSR12.Files.FileHeader.SpacingAfterBlock,PSR2.Methods.MethodDeclaration.Underscore
namespace Laminas\Mime;
use Laminas\Mail\Header\HeaderInterface;
use Laminas\Mime\Mime;
use Laminas\Mime\Part;
use function array_keys;
use function base64_decode;
use function count;
use function current;
use function quoted_printable_decode;
use function sprintf;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
use function trim;
class Message
{
/** @var Part[] */
protected $parts = [];
/** @var null|Mime */
protected $mime;
/**
* Returns the list of all Laminas\Mime\Part in the message
*
* @return Part[]
*/
public function getParts()
{
return $this->parts;
}
/**
* Sets the given array of Laminas\Mime\Part as the array for the message
*
* @param array $parts
* @return self
*/
public function setParts($parts)
{
$this->parts = $parts;
return $this;
}
/**
* Append a new Laminas\Mime\Part to the current message
*
* @throws Exception\InvalidArgumentException
* @return self
*/
public function addPart(Part $part)
{
foreach ($this->getParts() as $row) {
if ($part === $row) {
throw new Exception\InvalidArgumentException(sprintf(
'Provided part %s already defined.',
$part->getId()
));
}
}
$this->parts[] = $part;
return $this;
}
/**
* Check if message needs to be sent as multipart
* MIME message or if it has only one part.
*
* @return bool
*/
public function isMultiPart()
{
return count($this->parts) > 1;
}
/**
* Set Laminas\Mime\Mime object for the message
*
* This can be used to set the boundary specifically or to use a subclass of
* Laminas\Mime for generating the boundary.
*
* @return self
*/
public function setMime(Mime $mime)
{
$this->mime = $mime;
return $this;
}
/**
* Returns the Laminas\Mime\Mime object in use by the message
*
* If the object was not present, it is created and returned. Can be used to
* determine the boundary used in this message.
*
* @return Mime
*/
public function getMime()
{
if ($this->mime === null) {
$this->mime = new Mime();
}
return $this->mime;
}
/**
* Generate MIME-compliant message from the current configuration
*
* This can be a multipart message if more than one MIME part was added. If
* only one part is present, the content of this part is returned. If no
* part had been added, an empty string is returned.
*
* Parts are separated by the mime boundary as defined in Laminas\Mime\Mime. If
* {@link setMime()} has been called before this method, the Laminas\Mime\Mime
* object set by this call will be used. Otherwise, a new Laminas\Mime\Mime object
* is generated and used.
*
* @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND}
* @return string
*/
public function generateMessage($EOL = Mime::LINEEND)
{
if (! $this->isMultiPart()) {
if (empty($this->parts)) {
return '';
}
$part = current($this->parts);
$body = $part->getContent($EOL);
} else {
$mime = $this->getMime();
$boundaryLine = $mime->boundaryLine($EOL);
$body = 'This is a message in Mime Format. If you see this, '
. "your mail reader does not support this format." . $EOL;
foreach (array_keys($this->parts) as $p) {
$body .= $boundaryLine
. $this->getPartHeaders($p, $EOL)
. $EOL
. $this->getPartContent($p, $EOL);
}
$body .= $mime->mimeEnd($EOL);
}
return trim($body);
}
/**
* Get the headers of a given part as an array
*
* @param int $partnum
* @return array
*/
public function getPartHeadersArray($partnum)
{
return $this->parts[$partnum]->getHeadersArray();
}
/**
* Get the headers of a given part as a string
*
* @param int $partnum
* @param string $EOL
* @return string
*/
public function getPartHeaders($partnum, $EOL = Mime::LINEEND)
{
return $this->parts[$partnum]->getHeaders($EOL);
}
/**
* Get the (encoded) content of a given part as a string
*
* @param int $partnum
* @param string $EOL
* @return string
*/
public function getPartContent($partnum, $EOL = Mime::LINEEND)
{
return $this->parts[$partnum]->getContent($EOL);
}
/**
* Explode MIME multipart string into separate parts
*
* Parts consist of the header and the body of each MIME part.
*
* @param string $body
* @param string $boundary
* @throws Exception\RuntimeException
* @return array
*/
protected static function _disassembleMime($body, $boundary)
{
$start = 0;
$res = [];
// find every mime part limiter and cut out the
// string before it.
// the part before the first boundary string is discarded:
$p = strpos($body, '--' . $boundary . "\n", $start);
if ($p === false) {
// no parts found!
return [];
}
// position after first boundary line
$start = $p + 3 + strlen($boundary);
while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
$res[] = substr($body, $start, $p - $start);
$start = $p + 3 + strlen($boundary);
}
// no more parts, find end boundary
$p = strpos($body, '--' . $boundary . '--', $start);
if ($p === false) {
throw new Exception\RuntimeException('Not a valid Mime Message: End Missing');
}
// the remaining part also needs to be parsed:
$res[] = substr($body, $start, $p - $start);
return $res;
}
/**
* Decodes a MIME encoded string and returns a Laminas\Mime\Message object with
* all the MIME parts set according to the given string
*
* @param string $message
* @param string $boundary Multipart boundary; if omitted, $message will be
* treated as a single part.
* @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND}
* @throws Exception\RuntimeException
* @return Message
*/
public static function createFromMessage($message, $boundary = null, $EOL = Mime::LINEEND)
{
if ($boundary) {
$parts = Decode::splitMessageStruct($message, $boundary, $EOL);
} else {
Decode::splitMessage($message, $headers, $body, $EOL);
$parts = [
[
'header' => $headers,
'body' => $body,
],
];
}
$res = new static();
foreach ($parts as $part) {
// now we build a new MimePart for the current Message Part:
$properties = [];
foreach ($part['header'] as $header) {
/** @var HeaderInterface $header */
/**
* @todo check for characterset and filename
*/
$fieldName = $header->getFieldName();
$fieldValue = $header->getFieldValue();
switch (strtolower($fieldName)) {
case 'content-type':
$properties['type'] = $fieldValue;
break;
case 'content-transfer-encoding':
$properties['encoding'] = $fieldValue;
break;
case 'content-id':
$properties['id'] = trim($fieldValue, '<>');
break;
case 'content-disposition':
$properties['disposition'] = $fieldValue;
break;
case 'content-description':
$properties['description'] = $fieldValue;
break;
case 'content-location':
$properties['location'] = $fieldValue;
break;
case 'content-language':
$properties['language'] = $fieldValue;
break;
default:
// Ignore unknown header
break;
}
}
$body = $part['body'];
if (isset($properties['encoding'])) {
switch ($properties['encoding']) {
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
case 'base64':
$body = base64_decode($body);
break;
}
}
$newPart = new Part($body);
foreach ($properties as $key => $value) {
$newPart->$key = $value;
}
$res->addPart($newPart);
}
return $res;
}
}

View File

@@ -1,721 +0,0 @@
<?php
namespace Laminas\Mime;
use function base64_encode;
use function chunk_split;
use function count;
use function implode;
use function max;
use function md5;
use function microtime;
use function ord;
use function preg_match;
use function rtrim;
use function sprintf;
use function str_replace;
use function strcspn;
use function strlen;
use function strpos;
use function strrpos;
use function strtoupper;
use function substr;
use function substr_replace;
use function trim;
/**
* Support class for MultiPart Mime Messages
*/
class Mime
{
// phpcs:disable Generic.Files.LineLength.TooLong
public const TYPE_OCTETSTREAM = 'application/octet-stream';
public const TYPE_TEXT = 'text/plain';
public const TYPE_HTML = 'text/html';
public const TYPE_ENRICHED = 'text/enriched';
public const TYPE_XML = 'text/xml';
public const ENCODING_7BIT = '7bit';
public const ENCODING_8BIT = '8bit';
public const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
public const ENCODING_BASE64 = 'base64';
public const DISPOSITION_ATTACHMENT = 'attachment';
public const DISPOSITION_INLINE = 'inline';
public const LINELENGTH = 72;
public const LINEEND = "\n";
public const MULTIPART_ALTERNATIVE = 'multipart/alternative';
public const MULTIPART_MIXED = 'multipart/mixed';
public const MULTIPART_RELATED = 'multipart/related';
public const MULTIPART_RELATIVE = 'multipart/relative';
public const MULTIPART_REPORT = 'multipart/report';
public const MESSAGE_RFC822 = 'message/rfc822';
public const MESSAGE_DELIVERY_STATUS = 'message/delivery-status';
public const CHARSET_REGEX = '#=\?(?P<charset>[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P<encoding>[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P<text>[\x21-\x3e\x40-\x7e]+)#';
// phpcs:enable
/** @var null|string */
protected $boundary;
/** @var int */
protected static $makeUnique = 0;
/**
* Lookup-tables for QuotedPrintable
*
* @var string[]
*/
public static $qpKeys = [
"\x00",
"\x01",
"\x02",
"\x03",
"\x04",
"\x05",
"\x06",
"\x07",
"\x08",
"\x09",
"\x0A",
"\x0B",
"\x0C",
"\x0D",
"\x0E",
"\x0F",
"\x10",
"\x11",
"\x12",
"\x13",
"\x14",
"\x15",
"\x16",
"\x17",
"\x18",
"\x19",
"\x1A",
"\x1B",
"\x1C",
"\x1D",
"\x1E",
"\x1F",
"\x7F",
"\x80",
"\x81",
"\x82",
"\x83",
"\x84",
"\x85",
"\x86",
"\x87",
"\x88",
"\x89",
"\x8A",
"\x8B",
"\x8C",
"\x8D",
"\x8E",
"\x8F",
"\x90",
"\x91",
"\x92",
"\x93",
"\x94",
"\x95",
"\x96",
"\x97",
"\x98",
"\x99",
"\x9A",
"\x9B",
"\x9C",
"\x9D",
"\x9E",
"\x9F",
"\xA0",
"\xA1",
"\xA2",
"\xA3",
"\xA4",
"\xA5",
"\xA6",
"\xA7",
"\xA8",
"\xA9",
"\xAA",
"\xAB",
"\xAC",
"\xAD",
"\xAE",
"\xAF",
"\xB0",
"\xB1",
"\xB2",
"\xB3",
"\xB4",
"\xB5",
"\xB6",
"\xB7",
"\xB8",
"\xB9",
"\xBA",
"\xBB",
"\xBC",
"\xBD",
"\xBE",
"\xBF",
"\xC0",
"\xC1",
"\xC2",
"\xC3",
"\xC4",
"\xC5",
"\xC6",
"\xC7",
"\xC8",
"\xC9",
"\xCA",
"\xCB",
"\xCC",
"\xCD",
"\xCE",
"\xCF",
"\xD0",
"\xD1",
"\xD2",
"\xD3",
"\xD4",
"\xD5",
"\xD6",
"\xD7",
"\xD8",
"\xD9",
"\xDA",
"\xDB",
"\xDC",
"\xDD",
"\xDE",
"\xDF",
"\xE0",
"\xE1",
"\xE2",
"\xE3",
"\xE4",
"\xE5",
"\xE6",
"\xE7",
"\xE8",
"\xE9",
"\xEA",
"\xEB",
"\xEC",
"\xED",
"\xEE",
"\xEF",
"\xF0",
"\xF1",
"\xF2",
"\xF3",
"\xF4",
"\xF5",
"\xF6",
"\xF7",
"\xF8",
"\xF9",
"\xFA",
"\xFB",
"\xFC",
"\xFD",
"\xFE",
"\xFF",
];
/** @var string[] */
public static $qpReplaceValues = [
"=00",
"=01",
"=02",
"=03",
"=04",
"=05",
"=06",
"=07",
"=08",
"=09",
"=0A",
"=0B",
"=0C",
"=0D",
"=0E",
"=0F",
"=10",
"=11",
"=12",
"=13",
"=14",
"=15",
"=16",
"=17",
"=18",
"=19",
"=1A",
"=1B",
"=1C",
"=1D",
"=1E",
"=1F",
"=7F",
"=80",
"=81",
"=82",
"=83",
"=84",
"=85",
"=86",
"=87",
"=88",
"=89",
"=8A",
"=8B",
"=8C",
"=8D",
"=8E",
"=8F",
"=90",
"=91",
"=92",
"=93",
"=94",
"=95",
"=96",
"=97",
"=98",
"=99",
"=9A",
"=9B",
"=9C",
"=9D",
"=9E",
"=9F",
"=A0",
"=A1",
"=A2",
"=A3",
"=A4",
"=A5",
"=A6",
"=A7",
"=A8",
"=A9",
"=AA",
"=AB",
"=AC",
"=AD",
"=AE",
"=AF",
"=B0",
"=B1",
"=B2",
"=B3",
"=B4",
"=B5",
"=B6",
"=B7",
"=B8",
"=B9",
"=BA",
"=BB",
"=BC",
"=BD",
"=BE",
"=BF",
"=C0",
"=C1",
"=C2",
"=C3",
"=C4",
"=C5",
"=C6",
"=C7",
"=C8",
"=C9",
"=CA",
"=CB",
"=CC",
"=CD",
"=CE",
"=CF",
"=D0",
"=D1",
"=D2",
"=D3",
"=D4",
"=D5",
"=D6",
"=D7",
"=D8",
"=D9",
"=DA",
"=DB",
"=DC",
"=DD",
"=DE",
"=DF",
"=E0",
"=E1",
"=E2",
"=E3",
"=E4",
"=E5",
"=E6",
"=E7",
"=E8",
"=E9",
"=EA",
"=EB",
"=EC",
"=ED",
"=EE",
"=EF",
"=F0",
"=F1",
"=F2",
"=F3",
"=F4",
"=F5",
"=F6",
"=F7",
"=F8",
"=F9",
"=FA",
"=FB",
"=FC",
"=FD",
"=FE",
"=FF",
];
// @codingStandardsIgnoreStart
public static $qpKeysString =
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
// @codingStandardsIgnoreEnd
/**
* Check if the given string is "printable"
*
* Checks that a string contains no unprintable characters. If this returns
* false, encode the string for secure delivery.
*
* @param string $str
* @return bool
*/
public static function isPrintable($str)
{
return strcspn($str, static::$qpKeysString) === strlen($str);
}
/**
* Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
*
* @param string $str
* @param int $lineLength Defaults to {@link LINELENGTH}
* @param string $lineEnd Defaults to {@link LINEEND}
* @return string
*/
public static function encodeQuotedPrintable(
$str,
$lineLength = self::LINELENGTH,
$lineEnd = self::LINEEND
) {
$out = '';
$str = self::_encodeQuotedPrintable($str);
// Split encoded text into separate lines
$initialPtr = 0;
$strLength = strlen($str);
while ($initialPtr < $strLength) {
$continueAt = $strLength - $initialPtr;
if ($continueAt > $lineLength) {
$continueAt = $lineLength;
}
$chunk = substr($str, $initialPtr, $continueAt);
// Ensure we are not splitting across an encoded character
$endingMarkerPos = strrpos($chunk, '=');
if ($endingMarkerPos !== false && $endingMarkerPos >= strlen($chunk) - 2) {
$chunk = substr($chunk, 0, $endingMarkerPos);
$continueAt = $endingMarkerPos;
}
if (ord($chunk[0]) === 0x2E) { // 0x2E is a dot
$chunk = '=2E' . substr($chunk, 1);
}
// copied from swiftmailer https://git.io/vAXU1
switch (ord(substr($chunk, strlen($chunk) - 1))) {
case 0x09: // Horizontal Tab
$chunk = substr_replace($chunk, '=09', strlen($chunk) - 1, 1);
break;
case 0x20: // Space
$chunk = substr_replace($chunk, '=20', strlen($chunk) - 1, 1);
break;
}
// Add string and continue
$out .= $chunk . '=' . $lineEnd;
$initialPtr += $continueAt;
}
$out = rtrim($out, $lineEnd);
$out = rtrim($out, '=');
return $out;
}
/**
* Converts a string into quoted printable format.
*
* @param string $str
* @return string
*/
// @codingStandardsIgnoreStart
private static function _encodeQuotedPrintable($str)
{
// @codingStandardsIgnoreEnd
$str = str_replace('=', '=3D', $str);
$str = str_replace(static::$qpKeys, static::$qpReplaceValues, $str);
$str = rtrim($str);
return $str;
}
/**
* Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
*
* Mail headers depend on an extended quoted printable algorithm otherwise
* a range of bugs can occur.
*
* @param string $str
* @param string $charset
* @param int $lineLength Defaults to {@link LINELENGTH}
* @param string $lineEnd Defaults to {@link LINEEND}
* @param positive-int|0 $headerNameSize When folding a line, it is necessary to calculate
* the length of the entire line (together with the header name).
* Therefore, you can specify the header name and colon length
* in this argument to fold the string properly.
* @return string
*/
public static function encodeQuotedPrintableHeader(
$str,
$charset,
$lineLength = self::LINELENGTH,
$lineEnd = self::LINEEND,
$headerNameSize = 0
) {
// Reduce line-length by the length of the required delimiter, charsets and encoding
$prefix = sprintf('=?%s?Q?', $charset);
$lineLength = $lineLength - strlen($prefix) - 3;
$str = self::_encodeQuotedPrintable($str);
// Mail-Header required chars have to be encoded also:
$str = str_replace(['?', ',', ' ', '_'], ['=3F', '=2C', '=20', '=5F'], $str);
// initialize first line, we need it anyways
$lines = [0 => ''];
// Split encoded text into separate lines
$tmp = '';
while (strlen($str) > 0) {
$currentLine = max(count($lines) - 1, 0);
$token = static::getNextQuotedPrintableToken($str);
$substr = substr($str, strlen($token));
$str = false === $substr ? '' : $substr;
$tmp .= $token;
if ($token === '=20') {
// only if we have a single char token or space, we can append the
// tempstring it to the current line or start a new line if necessary.
if ($currentLine === 0) {
// The size of the first line should be calculated with the header name.
$currentLineLength = strlen($lines[$currentLine] . $tmp) + $headerNameSize;
} else {
$currentLineLength = strlen($lines[$currentLine] . $tmp);
}
$lineLimitReached = $currentLineLength > $lineLength;
$noCurrentLine = $lines[$currentLine] === '';
if ($noCurrentLine && $lineLimitReached) {
$lines[$currentLine] = $tmp;
$lines[$currentLine + 1] = '';
} elseif ($lineLimitReached) {
$lines[$currentLine + 1] = $tmp;
} else {
$lines[$currentLine] .= $tmp;
}
$tmp = '';
}
// don't forget to append the rest to the last line
if (strlen($str) === 0) {
$lines[$currentLine] .= $tmp;
}
}
// assemble the lines together by pre- and appending delimiters, charset, encoding.
for ($i = 0, $count = count($lines); $i < $count; $i++) {
$lines[$i] = " " . $prefix . $lines[$i] . "?=";
}
$str = trim(implode($lineEnd, $lines));
return $str;
}
/**
* Retrieves the first token from a quoted printable string.
*
* @param string $str
* @return string
*/
private static function getNextQuotedPrintableToken($str)
{
if (0 === strpos($str, '=')) {
$token = substr($str, 0, 3);
} else {
$token = substr($str, 0, 1);
}
return $token;
}
/**
* Encode a given string in mail header compatible base64 encoding.
*
* @param string $str
* @param string $charset
* @param int $lineLength Defaults to {@link LINELENGTH}
* @param string $lineEnd Defaults to {@link LINEEND}
* @return string
*/
public static function encodeBase64Header(
$str,
$charset,
$lineLength = self::LINELENGTH,
$lineEnd = self::LINEEND
) {
$prefix = '=?' . $charset . '?B?';
$suffix = '?=';
$remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
$encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd);
$encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue);
$encodedValue = $prefix . $encodedValue . $suffix;
return $encodedValue;
}
/**
* Encode a given string in base64 encoding and break lines
* according to the maximum linelength.
*
* @param string $str
* @param int $lineLength Defaults to {@link LINELENGTH}
* @param string $lineEnd Defaults to {@link LINEEND}
* @return string
*/
public static function encodeBase64(
$str,
$lineLength = self::LINELENGTH,
$lineEnd = self::LINEEND
) {
$lineLength = $lineLength - ($lineLength % 4);
return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
}
/**
* Constructor
*
* @param null|string $boundary
* @access public
*/
public function __construct($boundary = null)
{
// This string needs to be somewhat unique
if ($boundary === null) {
$this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++);
} else {
$this->boundary = $boundary;
}
}
// phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
/**
* Encode the given string with the given encoding.
*
* @param string $str
* @param string $encoding
* @param string $EOL EOL string; defaults to {@link LINEEND}
* @return string
*/
public static function encode($str, $encoding, $EOL = self::LINEEND)
{
switch ($encoding) {
case self::ENCODING_BASE64:
return static::encodeBase64($str, self::LINELENGTH, $EOL);
case self::ENCODING_QUOTEDPRINTABLE:
return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
default:
/**
* @todo 7Bit and 8Bit is currently handled the same way.
*/
return $str;
}
}
/**
* Return a MIME boundary
*
* @access public
* @return string
*/
public function boundary()
{
return $this->boundary;
}
/**
* Return a MIME boundary line
*
* @param string $EOL Defaults to {@link LINEEND}
* @access public
* @return string
*/
public function boundaryLine($EOL = self::LINEEND)
{
return $EOL . '--' . $this->boundary . $EOL;
}
/**
* Return MIME ending
*
* @param string $EOL Defaults to {@link LINEEND}
* @access public
* @return string
*/
public function mimeEnd($EOL = self::LINEEND)
{
return $EOL . '--' . $this->boundary . '--' . $EOL;
}
/**
* Detect MIME charset
*
* Extract parts according to https://tools.ietf.org/html/rfc2047#section-2
*
* @param string $str
* @return string
*/
public static function mimeDetectCharset($str)
{
if (preg_match(self::CHARSET_REGEX, $str, $matches)) {
return strtoupper($matches['charset']);
}
return 'ASCII';
}
}

View File

@@ -1,548 +0,0 @@
<?php
namespace Laminas\Mime;
use function array_key_exists;
use function gettype;
use function is_object;
use function is_resource;
use function is_string;
use function rewind;
use function sprintf;
use function stream_filter_append;
use function stream_filter_remove;
use function stream_get_contents;
use function stream_get_meta_data;
use const STREAM_FILTER_READ;
/**
* Class representing a MIME part.
*/
class Part
{
/** @var string */
public $type = Mime::TYPE_OCTETSTREAM;
/** @var string */
public $encoding = Mime::ENCODING_8BIT;
/** @var null|string */
public $id;
/** @var null|string */
public $disposition;
/** @var null|string */
public $filename;
/** @var null|string */
public $description;
/** @var null|string */
public $charset;
/** @var null|string */
public $boundary;
/** @var null|string */
public $location;
/** @var null|string */
public $language;
/**
* String or stream containing the content
*
* @var string|resource
*/
protected $content;
/** @var bool */
protected $isStream = false;
/** @var array<array-key, resource> */
protected $filters = [];
/**
* create a new Mime Part.
* The (unencoded) content of the Part as passed
* as a string or stream
*
* @param mixed $content String or Stream containing the content
* @throws Exception\InvalidArgumentException
*/
public function __construct($content = '')
{
$this->setContent($content);
}
/**
* @todo error checking for setting $type
* @todo error checking for setting $encoding
*/
/**
* Set type
*
* @param string $type
* @return self
*/
public function setType($type = Mime::TYPE_OCTETSTREAM)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set encoding
*
* @param string $encoding
* @return self
*/
public function setEncoding($encoding = Mime::ENCODING_8BIT)
{
$this->encoding = $encoding;
return $this;
}
/**
* Get encoding
*
* @return string
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* Set id
*
* @param string $id
* @return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Set disposition
*
* @param string $disposition
* @return self
*/
public function setDisposition($disposition)
{
$this->disposition = $disposition;
return $this;
}
/**
* Get disposition
*
* @return string
*/
public function getDisposition()
{
return $this->disposition;
}
/**
* Set description
*
* @param string $description
* @return self
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set filename
*
* @param string $fileName
* @return self
*/
public function setFileName($fileName)
{
$this->filename = $fileName;
return $this;
}
/**
* Get filename
*
* @return string
*/
public function getFileName()
{
return $this->filename;
}
/**
* Set charset
*
* @param string $charset
* @return self
*/
public function setCharset($charset)
{
$this->charset = $charset;
return $this;
}
/**
* Get charset
*
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* Set boundary
*
* @param string $boundary
* @return self
*/
public function setBoundary($boundary)
{
$this->boundary = $boundary;
return $this;
}
/**
* Get boundary
*
* @return string
*/
public function getBoundary()
{
return $this->boundary;
}
/**
* Set location
*
* @param string $location
* @return self
*/
public function setLocation($location)
{
$this->location = $location;
return $this;
}
/**
* Get location
*
* @return string
*/
public function getLocation()
{
return $this->location;
}
/**
* Set language
*
* @param string $language
* @return self
*/
public function setLanguage($language)
{
$this->language = $language;
return $this;
}
/**
* Get language
*
* @return string
*/
public function getLanguage()
{
return $this->language;
}
/**
* Set content
*
* @param mixed $content String or Stream containing the content
* @throws Exception\InvalidArgumentException
* @return self
*/
public function setContent($content)
{
if (! is_string($content) && ! is_resource($content)) {
throw new Exception\InvalidArgumentException(sprintf(
'Content must be string or resource; received "%s"',
is_object($content) ? $content::class : gettype($content)
));
}
$this->content = $content;
if (is_resource($content)) {
$this->isStream = true;
}
return $this;
}
/**
* Set isStream
*
* @param bool $isStream
* @return self
*/
public function setIsStream($isStream = false)
{
$this->isStream = (bool) $isStream;
return $this;
}
/**
* Get isStream
*
* @return bool
*/
public function getIsStream()
{
return $this->isStream;
}
/**
* Set filters
*
* @param array<array-key, resource> $filters
* @return self
*/
public function setFilters($filters = [])
{
$this->filters = $filters;
return $this;
}
/**
* Get Filters
*
* @return array<array-key, resource>
*/
public function getFilters()
{
return $this->filters;
}
/**
* check if this part can be read as a stream.
* if true, getEncodedStream can be called, otherwise
* only getContent can be used to fetch the encoded
* content of the part
*
* @return bool
*/
public function isStream()
{
return $this->isStream;
}
// phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
/**
* if this was created with a stream, return a filtered stream for
* reading the content. very useful for large file attachments.
*
* @param string $EOL
* @return resource
* @throws Exception\RuntimeException If not a stream or unable to append filter.
*/
public function getEncodedStream($EOL = Mime::LINEEND)
{
if (! $this->isStream) {
throw new Exception\RuntimeException('Attempt to get a stream from a string part');
}
//stream_filter_remove(); // ??? is that right?
switch ($this->encoding) {
case Mime::ENCODING_QUOTEDPRINTABLE:
if (array_key_exists(Mime::ENCODING_QUOTEDPRINTABLE, $this->filters)) {
stream_filter_remove($this->filters[Mime::ENCODING_QUOTEDPRINTABLE]);
}
$filter = stream_filter_append(
$this->content,
'convert.quoted-printable-encode',
STREAM_FILTER_READ,
[
'line-length' => 76,
'line-break-chars' => $EOL,
]
);
$this->filters[Mime::ENCODING_QUOTEDPRINTABLE] = $filter;
if (! is_resource($filter)) {
throw new Exception\RuntimeException('Failed to append quoted-printable filter');
}
break;
case Mime::ENCODING_BASE64:
if (array_key_exists(Mime::ENCODING_BASE64, $this->filters)) {
stream_filter_remove($this->filters[Mime::ENCODING_BASE64]);
}
$filter = stream_filter_append(
$this->content,
'convert.base64-encode',
STREAM_FILTER_READ,
[
'line-length' => 76,
'line-break-chars' => $EOL,
]
);
$this->filters[Mime::ENCODING_BASE64] = $filter;
if (! is_resource($filter)) {
throw new Exception\RuntimeException('Failed to append base64 filter');
}
break;
default:
}
return $this->content;
}
/**
* Get the Content of the current Mime Part in the given encoding.
*
* @param string $EOL
* @return string
*/
public function getContent($EOL = Mime::LINEEND)
{
if ($this->isStream) {
$encodedStream = $this->getEncodedStream($EOL);
$encodedStreamContents = stream_get_contents($encodedStream);
$streamMetaData = stream_get_meta_data($encodedStream);
if (isset($streamMetaData['seekable']) && $streamMetaData['seekable']) {
rewind($encodedStream);
}
return $encodedStreamContents;
}
return Mime::encode($this->content, $this->encoding, $EOL);
}
/**
* Get the RAW unencoded content from this part
*
* @return string
*/
public function getRawContent()
{
if ($this->isStream) {
return stream_get_contents($this->content);
}
return $this->content;
}
/**
* Create and return the array of headers for this MIME part
*
* @access public
* @param string $EOL
* @return array
*/
public function getHeadersArray($EOL = Mime::LINEEND)
{
$headers = [];
$contentType = $this->type;
if ($this->charset) {
$contentType .= '; charset=' . $this->charset;
}
if ($this->boundary) {
$contentType .= ';' . $EOL
. " boundary=\"" . $this->boundary . '"';
}
$headers[] = ['Content-Type', $contentType];
if ($this->encoding) {
$headers[] = ['Content-Transfer-Encoding', $this->encoding];
}
if ($this->id) {
$headers[] = ['Content-ID', '<' . $this->id . '>'];
}
if ($this->disposition) {
$disposition = $this->disposition;
if ($this->filename) {
$disposition .= '; filename="' . $this->filename . '"';
}
$headers[] = ['Content-Disposition', $disposition];
}
if ($this->description) {
$headers[] = ['Content-Description', $this->description];
}
if ($this->location) {
$headers[] = ['Content-Location', $this->location];
}
if ($this->language) {
$headers[] = ['Content-Language', $this->language];
}
return $headers;
}
/**
* Return the headers for this part as a string
*
* @param string $EOL
* @return String
*/
public function getHeaders($EOL = Mime::LINEEND)
{
$res = '';
foreach ($this->getHeadersArray($EOL) as $header) {
$res .= $header[0] . ': ' . $header[1] . $EOL;
}
return $res;
}
}