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 function mnet_encxml_parser() {
21 return $this->initialise();
25 * Set default element handlers and initialise properties to empty.
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
38 $this->remote_timestamp
= '';
39 $this->remote_wwwroot
= '';
40 $this->signature
= '';
41 $this->data_object
= '';
43 $this->payload_encrypted
= false;
44 $this->cipher
= array();
45 $this->error
= array();
46 $this->remoteerror
= null;
47 $this->errorstarted
= false;
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---------------------------------
66 * | Symmetric-key-------------------------- |
67 * | |_____________________________________| |
69 * | Encrypted data------------------------- |
71 * | | -XML-Envelope------------------ | |
73 * | | | --Signature------------- | | |
74 * | | | |______________________| | | |
76 * | | | --Signed-Payload-------- | | |
78 * | | | | XML-RPC Request | | | |
79 * | | | |______________________| | | |
81 * | | |_____________________________| | |
82 * | |_____________________________________| |
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);
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");
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;
111 $this->error
[] = array('code' => $errcode, 'string' => $errstring);
115 if (!empty($this->remoteerror
)) {
119 if (count($this->cipher
) > 0) {
120 $this->cipher
= array_values($this->cipher
);
121 $this->payload_encrypted
= true;
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).
146 function start_element($parser, $name, $attrs) {
148 $handler = 'discard_data';
149 switch(strtoupper($name)) {
151 $handler = 'parse_digest';
153 case 'SIGNATUREVALUE':
154 $handler = 'parse_signature';
157 $handler = 'parse_object';
159 case 'RETRIEVALMETHOD':
160 $this->key_URI
= $attrs['URI'];
163 $handler = 'parse_timestamp';
166 $handler = 'parse_wwwroot';
169 $this->cipher
[$this->tag_number
] = '';
170 $handler = 'parse_cipher';
173 $handler = 'parse_fault';
177 xml_set_character_data_handler($this->parser
, $handler);
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)
188 function parse_timestamp($parser, $data) {
189 $this->remote_timestamp
.= $data;
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)
209 function parse_cipher($parser, $data) {
210 $this->cipher
[$this->tag_number
] .= $data;
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)
221 function parse_wwwroot($parser, $data) {
222 $this->remote_wwwroot
.= $data;
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)
233 function parse_digest($parser, $data) {
234 $this->digest
.= $data;
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)
245 function parse_signature($parser, $data) {
246 $this->signature
.= $data;
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)
257 function parse_object($parser, $data) {
258 $this->data_object
.= $data;
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)
271 function discard_data($parser, $data) {
272 if (!$this->errorstarted
) {
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;
291 function parse_fault($parser, $data) {
292 $this->errorstarted
= new StdClass
;
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
303 function end_element($parser, $name) {
304 $ok = xml_set_character_data_handler($this->parser
, "discard_data");