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 $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 => $recipient) {
74: if ($recipient !== NULL && !NStrings::checkEncoding($recipient)) {
75: NValidators::assert($recipient, 'unicode', "header '$name'");
76: }
77: if (preg_match('#[\r\n]#', $recipient)) {
78: throw new InvalidArgumentException("Name must not contain line separator.");
79: }
80: NValidators::assert($email, 'email', "header '$name'");
81: $tmp[$email] = $recipient;
82: }
83:
84: } else {
85: $value = (string) $value;
86: if (!NStrings::checkEncoding($value)) {
87: throw new InvalidArgumentException("Header is not valid UTF-8 string.");
88: }
89: $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
90: }
91: return $this;
92: }
93:
94:
95:
96: 97: 98: 99: 100:
101: public function getHeader($name)
102: {
103: return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
104: }
105:
106:
107:
108: 109: 110: 111: 112:
113: public function clearHeader($name)
114: {
115: unset($this->headers[$name]);
116: return $this;
117: }
118:
119:
120:
121: 122: 123: 124: 125: 126:
127: public function getEncodedHeader($name)
128: {
129: $offset = strlen($name) + 2;
130:
131: if (!isset($this->headers[$name])) {
132: return NULL;
133:
134: } elseif (is_array($this->headers[$name])) {
135: $s = '';
136: foreach ($this->headers[$name] as $email => $name) {
137: if ($name != NULL) {
138: $s .= self::encodeHeader(
139: strpbrk($name, '.,;<@>()[]"=?') ? '"' . addcslashes($name, '"\\') . '"' : $name,
140: $offset
141: );
142: $email = " <$email>";
143: }
144: $email .= ',';
145: if ($s !== '' && $offset + strlen($email) > self::LINE_LENGTH) {
146: $s .= self::EOL . "\t";
147: $offset = 1;
148: }
149: $s .= $email;
150: $offset += strlen($email);
151: }
152: return substr($s, 0, -1);
153:
154: } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"$#', $this->headers[$name], $m)) {
155: $offset += strlen($m[1]);
156: return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
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: public function addPart(NMailMimePart $part = NULL)
219: {
220: return $this->parts[] = $part === NULL ? new self : $part;
221: }
222:
223:
224:
225: 226: 227: 228: 229:
230: public function setBody($body)
231: {
232: $this->body = $body;
233: return $this;
234: }
235:
236:
237:
238: 239: 240: 241:
242: public function getBody()
243: {
244: return $this->body;
245: }
246:
247:
248:
249:
250:
251:
252:
253: 254: 255: 256:
257: public function generateMessage()
258: {
259: $output = '';
260: $boundary = '--------' . NStrings::random();
261:
262: foreach ($this->headers as $name => $value) {
263: $output .= $name . ': ' . $this->getEncodedHeader($name);
264: if ($this->parts && $name === 'Content-Type') {
265: $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
266: }
267: $output .= self::EOL;
268: }
269: $output .= self::EOL;
270:
271: $body = (string) $this->body;
272: if ($body !== '') {
273: switch ($this->getEncoding()) {
274: case self::ENCODING_QUOTED_PRINTABLE:
275: $output .= function_exists('quoted_printable_encode') ? quoted_printable_encode($body) : self::encodeQuotedPrintable($body);
276: break;
277:
278: case self::ENCODING_BASE64:
279: $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
280: break;
281:
282: case self::ENCODING_7BIT:
283: $body = preg_replace('#[\x80-\xFF]+#', '', $body);
284:
285:
286: case self::ENCODING_8BIT:
287: $body = str_replace(array("\x00", "\r"), '', $body);
288: $body = str_replace("\n", self::EOL, $body);
289: $output .= $body;
290: break;
291:
292: default:
293: throw new InvalidStateException('Unknown encoding.');
294: }
295: }
296:
297: if ($this->parts) {
298: if (substr($output, -strlen(self::EOL)) !== self::EOL) {
299: $output .= self::EOL;
300: }
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: ) {
334: $offset += strlen($s);
335: return $o . $s;
336: }
337:
338: $o .= str_replace("\n ", "\n\t", substr(iconv_mime_encode(str_repeat(' ', $offset), $s, array(
339: 'scheme' => 'B',
340: 'input-charset' => 'UTF-8',
341: 'output-charset' => 'UTF-8',
342: )), $offset + 2));
343:
344: $offset = strlen($o) - strrpos($o, "\n");
345: return $o;
346: }
347:
348:
349:
350: 351: 352: 353: 354: public static function encodeQuotedPrintable($s)
355: {
356: $range = '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}';
357: $pos = 0;
358: $len = 0;
359: $o = '';
360: $size = strlen($s);
361: while ($pos < $size) {
362: if ($l = strspn($s, $range, $pos)) {
363: while ($len + $l > self::LINE_LENGTH - 1) {
364: $lx = self::LINE_LENGTH - $len - 1;
365: $o .= substr($s, $pos, $lx) . '=' . self::EOL;
366: $pos += $lx;
367: $l -= $lx;
368: $len = 0;
369: }
370: $o .= substr($s, $pos, $l);
371: $len += $l;
372: $pos += $l;
373:
374: } else {
375: $len += 3;
376: if ($len > self::LINE_LENGTH - 1) {
377: $o .= '=' . self::EOL;
378: $len = 3;
379: }
380: $o .= '=' . strtoupper(bin2hex($s[$pos]));
381: $pos++;
382: }
383: }
384: return rtrim($o, '=' . self::EOL);
385: }
386:
387: }
388: