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 = 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 ($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 ($name)
105: {
106: return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
107: }
108:
109:
110:
111: 112: 113: 114: 115:
116: public function ($name)
117: {
118: unset($this->headers[$name]);
119: return $this;
120: }
121:
122:
123:
124: 125: 126: 127: 128: 129:
130: public function ($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=)"(.*)"\z#', $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 ()
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: 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 ($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: