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