1a74947141b89bb70115528f4a6846e51bddcf1c
[squirrelmail.git] / class / deliver / Deliver.class.php
1 <?php
2
3 /**
4 * Deliver.class.php
5 *
6 * Copyright (c) 1999-2002 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
8 *
9 * This contains all the functions needed to send messages through
10 * a delivery backend.
11 *
12 * $Id$
13 */
14
15 class Deliver {
16
17 function mail($message, $stream=false) {
18
19 $rfc822_header = $message->rfc822_header;
20 if (count($message->entities)) {
21 $boundary = $this->mimeBoundary();
22 $rfc822_header->contenttype->properties['boundary']=$boundary;
23 } else {
24 $boundary='';
25 }
26 $header = $this->prepareRFC822_Header($rfc822_header);
27 $raw_length = strlen($header);
28 if ($stream) {
29 $this->preWriteToStream($header);
30 $this->writeToStream($stream, $header);
31 } else {
32 /* DEBUG */
33 $out = htmlspecialchars($header);
34 $out = str_replace("\r\n",'<BR>',$out);
35 echo $out;
36 }
37 $this->writeBody($message, $stream, $raw_length, $boundary);
38 exit;
39 return $raw_length;
40
41 }
42
43 function writeBody($message, $stream, &$length_raw, $boundary='') {
44 if ($boundary) {
45 $s = '--'.$boundary."\r\n";
46 $s .= $this->prepareMIME_Header($message, $boundary);
47 $length_raw += strlen($s);
48 if ($stream) {
49 $this->preWriteToStream($s);
50 $this->writeToStream($stream, $s);
51 }
52 }
53 $this->writeBodyPart($message, $stream, $length_raw);
54 $boundary_depth = substr_count($message->entity_id,'.');
55 if ($boundary_depth) {
56 $boundary .= '_part'.$boundary_depth;
57 }
58 for ($i=0, $entCount=count($message->entities);$i<$entCount;$i++) {
59 $msg = $this->writeBody($message->entities[$i], $stream, $length_raw, $boundary);
60 }
61 if ($boundary) {
62 $s = '--'.$boundary."--\r\n";
63 $length_raw += strlen($s);
64 if ($stream) {
65 $this->preWriteToStream($s);
66 $this->writeToStream($stream, $s);
67 }
68 }
69 }
70
71 function writeBodyPart($message, $stream, &$length) {
72 switch ($message->type0) {
73 case 'text':
74 case 'message':
75 if ($message->body_part) {
76 $body_part = $message->body_part;
77 $length += $this->clean_crlf($body_part);
78 if ($stream) {
79 $this->preWriteToStream($body_part);
80 $this->writeToStream($stream, $body_part);
81 } else {
82 /* DEBUG */
83 $out = htmlspecialchars($tmp);
84 $out = str_replace("\r\n",'<BR>',$out);
85 echo $out;
86
87 }
88 } elseif ($message->att_local_name) {
89 $filename = $message->att_local_name;
90 $file = fopen ($filename, 'rb');
91 while ($tmp = fgets($file, 4096)) {
92 $length += $this->clean_crlf($tmp);
93 if ($stream) {
94 $this->preWriteToStream($tmp);
95 $this->writeToStream($stream, $tmp);
96 } else {
97 /* DEBUG */
98 $out = htmlspecialchars($tmp);
99 $out = str_replace("\r\n",'<BR>',$out);
100 echo $out;
101 }
102 }
103 fclose($file);
104 }
105 break;
106 default:
107 if ($message->body_part) {
108 $body_part = $message->body_part;
109 $length += $this->clean_crlf($body_part);
110 if ($stream) {
111 $this->writeToStream($stream, $body_part);
112 } else {
113 /* DEBUG */
114 $out = htmlspecialchars($body_part);
115 $out = str_replace("\r\n",'<BR>',$out);
116 echo $out;
117 }
118 } elseif ($message->att_local_name) {
119 $filename = $message->att_local_name;
120 $file = fopen ($filename, 'rb');
121 while ($tmp = fread($file, 1520)) {
122 $encoded = chunk_split(base64_encode($tmp));
123 $length += strlen($encoded);
124 if ($stream) {
125 $this->writeToStream($stream, $encoded);
126 } else {
127 /* DEBUG */
128 $out = htmlspecialchars($encoded);
129 $out = str_replace("\r\n",'<BR>',$out);
130 echo $out;
131 }
132 }
133 fclose($file);
134 }
135 break;
136 }
137 }
138
139 function clean_crlf(&$s) {
140 $s = str_replace("\r\n", "\n", $s);
141 $s = str_replace("\r", "\n", $s);
142 $s = str_replace("\n", "\r\n", $s);
143 return strlen($s);
144 }
145
146 function preWriteToStream(&$s) {
147 }
148
149 function writeToStream($stream, $data) {
150 }
151
152 function initStream($message, $length=0, $host='', $port='', $user='', $pass='') {
153 return $stream;
154 }
155
156 function getBcc($bcc) {
157 return false;
158 }
159
160 function prepareMIME_Header($message, $boundary) {
161 $mime_header = $message->mime_header;
162 $rn="\r\n";
163 $header = array();
164
165 $contenttype = 'Content-Type: '. $mime_header->type0 .'/'.
166 $mime_header->type1;
167 if (count($message->entities)) {
168 $contenttype .= ";\r\n " . 'boundary="'.$boundary.'"';
169 }
170 if (isset($mime_header->parameters['name'])) {
171 $contenttype .= ";\r\n " . 'name="'.
172 encodeHeader($mime_header->parameters['name']). '"';
173 }
174 $header[] = $contenttype . $rn;
175 if ($mime_header->description) {
176 $header[] .= 'Content-Description: ' . $mime_header->description . $rn;
177 }
178 if ($mime_header->encoding) {
179 $header[] .= 'Content-Transfer-Encoding: ' . $mime_header->encoding . $rn;
180 }
181 if ($mime_header->id) {
182 $header[] .= 'Content-ID: ' . $mime_header->id . $rn;
183 }
184 if ($mime_header->disposition) {
185 $contentdisp .= 'Content-Disposition: ' . $mime_header->disposition;
186 if (isset($mime_header->parameters['filename'])) {
187 $contentdisp .= ";\r\n " . 'filename="'.
188 encodeHeader($mime_header->parameters['filename']). '"';
189 }
190 $header[] = $contentdisp . $rn;
191 }
192 if ($mime_header->md5) {
193 $header[] .= 'Content-MD5: ' . $mime_header->md5 . $rn;
194 }
195 if ($mime_header->language) {
196 $header[] .= 'Content-Language: ' . $mime_header->language . $rn;
197 }
198
199 $cnt = count($header);
200 $hdr_s = '';
201 for ($i = 0 ; $i < $cnt ; $i++) {
202 $hdr_s .= $this->foldLine($header[$i], 78, ' ');
203 }
204 $header = $hdr_s;
205 $header .= $rn; /* One blank line to separate mimeheader and body-entity */
206 return $header;
207 }
208
209 function prepareRFC822_Header($rfc822_header) {
210 global $REMOTE_ADDR, $SERVER_NAME, $REMOTE_PORT;
211 global $version, $useSendmail, $username;
212 global $HTTP_VIA, $HTTP_X_FORWARDED_FOR;
213 global $REMOTE_HOST;
214 $rn = "\r\n";
215 /* This creates an RFC 822 date */
216 $date = date("D, j M Y H:i:s ", mktime()) . $this->timezone();
217 /* Create a message-id */
218 $message_id = '<' . $REMOTE_PORT . '.' . $REMOTE_ADDR . '.';
219 $message_id .= time() . '.squirrel@' . $SERVER_NAME .'>';
220 $old_message_id = $rfc822_header->message_id;
221 /* Make an RFC822 Received: line */
222 if (isset($REMOTE_HOST)) {
223 $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
224 } else {
225 $received_from = $REMOTE_ADDR;
226 }
227 if (isset($HTTP_VIA) || isset ($HTTP_X_FORWARDED_FOR)) {
228 if ($HTTP_X_FORWARDED_FOR == '') {
229 $HTTP_X_FORWARDED_FOR = 'unknown';
230 }
231 $received_from .= " (proxying for $HTTP_X_FORWARDED_FOR)";
232 }
233 $header = array();
234 $header[] = "Received: from $received_from" . $rn;
235 $header[] = " (SquirrelMail authenticated user $username)" . $rn;
236 $header[] = " by $SERVER_NAME with HTTP;" . $rn;
237 $header[] = " $date" . $rn;
238 /* Insert the rest of the header fields */
239 $header[] = 'Message-ID: '. $message_id . $rn;
240 if ($old_message_id) {
241 $header[] = 'In-Reply-To: '.$old_message_id . $rn;
242 $references = $this->calculate_references($rfc822_header->references,
243 $old_message_id, $rfc822_header->in_reply_to);
244
245 $header[] = 'References: '.$references . $rn;
246 }
247 $header[] = "Date: $date" . $rn;
248 $header[] = 'Subject: '.encodeHeader($rfc822_header->subject) . $rn;
249 $header[] = 'From: '. encodeHeader($rfc822_header->getAddr_s('from')) . $rn;
250 /* RFC2822 if from contains more then 1 address */
251 if (count($rfc822_header->from) > 1) {
252 $header[] = 'Sender: '. encodeHeader($rfc822_header->getAddr_s('sender')) . $rn;
253 }
254 $header[] = 'To: '. encodeHeader($rfc822_header->getAddr_s('to')) . $rn; // Who it's TO
255 if (count($rfc822_header->cc)) {
256 $header[] = 'Cc: '. encodeHeader($rfc822_header->getAddr_s('cc')) . $rn;
257 }
258 if (count($rfc822_header->reply_to)) {
259 $header[] = 'Reply-To: '. encodeHeader($rfc822_header->getAddr_s('reply_to')) . $rn;
260 }
261 /* Sendmail should return true. Default = false */
262 $bcc = $this->getBcc($rfc822_header->bcc);
263 if ($bcc && count($rfc822_header->bcc)) {
264 $header[] = 'Bcc: '. encodeHeader($rfc822_header->getAddr_s('bcc')) . $rn;
265 }
266 /* Identify SquirrelMail */
267 $header[] = "X-Mailer: SquirrelMail (version $version)" . $rn;
268 /* Do the MIME-stuff */
269 $header[] = "MIME-Version: 1.0" . $rn;
270 $contenttype = 'Content-Type: '. $rfc822_header->content_type->type0 .'/'.
271 $rfc822_header->content_type->type1;
272 if (count($rfc822_header->content_type->properties)) {
273 foreach ($rfc822_header->contenttype->properties as $k => $v) {
274 $contenttype .= ';'. "\r\n " .$k.'='.$v; /* FOLDING */
275 }
276 }
277 $header[] = $contenttype . $rn;
278 if ($rfc822_header->dnt) {
279 $dnt = $rfc822_header->getAddr_s('dnt');
280 /* Pegasus Mail */
281 $header[] = 'X-Confirm-Reading-To: '.$dnt. $rn;
282 /* RFC 2298 */
283 $header[] = 'Disposition-Notification-To: '.$dnt. $rn;
284 }
285 if ($rfc822_header->priority) {
286 $prio = $rfc822_header->priority;
287 $header[] = 'X-Priority: '.$prio. $rn;
288 switch($prio) {
289 case 1: $header[] = 'Importance: High'. $rn; break;
290 case 3: $header[] = 'Importance: Normal'. $rn; break;
291 case 5: $header[] = 'Importance: Low'. $rn; break;
292 default: break;
293 }
294 }
295 /* Insert headers from the $more_headers array */
296 if(count($rfc822_header->more_headers)) {
297 reset($rfc822_header->more_headers);
298 foreach ($rfc822_header->more_headers as $k => $v) {
299 $header[] = $k.': '.$v .$rn;
300 }
301 }
302 $cnt = count($header);
303 $hdr_s = '';
304 for ($i = 0 ; $i < $cnt ; $i++) {
305 $hdr_s .= $this->foldLine($header[$i], 78, ' ');
306 }
307 $header = $hdr_s;
308 $header .= $rn; /* One blank line to separate header and body */
309 return $header;
310 }
311
312 /*
313 * function for cleanly folding of headerlines
314 */
315 function foldLine($line, $length, $pre) {
316 $cnt = strlen($line);
317 $res = '';
318 if ($cnt > $length)
319 {
320 $fold_string = $pre.' '."\r\n";
321 for ($i=0;$i<($cnt-$length);$i++)
322 {
323 $fold_pos = 0;
324 /* first try to fold at delimiters */
325 for ($j=($i+$length); $j>$i; $j--)
326 {
327 switch ($line{$j})
328 {
329 case (','):
330 case (';'):
331 $fold_pos = $j;
332 break;
333 default:
334 break;
335 }
336 if ($fold_pos)
337 {
338 $j=$i;
339 }
340 }
341 if (!$fold_pos)
342 {
343 /* not succeed yet so we try at spaces and = */
344 for ($j=($i+$length); $j>$i; $j--)
345 {
346 switch ($line{$j})
347 {
348 case (' '):
349 case ('='):
350 $fold_pos = $j;
351 break;
352 default:
353 break;
354 }
355 if ($fold_pos)
356 {
357 $j=$i;
358 }
359 }
360 }
361 if (!$fold_pos)
362 {
363 /* clean folding didn't work */
364 $fold_pos = $i+$length;
365 }
366 $line = substr_replace($line,$line{$fold_pos}.$fold_string,$fold_pos,1);
367 $cnt += strlen($fold_string);
368 $i = $j + strlen($fold_string);
369 }
370 }
371 return $line;
372 }
373
374
375 function mimeBoundary () {
376 static $mimeBoundaryString;
377
378 if ( !isset( $mimeBoundaryString ) ||
379 $mimeBoundaryString == '') {
380 $mimeBoundaryString = '----=_' . date( 'YmdHis' ) . '_' .
381 mt_rand( 10000, 99999 );
382 }
383 return $mimeBoundaryString;
384 }
385
386 /* Time offset for correct timezone */
387 function timezone () {
388 global $invert_time;
389
390 $diff_second = date('Z');
391 if ($invert_time) {
392 $diff_second = - $diff_second;
393 }
394 if ($diff_second > 0) {
395 $sign = '+';
396 } else {
397 $sign = '-';
398 }
399 $diff_second = abs($diff_second);
400 $diff_hour = floor ($diff_second / 3600);
401 $diff_minute = floor (($diff_second-3600*$diff_hour) / 60);
402 $zonename = '('.strftime('%Z').')';
403 $result = sprintf ("%s%02d%02d %s", $sign, $diff_hour, $diff_minute,
404 $zonename);
405 return ($result);
406 }
407
408 function calculate_references($refer, $old_message_id, $old_in_reply_to) {
409 if (strlen($refer) > 2) {
410 $refer .= ' ' . $old_message_id;
411 } else {
412 if ($old_in_reply_to) {
413 $refer .= $old_in_reply_to . ' ' . $old_message_id;
414 } else {
415 $refer .= $old_message_id;
416 }
417 }
418 trim($refer);
419 return $refer;
420 }
421 }
422 ?>