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