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