summaryrefslogtreecommitdiffstats
path: root/3rdparty/Sabre/DAV/XMLUtil.php
blob: 60eff3b159ac15bf34a4f09fd0f2d7a721542785 (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
<?php

/**
 * XML utilities for WebDAV
 *
 * @package Sabre
 * @subpackage DAV
 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
 */
class Sabre_DAV_XMLUtil {

    /**
     * Returns the 'clark notation' for an element.
     *
     * For example, and element encoded as:
     * <b:myelem xmlns:b="http://www.example.org/" />
     * will be returned as:
     * {http://www.example.org}myelem
     *
     * This format is used throughout the SabreDAV sourcecode.
     * Elements encoded with the urn:DAV namespace will
     * be returned as if they were in the DAV: namespace. This is to avoid
     * compatibility problems.
     *
     * This function will return null if a nodetype other than an Element is passed.
     *
     * @param DOMNode $dom
     * @return string
     */
    static function toClarkNotation(DOMNode $dom) {

        if ($dom->nodeType !== XML_ELEMENT_NODE) return null;

        // Mapping back to the real namespace, in case it was dav
        if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI;

        // Mapping to clark notation
        return '{' . $ns . '}' . $dom->localName;

    }

    /**
     * Parses a clark-notation string, and returns the namespace and element
     * name components.
     *
     * If the string was invalid, it will throw an InvalidArgumentException.
     *
     * @param string $str
     * @throws InvalidArgumentException
     * @return array
     */
    static function parseClarkNotation($str) {

        if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) {
            throw new InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
        }

        return array(
            $matches[1],
            $matches[2]
        );

    }

    /**
     * This method takes an XML document (as string) and converts all instances of the
     * DAV: namespace to urn:DAV
     *
     * This is unfortunately needed, because the DAV: namespace violates the xml namespaces
     * spec, and causes the DOM to throw errors
     *
     * @param string $xmlDocument
     * @return array|string|null
     */
    static function convertDAVNamespace($xmlDocument) {

        // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
        // namespace is actually a violation of the XML namespaces specification, and will cause errors
        return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument);

    }

    /**
     * This method provides a generic way to load a DOMDocument for WebDAV use.
     *
     * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors.
     * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
     *
     * @param string $xml
     * @throws Sabre_DAV_Exception_BadRequest
     * @return DOMDocument
     */
    static function loadDOMDocument($xml) {

        if (empty($xml))
            throw new Sabre_DAV_Exception_BadRequest('Empty XML document sent');

        // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower)
        // does not support this, so we must intercept this and convert to UTF-8.
        if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") {

            // Note: the preceeding byte sequence is "<?xml" encoded as UTF_16, without the BOM.
            $xml = iconv('UTF-16LE','UTF-8',$xml);

            // Because the xml header might specify the encoding, we must also change this.
            // This regex looks for the string encoding="UTF-16" and replaces it with
            // encoding="UTF-8".
            $xml = preg_replace('|<\?xml([^>]*)encoding="UTF-16"([^>]*)>|u','<?xml\1encoding="UTF-8"\2>',$xml);

        }

        // Retaining old error setting
        $oldErrorSetting =  libxml_use_internal_errors(true);

        // Clearing any previous errors
        libxml_clear_errors();

        $dom = new DOMDocument();
        $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR);

        // We don't generally care about any whitespace
        $dom->preserveWhiteSpace = false;

        if ($error = libxml_get_last_error()) {
            libxml_clear_errors();
            throw new Sabre_DAV_Exception_BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')');
        }

        // Restoring old mechanism for error handling
        if ($oldErrorSetting===false) libxml_use_internal_errors(false);

        return $dom;

    }

    /**
     * Parses all WebDAV properties out of a DOM Element
     *
     * Generally WebDAV properties are enclosed in {DAV:}prop elements. This
     * method helps by going through all these and pulling out the actual
     * propertynames, making them array keys and making the property values,
     * well.. the array values.
     *
     * If no value was given (self-closing element) null will be used as the
     * value. This is used in for example PROPFIND requests.
     *
     * Complex values are supported through the propertyMap argument. The
     * propertyMap should have the clark-notation properties as it's keys, and
     * classnames as values.
     *
     * When any of these properties are found, the unserialize() method will be
     * (statically) called. The result of this method is used as the value.
     *
     * @param DOMElement $parentNode
     * @param array $propertyMap
     * @return array
     */
    static function parseProperties(DOMElement $parentNode, array $propertyMap = array()) {

        $propList = array();
        foreach($parentNode->childNodes as $propNode) {

            if (Sabre_DAV_XMLUtil::toClarkNotation($propNode)!=='{DAV:}prop') continue;

            foreach($propNode->childNodes as $propNodeData) {

                /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */
                if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue;

                $propertyName = Sabre_DAV_XMLUtil::toClarkNotation($propNodeData);
                if (isset($propertyMap[$propertyName])) {
                    $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData);
                } else {
                    $propList[$propertyName] = $propNodeData->textContent;
                }
            }


        }
        return $propList;

    }

}