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