Merge branch 'MDL-47000-28' of git://github.com/merrill-oakland/moodle into MOODLE_28...
[moodle.git] / mnet / xmlrpc / xmlparser.php
blob95ccd2a6547a39f747ab566d95e9613c019170af
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 function mnet_encxml_parser() {
21 return $this->initialise();
24 /**
25 * Set default element handlers and initialise properties to empty.
27 * @return bool True
29 function initialise() {
30 $this->parser = xml_parser_create();
31 xml_set_object($this->parser, $this);
33 xml_set_element_handler($this->parser, "start_element", "end_element");
34 xml_set_character_data_handler($this->parser, "discard_data");
36 $this->tag_number = 0; // Just a unique ID for each tag
37 $this->digest = '';
38 $this->remote_timestamp = '';
39 $this->remote_wwwroot = '';
40 $this->signature = '';
41 $this->data_object = '';
42 $this->key_URI = '';
43 $this->payload_encrypted = false;
44 $this->cipher = array();
45 $this->error = array();
46 $this->remoteerror = null;
47 $this->errorstarted = false;
48 return true;
51 /**
52 * Parse a block of XML text
54 * The XML Text will be an XML-RPC request which is wrapped in an XML doc
55 * with a signature from the sender. This envelope may be encrypted and
56 * delivered within another XML envelope with a symmetric key. The parser
57 * should first decrypt this XML, and then place the XML-RPC request into
58 * the data_object property, and the signature into the signature property.
60 * See the W3C's {@link http://www.w3.org/TR/xmlenc-core/ XML Encryption Syntax and Processing}
61 * and {@link http://www.w3.org/TR/2001/PR-xmldsig-core-20010820/ XML-Signature Syntax and Processing}
62 * guidelines for more detail on the XML.
64 * -----XML-Envelope---------------------------------
65 * | |
66 * | Symmetric-key-------------------------- |
67 * | |_____________________________________| |
68 * | |
69 * | Encrypted data------------------------- |
70 * | | | |
71 * | | -XML-Envelope------------------ | |
72 * | | | | | |
73 * | | | --Signature------------- | | |
74 * | | | |______________________| | | |
75 * | | | | | |
76 * | | | --Signed-Payload-------- | | |
77 * | | | | | | | |
78 * | | | | XML-RPC Request | | | |
79 * | | | |______________________| | | |
80 * | | | | | |
81 * | | |_____________________________| | |
82 * | |_____________________________________| |
83 * | |
84 * |________________________________________________|
86 * @param string $data The XML that you want to parse
87 * @return bool True on success - false on failure
89 function parse($data) {
90 $p = xml_parse($this->parser, $data);
92 if ($p == 0) {
93 // Parse failed
94 $errcode = xml_get_error_code($this->parser);
95 $errstring = xml_error_string($errcode);
96 $lineno = xml_get_current_line_number($this->parser);
97 if ($lineno !== false) {
98 $error = array('lineno' => $lineno);
99 $lineno--; // Line numbering starts at 1.
100 while ($lineno > 0) {
101 $data = strstr($data, "\n");
102 $lineno--;
104 $data .= "\n"; // In case there's only one line (no newline)
105 $line = substr($data, 0, strpos($data, "\n"));
106 $error['code'] = $errcode;
107 $error['string'] = $errstring;
108 $error['line'] = $line;
109 $this->error[] = $error;
110 } else {
111 $this->error[] = array('code' => $errcode, 'string' => $errstring);
115 if (!empty($this->remoteerror)) {
116 return false;
119 if (count($this->cipher) > 0) {
120 $this->cipher = array_values($this->cipher);
121 $this->payload_encrypted = true;
124 return (bool)$p;
128 * Destroy the parser and free up any related resource.
130 function free_resource() {
131 $free = xml_parser_free($this->parser);
135 * Set the character-data handler to the right function for each element
137 * For each tag (element) name, this function switches the character-data
138 * handler to the function that handles that element. Note that character
139 * data is referred to the handler in blocks of 1024 bytes.
141 * @param mixed $parser The XML parser
142 * @param string $name The name of the tag, e.g. method_call
143 * @param array $attrs The tag's attributes (if any exist).
144 * @return bool True
146 function start_element($parser, $name, $attrs) {
147 $this->tag_number++;
148 $handler = 'discard_data';
149 switch(strtoupper($name)) {
150 case 'DIGESTVALUE':
151 $handler = 'parse_digest';
152 break;
153 case 'SIGNATUREVALUE':
154 $handler = 'parse_signature';
155 break;
156 case 'OBJECT':
157 $handler = 'parse_object';
158 break;
159 case 'RETRIEVALMETHOD':
160 $this->key_URI = $attrs['URI'];
161 break;
162 case 'TIMESTAMP':
163 $handler = 'parse_timestamp';
164 break;
165 case 'WWWROOT':
166 $handler = 'parse_wwwroot';
167 break;
168 case 'CIPHERVALUE':
169 $this->cipher[$this->tag_number] = '';
170 $handler = 'parse_cipher';
171 break;
172 case 'FAULT':
173 $handler = 'parse_fault';
174 default:
175 break;
177 xml_set_character_data_handler($this->parser, $handler);
178 return true;
182 * Add the next chunk of character data to the remote_timestamp string
184 * @param mixed $parser The XML parser
185 * @param string $data The content of the current tag (1024 byte chunk)
186 * @return bool True
188 function parse_timestamp($parser, $data) {
189 $this->remote_timestamp .= $data;
190 return true;
194 * Add the next chunk of character data to the cipher string for that tag
196 * The XML parser calls the character-data handler with 1024-character
197 * chunks of data. This means that the handler may be called several times
198 * for a single tag, so we use the concatenate operator (.) to build the
199 * tag content into a string.
200 * We should not encounter more than one of each tag type, except for the
201 * cipher tag. We will often see two of those. We prevent the content of
202 * these two tags being concatenated together by counting each tag, and
203 * using its 'number' as the key to an array of ciphers.
205 * @param mixed $parser The XML parser
206 * @param string $data The content of the current tag (1024 byte chunk)
207 * @return bool True
209 function parse_cipher($parser, $data) {
210 $this->cipher[$this->tag_number] .= $data;
211 return true;
215 * Add the next chunk of character data to the remote_wwwroot string
217 * @param mixed $parser The XML parser
218 * @param string $data The content of the current tag (1024 byte chunk)
219 * @return bool True
221 function parse_wwwroot($parser, $data) {
222 $this->remote_wwwroot .= $data;
223 return true;
227 * Add the next chunk of character data to the digest string
229 * @param mixed $parser The XML parser
230 * @param string $data The content of the current tag (1024 byte chunk)
231 * @return bool True
233 function parse_digest($parser, $data) {
234 $this->digest .= $data;
235 return true;
239 * Add the next chunk of character data to the signature string
241 * @param mixed $parser The XML parser
242 * @param string $data The content of the current tag (1024 byte chunk)
243 * @return bool True
245 function parse_signature($parser, $data) {
246 $this->signature .= $data;
247 return true;
251 * Add the next chunk of character data to the data_object string
253 * @param mixed $parser The XML parser
254 * @param string $data The content of the current tag (1024 byte chunk)
255 * @return bool True
257 function parse_object($parser, $data) {
258 $this->data_object .= $data;
259 return true;
263 * Discard the next chunk of character data
265 * This is used for tags that we're not interested in.
267 * @param mixed $parser The XML parser
268 * @param string $data The content of the current tag (1024 byte chunk)
269 * @return bool True
271 function discard_data($parser, $data) {
272 if (!$this->errorstarted) {
273 // Not interested
274 return true;
276 $data = trim($data);
277 if (isset($this->errorstarted->faultstringstarted) && !empty($data)) {
278 $this->remoteerror .= ', message: ' . $data;
279 } else if (isset($this->errorstarted->faultcodestarted)) {
280 $this->remoteerror = 'code: ' . $data;
281 unset($this->errorstarted->faultcodestarted);
282 } else if ($data == 'faultCode') {
283 $this->errorstarted->faultcodestarted = true;
284 } else if ($data == 'faultString') {
285 $this->errorstarted->faultstringstarted = true;
287 return true;
291 function parse_fault($parser, $data) {
292 $this->errorstarted = new StdClass;
293 return true;
297 * Switch the character-data handler to ignore the next chunk of data
299 * @param mixed $parser The XML parser
300 * @param string $name The name of the tag, e.g. method_call
301 * @return bool True
303 function end_element($parser, $name) {
304 $ok = xml_set_character_data_handler($this->parser, "discard_data");
305 return true;