1<?php
2
3namespace Sabre\DAV\Xml\Element;
4
5use Sabre\Xml\Element;
6use Sabre\Xml\Reader;
7use Sabre\Xml\Writer;
8
9/**
10 * WebDAV {DAV:}response parser
11 *
12 * This class parses the {DAV:}response element, as defined in:
13 *
14 * https://tools.ietf.org/html/rfc4918#section-14.24
15 *
16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17 * @author Evert Pot (http://www.rooftopsolutions.nl/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class Response implements Element {
21
22    /**
23     * Url for the response
24     *
25     * @var string
26     */
27    protected $href;
28
29    /**
30     * Propertylist, ordered by HTTP status code
31     *
32     * @var array
33     */
34    protected $responseProperties;
35
36    /**
37     * The HTTP status for an entire response.
38     *
39     * This is currently only used in WebDAV-Sync
40     *
41     * @var string
42     */
43    protected $httpStatus;
44
45    /**
46     * The href argument is a url relative to the root of the server. This
47     * class will calculate the full path.
48     *
49     * The responseProperties argument is a list of properties
50     * within an array with keys representing HTTP status codes
51     *
52     * Besides specific properties, the entire {DAV:}response element may also
53     * have a http status code.
54     * In most cases you don't need it.
55     *
56     * This is currently used by the Sync extension to indicate that a node is
57     * deleted.
58     *
59     * @param string $href
60     * @param array $responseProperties
61     * @param string $httpStatus
62     */
63    function __construct($href, array $responseProperties, $httpStatus = null) {
64
65        $this->href = $href;
66        $this->responseProperties = $responseProperties;
67        $this->httpStatus = $httpStatus;
68
69    }
70
71    /**
72     * Returns the url
73     *
74     * @return string
75     */
76    function getHref() {
77
78        return $this->href;
79
80    }
81
82    /**
83     * Returns the httpStatus value
84     *
85     * @return string
86     */
87    function getHttpStatus() {
88
89        return $this->httpStatus;
90
91    }
92
93    /**
94     * Returns the property list
95     *
96     * @return array
97     */
98    function getResponseProperties() {
99
100        return $this->responseProperties;
101
102    }
103
104
105    /**
106     * The serialize method is called during xml writing.
107     *
108     * It should use the $writer argument to encode this object into XML.
109     *
110     * Important note: it is not needed to create the parent element. The
111     * parent element is already created, and we only have to worry about
112     * attributes, child elements and text (if any).
113     *
114     * Important note 2: If you are writing any new elements, you are also
115     * responsible for closing them.
116     *
117     * @param Writer $writer
118     * @return void
119     */
120    function xmlSerialize(Writer $writer) {
121
122        if ($status = $this->getHTTPStatus()) {
123            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
124        }
125        $writer->writeElement('{DAV:}href', $writer->contextUri . \Sabre\HTTP\encodePath($this->getHref()));
126
127        $empty = true;
128
129        foreach ($this->getResponseProperties() as $status => $properties) {
130
131            // Skipping empty lists
132            if (!$properties || (!ctype_digit($status) && !is_int($status))) {
133                continue;
134            }
135            $empty = false;
136            $writer->startElement('{DAV:}propstat');
137            $writer->writeElement('{DAV:}prop', $properties);
138            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
139            $writer->endElement(); // {DAV:}propstat
140
141        }
142        if ($empty) {
143            /*
144             * The WebDAV spec _requires_ at least one DAV:propstat to appear for
145             * every DAV:response. In some circumstances however, there are no
146             * properties to encode.
147             *
148             * In those cases we MUST specify at least one DAV:propstat anyway, with
149             * no properties.
150             */
151            $writer->writeElement('{DAV:}propstat', [
152                '{DAV:}prop'   => [],
153                '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418]
154            ]);
155
156        }
157
158    }
159
160    /**
161     * The deserialize method is called during xml parsing.
162     *
163     * This method is called statically, this is because in theory this method
164     * may be used as a type of constructor, or factory method.
165     *
166     * Often you want to return an instance of the current class, but you are
167     * free to return other data as well.
168     *
169     * You are responsible for advancing the reader to the next element. Not
170     * doing anything will result in a never-ending loop.
171     *
172     * If you just want to skip parsing for this element altogether, you can
173     * just call $reader->next();
174     *
175     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
176     * the next element.
177     *
178     * @param Reader $reader
179     * @return mixed
180     */
181    static function xmlDeserialize(Reader $reader) {
182
183        $reader->pushContext();
184
185        $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';
186
187        // We are overriding the parser for {DAV:}prop. This deserializer is
188        // almost identical to the one for Sabre\Xml\Element\KeyValue.
189        //
190        // The difference is that if there are any child-elements inside of
191        // {DAV:}prop, that have no value, normally any deserializers are
192        // called. But we don't want this, because a singular element without
193        // child-elements implies 'no value' in {DAV:}prop, so we want to skip
194        // deserializers and just set null for those.
195        $reader->elementMap['{DAV:}prop'] = function(Reader $reader) {
196
197            if ($reader->isEmptyElement) {
198                $reader->next();
199                return [];
200            }
201            $values = [];
202            $reader->read();
203            do {
204                if ($reader->nodeType === Reader::ELEMENT) {
205                    $clark = $reader->getClark();
206
207                    if ($reader->isEmptyElement) {
208                        $values[$clark] = null;
209                        $reader->next();
210                    } else {
211                        $values[$clark] = $reader->parseCurrentElement()['value'];
212                    }
213                } else {
214                    $reader->read();
215                }
216            } while ($reader->nodeType !== Reader::END_ELEMENT);
217            $reader->read();
218            return $values;
219
220        };
221        $elems = $reader->parseInnerTree();
222        $reader->popContext();
223
224        $href = null;
225        $propertyLists = [];
226        $statusCode = null;
227
228        foreach ($elems as $elem) {
229
230            switch ($elem['name']) {
231
232                case '{DAV:}href' :
233                    $href = $elem['value'];
234                    break;
235                case '{DAV:}propstat' :
236                    $status = $elem['value']['{DAV:}status'];
237                    list(, $status, ) = explode(' ', $status, 3);
238                    $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
239                    if ($properties) $propertyLists[$status] = $properties;
240                    break;
241                case '{DAV:}status' :
242                    list(, $statusCode, ) = explode(' ', $elem['value'], 3);
243                    break;
244
245            }
246
247        }
248
249        return new self($href, $propertyLists, $statusCode);
250
251    }
252
253}
254