]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/mailhandler.php
Introduced isCurrentProfileInScope() which shall check if current profile is
[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('Mail/mimeDecode.php');
22
23 // @todo FIXME: we use both Mail_mimeDecode and mailparse
24 // Need to move everything to mailparse
25
26 class MailHandler
27 {
28     function __construct()
29     {
30     }
31
32     function handle_message($rawmessage)
33     {
34         list($from, $to, $msg, $attachments) = $this->parse_message($rawmessage);
35         if (!$from || !$to || !$msg) {
36             // TRANS: Error message in incoming mail handler used when an incoming e-mail cannot be processed.
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             // TRANS: Error message in incoming mail handler used when an incoming e-mail is not from a registered user.
43             $this->error($from, _('Not a registered user.'));
44             return false;
45         }
46         if (!$this->user_match_to($user, $to)) {
47             // TRANS: Error message in incoming mail handler used when an incoming e-mail is not from a user's incoming e-mail address.
48             $this->error($from, _('Sorry, that is not your incoming email address.'));
49             return false;
50         }
51         if (!$user->emailpost) {
52             // TRANS: Error message in incoming mail handler used when no incoming e-mail is allowed.
53             $this->error($from, _('Sorry, no incoming email allowed.'));
54             return false;
55         }
56         $response = $this->handle_command($user, $from, $msg);
57         if ($response) {
58             return true;
59         }
60         $msg = $this->cleanup_msg($msg);
61         $msg = $user->shortenLinks($msg);
62         if (Notice::contentTooLong($msg)) {
63             // TRANS: Error message in incoming mail handler used when an incoming e-mail contains too many characters.
64             $this->error($from, sprintf(_m('That\'s too long. Maximum notice size is %d character.',
65                                           'That\'s too long. Maximum notice size is %d characters.',
66                                           Notice::maxContent()),
67                                         Notice::maxContent()));
68         }
69
70         $mediafiles = array();
71
72         foreach($attachments as $attachment){
73             $mf = null;
74
75             try {
76                 $mf = MediaFile::fromFilehandle($attachment, $user->getProfile());
77             } catch(ClientException $ce) {
78                 $this->error($from, $ce->getMessage());
79             }
80
81             $msg .= ' ' . $mf->shortUrl();
82
83             array_push($mediafiles, $mf);
84             fclose($attachment);
85         }
86
87         $err = $this->add_notice($user, $msg, $mediafiles);
88
89         if (is_string($err)) {
90             $this->error($from, $err);
91             return false;
92         } else {
93             return true;
94         }
95     }
96
97     function error($from, $msg)
98     {
99         file_put_contents("php://stderr", $msg . "\n");
100         exit(1);
101     }
102
103     function user_from_header($from_hdr)
104     {
105         $froms = mailparse_rfc822_parse_addresses($from_hdr);
106         if (!$froms) {
107             return null;
108         }
109         $from = $froms[0];
110         $addr = common_canonical_email($from['address']);
111         $user = User::getKV('email', $addr);
112         if (!$user) {
113             $user = User::getKV('smsemail', $addr);
114         }
115         return $user;
116     }
117
118     function user_match_to($user, $to_hdr)
119     {
120         $incoming = $user->incomingemail;
121         $tos = mailparse_rfc822_parse_addresses($to_hdr);
122         foreach ($tos as $to) {
123             if (strcasecmp($incoming, $to['address']) == 0) {
124                 return true;
125             }
126         }
127         return false;
128     }
129
130     function handle_command($user, $from, $msg)
131     {
132         $inter = new CommandInterpreter();
133         $cmd = $inter->handle_command($user, $msg);
134         if ($cmd) {
135             $cmd->execute(new MailChannel($from));
136             return true;
137         }
138         return false;
139     }
140
141     function respond($from, $to, $response)
142     {
143         $headers['From'] = $to;
144         $headers['To'] = $from;
145         // TRANS: E-mail subject for reply to an e-mail command.
146         $headers['Subject'] = _('Command complete');
147
148         return mail_send(array($from), $headers, $response);
149     }
150
151     function log($level, $msg)
152     {
153         common_log($level, 'MailDaemon: '.$msg);
154     }
155
156     function add_notice($user, $msg, $mediafiles)
157     {
158         try {
159             $notice = Notice::saveNew($user->id, $msg, 'mail');
160         } catch (Exception $e) {
161             $this->log(LOG_ERR, $e->getMessage());
162             return $e->getMessage();
163         }
164         foreach($mediafiles as $mf){
165             $mf->attachToNotice($notice);
166         }
167
168         $this->log(LOG_INFO,
169                    'Added notice ' . $notice->id . ' from user ' . $user->nickname);
170         return true;
171     }
172
173     function parse_message($contents)
174     {
175         $parsed = Mail_mimeDecode::decode(array('input' => $contents,
176                                                 'include_bodies' => true,
177                                                 'decode_headers' => true,
178                                                 'decode_bodies' => true));
179         if (!$parsed) {
180             return null;
181         }
182
183         $from = $parsed->headers['from'];
184
185         $to = $parsed->headers['to'];
186
187         $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
188
189         $attachments = array();
190
191         $this->extract_part($parsed,$msg,$attachments);
192
193         return array($from, $to, $msg, $attachments);
194     }
195
196     function extract_part($parsed,&$msg,&$attachments){
197         if ($parsed->ctype_primary == 'multipart') {
198             if($parsed->ctype_secondary == 'alternative'){
199                 $altmsg = $this->extract_msg_from_multipart_alternative_part($parsed);
200                 if(!empty($altmsg)) $msg = $altmsg;
201             }else{
202                 foreach($parsed->parts as $part){
203                     $this->extract_part($part,$msg,$attachments);
204                 }
205             }
206         } else if ($parsed->ctype_primary == 'text'
207             && $parsed->ctype_secondary=='plain') {
208             $msg = $parsed->body;
209             if(strtolower($parsed->ctype_parameters['charset']) != "utf-8"){
210                 $msg = utf8_encode($msg);
211             }
212         }else if(!empty($parsed->body)){
213             if(common_config('attachments', 'uploads')){
214                 //only save attachments if uploads are enabled
215                 $attachment = tmpfile();
216                 fwrite($attachment, $parsed->body);
217                 $attachments[] = $attachment;
218             }
219         }
220     }
221
222     function extract_msg_from_multipart_alternative_part($parsed){
223         foreach ($parsed->parts as $part) {
224             $this->extract_part($part,$msg,$attachments);
225         }
226         //we don't want any attachments that are a result of this parsing
227         return $msg;
228     }
229
230     function unsupported_type($type)
231     {
232         // TRANS: Error message in incoming mail handler used when an incoming e-mail is of an unsupported type.
233         // TRANS: %s is the unsupported type.
234         $this->error(null, sprintf(_('Unsupported message type: %s.'), $type));
235     }
236
237     function cleanup_msg($msg)
238     {
239         $lines = explode("\n", $msg);
240
241         $output = '';
242
243         foreach ($lines as $line) {
244             // skip quotes
245             if (preg_match('/^\s*>.*$/', $line)) {
246                 continue;
247             }
248             // skip start of quote
249             if (preg_match('/^\s*On.*wrote:\s*$/', $line)) {
250                 continue;
251             }
252             // probably interesting to someone, not us
253             if (preg_match('/^\s*Sent via/', $line)) {
254                 continue;
255             }
256             if (preg_match('/^\s*Sent from my/', $line)) {
257                 continue;
258             }
259
260             // skip everything after a sig
261             if (preg_match('/^\s*--+\s*$/', $line) ||
262                 preg_match('/^\s*__+\s*$/', $line))
263             {
264                 break;
265             }
266             // skip everything after Outlook quote
267             if (preg_match('/^\s*-+\s*Original Message\s*-+\s*$/', $line)) {
268                 break;
269             }
270             // skip everything after weird forward
271             if (preg_match('/^\s*Begin\s+forward/', $line)) {
272                 break;
273             }
274             // skip everything after a blank line if we already have content
275             if ($output !== '' && $line === '') {
276                 break;
277             }
278
279             $output .= ' ' . $line;
280         }
281
282         preg_replace('/\s+/', ' ', $output);
283         return trim($output);
284     }
285 }