ServerApi::checkSignature()   B
last analyzed

Complexity

Conditions 7
Paths 32

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 8.2222
cc 7
eloc 13
nc 32
nop 1
1
<?php
2
namespace Wechat\API;
3
4
use Wechat\Api;
5
use Wechat\Utils\Code\WXBizMsgCrypt;
6
7
/**
8
 * 微信接收回调相关接口.
9
 *
10
 * @author Tian.
11
 */
12
class ServerApi extends BaseApi
13
{
14
    private static $XML_OBJECT;
15
16
    /**
17
     * 监听
18
     *
19
     * @param string          $target
20
     * @param string|callable $event
21
     * @param callable        $callback
22
     *
23
     * @return bool Server
24
     */
25
26
    public function on($target, $event, $callback = null)
0 ignored issues
show
Coding Style introduced by
on uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
27
    {
28
        $echostr        = isset($_GET['echostr']) ? $_GET['echostr'] : null;
29
        $checkSignature = $this->checkSignature();
30
31
        if (!$checkSignature) {
32
            exit("签名验证失败");
0 ignored issues
show
Coding Style Compatibility introduced by
The method on() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
33
        }
34
35
        if (!empty($echostr)) {
36
            echo $echostr;
37
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method on() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
38
        }
39
40
        if (is_null($callback)) {
41
            $callback = $event;
42
            $event    = '*';
43
        }
44
45
        if (!is_callable($callback)) {
46
            exit("$callback 不是一个可调用的函数或方法");
0 ignored issues
show
Coding Style Compatibility introduced by
The method on() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
47
        }
48
49
        $rest = $this->$target($event);
50
51
        $datas         = [];
52
        $datas['data'] = $rest;
53
54
        if (!empty($rest)) {
55
            $rest_user                 = [];
56
            $rest_user['ToUserName']   = $rest['FromUserName'];
57
            $rest_user['FromUserName'] = $rest['ToUserName'];
58
            $rest_msg                  = call_user_func_array($callback, $datas);
59
60
            if ($rest_msg == 'success') {
61
                exit('success');
0 ignored issues
show
Coding Style Compatibility introduced by
The method on() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
62
            }
63
64
            $rest_array = array_merge($rest_user, $rest_msg);
65
66
            $rest_xml = $this->arrayToXml($rest_array);
67
68
            $msg_signature = isset($_GET['msg_signature']) ? $_GET['msg_signature'] : null;
69
            if (!empty($msg_signature) && $msg_signature != '') {
70
                $rest_xml = $this->encryptMsg($rest_xml);
71
            }
72
73
            exit($rest_xml);
0 ignored issues
show
Coding Style Compatibility introduced by
The method on() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
74
        } else {
75
            return false;
76
        }
77
    }
78
79
    /**
80
     * 验证签名
81
     *
82
     * string $signature [签名]
83
     * int    $timestamp [时间戳]
84
     * string $nonce     [随机字符串]
85
     *
86
     * @param string $token [token]
87
     *
88
     * @return mixed|bool
89
     */
90
    public function checkSignature($token = null)
0 ignored issues
show
Coding Style introduced by
checkSignature uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
91
    {
92
        $config_token = Api::getToken();
93
        $signature    = isset($_GET['signature']) ? $_GET['signature'] : null;
94
        $timestamp    = isset($_GET['timestamp']) ? $_GET['timestamp'] : null;
95
        $nonce        = isset($_GET['nonce']) ? $_GET['nonce'] : null;
96
        $token        = empty($token) ? $config_token : $token;
97
        $tmpArr       = [$token, $timestamp, $nonce];
98
        sort($tmpArr, SORT_STRING);
99
        $tmpStr = implode($tmpArr);
100
        $tmpStr = sha1($tmpStr);
101
102
        if ($tmpStr == $signature && $signature != null) {
103
            return $this;
104
        }
105
106
        return false;
107
    }
108
109
    /**
110
     * 生成签名 - 用作 转发到第三方
111
     *
112
     * @param string $token
113
     * @param string $time
114
     * @param string $nonce
115
     *
116
     * @return bool|string
117
     */
118
    public function makeSignature($token = null, $time = null, $nonce = null)
0 ignored issues
show
Coding Style introduced by
makeSignature uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
119
    {
120
        if (!is_string($token) || empty($token)) {
121
            exit("$token 参数错误");
0 ignored issues
show
Coding Style Compatibility introduced by
The method makeSignature() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
122
        }
123
124
        $timestamp = !empty($time) ? $time : $_GET['timestamp'];
125
        $nonce     = !empty($nonce) ? $nonce : $_GET['nonce'];
126
        $tmpArr    = [$token, $timestamp, $nonce];
127
        sort($tmpArr, SORT_STRING);
128
        $tmpStr = implode($tmpArr);
129
        $tmpStr = sha1($tmpStr);
130
131
        return $tmpStr;
132
    }
133
134
    /**
135
     * 推送到第三方
136
     *
137
     * @param string $url      [地址]
138
     * @param string $token    [token]
139
     * @param string $xml      [XML]
140
     * @param bool   $encipher [是否加密]
141
     *
142
     * @return array|bool XML
143
     */
144
    public function receiveAgent($url = '', $token = '', $xml = '', $encipher = false)
145
    {
146
        $object = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
147
148
        $object = json_decode(json_encode($object), true);
149
150
        if (isset($object['Encrypt']) && !empty($object['Encrypt']) && $object['Encrypt'] != '') {
151
            $xml = $this->decryptMsg($xml);
152
        }
153
154
        if ($encipher) {
155
            $xml = $this->encryptMsg($xml);
156
157
            $object_enc                    = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
158
            $object_enc                    = json_decode(json_encode($object_enc), true);
159
            $postQueryStr                  = [];
160
            $postQueryStr['timestamp']     = $object_enc['TimeStamp'];
161
            $postQueryStr['nonce']         = $object_enc['Nonce'];
162
            $postQueryStr['signature']     = $this->makeSignature($token, $postQueryStr['timestamp'], $postQueryStr['nonce']);
163
            $postQueryStr['msg_signature'] = $object_enc['MsgSignature'];
164
165
            $array               = [];
166
            $array['ToUserName'] = $object['ToUserName'];
167
            $array['Encrypt']    = $object_enc['Encrypt'];
168
169
            $xml = $this->arrayToXml($array);
170
        } else {
171
            $postQueryStr['timestamp'] = time();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$postQueryStr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $postQueryStr = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
172
            $postQueryStr['nonce']     = $this->getRandomStr();
173
            $postQueryStr['signature'] = $this->makeSignature($token, $postQueryStr['timestamp'], $postQueryStr['nonce']);
174
        }
175
176
        asort($postQueryStr);
177
        $postQueryStr = http_build_query($postQueryStr);
178
179
        $rest = $this->https_xml($url . '?' . $postQueryStr, $xml);
180
181
        if ($rest) {
182
            $object_rest = simplexml_load_string($rest, 'SimpleXMLElement', LIBXML_NOCDATA);
183
            $object_rest = json_decode(json_encode($object_rest), true);
184
185
            if (!empty($object_rest['MsgSignature']) && !empty($object_rest['TimeStamp']) && !empty($object_rest['Nonce']) && !empty($object_rest['Encrypt'])) {
186
                $rest = $this->decryptMsg($rest, $object_rest['MsgSignature'], $object_rest['TimeStamp'], $object_rest['Nonce']);
187
            }
188
            $object_rest = simplexml_load_string($rest, 'SimpleXMLElement', LIBXML_NOCDATA);
189
            $rest_array  = json_decode(json_encode($object_rest), true);
190
191
            return $rest_array;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * 加密XML
199
     *
200
     * @param $xml
201
     *
202
     * @return string
203
     */
204
    public function encryptMsg($xml)
205
    {
206
        $appId          = Api::getAppId();
207
        $token          = Api::getToken();
208
        $encodingAesKey = Api::getEncoding_Aes_Key();
209
210
        if (empty($token) || !$token || empty($encodingAesKey) || !$encodingAesKey) {
211
            return $xml;
212
        }
213
214
        $timeStamp = time();
215
        $nonce     = $this->getRandomStr();
216
217
        $pc         = new WXBizMsgCrypt($token, $encodingAesKey, $appId);
218
        $encryptMsg = '';
219
        $errCode    = $pc->encryptMsg($xml, $timeStamp, $nonce, $encryptMsg);
220
        if ($errCode == 0) {
221
            return $encryptMsg;
222
        } else {
223
            exit($errCode);
0 ignored issues
show
Coding Style Compatibility introduced by
The method encryptMsg() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
224
        }
225
    }
226
227
    /**
228
     * 解密XML
229
     *
230
     * @param      $xml
231
     * @param null $msg_signature
232
     * @param null $timeStamp
233
     * @param null $nonce
234
     *
235
     * @return string
236
     */
237
    public function decryptMsg($xml, $msg_signature = null, $timeStamp = null, $nonce = null)
0 ignored issues
show
Coding Style introduced by
decryptMsg uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
238
    {
239
        $appId          = Api::getAppId();
240
        $token          = Api::getToken();
241
        $encodingAesKey = Api::getEncoding_Aes_Key();
242
243
        $msg_signature = !empty($msg_signature) ? $msg_signature : $_GET['msg_signature'];
244
        $timeStamp     = !empty($timeStamp) ? $timeStamp : $_GET['timestamp'];
245
        $nonce         = !empty($nonce) ? $nonce : $_GET['nonce'];
246
247
        $pc  = new WXBizMsgCrypt($token, $encodingAesKey, $appId);
248
        $msg = '';
249
250
        $errCode = $pc->decryptMsg($msg_signature, $timeStamp, $nonce, $xml, $msg);
251
252
        if ($errCode == 0) {
253
            return $msg;
254
        } else {
255
            exit($errCode);
0 ignored issues
show
Coding Style Compatibility introduced by
The method decryptMsg() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
256
        }
257
    }
258
259
    /**
260
     * 魔术方法 处理 返回结果
261
     *
262
     * @param  string $target [事件]
263
     * @param         $event
264
     *
265
     * @return string $event  [类型]
266
     * @return array  $xml    [xml]
267
     */
268
    public function __call($target, $event)
0 ignored issues
show
Coding Style introduced by
__call uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
269
    {
270
        $object = self::$XML_OBJECT;
271
        if (!$object || !is_object($object) || empty($object)) {
272
            $xml = file_get_contents('php://input');
273
274
            $msg_signature = isset($_GET['msg_signature']) ? $_GET['msg_signature'] : null;
275
276
            if (!empty($msg_signature) && $msg_signature != '') {
277
                $xml = $this->decryptMsg($xml);
278
            }
279
280
            $object           = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
281
            self::$XML_OBJECT = $object;
282
        }
283
284
        $target = strtolower(trim($target));
285
286
        $event = strtolower(trim(reset($event)));
287
288
        $rx_target = strtolower(trim($object->MsgType));
289
        $rx_event  = strtolower(trim($object->Event));
290
291
        if ($target == $rx_target) {
292
            if ($event == '*') {
293
                $array = json_decode(json_encode($object), true);
294
            } elseif ($event == $rx_event) {
295
                $array = json_decode(json_encode($object), true);
296
            } else {
297
                $array = false;
298
            }
299
        } else {
300
            $array = false;
301
        }
302
303
        return $array;
304
    }
305
306
    /**
307
     * array转xml.
308
     *
309
     * @param array $arr
310
     * @param bool  $flag
311
     * @param bool  $especial
312
     *
313
     * @return string
314
     */
315
    public function arrayToXml($arr = [], $flag = true, $especial = false)
316
    {
317
        if ($flag) {
318
            $xml = '<xml>';
319
        } else {
320
            $xml = '';
321
        }
322
323
        foreach ($arr as $key => $val) {
324
            if (is_numeric($val)) {
325
                $xml .= '<' . $key . '>' . $val . '</' . $key . '>';
326
            } elseif (is_array($val)) {
327
                if (strtolower($key) == 'item') {
328
                    $xml .= $this->arrayToXml($val, false, true);
329
                } elseif ($especial == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
330
                    $xml .= '<item>';
331
                    $xml .= $this->arrayToXml($val, false, true);
332
                    $xml .= '</item>';
333
                } else {
334
                    $xml .= !is_numeric($key) ? '<' . ucfirst($key) . '>' : '';
335
                    $xml .= $this->arrayToXml($val, false);
336
                    $xml .= !is_numeric($key) ? '</' . ucfirst($key) . '>' : '';
337
                }
338
            } else {
339
                $xml .= '<' . $key . '><![CDATA[' . $val . ']]></' . $key . '>';
340
            }
341
        }
342
343
        if ($flag) {
344
            $xml .= '</xml>';
345
        }
346
347
        return $xml;
348
    }
349
350
    public function getXmlObject()
351
    {
352
        return self::$XML_OBJECT;
353
    }
354
355
    /**
356
     * 第三方 post 推送
357
     *
358
     * @param      $url
359
     * @param null $xml
360
     *
361
     * @return mixed
362
     */
363
    public function https_xml($url, $xml = null)
364
    {
365
        $ch = curl_init($url);
366
        curl_setopt($ch, CURLOPT_URL, $url);
367
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
368
        //curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
369
        curl_setopt($ch, CURLOPT_POST, 1);
370
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
371
        $response = curl_exec($ch);
372
        if (curl_errno($ch)) {
373
            print curl_error($ch);
374
        }
375
        curl_close($ch);
376
377
        return $response;
378
    }
379
380
    /**
381
     * 随机生成16位字符串
382
     *
383
     * @return string 生成的字符串
384
     */
385 View Code Duplication
    public function getRandomStr()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386
    {
387
        $str     = "";
388
        $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
389
        $max     = strlen($str_pol) - 1;
390
        for ($i = 0; $i < 16; $i++) {
391
            $str .= $str_pol[mt_rand(0, $max)];
392
        }
393
394
        return $str;
395
    }
396
}