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) {
69:
70: } elseif (!Strings::checkEncoding($recipient)) {
71: Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
72: } elseif (preg_match('#[\r\n]#', $recipient)) {
73: throw new Nette\InvalidArgumentException('Name must not contain line separator.');
74: }
75: Nette\Utils\Validators::assert($email, 'email', "header '$name'");
76: $tmp[$email] = $recipient;
77: }
78:
79: } else {
80: $value = (string) $value;
81: if (!Strings::checkEncoding($value)) {
82: throw new Nette\InvalidArgumentException('Header is not valid UTF-8 string.');
83: }
84: $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
85: }
86: return $this;
87: }
88:
89:
90: 91: 92: 93: 94:
95: public function ($name)
96: {
97: return isset($this->headers[$name]) ? $this->headers[$name] : null;
98: }
99:
100:
101: 102: 103: 104: 105:
106: public function ($name)
107: {
108: unset($this->headers[$name]);
109: return $this;
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:
206: public function setBody($body)
207: {
208: $this->body = (string) $body;
209: return $this;
210: }
211:
212:
213: 214: 215: 216:
217: public function getBody()
218: {
219: return $this->body;
220: }
221:
222:
223:
224:
225:
226: 227: 228: 229:
230: public function getEncodedMessage()
231: {
232: $output = '';
233: $boundary = '--------' . Nette\Utils\Random::generate();
234:
235: foreach ($this->headers as $name => $value) {
236: $output .= $name . ': ' . $this->getEncodedHeader($name);
237: if ($this->parts && $name === 'Content-Type') {
238: $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
239: }
240: $output .= self::EOL;
241: }
242: $output .= self::EOL;
243:
244: $body = $this->body;
245: if ($body !== '') {
246: switch ($this->getEncoding()) {
247: case self::ENCODING_QUOTED_PRINTABLE:
248: $output .= quoted_printable_encode($body);
249: break;
250:
251: case self::ENCODING_BASE64:
252: $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
253: break;
254:
255: case self::ENCODING_7BIT:
256: $body = preg_replace('#[\x80-\xFF]+#', '', $body);
257:
258:
259: case self::ENCODING_8BIT:
260: $body = str_replace(["\x00", "\r"], '', $body);
261: $body = str_replace("\n", self::EOL, $body);
262: $output .= $body;
263: break;
264:
265: default:
266: throw new Nette\InvalidStateException('Unknown encoding.');
267: }
268: }
269:
270: if ($this->parts) {
271: if (substr($output, -strlen(self::EOL)) !== self::EOL) {
272: $output .= self::EOL;
273: }
274: foreach ($this->parts as $part) {
275: $output .= '--' . $boundary . self::EOL . $part->getEncodedMessage() . self::EOL;
276: }
277: $output .= '--' . $boundary . '--';
278: }
279:
280: return $output;
281: }
282:
283:
284:
285:
286:
287: 288: 289: 290: 291: 292: 293:
294: private static function ($s, &$offset = 0, $quotes = false)
295: {
296: if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
297: if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) {
298: return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
299: }
300: return self::append($s, $offset);
301: }
302:
303: $o = '';
304: if ($offset >= 55) {
305: $o = self::EOL . "\t";
306: $offset = 1;
307: }
308:
309: $s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, [
310: 'scheme' => 'B',
311: 'input-charset' => 'UTF-8',
312: 'output-charset' => 'UTF-8',
313: ]);
314:
315: $offset = strlen($s) - strrpos($s, "\n");
316: $s = str_replace("\n ", "\n\t", substr($s, $old + 2));
317: return $o . $s;
318: }
319:
320:
321: private static function append($s, &$offset = 0)
322: {
323: if ($offset + strlen($s) > self::LINE_LENGTH) {
324: $offset = 1;
325: $s = self::EOL . "\t" . $s;
326: }
327: $offset += strlen($s);
328: return $s;
329: }
330: }
331: