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