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, TRUE);
131: $email = " <$email>";
132: }
133: $s .= self::append($email . ',', $offset);
134: }
135: return ltrim(substr($s, 0, -1));
136:
137: } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) {
138: $offset += strlen($m[1]);
139: return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
140:
141: } else {
142: return ltrim(self::encodeHeader($this->headers[$name], $offset));
143: }
144: }
145:
146:
147: 148: 149: 150:
151: public function ()
152: {
153: return $this->headers;
154: }
155:
156:
157: 158: 159: 160: 161: 162:
163: public function setContentType($contentType, $charset = NULL)
164: {
165: $this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
166: return $this;
167: }
168:
169:
170: 171: 172: 173: 174:
175: public function setEncoding($encoding)
176: {
177: $this->setHeader('Content-Transfer-Encoding', $encoding);
178: return $this;
179: }
180:
181:
182: 183: 184: 185:
186: public function getEncoding()
187: {
188: return $this->getHeader('Content-Transfer-Encoding');
189: }
190:
191:
192: 193: 194: 195:
196: public function addPart(NMailMimePart $part = NULL)
197: {
198: return $this->parts[] = $part === NULL ? new self : $part;
199: }
200:
201:
202: 203: 204: 205:
206: public function setBody($body)
207: {
208: $this->body = $body;
209: return $this;
210: }
211:
212:
213: 214: 215: 216:
217: public function getBody()
218: {
219: return $this->body;
220: }
221:
222:
223:
224:
225:
226: 227: 228: 229:
230: public function generateMessage()
231: {
232: $output = '';
233: $boundary = '--------' . NStrings::random();
234:
235: foreach ($this->headers as $name => $value) {
236: $output .= $name . ': ' . $this->getEncodedHeader($name);
237: if ($this->parts && $name === 'Content-Type') {
238: $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
239: }
240: $output .= self::EOL;
241: }
242: $output .= self::EOL;
243:
244: $body = (string) $this->body;
245: if ($body !== '') {
246: switch ($this->getEncoding()) {
247: case self::ENCODING_QUOTED_PRINTABLE:
248: $output .= function_exists('quoted_printable_encode') ? quoted_printable_encode($body) : self::encodeQuotedPrintable($body);
249: break;
250:
251: case self::ENCODING_BASE64:
252: $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
253: break;
254:
255: case self::ENCODING_7BIT:
256: $body = preg_replace('#[\x80-\xFF]+#', '', $body);
257:
258:
259: case self::ENCODING_8BIT:
260: $body = str_replace(array("\x00", "\r"), '', $body);
261: $body = str_replace("\n", self::EOL, $body);
262: $output .= $body;
263: break;
264:
265: default:
266: throw new InvalidStateException('Unknown encoding.');
267: }
268: }
269:
270: if ($this->parts) {
271: if (substr($output, -strlen(self::EOL)) !== self::EOL) {
272: $output .= self::EOL;
273: }
274: foreach ($this->parts as $part) {
275: $output .= '--' . $boundary . self::EOL . $part->generateMessage() . self::EOL;
276: }
277: $output .= '--' . $boundary.'--';
278: }
279:
280: return $output;
281: }
282:
283:
284:
285:
286:
287: 288: 289: 290: 291: 292: 293:
294: private static function ($s, & $offset = 0, $quotes = FALSE)
295: {
296: if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
297: if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) {
298: return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
299: }
300: return self::append($s, $offset);
301: }
302:
303: $o = '';
304: if ($offset >= 55) {
305: $o = self::EOL . "\t";
306: $offset = 1;
307: }
308:
309: $s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, array(
310: 'scheme' => 'B',
311: 'input-charset' => 'UTF-8',
312: 'output-charset' => 'UTF-8',
313: ));
314:
315: $offset = strlen($s) - strrpos($s, "\n");
316: $s = str_replace("\n ", "\n\t", substr($s, $old + 2));
317: return $o . $s;
318: }
319:
320:
321: private static function append($s, & $offset = 0)
322: {
323: if ($offset + strlen($s) > self::LINE_LENGTH) {
324: $offset = 1;
325: $s = self::EOL . "\t" . $s;
326:
327: }
328: $offset += strlen($s);
329: return $s;
330: }
331:
332:
333: 334: 335: 336: 337: public static function encodeQuotedPrintable($s)
338: {
339: $range = '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}';
340: $pos = 0;
341: $len = 0;
342: $o = '';
343: $size = strlen($s);
344: while ($pos < $size) {
345: if ($l = strspn($s, $range, $pos)) {
346: while ($len + $l > self::LINE_LENGTH - 1) {
347: $lx = self::LINE_LENGTH - $len - 1;
348: $o .= substr($s, $pos, $lx) . '=' . self::EOL;
349: $pos += $lx;
350: $l -= $lx;
351: $len = 0;
352: }
353: $o .= substr($s, $pos, $l);
354: $len += $l;
355: $pos += $l;
356:
357: } else {
358: $len += 3;
359: if ($len > self::LINE_LENGTH - 1) {
360: $o .= '=' . self::EOL;
361: $len = 3;
362: }
363: $o .= '=' . strtoupper(bin2hex($s[$pos]));
364: $pos++;
365: }
366: }
367: return rtrim($o, '=' . self::EOL);
368: }
369:
370: }
371: