3 * Custom XML parser for signed and/or encrypted XML Docs
5 * @author Donal McMullan donal@catalyst.net.nz
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12 * Custom XML parser class for signed and/or encrypted XML Docs
14 class mnet_encxml_parser
{
16 * Constructor creates and initialises parser resource and calls initialise
20 public function __construct() {
21 return $this->initialise();
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
);
35 * Set default element handlers and initialise properties to empty.
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
48 $this->remote_timestamp
= '';
49 $this->remote_wwwroot
= '';
50 $this->signature
= '';
51 $this->data_object
= '';
53 $this->payload_encrypted
= false;
54 $this->cipher
= array();
55 $this->error
= array();
56 $this->remoteerror
= null;
57 $this->errorstarted
= false;
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---------------------------------
76 * | Symmetric-key-------------------------- |
77 * | |_____________________________________| |
79 * | Encrypted data------------------------- |
81 * | | -XML-Envelope------------------ | |
83 * | | | --Signature------------- | | |
84 * | | | |______________________| | | |
86 * | | | --Signed-Payload-------- | | |
88 * | | | | XML-RPC Request | | | |
89 * | | | |______________________| | | |
91 * | | |_____________________________| | |
92 * | |_____________________________________| |
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);
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");
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;
121 $this->error
[] = array('code' => $errcode, 'string' => $errstring);
125 if (!empty($this->remoteerror
)) {
129 if (count($this->cipher
) > 0) {
130 $this->cipher
= array_values($this->cipher
);
131 $this->payload_encrypted
= true;
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).
156 function start_element($parser, $name, $attrs) {
158 $handler = 'discard_data';
159 switch(strtoupper($name)) {
161 $handler = 'parse_digest';
163 case 'SIGNATUREVALUE':
164 $handler = 'parse_signature';
167 $handler = 'parse_object';
169 case 'RETRIEVALMETHOD':
170 $this->key_URI
= $attrs['URI'];
173 $handler = 'parse_timestamp';
176 $handler = 'parse_wwwroot';
179 $this->cipher
[$this->tag_number
] = '';
180 $handler = 'parse_cipher';
183 $handler = 'parse_fault';
187 xml_set_character_data_handler($this->parser
, $handler);
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)
198 function parse_timestamp($parser, $data) {
199 $this->remote_timestamp
.= $data;
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)
219 function parse_cipher($parser, $data) {
220 $this->cipher
[$this->tag_number
] .= $data;
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)
231 function parse_wwwroot($parser, $data) {
232 $this->remote_wwwroot
.= $data;
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)
243 function parse_digest($parser, $data) {
244 $this->digest
.= $data;
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)
255 function parse_signature($parser, $data) {
256 $this->signature
.= $data;
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)
267 function parse_object($parser, $data) {
268 $this->data_object
.= $data;
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)
281 function discard_data($parser, $data) {
282 if (!$this->errorstarted
) {
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;
301 function parse_fault($parser, $data) {
302 $this->errorstarted
= new StdClass
;
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
313 function end_element($parser, $name) {
314 $ok = xml_set_character_data_handler($this->parser
, "discard_data");