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