source: github/plugins/managesieve/lib/rcube_sieve.php @ a7c51ac

release-0.6
Last change on this file since a7c51ac was a7c51ac, checked in by alecpl <alec@…>, 21 months ago
  • Applied fixes from trunk up to r5202
  • Property mode set to 100644
File size: 11.1 KB
Line 
1<?php
2
3/**
4  Classes for managesieve operations (using PEAR::Net_Sieve)
5
6  Author: Aleksander Machniak <alec@alec.pl>
7
8  $Id$
9
10*/
11
12// Managesieve Protocol: RFC5804
13
14define('SIEVE_ERROR_CONNECTION', 1);
15define('SIEVE_ERROR_LOGIN', 2);
16define('SIEVE_ERROR_NOT_EXISTS', 3);    // script not exists
17define('SIEVE_ERROR_INSTALL', 4);       // script installation
18define('SIEVE_ERROR_ACTIVATE', 5);      // script activation
19define('SIEVE_ERROR_DELETE', 6);        // script deletion
20define('SIEVE_ERROR_INTERNAL', 7);      // internal error
21define('SIEVE_ERROR_DEACTIVATE', 8);    // script activation
22define('SIEVE_ERROR_OTHER', 255);       // other/unknown error
23
24
25class rcube_sieve
26{
27    private $sieve;                 // Net_Sieve object
28    private $error = false;         // error flag
29    private $list = array();        // scripts list
30
31    public $script;                 // rcube_sieve_script object
32    public $current;                // name of currently loaded script
33    private $disabled;              // array of disabled extensions
34    private $exts;                  // array of supported extensions
35
36
37    /**
38     * Object constructor
39     *
40     * @param string  Username (for managesieve login)
41     * @param string  Password (for managesieve login)
42     * @param string  Managesieve server hostname/address
43     * @param string  Managesieve server port number
44     * @param string  Managesieve authentication method
45     * @param boolean Enable/disable TLS use
46     * @param array   Disabled extensions
47     * @param boolean Enable/disable debugging
48     * @param string  Proxy authentication identifier
49     * @param string  Proxy authentication password
50     */
51    public function __construct($username, $password='', $host='localhost', $port=2000,
52        $auth_type=null, $usetls=true, $disabled=array(), $debug=false,
53        $auth_cid=null, $auth_pw=null)
54    {
55        $this->sieve = new Net_Sieve();
56
57        if ($debug) {
58            $this->sieve->setDebug(true, array($this, 'debug_handler'));
59        }
60
61        if (PEAR::isError($this->sieve->connect($host, $port, null, $usetls))) {
62            return $this->_set_error(SIEVE_ERROR_CONNECTION);
63        }
64
65        if (!empty($auth_cid)) {
66            $authz    = $username;
67            $username = $auth_cid;
68            $password = $auth_pw;
69        }
70
71        if (PEAR::isError($this->sieve->login($username, $password,
72            $auth_type ? strtoupper($auth_type) : null, $authz))
73        ) {
74            return $this->_set_error(SIEVE_ERROR_LOGIN);
75        }
76
77        $this->exts     = $this->get_extensions();
78        $this->disabled = $disabled;
79    }
80
81    public function __destruct() {
82        $this->sieve->disconnect();
83    }
84
85    /**
86     * Getter for error code
87     */
88    public function error()
89    {
90        return $this->error ? $this->error : false;
91    }
92
93    /**
94     * Saves current script into server
95     */
96    public function save($name = null)
97    {
98        if (!$this->sieve)
99            return $this->_set_error(SIEVE_ERROR_INTERNAL);
100
101        if (!$this->script)
102            return $this->_set_error(SIEVE_ERROR_INTERNAL);
103
104        if (!$name)
105            $name = $this->current;
106
107        $script = $this->script->as_text();
108
109        if (!$script)
110            $script = '/* empty script */';
111
112        if (PEAR::isError($this->sieve->installScript($name, $script)))
113            return $this->_set_error(SIEVE_ERROR_INSTALL);
114
115        return true;
116    }
117
118    /**
119     * Saves text script into server
120     */
121    public function save_script($name, $content = null)
122    {
123        if (!$this->sieve)
124            return $this->_set_error(SIEVE_ERROR_INTERNAL);
125
126        if (!$content)
127            $content = '/* empty script */';
128
129        if (PEAR::isError($this->sieve->installScript($name, $content)))
130            return $this->_set_error(SIEVE_ERROR_INSTALL);
131
132        return true;
133    }
134
135    /**
136     * Activates specified script
137     */
138    public function activate($name = null)
139    {
140        if (!$this->sieve)
141            return $this->_set_error(SIEVE_ERROR_INTERNAL);
142
143        if (!$name)
144            $name = $this->current;
145
146        if (PEAR::isError($this->sieve->setActive($name)))
147            return $this->_set_error(SIEVE_ERROR_ACTIVATE);
148
149        return true;
150    }
151
152    /**
153     * De-activates specified script
154     */
155    public function deactivate()
156    {
157        if (!$this->sieve)
158            return $this->_set_error(SIEVE_ERROR_INTERNAL);
159
160        if (PEAR::isError($this->sieve->setActive('')))
161            return $this->_set_error(SIEVE_ERROR_DEACTIVATE);
162
163        return true;
164    }
165
166    /**
167     * Removes specified script
168     */
169    public function remove($name = null)
170    {
171        if (!$this->sieve)
172            return $this->_set_error(SIEVE_ERROR_INTERNAL);
173
174        if (!$name)
175            $name = $this->current;
176
177        // script must be deactivated first
178        if ($name == $this->sieve->getActive())
179            if (PEAR::isError($this->sieve->setActive('')))
180                return $this->_set_error(SIEVE_ERROR_DELETE);
181
182        if (PEAR::isError($this->sieve->removeScript($name)))
183            return $this->_set_error(SIEVE_ERROR_DELETE);
184
185        if ($name == $this->current)
186            $this->current = null;
187
188        return true;
189    }
190
191    /**
192     * Gets list of supported by server Sieve extensions
193     */
194    public function get_extensions()
195    {
196        if ($this->exts)
197            return $this->exts;
198   
199        if (!$this->sieve)
200            return $this->_set_error(SIEVE_ERROR_INTERNAL);
201
202        $ext = $this->sieve->getExtensions();
203        // we're working on lower-cased names
204        $ext = array_map('strtolower', (array) $ext);
205
206        if ($this->script) {
207            $supported = $this->script->get_extensions();
208            foreach ($ext as $idx => $ext_name)
209                if (!in_array($ext_name, $supported))
210                    unset($ext[$idx]);
211        }
212
213        return array_values($ext);
214    }
215
216    /**
217     * Gets list of scripts from server
218     */
219    public function get_scripts()
220    {
221        if (!$this->list) {
222
223            if (!$this->sieve)
224                return $this->_set_error(SIEVE_ERROR_INTERNAL);
225
226            $list = $this->sieve->listScripts();
227
228            if (PEAR::isError($list))
229                return $this->_set_error(SIEVE_ERROR_OTHER);
230
231            $this->list = $list;
232        }
233
234        return $this->list;
235    }
236
237    /**
238     * Returns active script name
239     */
240    public function get_active()
241    {
242        if (!$this->sieve)
243            return $this->_set_error(SIEVE_ERROR_INTERNAL);
244
245        return $this->sieve->getActive();
246    }
247
248    /**
249     * Loads script by name
250     */
251    public function load($name)
252    {
253        if (!$this->sieve)
254            return $this->_set_error(SIEVE_ERROR_INTERNAL);
255
256        if ($this->current == $name)
257            return true;
258
259        $script = $this->sieve->getScript($name);
260
261        if (PEAR::isError($script))
262            return $this->_set_error(SIEVE_ERROR_OTHER);
263
264        // try to parse from Roundcube format
265        $this->script = $this->_parse($script);
266
267        $this->current = $name;
268
269        return true;
270    }
271
272    /**
273     * Loads script from text content
274     */
275    public function load_script($script)
276    {
277        if (!$this->sieve)
278            return $this->_set_error(SIEVE_ERROR_INTERNAL);
279
280        // try to parse from Roundcube format
281        $this->script = $this->_parse($script);
282    }
283
284    /**
285     * Creates rcube_sieve_script object from text script
286     */
287    private function _parse($txt)
288    {
289        // try to parse from Roundcube format
290        $script = new rcube_sieve_script($txt, $this->disabled, $this->exts);
291
292        // ... else try to import from different formats
293        if (empty($script->content)) {
294            $script = $this->_import_rules($txt);
295            $script = new rcube_sieve_script($script, $this->disabled, $this->exts);
296
297            // replace all elsif with if+stop, we support only ifs
298            foreach ($script->content as $idx => $rule) {
299                // 'stop' not found?
300                foreach ($rule['actions'] as $action) {
301                    if (preg_match('/^(stop|vacation)$/', $action['type'])) {
302                        continue 2;
303                    }
304                }
305                $script->content[$idx]['actions'][] = array('type' => 'stop');
306            }
307        }
308
309        return $script;
310    }
311
312    /**
313     * Gets specified script as text
314     */
315    public function get_script($name)
316    {
317        if (!$this->sieve)
318            return $this->_set_error(SIEVE_ERROR_INTERNAL);
319
320        $content = $this->sieve->getScript($name);
321
322        if (PEAR::isError($content))
323            return $this->_set_error(SIEVE_ERROR_OTHER);
324
325        return $content;
326    }
327
328    /**
329     * Creates empty script or copy of other script
330     */
331    public function copy($name, $copy)
332    {
333        if (!$this->sieve)
334            return $this->_set_error(SIEVE_ERROR_INTERNAL);
335
336        if ($copy) {
337            $content = $this->sieve->getScript($copy);
338
339            if (PEAR::isError($content))
340                return $this->_set_error(SIEVE_ERROR_OTHER);
341        }
342
343        return $this->save_script($name, $content);
344    }
345
346    private function _import_rules($script)
347    {
348        $i = 0;
349        $name = array();
350
351        // Squirrelmail (Avelsieve)
352        if (preg_match('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\r?\n/', $script)) {
353            $tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE);
354            foreach ($tokens as $token) {
355                if (preg_match('/^#START_SIEVE_RULE.*/', $token, $matches)) {
356                    $name[$i] = "unnamed rule ".($i+1);
357                    $content .= "# rule:[".$name[$i]."]\n";
358                }
359                elseif (isset($name[$i])) {
360                    // This preg_replace is added because I've found some Avelsieve scripts
361                    // with rules containing "if" here. I'm not sure it was working
362                    // before without this or not.
363                    $token = preg_replace('/^if\s+/', '', trim($token));
364                    $content .= "if $token\n";
365                    $i++;
366                }
367            }
368        }
369        // Horde (INGO)
370        else if (preg_match('/(# .+)\r?\n/', $script)) {
371            $tokens = preg_split('/(# .+)\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE);
372            foreach($tokens as $token) {
373                if (preg_match('/^# (.+)/', $token, $matches)) {
374                    $name[$i] = $matches[1];
375                    $content .= "# rule:[" . $name[$i] . "]\n";
376                }
377                elseif (isset($name[$i])) {
378                    $token = str_replace(":comparator \"i;ascii-casemap\" ", "", $token);
379                    $content .= $token . "\n";
380                    $i++;
381                }
382            }
383        }
384
385        return $content;
386    }
387
388    private function _set_error($error)
389    {
390        $this->error = $error;
391        return false;
392    }
393
394    /**
395     * This is our own debug handler for connection
396     */
397    public function debug_handler(&$sieve, $message)
398    {
399        write_log('sieve', preg_replace('/\r\n$/', '', $message));
400    }
401}
Note: See TracBrowser for help on using the repository browser.