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:
29: class MimePart extends Nette\Object
30: {
31:
32: const ENCODING_BASE64 = 'base64',
33: ENCODING_7BIT = '7bit',
34: ENCODING_8BIT = '8bit',
35: ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
36:
37:
38: const EOL = "\r\n";
39: const LINE_LENGTH = 76;
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 Nette\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 => $recipient) {
77: if ($recipient !== NULL && !Strings::checkEncoding($recipient)) {
78: Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
79: }
80: if (preg_match('#[\r\n]#', $recipient)) {
81: throw new Nette\InvalidArgumentException("Name must not contain line separator.");
82: }
83: Nette\Utils\Validators::assert($email, 'email', "header '$name'");
84: $tmp[$email] = $recipient;
85: }
86:
87: } else {
88: $value = (string) $value;
89: if (!Strings::checkEncoding($value)) {
90: throw new Nette\InvalidArgumentException("Header is not valid UTF-8 string.");
91: }
92: $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
93: }
94: return $this;
95: }
96:
97:
98:
99: 100: 101: 102: 103:
104: public function getHeader($name)
105: {
106: return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
107: }
108:
109:
110:
111: 112: 113: 114: 115:
116: public function clearHeader($name)
117: {
118: unset($this->headers[$name]);
119: return $this;
120: }
121:
122:
123:
124: 125: 126: 127: 128: 129:
130: public function getEncodedHeader($name)
131: {
132: $offset = strlen($name) + 2;
133:
134: if (!isset($this->headers[$name])) {
135: return NULL;
136:
137: } elseif (is_array($this->headers[$name])) {
138: $s = '';
139: foreach ($this->headers[$name] as $email => $name) {
140: if ($name != NULL) {
141: $s .= self::encodeHeader(
142: strpbrk($name, '.,;<@>()[]"=?') ? '"' . addcslashes($name, '"\\') . '"' : $name,
143: $offset
144: );
145: $email = " <$email>";
146: }
147: $email .= ',';
148: if ($s !== '' && $offset + strlen($email) > self::LINE_LENGTH) {
149: $s .= self::EOL . "\t";
150: $offset = 1;
151: }
152: $s .= $email;
153: $offset += strlen($email);
154: }
155: return substr($s, 0, -1);
156:
157: } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"$#', $this->headers[$name], $m)) {
158: $offset += strlen($m[1]);
159: return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
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(MimePart $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 = '--------' . Strings::random();
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 Nette\InvalidStateException('Unknown encoding.');
298: }
299: }
300:
301: if ($this->parts) {
302: if (substr($output, -strlen(self::EOL)) !== self::EOL) {
303: $output .= self::EOL;
304: }
305: foreach ($this->parts as $part) {
306: $output .= '--' . $boundary . self::EOL . $part->generateMessage() . self::EOL;
307: }
308: $output .= '--' . $boundary.'--';
309: }
310:
311: return $output;
312: }
313:
314:
315:
316:
317:
318:
319:
320: 321: 322: 323: 324: 325: 326:
327: private static function encodeHeader($s, & $offset = 0)
328: {
329: $o = '';
330: if ($offset >= 55) {
331: $o = self::EOL . "\t";
332: $offset = 1;
333: }
334:
335: if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}=? _\r\n\t") === strlen($s)
336: && ($offset + strlen($s) <= self::LINE_LENGTH)
337: ) {
338: $offset += strlen($s);
339: return $o . $s;
340: }
341:
342: $o .= str_replace("\n ", "\n\t", substr(iconv_mime_encode(str_repeat(' ', $offset), $s, array(
343: 'scheme' => 'B',
344: 'input-charset' => 'UTF-8',
345: 'output-charset' => 'UTF-8',
346: )), $offset + 2));
347:
348: $offset = strlen($o) - strrpos($o, "\n");
349: return $o;
350: }
351:
352:
353:
354: 355: 356: 357: 358: }
359: