summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube/rcube_imap_search.php
blob: eac64b03535c16eb1bc50ec5d7ed637df2260d96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?php

/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 |                                                                       |
 | Copyright (C) 2013, The Roundcube Dev Team                            |
 | Copyright (C) 2014, Kolab Systems AG                                  |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Execute (multi-threaded) searches in multiple IMAP folders          |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 +-----------------------------------------------------------------------+
*/

/**
 * Class to control search jobs on multiple IMAP folders.
 *
 * @package    Framework
 * @subpackage Storage
 * @author     Thomas Bruederli <roundcube@gmail.com>
 */
class rcube_imap_search
{
    public $options = array();

    protected $jobs      = array();
    protected $timelimit = 0;
    protected $results;
    protected $conn;

    /**
     * Default constructor
     */
    public function __construct($options, $conn)
    {
        $this->options = $options;
        $this->conn    = $conn;
    }

    /**
     * Invoke search request to IMAP server
     *
     * @param  array   $folders    List of IMAP folders to search in
     * @param  string  $str        Search criteria
     * @param  string  $charset    Search charset
     * @param  string  $sort_field Header field to sort by
     * @param  boolean $threading  True if threaded listing is active
     */
    public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
    {
        $start   = floor(microtime(true));
        $results = new rcube_result_multifolder($folders);

        // start a search job for every folder to search in
        foreach ($folders as $folder) {
            // a complete result for this folder already exists
            $result = $this->results ? $this->results->get_set($folder) : false;
            if ($result && !$result->incomplete) {
                $results->add($result);
            }
            else {
                $search = is_array($str) && $str[$folder] ? $str[$folder] : $str;
                $job = new rcube_imap_search_job($folder, $search, $charset, $sort_field, $threading);
                $job->worker = $this;
                $this->jobs[] = $job;
            }
        }

        // execute jobs and gather results
        foreach ($this->jobs as $job) {
            // only run search if within the configured time limit
            // TODO: try to estimate the required time based on folder size and previous search performance
            if (!$this->timelimit || floor(microtime(true)) - $start < $this->timelimit) {
                $job->run();
            }

            // add result (may have ->incomplete flag set)
            $results->add($job->get_result());
        }

        return $results;
    }

    /**
     * Setter for timelimt property
     */
    public function set_timelimit($seconds)
    {
        $this->timelimit = $seconds;
    }

    /**
     * Setter for previous (potentially incomplete) search results
     */
    public function set_results($res)
    {
        $this->results = $res;
    }

    /**
     * Get connection to the IMAP server
     * (used for single-thread mode)
     */
    public function get_imap()
    {
        return $this->conn;
    }
}


/**
 * Stackable item to run the search on a specific IMAP folder
 */
class rcube_imap_search_job /* extends Stackable */
{
    private $folder;
    private $search;
    private $charset;
    private $sort_field;
    private $threading;
    private $result;

    public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
    {
        $this->folder     = $folder;
        $this->search     = $str;
        $this->charset    = $charset;
        $this->sort_field = $sort_field;
        $this->threading  = $threading;

        $this->result = new rcube_result_index($folder);
        $this->result->incomplete = true;
    }

    public function run()
    {
        $this->result = $this->search_index();
    }

    /**
     * Copy of rcube_imap::search_index()
     */
    protected function search_index()
    {
        $criteria = $this->search;
        $charset  = $this->charset;
        $imap     = $this->worker->get_imap();

        if (!$imap->connected()) {
            trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);

            if ($this->threading) {
                return new rcube_result_thread($this->folder);
            }
            else {
                return new rcube_result_index($this->folder);
            }
        }

        if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
            $criteria = 'UNDELETED '.$criteria;
        }

        // unset CHARSET if criteria string is ASCII, this way
        // SEARCH won't be re-sent after "unsupported charset" response
        if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
            $charset = 'US-ASCII';
        }

        if ($this->threading) {
            $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset);

            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
            // but I've seen that Courier doesn't support UTF-8)
            if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
                $threads = $imap->thread($this->folder, $this->threading,
                    rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
            }

            return $threads;
        }

        if ($this->sort_field) {
            $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset);

            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
            // but I've seen Courier with disabled UTF-8 support)
            if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
                $messages = $imap->sort($this->folder, $this->sort_field,
                    rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
            }
        }

        if (!$messages || $messages->is_error()) {
            $messages = $imap->search($this->folder,
                ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);

            // Error, try with US-ASCII (some servers may support only US-ASCII)
            if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
                $messages = $imap->search($this->folder,
                    rcube_imap::convert_criteria($criteria, $charset), true);
            }
        }

        return $messages;
    }

    public function get_search_set()
    {
        return array(
            $this->search,
            $this->result,
            $this->charset,
            $this->sort_field,
            $this->threading,
        );
    }

    public function get_result()
    {
        return $this->result;
    }
}