aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php
blob: df92914650ef2538a38a607268df898d1443d88e (plain) (blame)
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
<?php

declare(strict_types=1);

namespace Sabre\DAV\Xml\Element;

use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * WebDAV {DAV:}response parser.
 *
 * This class parses the {DAV:}response element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4918#section-14.24
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Response implements Element
{
    /**
     * Url for the response.
     *
     * @var string
     */
    protected $href;

    /**
     * Propertylist, ordered by HTTP status code.
     *
     * @var array
     */
    protected $responseProperties;

    /**
     * The HTTP status for an entire response.
     *
     * This is currently only used in WebDAV-Sync
     *
     * @var string|null
     */
    protected $httpStatus;

    /**
     * The href argument is a url relative to the root of the server. This
     * class will calculate the full path.
     *
     * The responseProperties argument is a list of properties
     * within an array with keys representing HTTP status codes
     *
     * Besides specific properties, the entire {DAV:}response element may also
     * have a http status code.
     * In most cases you don't need it.
     *
     * This is currently used by the Sync extension to indicate that a node is
     * deleted.
     *
     * @param string $href
     * @param string $httpStatus
     */
    public function __construct($href, array $responseProperties, $httpStatus = null)
    {
        $this->href = $href;
        $this->responseProperties = $responseProperties;
        $this->httpStatus = $httpStatus;
    }

    /**
     * Returns the url.
     *
     * @return string
     */
    public function getHref()
    {
        return $this->href;
    }

    /**
     * Returns the httpStatus value.
     *
     * @return string
     */
    public function getHttpStatus()
    {
        return $this->httpStatus;
    }

    /**
     * Returns the property list.
     *
     * @return array
     */
    public function getResponseProperties()
    {
        return $this->responseProperties;
    }

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     */
    public function xmlSerialize(Writer $writer)
    {
        /*
         * Accordingly to the RFC the element looks like:
         * <!ELEMENT response (href, ((href*, status)|(propstat+)), error?, responsedescription? , location?) >
         *
         * So the response
         *   - MUST contain a href and
         *   - EITHER a status and additional href(s)
         *     OR one or more propstat(s)
         */
        $writer->writeElement('{DAV:}href', $writer->contextUri.\Sabre\HTTP\encodePath($this->getHref()));

        $empty = true;
        $httpStatus = $this->getHTTPStatus();

        // Add propstat elements
        foreach ($this->getResponseProperties() as $status => $properties) {
            // Skipping empty lists
            if (!$properties || (!is_int($status) && !ctype_digit($status))) {
                continue;
            }
            $empty = false;
            $writer->startElement('{DAV:}propstat');
            $writer->writeElement('{DAV:}prop', $properties);
            $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
            $writer->endElement(); // {DAV:}propstat
        }

        // The WebDAV spec only allows the status element on responses _without_ a propstat
        if ($empty) {
            if (null !== $httpStatus) {
                $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$httpStatus.' '.\Sabre\HTTP\Response::$statusCodes[$httpStatus]);
            } else {
                /*
                * The WebDAV spec _requires_ at least one DAV:propstat to appear for
                * every DAV:response if there is no status.
                * In some circumstances however, there are no properties to encode.
                *
                * In those cases we MUST specify at least one DAV:propstat anyway, with
                * no properties.
                */
                $writer->writeElement('{DAV:}propstat', [
                    '{DAV:}prop' => [],
                    '{DAV:}status' => 'HTTP/1.1 418 '.\Sabre\HTTP\Response::$statusCodes[418],
                ]);
            }
        }
    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @return mixed
     */
    public static function xmlDeserialize(Reader $reader)
    {
        $reader->pushContext();

        $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';

        // We are overriding the parser for {DAV:}prop. This deserializer is
        // almost identical to the one for Sabre\Xml\Element\KeyValue.
        //
        // The difference is that if there are any child-elements inside of
        // {DAV:}prop, that have no value, normally any deserializers are
        // called. But we don't want this, because a singular element without
        // child-elements implies 'no value' in {DAV:}prop, so we want to skip
        // deserializers and just set null for those.
        $reader->elementMap['{DAV:}prop'] = function (Reader $reader) {
            if ($reader->isEmptyElement) {
                $reader->next();

                return [];
            }

            if (!$reader->read()) {
                $reader->next();

                return [];
            }

            if (Reader::END_ELEMENT === $reader->nodeType) {
                $reader->next();

                return [];
            }

            $values = [];

            do {
                if (Reader::ELEMENT === $reader->nodeType) {
                    $clark = $reader->getClark();

                    if ($reader->isEmptyElement) {
                        $values[$clark] = null;
                        $reader->next();
                    } else {
                        $values[$clark] = $reader->parseCurrentElement()['value'];
                    }
                } else {
                    if (!$reader->read()) {
                        break;
                    }
                }
            } while (Reader::END_ELEMENT !== $reader->nodeType);

            $reader->read();

            return $values;
        };
        $elems = $reader->parseInnerTree();
        $reader->popContext();

        $href = null;
        $propertyLists = [];
        $statusCode = null;

        foreach ($elems as $elem) {
            switch ($elem['name']) {
                case '{DAV:}href':
                    $href = $elem['value'];
                    break;
                case '{DAV:}propstat':
                    $status = $elem['value']['{DAV:}status'];
                    list(, $status) = explode(' ', $status, 3);
                    $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
                    if ($properties) {
                        $propertyLists[$status] = $properties;
                    }
                    break;
                case '{DAV:}status':
                    list(, $statusCode) = explode(' ', $elem['value'], 3);
                    break;
            }
        }

        return new self($href, $propertyLists, $statusCode);
    }
}