Passed
Push — master ( 526b0d...b337d2 )
by Gaetano
04:53
created

Encoder::setParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Logger;
6
use PhpXmlRpc\Helper\XMLParser;
7
8
/**
9
 * A helper class to easily convert between Value objects and php native values
10
 * @todo implement an interface
11
 * @todo add class constants for the options values
12
 */
13
class Encoder
14
{
15
    protected static $logger;
16
    protected static $parser;
17
18
    public function getLogger()
19
    {
20
        if (self::$logger === null) {
21
            self::$logger = Logger::instance();
22
        }
23
        return self::$logger;
24
    }
25
26
    public static function setLogger($logger)
27
    {
28
        self::$logger = $logger;
29
    }
30
31 3
    public function getParser()
32
    {
33 3
        if (self::$parser === null) {
34 1
            self::$parser = new XMLParser();
35
        }
36 3
        return self::$parser;
37
    }
38
39
    public static function setParser($parser)
40
    {
41
        self::$parser = $parser;
42
    }
43
44
    /**
45
     * Takes an xmlrpc value in object format and translates it into native PHP types.
46
     *
47
     * Works with xmlrpc requests objects as input, too.
48
     *
49
     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
50
     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
51
     * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
52
     * This means that the remote communication end can decide which php code will get executed on your server, leaving
53
     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
54
     * that might wreak havoc if instances are built outside an appropriate context).
55
     * Make sure you trust the remote server/client before enabling this!
56
     *
57
     * @author Dan Libby ([email protected])
58
     *
59
     * @param Value|Request $xmlrpcVal
60
     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
61
     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
62
     *
63
     * @return mixed
64
     */
65 234
    public function decode($xmlrpcVal, $options = array())
66
    {
67 234
        switch ($xmlrpcVal->kindOf()) {
68 234
            case 'scalar':
69 234
                if (in_array('extension_api', $options)) {
70
                    $val = reset($xmlrpcVal->me);
0 ignored issues
show
Bug introduced by
The property me does not seem to exist on PhpXmlRpc\Request.
Loading history...
71
                    $typ = key($xmlrpcVal->me);
72
                    switch ($typ) {
73
                        case 'dateTime.iso8601':
74
                            $xmlrpcVal = array(
75
                                'xmlrpc_type' => 'datetime',
76
                                'scalar' => $val,
77
                                'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val)
78
                            );
79
                            return (object)$xmlrpcVal;
80
                        case 'base64':
81
                            $xmlrpcVal = array(
82
                                'xmlrpc_type' => 'base64',
83
                                'scalar' => $val
84
                            );
85
                            return (object)$xmlrpcVal;
86
                        case 'string':
87
                            if (isset($options['extension_api_encoding'])) {
88
                                $dval = @iconv('UTF-8', $options['extension_api_encoding'], $val);
89
                                if ($dval !== false) {
90
                                    return $dval;
91
                                }
92
                            }
93
                            //return $val;
94
                            // break through voluntarily
95
                        default:
96
                            return $val;
97
                    }
98
                }
99 234
                if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') {
0 ignored issues
show
Bug introduced by
The method scalartyp() does not exist on PhpXmlRpc\Request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
                if (in_array('dates_as_objects', $options) && $xmlrpcVal->/** @scrutinizer ignore-call */ scalartyp() == 'dateTime.iso8601') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
100
                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
101
                    // safely strings, ints and datetimes, we cater to all 3 cases here
102
                    $out = $xmlrpcVal->scalarval();
0 ignored issues
show
Bug introduced by
The method scalarval() does not exist on PhpXmlRpc\Request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

102
                    /** @scrutinizer ignore-call */ 
103
                    $out = $xmlrpcVal->scalarval();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
103
                    if (is_string($out)) {
104
                        $out = strtotime($out);
105
                    }
106
                    if (is_int($out)) {
107
                        $result = new \DateTime();
108
                        $result->setTimestamp($out);
109
110
                        return $result;
111
                    } elseif (is_a($out, 'DateTimeInterface')) {
112
                        return $out;
113
                    }
114
                }
115 234
                return $xmlrpcVal->scalarval();
116
117 232
            case 'array':
118 96
                $arr = array();
119 96
                foreach($xmlrpcVal as $value) {
120 96
                    $arr[] = $this->decode($value, $options);
121
                }
122 96
                return $arr;
123
124 175
            case 'struct':
125
                // If user said so, try to rebuild php objects for specific struct vals.
126
                /// @todo should we raise a warning for class not found?
127
                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
128
                // what we can do?
129 59
                if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != ''
0 ignored issues
show
Bug introduced by
The property _php_class does not seem to exist on PhpXmlRpc\Request.
Loading history...
130 39
                    && class_exists($xmlrpcVal->_php_class)
0 ignored issues
show
Bug introduced by
It seems like $xmlrpcVal->_php_class can also be of type null; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
                    && class_exists(/** @scrutinizer ignore-type */ $xmlrpcVal->_php_class)
Loading history...
131
                ) {
132 20
                    $obj = @new $xmlrpcVal->_php_class();
133 20
                    foreach ($xmlrpcVal as $key => $value) {
134 20
                        $obj->$key = $this->decode($value, $options);
135
                    }
136 20
                    return $obj;
137
                } else {
138 40
                    $arr = array();
139 40
                    foreach ($xmlrpcVal as $key => $value) {
140 40
                        $arr[$key] = $this->decode($value, $options);
141
                    }
142 40
                    return $arr;
143
                }
144
145 117
            case 'msg':
146 117
                $paramCount = $xmlrpcVal->getNumParams();
0 ignored issues
show
Bug introduced by
The method getNumParams() does not exist on PhpXmlRpc\Value. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
                /** @scrutinizer ignore-call */ 
147
                $paramCount = $xmlrpcVal->getNumParams();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
147 117
                $arr = array();
148 117
                for ($i = 0; $i < $paramCount; $i++) {
149 117
                    $arr[] = $this->decode($xmlrpcVal->getParam($i), $options);
0 ignored issues
show
Bug introduced by
The method getParam() does not exist on PhpXmlRpc\Value. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

149
                    $arr[] = $this->decode($xmlrpcVal->/** @scrutinizer ignore-call */ getParam($i), $options);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
150
                }
151 117
                return $arr;
152
153
            /// @todo throw on unsupported type
154
        }
155
    }
156
157
    /**
158
     * Takes native php types and encodes them into xmlrpc PHP object format.
159
     * It will not re-encode xmlrpc value objects.
160
     *
161
     * Feature creep -- could support more types via optional type argument
162
     * (string => datetime support has been added, ??? => base64 not yet)
163
     *
164
     * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can
165
     * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option
166
     *
167
     * @author Dan Libby ([email protected])
168
     *
169
     * @param mixed $phpVal the value to be converted into an xmlrpc value object
170
     * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
171
     *
172
     * @return Value
173
     */
174 219
    public function encode($phpVal, $options = array())
175
    {
176 219
        $type = gettype($phpVal);
177 219
        switch ($type) {
178 219
            case 'string':
179
                /// @todo should we be stricter in the accepted dates (ie. reject more of invalid days & times)?
180 214
                if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) {
181 1
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime);
182
                } else {
183 214
                    $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString);
184
                }
185 214
                break;
186 105
            case 'integer':
187 100
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt);
188 100
                break;
189 27
            case 'double':
190 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble);
191 1
                break;
192
            // Add support for encoding/decoding of booleans, since they are supported in PHP
193 27
            case 'boolean':
194 1
                $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean);
195 1
                break;
196 27
            case 'array':
197
                // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes
198
                // or plain 0..n integer indexed
199
                // A shorter one-liner would be
200
                // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1));
201
                // but execution time skyrockets!
202 23
                $j = 0;
203 23
                $arr = array();
204 23
                $ko = false;
205 23
                foreach ($phpVal as $key => $val) {
206 23
                    $arr[$key] = $this->encode($val, $options);
207 23
                    if (!$ko && $key !== $j) {
208 22
                        $ko = true;
209
                    }
210 23
                    $j++;
211
                }
212 23
                if ($ko) {
213 22
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
214
                } else {
215 3
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcArray);
216
                }
217 23
                break;
218 5
            case 'object':
219 3
                if (is_a($phpVal, 'PhpXmlRpc\Value')) {
220 1
                    $xmlrpcVal = $phpVal;
221 2
                } elseif (is_a($phpVal, 'DateTimeInterface')) {
222 1
                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcDateTime);
223 1
                } elseif (in_array('extension_api', $options) && $phpVal instanceof \stdClass && isset($phpVal->xmlrpc_type)) {
224
                    // Handle the 'pre-converted' base64 and datetime values
225
                    if (isset($phpVal->scalar)) {
226
                        switch ($phpVal->xmlrpc_type) {
227
                            case 'base64':
228
                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcBase64);
229
                                break;
230
                            case 'datetime':
231
                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcDateTime);
232
                                break;
233
                            default:
234
                                $xmlrpcVal = new Value();
235
                        }
236
                    } else {
237
                        $xmlrpcVal = new Value();
238
                    }
239
240
                } else {
241 1
                    $arr = array();
242 1
                    foreach($phpVal as $k => $v) {
243 1
                        $arr[$k] = $this->encode($v, $options);
244
                    }
245 1
                    $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
246 1
                    if (in_array('encode_php_objs', $options)) {
247
                        // let's save original class name into xmlrpc value:
248
                        // might be useful later on...
249 1
                        $xmlrpcVal->_php_class = get_class($phpVal);
250
                    }
251
                }
252 3
                break;
253 2
            case 'NULL':
254 2
                if (in_array('extension_api', $options)) {
255
                    $xmlrpcVal = new Value('', Value::$xmlrpcString);
256 2
                } elseif (in_array('null_extension', $options)) {
257
                    $xmlrpcVal = new Value('', Value::$xmlrpcNull);
258
                } else {
259 2
                    $xmlrpcVal = new Value();
260
                }
261 2
                break;
262
            case 'resource':
263
                if (in_array('extension_api', $options)) {
264
                    $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt);
265
                } else {
266
                    $xmlrpcVal = new Value();
267
                }
268
                break;
269
            // catch "user function", "unknown type"
270
            default:
271
                // giancarlo pinerolo <[email protected]>
272
                // it has to return an empty object in case, not a boolean.
273
                $xmlrpcVal = new Value();
274
                break;
275
        }
276
277 219
        return $xmlrpcVal;
278
    }
279
280
    /**
281
     * Convert the xml representation of a method response, method request or single
282
     * xmlrpc value into the appropriate object (a.k.a. deserialize).
283
     *
284
     * @todo is this a good name/class for this method? It does something quite different from 'decode' after all
285
     *       (returning objects vs returns plain php values)... In fact it belongs rather to a Parser class
286
     *
287
     * @param string $xmlVal
288
     * @param array $options
289
     *
290
     * @return Value|Request|Response|false false on error, or an instance of either Value, Request or Response
291
     */
292 3
    public function decodeXml($xmlVal, $options = array())
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

292
    public function decodeXml($xmlVal, /** @scrutinizer ignore-unused */ $options = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
293
    {
294
        // 'guestimate' encoding
295 3
        $valEncoding = XMLParser::guessEncoding('', $xmlVal);
296 3
        if ($valEncoding != '') {
297
298
            // Since parsing will fail if
299
            // - charset is not specified in the xml prologue,
300
            // - the encoding is not UTF8 and
301
            // - there are non-ascii chars in the text,
302
            // we try to work round that...
303
            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
304
            //if (!is_valid_charset($valEncoding, array('UTF-8'))
305 3
            if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
306
                if ($valEncoding == 'ISO-8859-1') {
307
                    $xmlVal = utf8_encode($xmlVal);
308
                } else {
309
                    if (extension_loaded('mbstring')) {
310
                        $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
311
                    } else {
312
                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
313
                    }
314
                }
315
            }
316
        }
317
318
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
319 3
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
320
            /// @todo emit a warning
321
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
322
        } else {
323 3
            $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
324
        }
325
326 3
        $xmlRpcParser = $this->getParser();
327 3
        $xmlRpcParser->parse(
328 3
            $xmlVal,
329 3
            XMLParser::RETURN_XMLRPCVALS,
330 3
            XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT,
331
            $parserOptions
332
        );
333
334 3
        if ($xmlRpcParser->_xh['isf'] > 1) {
335
            // test that $xmlrpc->_xh['value'] is an obj, too???
336
337
            $this->getLogger()->errorLog($xmlRpcParser->_xh['isf_reason']);
338
339
            return false;
340
        }
341
342 3
        switch ($xmlRpcParser->_xh['rt']) {
343 3
            case 'methodresponse':
344 3
                $v = $xmlRpcParser->_xh['value'];
345 3
                if ($xmlRpcParser->_xh['isf'] == 1) {
346
                    /** @var Value $vc */
347
                    $vc = $v['faultCode'];
348
                    /** @var Value $vs */
349
                    $vs = $v['faultString'];
350
                    $r = new Response(0, $vc->scalarval(), $vs->scalarval());
351
                } else {
352 3
                    $r = new Response($v);
353
                }
354 3
                return $r;
355
356 1
            case 'methodcall':
357 1
                $req = new Request($xmlRpcParser->_xh['method']);
358 1
                for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
359 1
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
360
                }
361 1
                return $req;
362
363 1
            case 'value':
364 1
                return $xmlRpcParser->_xh['value'];
365
366
            case 'fault':
367
                // EPI api emulation
368
                $v = $xmlRpcParser->_xh['value'];
369
                // use a known error code
370
                /** @var Value $vc */
371
                $vc = isset($v['faultCode']) ? $v['faultCode']->scalarval() : PhpXmlRpc::$xmlrpcerr['invalid_return'];
372
                /** @var Value $vs */
373
                $vs = isset($v['faultString']) ? $v['faultString']->scalarval() : '';
374
                if (!is_int($vc) || $vc == 0) {
0 ignored issues
show
introduced by
The condition is_int($vc) is always false.
Loading history...
375
                    $vc = PhpXmlRpc::$xmlrpcerr['invalid_return'];
376
                }
377
                return new Response(0, $vc, $vs);
0 ignored issues
show
Bug introduced by
$vs of type PhpXmlRpc\Value is incompatible with the type string expected by parameter $fString of PhpXmlRpc\Response::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

377
                return new Response(0, $vc, /** @scrutinizer ignore-type */ $vs);
Loading history...
378
            default:
379
                return false;
380
        }
381
    }
382
}
383