See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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 */ 14 class mnet_encxml_parser { 15 /** 16 * Constructor creates and initialises parser resource and calls initialise 17 * 18 * @return bool True 19 */ 20 public function __construct() { 21 return $this->initialise(); 22 } 23 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 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 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; 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 * 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) { 100 $p = xml_parse($this->parser, $data); 101 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--; 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 } 123 } 124 125 if (!empty($this->remoteerror)) { 126 return false; 127 } 128 129 if (count($this->cipher) > 0) { 130 $this->cipher = array_values($this->cipher); 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; 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; 186 } 187 xml_set_character_data_handler($this->parser, $handler); 188 return true; 189 } 190 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 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) { 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; 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body