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