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