1: <?php
2:
3: /**
4: * SMTP libs
5: * @package modules
6: * @subpackage smtp
7: */
8:
9: /**
10: * Build a MIME message
11: * @subpackage smtp/lib
12: */
13: class Hm_MIME_Msg {
14: private $bcc = '';
15: private $headers = array('X-Mailer' => 'Cypht', 'MIME-Version' => '1.0');
16: private $boundary = '';
17: private $attachments = array();
18: private $body = '';
19: private $text_body = '';
20: private $html = false;
21: private $final_msg = '';
22:
23: /* build mime message data */
24: function __construct($to, $subject, $body, $from, $html=false, $cc='', $bcc='', $in_reply_to_id='', $from_name='', $reply_to='', $delivery_receipt='', $schedule='', $profile_id = '') {
25: if ($cc) {
26: $this->headers['Cc'] = $cc;
27: }
28: if ($schedule) {
29: $this->headers['X-Schedule'] = $schedule;
30: $this->headers['X-Profile-ID'] = $profile_id;
31: }
32:
33: if ($in_reply_to_id) {
34: $this->headers['In-Reply-To'] = $in_reply_to_id;
35: }
36: $this->bcc = $bcc;
37: if ($from_name) {
38: $this->headers['From'] = sprintf('"%s" <%s>', $from_name, $from);
39: }
40: else {
41: $this->headers['From'] = $from;
42: }
43: if ($reply_to) {
44: $this->headers['Reply-To'] = $reply_to;
45: }
46: else {
47: $this->headers['Reply-To'] = $from;
48: }
49: if ($delivery_receipt) {
50: $this->headers['X-Delivery'] = $delivery_receipt;
51: }
52: $this->headers['To'] = $to;
53: $this->headers['Subject'] = html_entity_decode($subject, ENT_QUOTES);
54: $this->headers['Date'] = date('r');
55: $this->headers['Message-Id'] = '<'.md5(uniqid(rand(),1)).'@'.php_uname('n').'>';
56: $this->boundary = str_replace(array('=', '/', '+'), '', Hm_Crypt::unique_id(48));
57: $this->html = $html;
58: $this->body = $body;
59: }
60:
61: /* return headers array */
62: function get_headers() {
63: return $this->headers;
64: }
65:
66: /* add attachments */
67: function add_attachments($files) {
68: $this->attachments = $files;
69: }
70:
71: function process_attachments() {
72: $res = '';
73: $closing = false;
74: foreach ($this->attachments as $file) {
75: $content = Hm_Crypt::plaintext(@file_get_contents($file['filename']), Hm_Request_Key::generate());
76: if ($content) {
77: $closing = true;
78: if (array_key_exists('no_encoding', $file) || (array_key_exists('type', $file) && $file['type'] == 'message/rfc822')) {
79: $res .= sprintf("\r\n--%s\r\nContent-Type: %s; name=\"%s\"\r\nContent-Description: %s\r\n".
80: "Content-Disposition: attachment; filename=\"%s\"\r\nContent-Transfer-Encoding: 7bit\r\n\r\n%s",
81: $this->boundary, $file['type'], $file['name'], $file['name'], $file['name'], $content);
82: }
83: else {
84: $content = chunk_split(base64_encode($content));
85: $res .= sprintf("\r\n--%s\r\nContent-Type: %s; name=\"%s\"\r\nContent-Description: %s\r\n".
86: "Content-Disposition: attachment; filename=\"%s\"\r\nContent-Transfer-Encoding: base64\r\n\r\n%s",
87: $this->boundary, $file['type'], $file['name'], $file['name'], $file['name'], $content);
88: }
89:
90: }
91: }
92: if ($closing) {
93: $res = rtrim($res, "\r\n").sprintf("\r\n--%s--\r\n", $this->boundary);
94: }
95: return $res;
96: }
97:
98: /* output mime message */
99: function get_mime_msg() {
100: if (!empty($this->body)) {
101: $this->prep_message_body();
102: }
103: $res = '';
104: $headers = '';
105: foreach ($this->headers as $name => $val) {
106: if (!trim($val)) {
107: continue;
108: }
109: $headers .= sprintf("%s: %s\r\n", $name, rtrim($this->prep_fld($val, $name)));
110: }
111: if (!$this->final_msg) {
112: if ($this->html) {
113: $res .= $this->text_body;
114: }
115: $res .="\r\n".$this->body;
116: if (!empty($this->attachments)) {
117: $res .= $this->process_attachments();
118: }
119: }
120: else {
121: $res = $this->final_msg;
122: }
123: $this->final_msg = $res;
124: return $headers.$res;
125: }
126:
127: function set_auto_bcc($addr) {
128: $this->headers['Bcc'] = $addr;
129: $this->headers['X-Auto-Bcc'] = 'cypht';
130: }
131:
132: function quote_fld($val) {
133: if (!trim($val)) {
134: return '';
135: }
136: if (!preg_match("/^[a-zA-Z0-9 !#$%&'\*\+\-\/\=\?\^_`\{\|\}]+$/", $val)) {
137: return sprintf('"%s"', $val);
138: }
139: return $val;
140: }
141:
142: function split_val($val, $bsize) {
143: $count = ceil($bsize/75);
144: $size = round(mb_strlen($val)/$count);
145: return mb_str_split($val, $size);
146: }
147:
148: function encode_fld($val, $single=false) {
149: if ($single) {
150: $parts = array($val);
151: }
152: else {
153: $parts = explode(' ', $val);
154: }
155: $res = array();
156: $prior = false;
157: foreach ($parts as $v) {
158: if (preg_match('/(?:[^\x00-\x7F])/',$v) === 1) {
159: $bsize = round(((mb_strlen($v)*4)/3)+13);
160: if ($bsize > 75) {
161: foreach ($this->split_val($v, $bsize) as $slice) {
162: $res[] = $this->encode_fld($slice);
163: }
164: }
165: else {
166: if ($prior && !$single) {
167: $res[] = '=?UTF-8?B?'.base64_encode(' '.$v).'?=';
168: }
169: else {
170: $res[] = '=?UTF-8?B?'.base64_encode($v).'?=';
171: }
172: $prior = true;
173: }
174: }
175: else {
176: $prior = false;
177: $res[] = $v;
178: }
179: }
180: return implode(' ', $res);
181: }
182:
183: function prep_fld($val, $name) {
184: if (in_array($name, array('To', 'From', 'Cc', 'Reply-to'), true)) {
185: $res = array();
186: foreach(process_address_fld($val) as $vals) {
187: $display_name = $this->encode_fld($vals['label'], true);
188: $display_name = $this->quote_fld($display_name);
189: if ($display_name) {
190: $res[] = sprintf('%s <%s>', $display_name, $vals['email']);
191: }
192: else {
193: $res[] = sprintf('<%s>', $vals['email']);
194: }
195: }
196: return implode(', ', $res);
197: }
198: return $this->encode_fld($val);
199: }
200:
201: static function find_addresses($str) {
202: $res = array();
203: foreach (process_address_fld($str) as $vals) {
204: $res[] = $vals['email'];
205: }
206: return $res;
207: }
208:
209: function get_recipient_addresses() {
210: $res = array();
211: foreach (array('To', 'Cc', 'Bcc') as $fld) {
212: if ($fld == 'Bcc') {
213: $v = $this->bcc;
214: }
215: elseif (array_key_exists($fld, $this->headers)) {
216: $v = $this->headers[$fld];
217: }
218: else {
219: continue;
220: }
221: $res = array_merge($res, self::find_addresses($v));
222: }
223: return $res;
224: }
225:
226: function format_message_text($body) {
227: $message = trim($body);
228: $message = str_replace("\r\n", "\n", $message);
229: $lines = explode("\n", $message);
230: $new_lines = array();
231: foreach($lines as $line) {
232: $new_lines[] = trim($line, "\r\n")."\r\n";
233: }
234: return $this->qp_encode(implode('', $new_lines));
235: }
236:
237: function prep_message_body() {
238: $body = $this->body;
239: if (!$this->html) {
240: if (!empty($this->attachments)) {
241: $this->headers['Content-Type'] = 'multipart/mixed; boundary='.$this->boundary;
242: $body = sprintf("--%s\r\nContent-Type: text/plain; charset=UTF-8; format=flowed\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n%s",
243: $this->boundary, $this->format_message_text($body));
244: }
245: else {
246: $this->headers['Content-Type'] = 'text/plain; charset=UTF-8; format=flowed';
247: $this->headers['Content-Transfer-Encoding'] = 'quoted-printable';
248: $body = $this->format_message_text($body);
249: }
250: }
251: else {
252: $txt = convert_html_to_text($body);
253:
254: if (!empty($this->attachments)) {
255: $alt_boundary = Hm_Crypt::unique_id(32);
256: $this->headers['Content-Type'] = 'multipart/mixed; boundary='.$this->boundary;
257: $this->text_body = sprintf("--%s\r\nContent-Type: multipart/alternative; boundary=".
258: "\"%s\"\r\n\r\n--%s\r\nContent-Type: text/plain; charset=UTF-8; ".
259: "format=flowed\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n%s",
260: $this->boundary, $alt_boundary, $alt_boundary, $this->format_message_text($txt));
261:
262: $body = sprintf("--%s\r\nContent-Type: text/html; charset=UTF-8; format=flowed\r\n".
263: "Content-Transfer-Encoding: quoted-printable\r\n\r\n%s\r\n\r\n--%s--",
264: $alt_boundary, $this->format_message_text($body), $alt_boundary);
265: }
266: else {
267: $this->headers['Content-Type'] = 'multipart/alternative; boundary='.$this->boundary;
268: $this->text_body = sprintf("--%s\r\nContent-Type: text/plain; charset=UTF-8; ".
269: "format=flowed\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n%s",
270: $this->boundary, $this->format_message_text($txt));
271: $body = sprintf("--%s\r\nContent-Type: text/html; charset=UTF-8; format=flowed\r\n".
272: "Content-Transfer-Encoding: quoted-printable\r\n\r\n%s",
273: $this->boundary, $this->format_message_text($body));
274: }
275: }
276: $this->body = $body;
277: }
278:
279: function qp_encode($string) {
280: return str_replace('.', '=2E', quoted_printable_encode($string));
281: }
282: }
283: