on-demand release 4.0dev+
[moodle.git] / mnet / xmlrpc / xmlparser.php
1 <?php
2 /**
3  * Custom XML parser for signed and/or encrypted XML Docs
4  *
5  * @author  Donal McMullan  donal@catalyst.net.nz
6  * @version 0.0.1
7  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8  * @package mnet
9  */
11 /**
12  * Custom XML parser class for signed and/or encrypted XML Docs
13  */
14 class mnet_encxml_parser {
15     /**
16      * Constructor creates and initialises parser resource and calls initialise
17      *
18      * @return bool True
19      */
20     public function __construct() {
21         return $this->initialise();
22     }
24     /**
25      * Old syntax of class constructor. Deprecated in PHP7.
26      *
27      * @deprecated since Moodle 3.1
28      */
29     public function mnet_encxml_parser() {
30         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
31         self::__construct();
32     }
34     /**
35      * Set default element handlers and initialise properties to empty.
36      *
37      * @return bool True
38      */
39     function initialise() {
40         $this->parser = xml_parser_create();
41         xml_set_object($this->parser, $this);
43         xml_set_element_handler($this->parser, "start_element", "end_element");
44         xml_set_character_data_handler($this->parser, "discard_data");
46         $this->tag_number        = 0; // Just a unique ID for each tag
47         $this->digest            = '';
48         $this->remote_timestamp  = '';
49         $this->remote_wwwroot    = '';
50         $this->signature         = '';
51         $this->data_object       = '';
52         $this->key_URI           = '';
53         $this->payload_encrypted = false;
54         $this->cipher            = array();
55         $this->error             = array();
56         $this->remoteerror       = null;
57         $this->errorstarted      = false;
58         return true;
59     }
61     /**
62      * Parse a block of XML text
63      *
64      * The XML Text will be an XML-RPC request which is wrapped in an XML doc
65      * with a signature from the sender. This envelope may be encrypted and
66      * delivered within another XML envelope with a symmetric key. The parser
67      * should first decrypt this XML, and then place the XML-RPC request into
68      * the data_object property, and the signature into the signature property.
69      *
70      * See the W3C's {@link http://www.w3.org/TR/xmlenc-core/ XML Encryption Syntax and Processing}
71      * and {@link http://www.w3.org/TR/2001/PR-xmldsig-core-20010820/ XML-Signature Syntax and Processing}
72      * guidelines for more detail on the XML.
73      *
74      * -----XML-Envelope---------------------------------
75      * |                                                |
76      * |    Symmetric-key--------------------------     |
77      * |    |_____________________________________|     |
78      * |                                                |
79      * |    Encrypted data-------------------------     |
80      * |    |                                     |     |
81      * |    |  -XML-Envelope------------------    |     |
82      * |    |  |                             |    |     |
83      * |    |  |  --Signature-------------   |    |     |
84      * |    |  |  |______________________|   |    |     |
85      * |    |  |                             |    |     |
86      * |    |  |  --Signed-Payload--------   |    |     |
87      * |    |  |  |                      |   |    |     |
88      * |    |  |  |   XML-RPC Request    |   |    |     |
89      * |    |  |  |______________________|   |    |     |
90      * |    |  |                             |    |     |
91      * |    |  |_____________________________|    |     |
92      * |    |_____________________________________|     |
93      * |                                                |
94      * |________________________________________________|
95      *
96      * @param   string  $data   The XML that you want to parse
97      * @return  bool            True on success - false on failure
98      */
99     function parse($data) {
100         $p = xml_parse($this->parser, $data);
102         if ($p == 0) {
103             // Parse failed
104             $errcode = xml_get_error_code($this->parser);
105             $errstring = xml_error_string($errcode);
106             $lineno = xml_get_current_line_number($this->parser);
107             if ($lineno !== false) {
108                 $error = array('lineno' => $lineno);
109                 $lineno--; // Line numbering starts at 1.
110                 while ($lineno > 0) {
111                     $data = strstr($data, "\n");
112                     $lineno--;
113                 }
114                 $data .= "\n"; // In case there's only one line (no newline)
115                 $line = substr($data, 0, strpos($data, "\n"));
116                 $error['code']   = $errcode;
117                 $error['string'] = $errstring;
118                 $error['line']   = $line;
119                 $this->error[] = $error;
120             } else {
121                 $this->error[] = array('code' => $errcode, 'string' => $errstring);
122             }
123         }
125         if (!empty($this->remoteerror)) {
126             return false;
127         }
129         if (count($this->cipher) > 0) {
130             $this->cipher = array_values($this->cipher);
131             $this->payload_encrypted = true;
132         }
134         return (bool)$p;
135     }
137     /**
138      * Destroy the parser and free up any related resource.
139      */
140     function free_resource() {
141         $free = xml_parser_free($this->parser);
142     }
144     /**
145      * Set the character-data handler to the right function for each element
146      *
147      * For each tag (element) name, this function switches the character-data
148      * handler to the function that handles that element. Note that character
149      * data is referred to the handler in blocks of 1024 bytes.
150      *
151      * @param   mixed   $parser The XML parser
152      * @param   string  $name   The name of the tag, e.g. method_call
153      * @param   array   $attrs  The tag's attributes (if any exist).
154      * @return  bool            True
155      */
156     function start_element($parser, $name, $attrs) {
157         $this->tag_number++;
158         $handler = 'discard_data';
159         switch(strtoupper($name)) {
160             case 'DIGESTVALUE':
161                 $handler = 'parse_digest';
162                 break;
163             case 'SIGNATUREVALUE':
164                 $handler = 'parse_signature';
165                 break;
166             case 'OBJECT':
167                 $handler = 'parse_object';
168                 break;
169             case 'RETRIEVALMETHOD':
170                 $this->key_URI = $attrs['URI'];
171                 break;
172             case 'TIMESTAMP':
173                 $handler = 'parse_timestamp';
174                 break;
175             case 'WWWROOT':
176                 $handler = 'parse_wwwroot';
177                 break;
178             case 'CIPHERVALUE':
179                 $this->cipher[$this->tag_number] = '';
180                 $handler = 'parse_cipher';
181                 break;
182             case 'FAULT':
183                 $handler = 'parse_fault';
184             default:
185                 break;
186         }
187         xml_set_character_data_handler($this->parser, $handler);
188         return true;
189     }
191     /**
192      * Add the next chunk of character data to the remote_timestamp string
193      *
194      * @param   mixed   $parser The XML parser
195      * @param   string  $data   The content of the current tag (1024 byte chunk)
196      * @return  bool            True
197      */
198     function parse_timestamp($parser, $data) {
199         $this->remote_timestamp .= $data;
200         return true;
201     }
203     /**
204      * Add the next chunk of character data to the cipher string for that tag
205      *
206      * The XML parser calls the character-data handler with 1024-character
207      * chunks of data. This means that the handler may be called several times
208      * for a single tag, so we use the concatenate operator (.) to build the
209      * tag content into a string.
210      * We should not encounter more than one of each tag type, except for the
211      * cipher tag. We will often see two of those. We prevent the content of
212      * these two tags being concatenated together by counting each tag, and
213      * using its 'number' as the key to an array of ciphers.
214      *
215      * @param   mixed   $parser The XML parser
216      * @param   string  $data   The content of the current tag (1024 byte chunk)
217      * @return  bool            True
218      */
219     function parse_cipher($parser, $data) {
220         $this->cipher[$this->tag_number] .= $data;
221         return true;
222     }
224     /**
225      * Add the next chunk of character data to the remote_wwwroot string
226      *
227      * @param   mixed   $parser The XML parser
228      * @param   string  $data   The content of the current tag (1024 byte chunk)
229      * @return  bool            True
230      */
231     function parse_wwwroot($parser, $data) {
232         $this->remote_wwwroot .= $data;
233         return true;
234     }
236     /**
237      * Add the next chunk of character data to the digest string
238      *
239      * @param   mixed   $parser The XML parser
240      * @param   string  $data   The content of the current tag (1024 byte chunk)
241      * @return  bool            True
242      */
243     function parse_digest($parser, $data) {
244         $this->digest .= $data;
245         return true;
246     }
248     /**
249      * Add the next chunk of character data to the signature string
250      *
251      * @param   mixed   $parser The XML parser
252      * @param   string  $data   The content of the current tag (1024 byte chunk)
253      * @return  bool            True
254      */
255     function parse_signature($parser, $data) {
256         $this->signature .= $data;
257         return true;
258     }
260     /**
261      * Add the next chunk of character data to the data_object string
262      *
263      * @param   mixed   $parser The XML parser
264      * @param   string  $data   The content of the current tag (1024 byte chunk)
265      * @return  bool            True
266      */
267     function parse_object($parser, $data) {
268         $this->data_object .= $data;
269         return true;
270     }
272     /**
273      * Discard the next chunk of character data
274      *
275      * This is used for tags that we're not interested in.
276      *
277      * @param   mixed   $parser The XML parser
278      * @param   string  $data   The content of the current tag (1024 byte chunk)
279      * @return  bool            True
280      */
281     function discard_data($parser, $data) {
282         if (!$this->errorstarted) {
283             // Not interested
284             return true;
285         }
286         $data = trim($data);
287         if (isset($this->errorstarted->faultstringstarted) && !empty($data)) {
288             $this->remoteerror .= ', message: ' . $data;
289         } else if (isset($this->errorstarted->faultcodestarted)) {
290             $this->remoteerror = 'code: ' . $data;
291             unset($this->errorstarted->faultcodestarted);
292         } else if ($data == 'faultCode') {
293             $this->errorstarted->faultcodestarted = true;
294         } else if ($data == 'faultString') {
295             $this->errorstarted->faultstringstarted = true;
296         }
297         return true;
299     }
301     function parse_fault($parser, $data) {
302         $this->errorstarted = new StdClass;
303         return true;
304     }
306     /**
307      * Switch the character-data handler to ignore the next chunk of data
308      *
309      * @param   mixed   $parser The XML parser
310      * @param   string  $name   The name of the tag, e.g. method_call
311      * @return  bool            True
312      */
313     function end_element($parser, $name) {
314         $ok = xml_set_character_data_handler($this->parser, "discard_data");
315         return true;
316     }