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