1<?php
2
3/**
4 * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
5 * and 5, respectively.
6 *
7 * @package OpenID
8 */
9
10/**
11 * The base class for wrappers for available PHP XML-parsing
12 * extensions.  To work with this Yadis library, subclasses of this
13 * class MUST implement the API as defined in the remarks for this
14 * class.  Subclasses of Auth_Yadis_XMLParser are used to wrap
15 * particular PHP XML extensions such as 'domxml'.  These are used
16 * internally by the library depending on the availability of
17 * supported PHP XML extensions.
18 *
19 * @package OpenID
20 */
21class Auth_Yadis_XMLParser {
22    /**
23     * Initialize an instance of Auth_Yadis_XMLParser with some
24     * XML and namespaces.  This SHOULD NOT be overridden by
25     * subclasses.
26     *
27     * @param string $xml_string A string of XML to be parsed.
28     * @param array $namespace_map An array of ($ns_name => $ns_uri)
29     * to be registered with the XML parser.  May be empty.
30     * @return boolean $result True if the initialization and
31     * namespace registration(s) succeeded; false otherwise.
32     */
33    function init($xml_string, $namespace_map)
34    {
35        if (!$this->setXML($xml_string)) {
36            return false;
37        }
38
39        foreach ($namespace_map as $prefix => $uri) {
40            if (!$this->registerNamespace($prefix, $uri)) {
41                return false;
42            }
43        }
44
45        return true;
46    }
47
48    /**
49     * Register a namespace with the XML parser.  This should be
50     * overridden by subclasses.
51     *
52     * @param string $prefix The namespace prefix to appear in XML tag
53     * names.
54     *
55     * @param string $uri The namespace URI to be used to identify the
56     * namespace in the XML.
57     *
58     * @return boolean $result True if the registration succeeded;
59     * false otherwise.
60     */
61    function registerNamespace($prefix, $uri)
62    {
63        // Not implemented.
64        return false;
65    }
66
67    /**
68     * Set this parser object's XML payload.  This should be
69     * overridden by subclasses.
70     *
71     * @param string $xml_string The XML string to pass to this
72     * object's XML parser.
73     *
74     * @return boolean $result True if the initialization succeeded;
75     * false otherwise.
76     */
77    function setXML($xml_string)
78    {
79        // Not implemented.
80        return false;
81    }
82
83    /**
84     * Evaluate an XPath expression and return the resulting node
85     * list.  This should be overridden by subclasses.
86     *
87     * @param string $xpath The XPath expression to be evaluated.
88     *
89     * @param mixed $node A node object resulting from a previous
90     * evalXPath call.  This node, if specified, provides the context
91     * for the evaluation of this xpath expression.
92     *
93     * @return array $node_list An array of matching opaque node
94     * objects to be used with other methods of this parser class.
95     */
96    function &evalXPath($xpath, $node = null)
97    {
98        // Not implemented.
99        return [];
100    }
101
102    /**
103     * Return the textual content of a specified node.
104     *
105     * @param mixed $node A node object from a previous call to
106     * $this->evalXPath().
107     *
108     * @return string $content The content of this node.
109     */
110    function content($node)
111    {
112        // Not implemented.
113        return '';
114    }
115
116    /**
117     * Return the attributes of a specified node.
118     *
119     * @param mixed $node A node object from a previous call to
120     * $this->evalXPath().
121     *
122     * @return array An array mapping attribute names to
123     * values.
124     */
125    function attributes($node)
126    {
127        // Not implemented.
128        return [];
129    }
130}
131
132/**
133 * This concrete implementation of Auth_Yadis_XMLParser implements
134 * the appropriate API for the 'domxml' extension which is typically
135 * packaged with PHP 4.  This class will be used whenever the 'domxml'
136 * extension is detected.  See the Auth_Yadis_XMLParser class for
137 * details on this class's methods.
138 *
139 * @package OpenID
140 */
141class Auth_Yadis_domxml extends Auth_Yadis_XMLParser {
142    function __construct()
143    {
144        $this->xml = null;
145        $this->doc = null;
146        $this->xpath = null;
147        $this->errors = [];
148    }
149
150    function setXML($xml_string)
151    {
152        $this->xml = $xml_string;
153        $this->doc = @domxml_open_mem($xml_string, DOMXML_LOAD_PARSING,
154                                      $this->errors);
155
156        if (!$this->doc) {
157            return false;
158        }
159
160        $this->xpath = $this->doc->xpath_new_context();
161
162        return true;
163    }
164
165    function registerNamespace($prefix, $uri)
166    {
167        return xpath_register_ns($this->xpath, $prefix, $uri);
168    }
169
170    function &evalXPath($xpath, $node = null)
171    {
172        if ($node) {
173            $result = @$this->xpath->xpath_eval($xpath, $node);
174        } else {
175            $result = @$this->xpath->xpath_eval($xpath);
176        }
177
178        if (!$result) {
179            $n = [];
180            return $n;
181        }
182
183        if (!$result->nodeset) {
184            $n = [];
185            return $n;
186        }
187
188        return $result->nodeset;
189    }
190
191    function content($node)
192    {
193        if ($node) {
194            return $node->get_content();
195        }
196    }
197
198    function attributes($node)
199    {
200        if ($node) {
201            $arr = $node->attributes();
202            $result = [];
203
204            if ($arr) {
205                foreach ($arr as $attrnode) {
206                    $result[$attrnode->name] = $attrnode->value;
207                }
208            }
209
210            return $result;
211        }
212    }
213}
214
215/**
216 * This concrete implementation of Auth_Yadis_XMLParser implements
217 * the appropriate API for the 'dom' extension which is typically
218 * packaged with PHP 5.  This class will be used whenever the 'dom'
219 * extension is detected.  See the Auth_Yadis_XMLParser class for
220 * details on this class's methods.
221 *
222 * @package OpenID
223 */
224class Auth_Yadis_dom extends Auth_Yadis_XMLParser {
225
226    /** @var string */
227    protected $xml = '';
228
229    protected $doc = null;
230
231    /** @var DOMXPath */
232    protected $xpath = null;
233
234    protected $errors = [];
235
236    function setXML($xml_string)
237    {
238        $this->xml = $xml_string;
239        $this->doc = new DOMDocument;
240
241        if (!$this->doc) {
242            return false;
243        }
244
245        // libxml_disable_entity_loader (PHP 5 >= 5.2.11)
246        if (function_exists('libxml_disable_entity_loader') && function_exists('libxml_use_internal_errors')) {
247            // disable external entities and libxml errors
248            $loader = libxml_disable_entity_loader(true);
249            $errors = libxml_use_internal_errors(true);
250            $parse_result = @$this->doc->loadXML($xml_string);
251            libxml_disable_entity_loader($loader);
252            libxml_use_internal_errors($errors);
253        } else {
254            $parse_result = @$this->doc->loadXML($xml_string);
255        }
256
257        if (!$parse_result) {
258            return false;
259        }
260
261        if (isset($this->doc->doctype)) {
262            return false;
263        }
264
265        $this->xpath = new DOMXPath($this->doc);
266
267        if ($this->xpath) {
268            return true;
269        } else {
270            return false;
271        }
272    }
273
274    function registerNamespace($prefix, $uri)
275    {
276        return $this->xpath->registerNamespace($prefix, $uri);
277    }
278
279    function &evalXPath($xpath, $node = null)
280    {
281        if ($node) {
282            $result = @$this->xpath->query($xpath, $node);
283        } else {
284            $result = @$this->xpath->query($xpath);
285        }
286
287        $n = [];
288
289        if (!$result) {
290            return $n;
291        }
292
293        for ($i = 0; $i < $result->length; $i++) {
294            $n[] = $result->item($i);
295        }
296
297        return $n;
298    }
299
300    function content($node)
301    {
302        if ($node) {
303            return $node->textContent;
304        }
305        return '';
306    }
307
308    /**
309     * @param DOMNode $node
310     * @return array
311     */
312    function attributes($node)
313    {
314        if ($node) {
315            /** @var DOMNamedNodeMap $arr */
316            $arr = $node->attributes;
317            $result = [];
318
319            if ($arr) {
320                for ($i = 0; $i < $arr->length; $i++) {
321                    $node = $arr->item($i);
322                    $result[$node->nodeName] = $node->nodeValue;
323                }
324            }
325
326            return $result;
327        }
328        return [];
329    }
330}
331
332global $__Auth_Yadis_defaultParser;
333$__Auth_Yadis_defaultParser = null;
334
335/**
336 * Set a default parser to override the extension-driven selection of
337 * available parser classes.  This is helpful in a test environment or
338 * one in which multiple parsers can be used but one is more
339 * desirable.
340 *
341 * @param Auth_Yadis_XMLParser $parser An instance of a
342 * Auth_Yadis_XMLParser subclass.
343 */
344function Auth_Yadis_setDefaultParser($parser)
345{
346    global $__Auth_Yadis_defaultParser;
347    $__Auth_Yadis_defaultParser = $parser;
348}
349
350function Auth_Yadis_getSupportedExtensions()
351{
352    return [
353        'dom' => 'Auth_Yadis_dom',
354        'domxml' => 'Auth_Yadis_domxml',
355    ];
356}
357
358/**
359 * Returns an instance of a Auth_Yadis_XMLParser subclass based on
360 * the availability of PHP extensions for XML parsing.  If
361 * Auth_Yadis_setDefaultParser has been called, the parser used in
362 * that call will be returned instead.
363 *
364 * @return Auth_Yadis_XMLParser|bool
365 */
366function Auth_Yadis_getXMLParser()
367{
368    global $__Auth_Yadis_defaultParser;
369
370    if (isset($__Auth_Yadis_defaultParser)) {
371        return $__Auth_Yadis_defaultParser;
372    }
373
374    foreach(Auth_Yadis_getSupportedExtensions() as $extension => $classname)
375    {
376      if (extension_loaded($extension))
377      {
378        $p = new $classname();
379        Auth_Yadis_setDefaultParser($p);
380        return $p;
381      }
382    }
383
384    return false;
385}
386
387
388