1<?php
2/**
3 * Horde_ActiveSync_Request_ResolveRecipients::
4 *
5 * @license   http://www.horde.org/licenses/gpl GPLv2
6 *
7 * @copyright 2012-2020 Horde LLC (http://www.horde.org)
8 * @author    Michael J Rubinsky <mrubinsk@horde.org>
9 * @package   ActiveSync
10 */
11/**
12 * ActiveSync Handler for resolving recipients.
13 *
14 * @license   http://www.horde.org/licenses/gpl GPLv2
15 *
16 * @copyright 2012-2020 Horde LLC (http://www.horde.org)
17 * @author    Michael J Rubinsky <mrubinsk@horde.org>
18 * @package   ActiveSync
19 * @internal
20 */
21class Horde_ActiveSync_Request_ResolveRecipients extends Horde_ActiveSync_Request_Base
22{
23    const TAG_RESOLVERECIPIENTS      = 'ResolveRecipients:ResolveRecipients';
24    const TAG_RESPONSE               = 'ResolveRecipients:Response';
25    const TAG_STATUS                 = 'ResolveRecipients:Status';
26    const TAG_TYPE                   = 'ResolveRecipients:Type';
27    const TAG_RECIPIENT              = 'ResolveRecipients:Recipient';
28    const TAG_DISPLAYNAME            = 'ResolveRecipients:DisplayName';
29    const TAG_EMAILADDRESS           = 'ResolveRecipients:EmailAddress';
30    const TAG_CERTIFICATES           = 'ResolveRecipients:Certificates';
31    const TAG_CERTIFICATE            = 'ResolveRecipients:Certificate';
32    const TAG_MINICERTIFICATE        = 'ResolveRecipients:MiniCertificate';
33    const TAG_OPTIONS                = 'ResolveRecipients:Options';
34    const TAG_TO                     = 'ResolveRecipients:To';
35    const TAG_CERTIFICATERETRIEVAL   = 'ResolveRecipients:CertificateRetrieval';
36    const TAG_RECIPIENTCOUNT         = 'ResolveRecipients:RecipientCount';
37    const TAG_MAXCERTIFICATES        = 'ResolveRecipients:MaxCertificates';
38    const TAG_MAXAMBIGUOUSRECIPIENTS = 'ResolveRecipients:MaxAmbiguousRecipients';
39    const TAG_CERTIFICATECOUNT       = 'ResolveRecipients:CertificateCount';
40    const TAG_MAXSIZE                = 'ResolveRecipients:MaxSize';
41    const TAG_DATA                   = 'ResolveRecipients:Data';
42    const TAG_PICTURE                = 'ResolveRecipients:Picture';
43    const TAG_MAXPICTURES            = 'ResolveRecipients:MaxPictures';
44
45    // 14
46    const TAG_AVAILABILITY           = 'ResolveRecipients:Availability';
47    const TAG_STARTTIME              = 'ResolveRecipients:StartTime';
48    const TAG_ENDTIME                = 'ResolveRecipients:EndTime';
49    const TAG_MERGEDFREEBUSY         = 'ResolveRecipients:MergedFreeBusy';
50
51    /* Certificate Retrieval */
52    const CERT_RETRIEVAL_NONE        = 1;
53    const CERT_RETRIEVAL_FULL        = 2;
54    const CERT_RETRIEVAL_MINI        = 3;
55
56    /* Global Status */
57    const STATUS_SUCCESS             = 1;
58    const STATUS_PROTERR             = 5;
59    const STATUS_SERVERERR           = 6;
60
61    /* Response Status */
62    const STATUS_RESPONSE_SUCCESS    = 1;
63    const STATUS_RESPONSE_AMBSUGG    = 2;
64    const STATUS_RESPONSE_NONE       = 4;
65
66    /* Certificate Status */
67    const STATUS_CERT_SUCCESS        = 1;
68    const STATUS_CERT_NOCERT         = 7;
69    const STATUS_LIMIT               = 8;
70
71    /* Availability Status */
72    const STATUS_AVAIL_SUCCESS       = 1;
73    const STATUS_AVAIL_MAXRECIPIENTS = 160;
74    const STATUS_AVAIL_MAXLIST       = 161;
75    const STATUS_AVAIL_TEMPFAILURE   = 162;
76    const STATUS_AVAIL_NOTFOUND      = 163;
77
78    /**
79     * Handle the request
80     *
81     * @return boolean
82     * @throws Horde_ActiveSync_Exception
83     */
84    protected function _handle()
85    {
86        $this->_logger->meta('Handling RESOLVERECIPIENTS command.');
87        if (!$this->_decoder->getElementStartTag(self::TAG_RESOLVERECIPIENTS)) {
88            return false;
89        }
90        $status = self::STATUS_SUCCESS;
91
92        while ($status == self::STATUS_SUCCESS &&
93            ($field = ($this->_decoder->getElementStartTag(self::TAG_TO) ? self::TAG_TO :
94             ($this->_decoder->getElementStartTag(self::TAG_OPTIONS) ? self::TAG_OPTIONS :
95            -1))) != -1) {
96
97            if ($field == self::TAG_OPTIONS) {
98                while ($status == self::STATUS_SUCCESS &&
99                    ($option = ($this->_decoder->getElementStartTag(self::TAG_CERTIFICATERETRIEVAL) ? self::TAG_CERTIFICATERETRIEVAL :
100                    ($this->_decoder->getElementStartTag(self::TAG_MAXCERTIFICATES) ? self::TAG_MAXCERTIFICATES :
101                    ($this->_decoder->getElementStartTag(self::TAG_MAXAMBIGUOUSRECIPIENTS) ? self::TAG_MAXAMBIGUOUSRECIPIENTS :
102                    ($this->_decoder->getElementStartTag(self::TAG_AVAILABILITY) ? self::TAG_AVAILABILITY :
103                    ($this->_decoder->getElementStartTag(self::TAG_PICTURE) ? self::TAG_PICTURE :
104                    -1)))))) != -1) {
105
106                    if ($option == self::TAG_AVAILABILITY) {
107                        $options[self::TAG_AVAILABILITY] = true;
108                        while ($status == self::STATUS_SUCCESS &&
109                            ($tag = ($this->_decoder->getElementStartTag(self::TAG_STARTTIME) ? self::TAG_STARTTIME :
110                                ($this->_decoder->getElementStartTag(self::TAG_ENDTIME) ? self::TAG_ENDTIME :
111                                -1))) != -1) {
112                            $options[$tag] = $this->_decoder->getElementContent();
113                            if (!$this->_decoder->getElementEndTag()) {
114                                $status = self::STATUS_PROTERR;
115                            }
116                        }
117                    } elseif ($option == self::TAG_PICTURE) {
118                        $options[self::TAG_PICTURE] = true;
119                        while ($status == self::STATUS_SUCCESS &&
120                            ($tag = ($this->_decoder->getElementStartTag(self::TAG_MAXSIZE) ? self::TAG_MAXSIZE :
121                            ($this->_decoder->getElementStartTag(self::TAG_MAXPICTURES) ? self::TAG_MAXPICTURES :
122                            -1 ))) != -1) {
123
124                            if ($tag == self::TAG_MAXSIZE) {
125                                $options[self::TAG_MAXSIZE] = $this->_decoder->getElementContent();
126                                if (!$this->_decoder->getElementEndTag()) {
127                                    $status = self::STATUS_PROTERR;
128                                }
129                            }
130                            if ($tag == self::TAG_MAXPICTURES) {
131                                $options[self::TAG_MAXPICTURES] = $this->_decoder->getElementContent();
132                                if (!$this->_decoder->getElementEndTag()) {
133                                   $status = self::STATUS_PROTERR;
134                                }
135                            }
136                        }
137                    } else {
138                        $options[$option] = $this->_decoder->getElementContent();
139                    }
140
141                    if (!$this->_decoder->getElementEndTag()) {
142                        $status = self::STATUS_PROTERR;
143                    }
144                }
145                if (!$this->_decoder->getElementEndTag()) {
146                    $status = self::STATUS_PROTERR;
147                }
148            } elseif ($field == self::TAG_TO) {
149                $content = $this->_decoder->getElementContent();
150                $to[] = $content;
151                if (!$this->_decoder->getElementEndTag()) {
152                    $status = self::STATUS_PROTERR;
153                }
154            }
155        }
156
157        // Verify max isn't attempted.
158        if (isset($options[self::TAG_AVAILABILITY]) && count($to) > 100) {
159            // Specs say to send this, but it's defined as a child of the
160            // self::TAG_AVAILABILITY response. If we have too many recipients,
161            // we don't check the availability?? Not sure what to do with this.
162            // For now, treat it as a protocol error.
163            //$avail_status = self::STATUS_AVAIL_MAXRECIPIENTS;
164            $status = self::STATUS_PROTERR;
165        }
166
167        $results = array();
168        if ($status == self::STATUS_SUCCESS) {
169            foreach ($to as $item) {
170                $driver_opts = array(
171                    'maxcerts' => !empty($options[self::TAG_MAXCERTIFICATES]) ? $options[self::TAG_MAXCERTIFICATES] : false,
172                    'maxambiguous' => !empty($options[self::TAG_MAXAMBIGUOUSRECIPIENTS]) ? $options[self::TAG_MAXAMBIGUOUSRECIPIENTS] : false,
173                    'starttime' => !empty($options[self::TAG_STARTTIME]) ? new Horde_Date($options[self::TAG_STARTTIME], 'utc') : false,
174                    'endtime' => !empty($options[self::TAG_ENDTIME]) ? new Horde_Date($options[self::TAG_ENDTIME], 'utc') : false,
175                    'pictures' => !empty($options[self::TAG_PICTURE]),
176                    'maxsize' => !empty($options[self::TAG_MAXSIZE]) ? $options[self::TAG_MAXSIZE] : false,
177                    'maxpictures' => !empty($options[self::TAG_MAXPICTURES]) ? $options[self::TAG_MAXPICTURES] : false,
178                );
179                $results[$item] = $this->_driver->resolveRecipient(
180                    isset($options[self::TAG_CERTIFICATERETRIEVAL]) ? 'certificate' : 'availability',
181                    $item,
182                    $driver_opts
183                );
184            }
185        }
186
187        $this->_encoder->startWBXML();
188        $this->_encoder->startTag(self::TAG_RESOLVERECIPIENTS);
189
190        $this->_encoder->startTag(self::TAG_STATUS);
191        $this->_encoder->content($status);
192        $this->_encoder->endTag();
193
194        foreach ($to as $item) {
195            $this->_encoder->startTag(self::TAG_RESPONSE);
196
197            $this->_encoder->startTag(self::TAG_TO);
198            $this->_encoder->content($item);
199            $this->_encoder->endTag();
200
201            $this->_encoder->startTag(self::TAG_STATUS);
202            if (empty($results[$item])) {
203                $responseStatus = self::STATUS_RESPONSE_NONE;
204            } elseif (count($results[$item]) > 1) {
205                $responseStatus = self::STATUS_RESPONSE_AMBSUGG;
206            } else {
207                $responseStatus = self::STATUS_RESPONSE_SUCCESS;
208            }
209            $this->_encoder->content($responseStatus);
210            $this->_encoder->endTag();
211
212            $this->_encoder->startTag(self::TAG_RECIPIENTCOUNT);
213            $this->_encoder->content(count($results[$item]));
214            $this->_encoder->endTag();
215
216            foreach ($results[$item] as $value) {
217                $this->_encoder->startTag(self::TAG_RECIPIENT);
218
219                $this->_encoder->startTag(self::TAG_TYPE);
220                $this->_encoder->content($value['type']);
221                $this->_encoder->endTag();
222
223                $this->_encoder->startTag(self::TAG_DISPLAYNAME);
224                $this->_encoder->content($value['displayname']);
225                $this->_encoder->endTag();
226
227                $this->_encoder->startTag(self::TAG_EMAILADDRESS);
228                $this->_encoder->content($value['emailaddress']);
229                $this->_encoder->endTag();
230
231                if (isset($options[self::TAG_CERTIFICATERETRIEVAL]) &&
232                    $options[self::TAG_CERTIFICATERETRIEVAL] > 1) {
233
234                    $this->_encoder->startTag(self::TAG_CERTIFICATES);
235
236                    $this->_encoder->startTag(self::TAG_STATUS);
237                    if (count($value['entries']) == 0) {
238                        $certStatus = self::STATUS_CERT_NOCERT;
239                    } else {
240                        $certStatus = self::STATUS_CERT_SUCCESS;
241                    }
242                    $this->_encoder->content($certStatus);
243                    $this->_encoder->endTag();
244
245                    $this->_encoder->startTag(self::TAG_CERTIFICATECOUNT);
246                    $this->_encoder->content(count($value['entries']));
247                    $this->_encoder->endTag();
248
249                    switch ($options[self::TAG_CERTIFICATERETRIEVAL]) {
250                    case self::CERT_RETRIEVAL_FULL:
251                        foreach($value['entries'] as $cert) {
252                            $this->_encoder->startTag(self::TAG_CERTIFICATE);
253                            $this->_encoder->content($cert);
254                            $this->_encoder->endTag();
255                        }
256                        break;
257                    case self::CERT_RETRIEVAL_MINI:
258                        foreach($value['entries'] as $cert) {
259                            $this->_encoder->startTag(self::TAG_MINICERTIFICATE);
260                            $this->_encoder->content($cert);
261                            $this->_encoder->endTag();
262                        }
263                    }
264                    $this->_encoder->endTag();
265                }
266
267                if (isset($options[self::TAG_AVAILABILITY])) {
268                    $this->_encoder->startTag(self::TAG_AVAILABILITY);
269
270                    $this->_encoder->startTag(self::TAG_STATUS);
271                    $this->_encoder->content(empty($value['availability']) ? self::STATUS_AVAIL_NOTFOUND : self::STATUS_AVAIL_SUCCESS);
272                    $this->_encoder->endTag();
273
274                    if (!empty($value['availability'])) {
275                        $this->_encoder->startTag(self::TAG_MERGEDFREEBUSY);
276                        $this->_encoder->content($value['availability']);
277                        $this->_encoder->endTag();
278                    }
279                    $this->_encoder->endTag();
280                }
281
282                if ($this->_device->version >= Horde_ActiveSync::VERSION_FOURTEENONE &&
283                    isset($options[self::TAG_PICTURE]) &&
284                    !empty($value['picture'])) {
285
286                    $this->_encoder->startTag(self::TAG_PICTURE);
287                    $value['picture']->encodeStream($this->_encoder);
288                    $this->_encoder->endTag();
289                }
290
291                $this->_encoder->endTag(); // end self::TAG_RECIPIENT
292            }
293            $this->_encoder->endTag(); // end self::TAG_RESPONSE
294        }
295        $this->_encoder->endTag(); // end self::TAG_RESOLVERECIPIENTS
296
297        return true;
298    }
299
300}
301