XRL  latest
Simple XML-RPC Library (both client and server)
NativeEncoder.php
1 <?php
2 /*
3  * This file is part of XRL, a simple XML-RPC Library for PHP.
4  *
5  * Copyright (c) 2012, XRL Team. All rights reserved.
6  * XRL is licensed under the 3-clause BSD License.
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
12 namespace fpoirotte\XRL;
13 
22 {
24  protected $encoder;
25 
33  {
34  $this->encoder = $encoder;
35  }
36 
50  protected static function isUTF8($text)
51  {
52  /*
53  Based on http://w3.org/International/questions/qa-forms-utf-8.html,
54  but rewritten to avoid a dependency on PCRE.
55 
56  Also, the regular expression in the post above seems to limit
57  valid inputs to printable characters (for compatibility reasons?).
58  See also section 2.2 of http://www.w3.org/TR/xml11 and section 2.2
59  of http://www.w3.org/TR/xml/ for a possible explanation.
60 
61  The code below does not suffer from such limitations.
62  */
63  $len = strlen($text);
64  $res = true;
65  for ($i = 0; $i < $len; $i++) {
66  // U+0000 - U+007F
67  $byte1 = ord($text[$i]);
68  if ($byte1 >= 0 && $byte1 <= 0x7F) {
69  continue;
70  }
71 
72  // Look for 2nd byte
73  if (++$i >= $len) {
74  return false;
75  }
76  $byte2 = ord($text[$i]);
77 
78  // U+0080 - U-07FF
79  if ($byte1 >= 0xC2 && $byte1 <= 0xDF) {
80  if ($byte2 >= 0x80 && $byte2 <= 0xBF) {
81  continue;
82  }
83  return false;
84  }
85 
86  // Look for 3nd byte
87  if (++$i >= $len) {
88  return false;
89  }
90  $byte3 = ord($text[$i]);
91 
92  // U+0800 - U+0FFF
93  if ($byte1 == 0xE0) {
94  if ($byte2 >= 0xA0 && $byte2 <= 0xBF &&
95  $byte3 >= 0x80 && $byte3 <= 0xBF) {
96  continue;
97  }
98  return false;
99  }
100 
101  // U+1000 - U+CFFF & U+E000 - U+FFFF
102  if ($byte1 >= 0xE1 && $byte1 <= 0xEF && $byte1 !== 0xED) {
103  if ($byte2 >= 0x80 && $byte2 <= 0xBF &&
104  $byte3 >= 0x80 && $byte3 <= 0xBF) {
105  $codepoint = (($byte1 & 0x0F) << 12) + (($byte2 & 0x3F) << 6) + ($byte3 & 0x3F);
106  if (($codepoint >= 0xE000 && $codepoint <= 0xF8FF) || // Private range
107  ($codepoint >= 0xFDD0 && $codepoint <= 0xFDEF) || // Non-characters
108  $codepoint == 0xFFFE || $codepoint == 0xFFFF) { // Non-characters
109  $res = null;
110  }
111  continue;
112  }
113  return false;
114  }
115 
116  // U+D000 - U+D7FF
117  if ($byte1 == 0xED) {
118  if ($byte2 >= 0x80 && $byte2 <= 0x9F &&
119  $byte3 >= 0x80 && $byte3 <= 0xBF) {
120  continue;
121  }
122  return false;
123  }
124 
125  // Look for 4nd byte
126  if (++$i >= $len) {
127  return false;
128  }
129  $byte4 = ord($text[$i]);
130 
131  // U+10000 - U+3FFFF
132  if ($byte1 == 0xF0) {
133  if ($byte2 >= 0x90 && $byte2 <= 0xBF &&
134  $byte3 >= 0x80 && $byte3 <= 0xBF &&
135  $byte4 >= 0x80 && $byte4 <= 0xBF) {
136  $codepoint = (($byte1 & 0x07) << 18) +
137  (($byte2 & 0x3F) << 12) +
138  (($byte3 & 0x3F) << 6) +
139  ($byte4 & 0x3F);
140  // Non-characters Reserved range
141  if ($codepoint == 0x1FFFE || $codepoint == 0x1FFFF || $codepoint >= 0x2FFFE) {
142  $res = null;
143  }
144  continue;
145  }
146  return false;
147  }
148 
149  // U+40000 - U+FFFFF
150  if ($byte1 >= 0xF1 && $byte1 <= 0xF3) {
151  if ($byte2 >= 0x80 && $byte2 <= 0xBF &&
152  $byte3 >= 0x80 && $byte3 <= 0xBF &&
153  $byte4 >= 0x80 && $byte4 <= 0xBF) {
154  $codepoint = (($byte1 & 0x07) << 18) +
155  (($byte2 & 0x3F) << 12) +
156  (($byte3 & 0x3F) << 6) +
157  ($byte4 & 0x3F);
158  // Reserved range Non characters & private ranges
159  if ($codepoint < 0xE0000 || $codepoint >= 0xEFFFE) {
160  $res = null;
161  }
162  continue;
163  }
164  return false;
165  }
166 
167  // U+100000 - U+10FFFF
168  if ($byte1 == 0xF4) {
169  if ($byte2 >= 0x80 && $byte2 <= 0x8F &&
170  $byte3 >= 0x80 && $byte3 <= 0xBF &&
171  $byte4 >= 0x80 && $byte4 <= 0xBF) {
172  // This part contains only non-characters & private ranges.
173  $res = null;
174  continue;
175  }
176  return false;
177  }
178 
179  // Byte #1 contained an invalid value.
180  return false;
181  }
182 
183  // No decoding error detected, but the given input may contain
184  // non-characters/reserved characters, depending on the value of $res.
185  return $res;
186  }
187 
188  protected static function isBinaryString($text)
189  {
190  if (!static::isUTF8($text)) {
191  return true;
192  }
193 
194  // Based on XML 1.1, section 2.2 (compatibility characters).
195  // We're a bit more lax as long as the whole string forms
196  // a valid UTF-8 codepoints sequence.
197  $restrictedChars =
198  "\x00\x01\x02\x03\x04\x05\x06\x07\x08" .
199  "\x0B\x0C" .
200  "\x0E\x0F\x10" .
201  "\x11\x12\x13\x14\15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" .
202  "\x7F";
203 
204  // If the text contains restricted characters,
205  // encode it as a binary string.
206  if (strcspn($text, $restrictedChars) !== strlen($text)) {
207  return true;
208  }
209 
210  // Otherwise, it is probably safe to encode the string "as is".
211  return false;
212  }
213 
258  public static function convert($value)
259  {
260  switch (gettype($value)) {
261  case 'NULL':
262  // Support for the <nil> extension
263  // (http://ontosys.com/xml-rpc/extensions.php)
264  return new \fpoirotte\XRL\Types\Nil(null);
265 
266  case 'boolean':
267  return new \fpoirotte\XRL\Types\Boolean($value);
268 
269  case 'integer':
270  try {
271  return new \fpoirotte\XRL\Types\I4($value);
272  } catch (\InvalidArgumentException $e) {
273  }
274  return new \fpoirotte\XRL\Types\I8($value);
275 
276  case 'double':
277  return new \fpoirotte\XRL\Types\Double($value);
278 
279  case 'string':
280  // We try to encode it as a regular string if possible.
281  if (static::isBinaryString($value)) {
282  return new \fpoirotte\XRL\Types\Base64($value);
283  }
284  return new \fpoirotte\XRL\Types\StringType($value);
285 
286  case 'array':
287  $newValue = array_map("static::convert", $value);
288  try {
289  return new \fpoirotte\XRL\Types\ArrayType($newValue);
290  } catch (\InvalidArgumentException $e) {
291  }
292  return new \fpoirotte\XRL\Types\Struct($newValue);
293 
294  case 'object':
295  case 'resource':
296  // A special treatment is applied afterwards.
297  break;
298  }
299 
300  // Only objects & resources remain after this point.
301  if ($value instanceof \fpoirotte\XRL\Types\AbstractType) {
302  return $value;
303  }
304 
305  if ($value instanceof \GMP) {
306  $candidates = array(
307  '\\fpoirotte\\XRL\\Types\\I4',
308  '\\fpoirotte\\XRL\\Types\\I8',
309  '\\fpoirotte\\XRL\\Types\\BigInteger',
310  );
311  foreach ($candidates as $candidate) {
312  try {
313  return new $candidate($value);
314  } catch (\InvalidArgumentException $e) {
315  }
316  }
317  }
318 
319  if ($value instanceof \DateTime) {
320  return new \fpoirotte\XRL\Types\DateTimeIso8601($value);
321  }
322 
323  if (($value instanceof \DOMNode) ||
324  ($value instanceof \XMLWriter) ||
325  ($value instanceof \SimpleXMLElement)) {
326  return new \fpoirotte\XRL\Types\Dom($value);
327  }
328 
329  if ($value instanceof \Exception) {
330  return new \fpoirotte\XRL\Types\Struct(
331  array(
332  'faultCode' => new \fpoirotte\XRL\Types\IntType($value->getCode()),
333  'faultString' => new \fpoirotte\XRL\Types\StringType(
334  get_class($value).': '.$value->getMessage()
335  ),
336  )
337  );
338  }
339 
340  if (is_object($value) && (
341  ($value instanceof \Serializable) ||
342  method_exists($value, '__sleep'))) {
343  $value = serialize($value);
344 
345  // We try to encode it as a regular string if possible.
346  if (static::isBinaryString($value)) {
347  return new \fpoirotte\XRL\Types\Base64($value);
348  }
349  return new \fpoirotte\XRL\Types\StringType($value);
350  }
351 
352  throw new \InvalidArgumentException('Unconvertible type');
353  }
354 
356  public function encodeRequest(\fpoirotte\XRL\Request $request)
357  {
358  $newParams = array_map('static::convert', $request->getParams());
359  return $this->encoder->encodeRequest(
360  new \fpoirotte\XRL\Request($request->getProcedure(), $newParams)
361  );
362  }
363 
365  public function encodeError(\Exception $error)
366  {
367  return $this->encoder->encodeError($error);
368  }
369 
371  public function encodeResponse($response)
372  {
373  return $this->encoder->encodeResponse(static::convert($response));
374  }
375 }
Interface for an XML-RPC encoder.
An exception that is used to represent XML-RPC errors.
Definition: Exception.php:21
__construct(\fpoirotte\XRL\EncoderInterface $encoder)
encodeError(\Exception $error)
An XML-RPC encoder that transparently converts PHP types to their XML-RPC counterpart.
encodeRequest(\fpoirotte\XRL\Request $request)
A class that represents an XML-RPC request.
Definition: Request.php:20