Commit | Line | Data |
---|---|---|
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 | */ | |
14 | class 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 | } |