4
* This file is part of the Nette Framework (http://nette.org)
6
* Copyright (c) 2004 David Grudl (http://davidgrudl.com)
8
* For the full copyright and license information, please view
9
* the file license.txt that was distributed with this source code.
23
* @property-read array $headers
24
* @property-write $contentType
25
* @property string $encoding
26
* @property mixed $body
28
class MimePart extends Nette\Object
31
const ENCODING_BASE64 = 'base64',
32
ENCODING_7BIT = '7bit',
33
ENCODING_8BIT = '8bit',
34
ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
38
const LINE_LENGTH = 76;
41
private $headers = array();
44
private $parts = array();
53
* @param string|array value or pair email => name
57
public function setHeader($name, $value, $append = FALSE)
59
if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
60
throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
63
if ($value == NULL) { // intentionally ==
65
unset($this->headers[$name]);
68
} elseif (is_array($value)) { // email
69
$tmp = & $this->headers[$name];
70
if (!$append || !is_array($tmp)) {
74
foreach ($value as $email => $recipient) {
75
if ($recipient !== NULL && !Strings::checkEncoding($recipient)) {
76
Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
78
if (preg_match('#[\r\n]#', $recipient)) {
79
throw new Nette\InvalidArgumentException("Name must not contain line separator.");
81
Nette\Utils\Validators::assert($email, 'email', "header '$name'");
82
$tmp[$email] = $recipient;
86
$value = (string) $value;
87
if (!Strings::checkEncoding($value)) {
88
throw new Nette\InvalidArgumentException("Header is not valid UTF-8 string.");
90
$this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
101
public function getHeader($name)
103
return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
112
public function clearHeader($name)
114
unset($this->headers[$name]);
120
* Returns an encoded header.
125
public function getEncodedHeader($name)
127
$offset = strlen($name) + 2; // colon + space
129
if (!isset($this->headers[$name])) {
132
} elseif (is_array($this->headers[$name])) {
134
foreach ($this->headers[$name] as $email => $name) {
135
if ($name != NULL) { // intentionally ==
136
$s .= self::encodeHeader($name, $offset, strpbrk($name, '.,;<@>()[]"=?'));
137
$email = " <$email>";
140
if ($s !== '' && $offset + strlen($email) > self::LINE_LENGTH) {
141
$s .= self::EOL . "\t";
145
$offset += strlen($email);
147
return substr($s, 0, -1); // last comma
149
} elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) { // Content-Disposition
150
$offset += strlen($m[1]);
151
return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
154
return self::encodeHeader($this->headers[$name], $offset);
160
* Returns all headers.
163
public function getHeaders()
165
return $this->headers;
170
* Sets Content-Type header.
175
public function setContentType($contentType, $charset = NULL)
177
$this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
183
* Sets Content-Transfer-Encoding header.
187
public function setEncoding($encoding)
189
$this->setHeader('Content-Transfer-Encoding', $encoding);
195
* Returns Content-Transfer-Encoding header.
198
public function getEncoding()
200
return $this->getHeader('Content-Transfer-Encoding');
205
* Adds or creates new multipart.
208
public function addPart(MimePart $part = NULL)
210
return $this->parts[] = $part === NULL ? new self : $part;
218
public function setBody($body)
229
public function getBody()
235
/********************* building ****************d*g**/
239
* Returns encoded message.
242
public function generateMessage()
245
$boundary = '--------' . Strings::random();
247
foreach ($this->headers as $name => $value) {
248
$output .= $name . ': ' . $this->getEncodedHeader($name);
249
if ($this->parts && $name === 'Content-Type') {
250
$output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
252
$output .= self::EOL;
254
$output .= self::EOL;
256
$body = (string) $this->body;
258
switch ($this->getEncoding()) {
259
case self::ENCODING_QUOTED_PRINTABLE:
260
$output .= function_exists('quoted_printable_encode') ? quoted_printable_encode($body) : self::encodeQuotedPrintable($body);
263
case self::ENCODING_BASE64:
264
$output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
267
case self::ENCODING_7BIT:
268
$body = preg_replace('#[\x80-\xFF]+#', '', $body);
269
// break intentionally omitted
271
case self::ENCODING_8BIT:
272
$body = str_replace(array("\x00", "\r"), '', $body);
273
$body = str_replace("\n", self::EOL, $body);
278
throw new Nette\InvalidStateException('Unknown encoding.');
283
if (substr($output, -strlen(self::EOL)) !== self::EOL) {
284
$output .= self::EOL;
286
foreach ($this->parts as $part) {
287
$output .= '--' . $boundary . self::EOL . $part->generateMessage() . self::EOL;
289
$output .= '--' . $boundary.'--';
296
/********************* QuotedPrintable helpers ****************d*g**/
300
* Converts a 8 bit header to a string.
306
private static function encodeHeader($s, & $offset = 0, $force = FALSE)
309
if ($offset >= 55) { // maximum for iconv_mime_encode
310
$o = self::EOL . "\t";
314
if (!$force && strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}=? _\r\n\t") === strlen($s)
315
&& ($offset + strlen($s) <= self::LINE_LENGTH)
317
$offset += strlen($s);
321
$o .= str_replace("\n ", "\n\t", substr(iconv_mime_encode(str_repeat(' ', $offset), $s, array(
322
'scheme' => 'B', // Q is broken
323
'input-charset' => 'UTF-8',
324
'output-charset' => 'UTF-8',
327
$offset = strlen($o) - strrpos($o, "\n");
333
* Converts a 8 bit string to a quoted-printable string.