on-demand release 4.0dev+
[moodle.git] / mnet / xmlrpc / xmlparser.php
CommitLineData
71558f85 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 */
10
11/**
12 * Custom XML parser class for signed and/or encrypted XML Docs
13 */
14class mnet_encxml_parser {
15 /**
16 * Constructor creates and initialises parser resource and calls initialise
17 *
18 * @return bool True
19 */
4a89e83b 20 public function __construct() {
71558f85 21 return $this->initialise();
22 }
23
1bd60ff6
DP
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 }
33
71558f85 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);
42
43 xml_set_element_handler($this->parser, "start_element", "end_element");
44 xml_set_character_data_handler($this->parser, "discard_data");
45
66ebd55b 46 $this->tag_number = 0; // Just a unique ID for each tag
47 $this->digest = '';
f0e4c270 48 $this->remote_timestamp = '';
66ebd55b 49 $this->remote_wwwroot = '';
50 $this->signature = '';
51 $this->data_object = '';
52 $this->key_URI = '';
71558f85 53 $this->payload_encrypted = false;
66ebd55b 54 $this->cipher = array();
fd7756e4 55 $this->error = array();
a5248bee 56 $this->remoteerror = null;
57 $this->errorstarted = false;
71558f85 58 return true;
59 }
60
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 *
71558f85 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) {
71558f85 100 $p = xml_parse($this->parser, $data);
101
fd7756e4 102 if ($p == 0) {
103 // Parse failed
104 $errcode = xml_get_error_code($this->parser);
105 $errstring = xml_error_string($errcode);
d60a9e40 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 }
fd7756e4 123 }
124
a5248bee 125 if (!empty($this->remoteerror)) {
126 return false;
127 }
128
71558f85 129 if (count($this->cipher) > 0) {
82b29ea1 130 $this->cipher = array_values($this->cipher);
71558f85 131 $this->payload_encrypted = true;
132 }
133
134 return (bool)$p;
135 }
136
137 /**
138 * Destroy the parser and free up any related resource.
139 */
140 function free_resource() {
141 $free = xml_parser_free($this->parser);
142 }
143
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;
f0e4c270 172 case 'TIMESTAMP':
173 $handler = 'parse_timestamp';
174 break;
71558f85 175 case 'WWWROOT':
176 $handler = 'parse_wwwroot';
177 break;
178 case 'CIPHERVALUE':
179 $this->cipher[$this->tag_number] = '';
180 $handler = 'parse_cipher';
181 break;
a5248bee 182 case 'FAULT':
183 $handler = 'parse_fault';
71558f85 184 default:
185 break;
186 }
187 xml_set_character_data_handler($this->parser, $handler);
188 return true;
189 }
190
f0e4c270 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 }
202
71558f85 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 }
223
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 }
235
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 }
247
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 }
259
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 }
271
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) {
a5248bee 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;
298
299 }
300
301 function parse_fault($parser, $data) {
302 $this->errorstarted = new StdClass;
71558f85 303 return true;
304 }
305
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 }
317}