1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Mail;
9:
10: use Nette;
11: use Nette\Utils\Strings;
12:
13:
14: 15: 16: 17: 18:
19: class MimePart
20: {
21: use Nette\SmartObject;
22:
23:
24: const ENCODING_BASE64 = 'base64',
25: ENCODING_7BIT = '7bit',
26: ENCODING_8BIT = '8bit',
27: ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
28:
29:
30: const EOL = "\r\n";
31: const LINE_LENGTH = 76;
32:
33:
34: private = [];
35:
36:
37: private $parts = [];
38:
39:
40: private $body;
41:
42:
43: 44: 45: 46: 47: 48: 49:
50: public function ($name, $value, $append = FALSE)
51: {
52: if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
53: throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
54: }
55:
56: if ($value == NULL) {
57: if (!$append) {
58: unset($this->headers[$name]);
59: }
60:
61: } elseif (is_array($value)) {
62: $tmp = & $this->headers[$name];
63: if (!$append || !is_array($tmp)) {
64: $tmp = [];
65: }
66:
67: foreach ($value as $email => $recipient) {
68: if ($recipient !== NULL && !Strings::checkEncoding($recipient)) {
69: Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
70: }
71: if (preg_match('#[\r\n]#', $recipient)) {
72: throw new Nette\InvalidArgumentException('Name must not contain line separator.');
73: }
74: Nette\Utils\Validators::assert($email, 'email', "header '$name'");
75: $tmp[$email] = $recipient;
76: }
77:
78: } else {
79: $value = (string) $value;
80: if (!Strings::checkEncoding($value)) {
81: throw new Nette\InvalidArgumentException('Header is not valid UTF-8 string.');
82: }
83: $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
84: }
85: return $this;
86: }
87:
88:
89: 90: 91: 92: 93:
94: public function ($name)
95: {
96: return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
97: }
98:
99:
100: 101: 102: 103: 104:
105: public function ($name)
106: {
107: unset($this->headers[$name]);
108: return $this;
109: }
110:
111:
112: 113: 114: 115: 116: 117:
118: public function ($name)
119: {
120: $offset = strlen($name) + 2;
121:
122: if (!isset($this->headers[$name])) {
123: return NULL;
124:
125: } elseif (is_array($this->headers[$name])) {
126: $s = '';
127: foreach ($this->headers[$name] as $email => $name) {
128: if ($name != NULL) {
129: $s .= self::encodeHeader($name, $offset, TRUE);
130: $email = " <$email>";
131: }
132: $s .= self::append($email . ',', $offset);
133: }
134: return ltrim(substr($s, 0, -1));
135:
136: } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) {
137: $offset += strlen($m[1]);
138: return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
139:
140: } else {
141: return ltrim(self::encodeHeader($this->headers[$name], $offset));
142: }
143: }
144:
145:
146: 147: 148: 149:
150: public function ()
151: {
152: return $this->headers;
153: }
154:
155:
156: 157: 158: 159: 160: 161:
162: public function setContentType($contentType, $charset = NULL)
163: {
164: $this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
165: return $this;
166: }
167:
168:
169: 170: 171: 172: 173:
174: public function setEncoding($encoding)
175: {
176: $this->setHeader('Content-Transfer-Encoding', $encoding);
177: return $this;
178: }
179:
180:
181: 182: 183: 184:
185: public function getEncoding()
186: {
187: return $this->getHeader('Content-Transfer-Encoding');
188: }
189:
190:
191: 192: 193: 194:
195: public function addPart(MimePart $part = NULL)
196: {
197: return $this->parts[] = $part === NULL ? new self : $part;
198: }
199:
200:
201: 202: 203: 204:
205: public function setBody($body)
206: {
207: $this->body = (string) $body;
208: return $this;
209: }
210:
211:
212: 213: 214: 215:
216: public function getBody()
217: {
218: return $this->body;
219: }
220:
221:
222:
223:
224:
225: 226: 227: 228:
229: public function getEncodedMessage()
230: {
231: $output = '';
232: $boundary = '--------' . Nette\Utils\Random::generate();
233:
234: foreach ($this->headers as $name => $value) {
235: $output .= $name . ': ' . $this->getEncodedHeader($name);
236: if ($this->parts && $name === 'Content-Type') {
237: $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
238: }
239: $output .= self::EOL;
240: }
241: $output .= self::EOL;
242:
243: $body = (string) $this->body;
244: if ($body !== '') {
245: switch ($this->getEncoding()) {
246: case self::ENCODING_QUOTED_PRINTABLE:
247: $output .= quoted_printable_encode($body);
248: break;
249:
250: case self::ENCODING_BASE64:
251: $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
252: break;
253:
254: case self::ENCODING_7BIT:
255: $body = preg_replace('#[\x80-\xFF]+#', '', $body);
256:
257:
258: case self::ENCODING_8BIT:
259: $body = str_replace(["\x00", "\r"], '', $body);
260: $body = str_replace("\n", self::EOL, $body);
261: $output .= $body;
262: break;
263:
264: default:
265: throw new Nette\InvalidStateException('Unknown encoding.');
266: }
267: }
268:
269: if ($this->parts) {
270: if (substr($output, -strlen(self::EOL)) !== self::EOL) {
271: $output .= self::EOL;
272: }
273: foreach ($this->parts as $part) {
274: $output .= '--' . $boundary . self::EOL . $part->getEncodedMessage() . self::EOL;
275: }
276: $output .= '--' . $boundary.'--';
277: }
278:
279: return $output;
280: }
281:
282:
283:
284:
285:
286: 287: 288: 289: 290: 291: 292:
293: private static function ($s, & $offset = 0, $quotes = FALSE)
294: {
295: if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
296: if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) {
297: return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
298: }
299: return self::append($s, $offset);
300: }
301:
302: $o = '';
303: if ($offset >= 55) {
304: $o = self::EOL . "\t";
305: $offset = 1;
306: }
307:
308: $s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, [
309: 'scheme' => 'B',
310: 'input-charset' => 'UTF-8',
311: 'output-charset' => 'UTF-8',
312: ]);
313:
314: $offset = strlen($s) - strrpos($s, "\n");
315: $s = str_replace("\n ", "\n\t", substr($s, $old + 2));
316: return $o . $s;
317: }
318:
319:
320: private static function append($s, & $offset = 0)
321: {
322: if ($offset + strlen($s) > self::LINE_LENGTH) {
323: $offset = 1;
324: $s = self::EOL . "\t" . $s;
325: }
326: $offset += strlen($s);
327: return $s;
328: }
329:
330: }
331: