Merge branch 'MDL-62899-search-icons-master' of https://github.com/dmitriim/moodle
[moodle.git] / mnet / xmlrpc / xmlparser.php
blob9afdcb49311c8cd6eebbd4c1a7fb94cf29db5376
1 <?php
2 /**
3 * Custom XML parser for signed and/or encrypted XML Docs
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
14 class mnet_encxml_parser {
15 /**
16 * Constructor creates and initialises parser resource and calls initialise
18 * @return bool True
20 public function __construct() {
21 return $this->initialise();
24 /**
25 * Old syntax of class constructor. Deprecated in PHP7.
27 * @deprecated since Moodle 3.1
29 public function mnet_encxml_parser() {
30 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
31 self::__construct();
34 /**
35 * Set default element handlers and initialise properties to empty.
37 * @return bool True
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;
61 /**
62 * Parse a block of XML text
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.
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.
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 * |________________________________________________|
96 * @param string $data The XML that you want to parse
97 * @return bool True on success - false on failure
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--;
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);
125 if (!empty($this->remoteerror)) {
126 return false;
129 if (count($this->cipher) > 0) {
130 $this->cipher = array_values($this->cipher);
131 $this->payload_encrypted = true;
134 return (bool)$p;
138 * Destroy the parser and free up any related resource.
140 function free_resource() {
141 $free = xml_parser_free($this->parser);
145 * Set the character-data handler to the right function for each element
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.
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
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;
187 xml_set_character_data_handler($this->parser, $handler);
188 return true;
192 * Add the next chunk of character data to the remote_timestamp string
194 * @param mixed $parser The XML parser
195 * @param string $data The content of the current tag (1024 byte chunk)
196 * @return bool True
198 function parse_timestamp($parser, $data) {
199 $this->remote_timestamp .= $data;
200 return true;
204 * Add the next chunk of character data to the cipher string for that tag
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.
215 * @param mixed $parser The XML parser
216 * @param string $data The content of the current tag (1024 byte chunk)
217 * @return bool True
219 function parse_cipher($parser, $data) {
220 $this->cipher[$this->tag_number] .= $data;
221 return true;
225 * Add the next chunk of character data to the remote_wwwroot string
227 * @param mixed $parser The XML parser
228 * @param string $data The content of the current tag (1024 byte chunk)
229 * @return bool True
231 function parse_wwwroot($parser, $data) {
232 $this->remote_wwwroot .= $data;
233 return true;
237 * Add the next chunk of character data to the digest string
239 * @param mixed $parser The XML parser
240 * @param string $data The content of the current tag (1024 byte chunk)
241 * @return bool True
243 function parse_digest($parser, $data) {
244 $this->digest .= $data;
245 return true;
249 * Add the next chunk of character data to the signature string
251 * @param mixed $parser The XML parser
252 * @param string $data The content of the current tag (1024 byte chunk)
253 * @return bool True
255 function parse_signature($parser, $data) {
256 $this->signature .= $data;
257 return true;
261 * Add the next chunk of character data to the data_object string
263 * @param mixed $parser The XML parser
264 * @param string $data The content of the current tag (1024 byte chunk)
265 * @return bool True
267 function parse_object($parser, $data) {
268 $this->data_object .= $data;
269 return true;
273 * Discard the next chunk of character data
275 * This is used for tags that we're not interested in.
277 * @param mixed $parser The XML parser
278 * @param string $data The content of the current tag (1024 byte chunk)
279 * @return bool True
281 function discard_data($parser, $data) {
282 if (!$this->errorstarted) {
283 // Not interested
284 return true;
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;
297 return true;
301 function parse_fault($parser, $data) {
302 $this->errorstarted = new StdClass;
303 return true;
307 * Switch the character-data handler to ignore the next chunk of data
309 * @param mixed $parser The XML parser
310 * @param string $name The name of the tag, e.g. method_call
311 * @return bool True
313 function end_element($parser, $name) {
314 $ok = xml_set_character_data_handler($this->parser, "discard_data");
315 return true;