1<?php
2
3namespace Sabre\CalDAV;
4
5use Sabre\DAV;
6use Sabre\DAVACL;
7use Sabre\HTTP;
8
9require_once 'Sabre/HTTP/ResponseMock.php';
10
11class ValidateICalTest extends \PHPUnit_Framework_TestCase {
12
13    /**
14     * @var Sabre\DAV\Server
15     */
16    protected $server;
17    /**
18     * @var Sabre\CalDAV\Backend\Mock
19     */
20    protected $calBackend;
21
22    function setUp() {
23
24        $calendars = [
25            [
26                'id'                                                              => 'calendar1',
27                'principaluri'                                                    => 'principals/admin',
28                'uri'                                                             => 'calendar1',
29                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT', 'VTODO', 'VJOURNAL']),
30            ],
31            [
32                'id'                                                              => 'calendar2',
33                'principaluri'                                                    => 'principals/admin',
34                'uri'                                                             => 'calendar2',
35                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VJOURNAL']),
36            ]
37        ];
38
39        $this->calBackend = new Backend\Mock($calendars, []);
40        $principalBackend = new DAVACL\PrincipalBackend\Mock();
41
42        $tree = [
43            new CalendarRoot($principalBackend, $this->calBackend),
44        ];
45
46        $this->server = new DAV\Server($tree);
47        $this->server->sapi = new HTTP\SapiMock();
48        $this->server->debugExceptions = true;
49
50        $plugin = new Plugin();
51        $this->server->addPlugin($plugin);
52
53        $response = new HTTP\ResponseMock();
54        $this->server->httpResponse = $response;
55
56    }
57
58    function request(HTTP\Request $request) {
59
60        $this->server->httpRequest = $request;
61        $this->server->exec();
62
63        return $this->server->httpResponse;
64
65    }
66
67    function testCreateFile() {
68
69        $request = HTTP\Sapi::createFromServerArray([
70            'REQUEST_METHOD' => 'PUT',
71            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
72        ]);
73
74        $response = $this->request($request);
75
76        $this->assertEquals(415, $response->status);
77
78    }
79
80    function testCreateFileValid() {
81
82        $request = new HTTP\Request(
83            'PUT',
84            '/calendars/admin/calendar1/blabla.ics',
85            ['Prefer' => 'handling=strict']
86        );
87
88        $ics = <<<ICS
89BEGIN:VCALENDAR
90VERSION:2.0
91PRODID:foo
92BEGIN:VEVENT
93UID:foo
94DTSTAMP:20160406T052348Z
95DTSTART:20160706T140000Z
96END:VEVENT
97END:VCALENDAR
98ICS;
99
100        $request->setBody($ics);
101
102        $response = $this->request($request);
103
104        $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
105        $this->assertEquals([
106            'X-Sabre-Version' => [DAV\Version::VERSION],
107            'Content-Length'  => ['0'],
108            'ETag'            => ['"' . md5($ics) . '"'],
109        ], $response->getHeaders());
110
111        $expected = [
112            'uri'          => 'blabla.ics',
113            'calendardata' => $ics,
114            'calendarid'   => 'calendar1',
115            'lastmodified' => null,
116        ];
117
118        $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics'));
119
120    }
121
122    function testCreateFileNoVersion() {
123
124        $request = new HTTP\Request(
125            'PUT',
126            '/calendars/admin/calendar1/blabla.ics',
127            ['Prefer' => 'handling=strict']
128        );
129
130        $ics = <<<ICS
131BEGIN:VCALENDAR
132PRODID:foo
133BEGIN:VEVENT
134UID:foo
135DTSTAMP:20160406T052348Z
136DTSTART:20160706T140000Z
137END:VEVENT
138END:VCALENDAR
139ICS;
140
141        $request->setBody($ics);
142
143        $response = $this->request($request);
144
145        $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
146
147    }
148
149    function testCreateFileNoVersionFixed() {
150
151        $request = new HTTP\Request(
152            'PUT',
153            '/calendars/admin/calendar1/blabla.ics',
154            ['Prefer' => 'handling=lenient']
155        );
156
157        $ics = <<<ICS
158BEGIN:VCALENDAR
159PRODID:foo
160BEGIN:VEVENT
161UID:foo
162DTSTAMP:20160406T052348Z
163DTSTART:20160706T140000Z
164END:VEVENT
165END:VCALENDAR
166ICS;
167
168        $request->setBody($ics);
169
170        $response = $this->request($request);
171
172        $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
173        $this->assertEquals([
174            'X-Sabre-Version'  => [DAV\Version::VERSION],
175            'Content-Length'   => ['0'],
176            'X-Sabre-Ew-Gross' => ['iCalendar validation warning: VERSION MUST appear exactly once in a VCALENDAR component'],
177        ], $response->getHeaders());
178
179        $ics = <<<ICS
180BEGIN:VCALENDAR\r
181VERSION:2.0\r
182PRODID:foo\r
183BEGIN:VEVENT\r
184UID:foo\r
185DTSTAMP:20160406T052348Z\r
186DTSTART:20160706T140000Z\r
187END:VEVENT\r
188END:VCALENDAR\r
189
190ICS;
191
192        $expected = [
193            'uri'          => 'blabla.ics',
194            'calendardata' => $ics,
195            'calendarid'   => 'calendar1',
196            'lastmodified' => null,
197        ];
198
199        $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics'));
200
201    }
202
203    function testCreateFileNoComponents() {
204
205        $request = new HTTP\Request(
206            'PUT',
207            '/calendars/admin/calendar1/blabla.ics',
208            ['Prefer' => 'handling=strict']
209        );
210        $ics = <<<ICS
211BEGIN:VCALENDAR
212VERSION:2.0
213PRODID:foo
214END:VCALENDAR
215ICS;
216
217        $request->setBody($ics);
218
219        $response = $this->request($request);
220        $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
221
222    }
223
224    function testCreateFileNoUID() {
225
226        $request = HTTP\Sapi::createFromServerArray([
227            'REQUEST_METHOD' => 'PUT',
228            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
229        ]);
230        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
231
232        $response = $this->request($request);
233
234        $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
235
236    }
237
238    function testCreateFileVCard() {
239
240        $request = HTTP\Sapi::createFromServerArray([
241            'REQUEST_METHOD' => 'PUT',
242            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
243        ]);
244        $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n");
245
246        $response = $this->request($request);
247
248        $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
249
250    }
251
252    function testCreateFile2Components() {
253
254        $request = HTTP\Sapi::createFromServerArray([
255            'REQUEST_METHOD' => 'PUT',
256            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
257        ]);
258        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VJOURNAL\r\nUID:foo\r\nEND:VJOURNAL\r\nEND:VCALENDAR\r\n");
259
260        $response = $this->request($request);
261
262        $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
263
264    }
265
266    function testCreateFile2UIDS() {
267
268        $request = HTTP\Sapi::createFromServerArray([
269            'REQUEST_METHOD' => 'PUT',
270            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
271        ]);
272        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:bar\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
273
274        $response = $this->request($request);
275
276        $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
277
278    }
279
280    function testCreateFileWrongComponent() {
281
282        $request = HTTP\Sapi::createFromServerArray([
283            'REQUEST_METHOD' => 'PUT',
284            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
285        ]);
286        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VFREEBUSY\r\nUID:foo\r\nEND:VFREEBUSY\r\nEND:VCALENDAR\r\n");
287
288        $response = $this->request($request);
289
290        $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
291
292    }
293
294    function testUpdateFile() {
295
296        $this->calBackend->createCalendarObject('calendar1', 'blabla.ics', 'foo');
297        $request = HTTP\Sapi::createFromServerArray([
298            'REQUEST_METHOD' => 'PUT',
299            'REQUEST_URI'    => '/calendars/admin/calendar1/blabla.ics',
300        ]);
301
302        $response = $this->request($request);
303
304        $this->assertEquals(415, $response->status);
305
306    }
307
308    function testUpdateFileParsableBody() {
309
310        $this->calBackend->createCalendarObject('calendar1', 'blabla.ics', 'foo');
311        $request = new HTTP\Request(
312            'PUT',
313            '/calendars/admin/calendar1/blabla.ics'
314        );
315        $ics = <<<ICS
316BEGIN:VCALENDAR
317VERSION:2.0
318PRODID:foo
319BEGIN:VEVENT
320UID:foo
321DTSTAMP:20160406T052348Z
322DTSTART:20160706T140000Z
323END:VEVENT
324END:VCALENDAR
325ICS;
326
327        $request->setBody($ics);
328        $response = $this->request($request);
329
330        $this->assertEquals(204, $response->status);
331
332        $expected = [
333            'uri'          => 'blabla.ics',
334            'calendardata' => $ics,
335            'calendarid'   => 'calendar1',
336            'lastmodified' => null,
337        ];
338
339        $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics'));
340
341    }
342
343    function testCreateFileInvalidComponent() {
344
345        $request = HTTP\Sapi::createFromServerArray([
346            'REQUEST_METHOD' => 'PUT',
347            'REQUEST_URI'    => '/calendars/admin/calendar2/blabla.ics',
348        ]);
349        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
350
351        $response = $this->request($request);
352
353        $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
354
355    }
356
357    function testUpdateFileInvalidComponent() {
358
359        $this->calBackend->createCalendarObject('calendar2', 'blabla.ics', 'foo');
360        $request = HTTP\Sapi::createFromServerArray([
361            'REQUEST_METHOD' => 'PUT',
362            'REQUEST_URI'    => '/calendars/admin/calendar2/blabla.ics',
363        ]);
364        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
365
366        $response = $this->request($request);
367
368        $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
369
370    }
371
372    /**
373     * What we are testing here, is if we send in a latin1 character, the
374     * server should automatically transform this into UTF-8.
375     *
376     * More importantly. If any transformation happens, the etag must no longer
377     * be returned by the server.
378     */
379    function testCreateFileModified() {
380
381        $request = new HTTP\Request(
382            'PUT',
383            '/calendars/admin/calendar1/blabla.ics'
384        );
385        $ics = <<<ICS
386BEGIN:VCALENDAR
387VERSION:2.0
388PRODID:foo
389BEGIN:VEVENT
390UID:foo
391SUMMARY:Meeting in M\xfcnster
392DTSTAMP:20160406T052348Z
393DTSTART:20160706T140000Z
394END:VEVENT
395END:VCALENDAR
396ICS;
397
398        $request->setBody($ics);
399
400        $response = $this->request($request);
401
402        $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
403        $this->assertNull($response->getHeader('ETag'));
404
405    }
406}
407