]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/mailhandler.php
Merge branch '1.0.x' of git://gitorious.org/statusnet/mainline
[quix0rs-gnu-social.git] / lib / mailhandler.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 require_once(INSTALLDIR . '/lib/mail.php');
21 require_once(INSTALLDIR . '/lib/mediafile.php');
22 require_once('Mail/mimeDecode.php');
23
24 // FIXME: we use both Mail_mimeDecode and mailparse
25 // Need to move everything to mailparse
26
27 class MailHandler
28 {
29     function __construct()
30     {
31     }
32
33     function handle_message($rawmessage)
34     {
35         list($from, $to, $msg, $attachments) = $this->parse_message($rawmessage);
36         if (!$from || !$to || !$msg) {
37             $this->error(null, _('Could not parse message.'));
38         }
39         common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20));
40         $user = $this->user_from_header($from);
41         if (!$user) {
42             $this->error($from, _('Not a registered user.'));
43             return false;
44         }
45         if (!$this->user_match_to($user, $to)) {
46             $this->error($from, _('Sorry, that is not your incoming email address.'));
47             return false;
48         }
49         if (!$user->emailpost) {
50             $this->error($from, _('Sorry, no incoming email allowed.'));
51             return false;
52         }
53         $response = $this->handle_command($user, $from, $msg);
54         if ($response) {
55             return true;
56         }
57         $msg = $this->cleanup_msg($msg);
58         $msg = $user->shortenLinks($msg);
59         if (Notice::contentTooLong($msg)) {
60             $this->error($from, sprintf(_('That\'s too long. Maximum notice size is %d character.',
61                                           'That\'s too long. Maximum notice size is %d characters.',
62                                           Notice::maxContent()),
63                                         Notice::maxContent()));
64         }
65
66         $mediafiles = array();
67
68         foreach($attachments as $attachment){
69
70             $mf = null;
71
72             try {
73                 $mf = MediaFile::fromFileHandle($attachment, $user);
74             } catch(ClientException $ce) {
75                 $this->error($from, $ce->getMessage());
76             }
77
78             $msg .= ' ' . $mf->shortUrl();
79
80             array_push($mediafiles, $mf);
81             fclose($attachment);
82         }
83
84         $err = $this->add_notice($user, $msg, $mediafiles);
85
86         if (is_string($err)) {
87             $this->error($from, $err);
88             return false;
89         } else {
90             return true;
91         }
92     }
93
94     function error($from, $msg)
95     {
96         file_put_contents("php://stderr", $msg . "\n");
97         exit(1);
98     }
99
100     function user_from_header($from_hdr)
101     {
102         $froms = mailparse_rfc822_parse_addresses($from_hdr);
103         if (!$froms) {
104             return null;
105         }
106         $from = $froms[0];
107         $addr = common_canonical_email($from['address']);
108         $user = User::staticGet('email', $addr);
109         if (!$user) {
110             $user = User::staticGet('smsemail', $addr);
111         }
112         return $user;
113     }
114
115     function user_match_to($user, $to_hdr)
116     {
117         $incoming = $user->incomingemail;
118         $tos = mailparse_rfc822_parse_addresses($to_hdr);
119         foreach ($tos as $to) {
120             if (strcasecmp($incoming, $to['address']) == 0) {
121                 return true;
122             }
123         }
124         return false;
125     }
126
127     function handle_command($user, $from, $msg)
128     {
129         $inter = new CommandInterpreter();
130         $cmd = $inter->handle_command($user, $msg);
131         if ($cmd) {
132             $cmd->execute(new MailChannel($from));
133             return true;
134         }
135         return false;
136     }
137
138     function respond($from, $to, $response)
139     {
140
141         $headers['From'] = $to;
142         $headers['To'] = $from;
143         $headers['Subject'] = _('Command complete');
144
145         return mail_send(array($from), $headers, $response);
146     }
147
148     function log($level, $msg)
149     {
150         common_log($level, 'MailDaemon: '.$msg);
151     }
152
153     function add_notice($user, $msg, $mediafiles)
154     {
155         try {
156             $notice = Notice::saveNew($user->id, $msg, 'mail');
157         } catch (Exception $e) {
158             $this->log(LOG_ERR, $e->getMessage());
159             return $e->getMessage();
160         }
161         foreach($mediafiles as $mf){
162             $mf->attachToNotice($notice);
163         }
164
165         $this->log(LOG_INFO,
166                    'Added notice ' . $notice->id . ' from user ' . $user->nickname);
167         return true;
168     }
169
170     function parse_message($contents)
171     {
172         $parsed = Mail_mimeDecode::decode(array('input' => $contents,
173                                                 'include_bodies' => true,
174                                                 'decode_headers' => true,
175                                                 'decode_bodies' => true));
176         if (!$parsed) {
177             return null;
178         }
179
180         $from = $parsed->headers['from'];
181
182         $to = $parsed->headers['to'];
183
184         $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
185
186         $attachments = array();
187
188         $this->extract_part($parsed,$msg,$attachments);
189
190         return array($from, $to, $msg, $attachments);
191     }
192
193     function extract_part($parsed,&$msg,&$attachments){
194         if ($parsed->ctype_primary == 'multipart') {
195             if($parsed->ctype_secondary == 'alternative'){
196                 $altmsg = $this->extract_msg_from_multipart_alternative_part($parsed);
197                 if(!empty($altmsg)) $msg = $altmsg;
198             }else{
199                 foreach($parsed->parts as $part){
200                     $this->extract_part($part,$msg,$attachments);
201                 }
202             }
203         } else if ($parsed->ctype_primary == 'text'
204             && $parsed->ctype_secondary=='plain') {
205             $msg = $parsed->body;
206             if(strtolower($parsed->ctype_parameters['charset']) != "utf-8"){
207                 $msg = utf8_encode($msg);
208             }
209         }else if(!empty($parsed->body)){
210             if(common_config('attachments', 'uploads')){
211                 //only save attachments if uploads are enabled
212                 $attachment = tmpfile();
213                 fwrite($attachment, $parsed->body);
214                 $attachments[] = $attachment;
215             }
216         }
217     }
218
219     function extract_msg_from_multipart_alternative_part($parsed){
220         foreach ($parsed->parts as $part) {
221             $this->extract_part($part,$msg,$attachments);
222         }
223         //we don't want any attachments that are a result of this parsing
224         return $msg;
225     }
226
227     function unsupported_type($type)
228     {
229         $this->error(null, sprintf(_('Unsupported message type: %s'), $type));
230     }
231
232     function cleanup_msg($msg)
233     {
234         $lines = explode("\n", $msg);
235
236         $output = '';
237
238         foreach ($lines as $line) {
239             // skip quotes
240             if (preg_match('/^\s*>.*$/', $line)) {
241                 continue;
242             }
243             // skip start of quote
244             if (preg_match('/^\s*On.*wrote:\s*$/', $line)) {
245                 continue;
246             }
247             // probably interesting to someone, not us
248             if (preg_match('/^\s*Sent via/', $line)) {
249                 continue;
250             }
251             if (preg_match('/^\s*Sent from my/', $line)) {
252                 continue;
253             }
254
255             // skip everything after a sig
256             if (preg_match('/^\s*--+\s*$/', $line) ||
257                 preg_match('/^\s*__+\s*$/', $line))
258             {
259                 break;
260             }
261             // skip everything after Outlook quote
262             if (preg_match('/^\s*-+\s*Original Message\s*-+\s*$/', $line)) {
263                 break;
264             }
265             // skip everything after weird forward
266             if (preg_match('/^\s*Begin\s+forward/', $line)) {
267                 break;
268             }
269             // skip everything after a blank line if we already have content
270             if ($output !== '' && $line === '') {
271                 break;
272             }
273
274             $output .= ' ' . $line;
275         }
276
277         preg_replace('/\s+/', ' ', $output);
278         return trim($output);
279     }
280 }