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