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