MDL-16094 File storage conversion Quiz and Questions
[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     function mnet_encxml_parser() {
21         return $this->initialise();
22     }
24     /**
25      * Set default element handlers and initialise properties to empty.
26      *
27      * @return bool True
28      */
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;
49     }
51     /**
52      * Parse a block of XML text
53      *
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.
59      *
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.
63      *
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      * |________________________________________________|
85      *
86      * @param   string  $data   The XML that you want to parse
87      * @return  bool            True on success - false on failure
88      */
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--;
103                 }
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);
112             }
113         }
115         if (!empty($this->remoteerror)) {
116             return false;
117         }
119         if (count($this->cipher) > 0) {
120             $this->cipher = array_values($this->cipher);
121             $this->payload_encrypted = true;
122         }
124         return (bool)$p;
125     }
127     /**
128      * Destroy the parser and free up any related resource.
129      */
130     function free_resource() {
131         $free = xml_parser_free($this->parser);
132     }
134     /**
135      * Set the character-data handler to the right function for each element
136      *
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.
140      *
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
145      */
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;
176         }
177         xml_set_character_data_handler($this->parser, $handler);
178         return true;
179     }
181     /**
182      * Add the next chunk of character data to the remote_timestamp string
183      *
184      * @param   mixed   $parser The XML parser
185      * @param   string  $data   The content of the current tag (1024 byte chunk)
186      * @return  bool            True
187      */
188     function parse_timestamp($parser, $data) {
189         $this->remote_timestamp .= $data;
190         return true;
191     }
193     /**
194      * Add the next chunk of character data to the cipher string for that tag
195      *
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.
204      *
205      * @param   mixed   $parser The XML parser
206      * @param   string  $data   The content of the current tag (1024 byte chunk)
207      * @return  bool            True
208      */
209     function parse_cipher($parser, $data) {
210         $this->cipher[$this->tag_number] .= $data;
211         return true;
212     }
214     /**
215      * Add the next chunk of character data to the remote_wwwroot string
216      *
217      * @param   mixed   $parser The XML parser
218      * @param   string  $data   The content of the current tag (1024 byte chunk)
219      * @return  bool            True
220      */
221     function parse_wwwroot($parser, $data) {
222         $this->remote_wwwroot .= $data;
223         return true;
224     }
226     /**
227      * Add the next chunk of character data to the digest string
228      *
229      * @param   mixed   $parser The XML parser
230      * @param   string  $data   The content of the current tag (1024 byte chunk)
231      * @return  bool            True
232      */
233     function parse_digest($parser, $data) {
234         $this->digest .= $data;
235         return true;
236     }
238     /**
239      * Add the next chunk of character data to the signature string
240      *
241      * @param   mixed   $parser The XML parser
242      * @param   string  $data   The content of the current tag (1024 byte chunk)
243      * @return  bool            True
244      */
245     function parse_signature($parser, $data) {
246         $this->signature .= $data;
247         return true;
248     }
250     /**
251      * Add the next chunk of character data to the data_object string
252      *
253      * @param   mixed   $parser The XML parser
254      * @param   string  $data   The content of the current tag (1024 byte chunk)
255      * @return  bool            True
256      */
257     function parse_object($parser, $data) {
258         $this->data_object .= $data;
259         return true;
260     }
262     /**
263      * Discard the next chunk of character data
264      *
265      * This is used for tags that we're not interested in.
266      *
267      * @param   mixed   $parser The XML parser
268      * @param   string  $data   The content of the current tag (1024 byte chunk)
269      * @return  bool            True
270      */
271     function discard_data($parser, $data) {
272         if (!$this->errorstarted) {
273             // Not interested
274             return true;
275         }
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;
286         }
287         return true;
289     }
291     function parse_fault($parser, $data) {
292         $this->errorstarted = new StdClass;
293         return true;
294     }
296     /**
297      * Switch the character-data handler to ignore the next chunk of data
298      *
299      * @param   mixed   $parser The XML parser
300      * @param   string  $name   The name of the tag, e.g. method_call
301      * @return  bool            True
302      */
303     function end_element($parser, $name) {
304         $ok = xml_set_character_data_handler($this->parser, "discard_data");
305         return true;
306     }