Passed
Push — master ( 40b0ee...7fc7d6 )
by Lawrence
01:28
created

Server::setInput()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 6
ccs 3
cts 5
cp 0.6
crap 2.2559
rs 9.4285
1
<?php
2
/*
3
 +------------------------------------------------------------------------+
4
 | Plinker-RPC PHP                                                        |
5
 +------------------------------------------------------------------------+
6
 | Copyright (c)2017-2018 (https://github.com/plinker-rpc/core)           |
7
 +------------------------------------------------------------------------+
8
 | This source file is subject to MIT License                             |
9
 | that is bundled with this package in the file LICENSE.                 |
10
 |                                                                        |
11
 | If you did not receive a copy of the license and are unable to         |
12
 | obtain it through the world-wide-web, please send an email             |
13
 | to [email protected] so we can send you a copy immediately.        |
14
 +------------------------------------------------------------------------+
15
 | Authors: Lawrence Cherone <[email protected]>                     |
16
 +------------------------------------------------------------------------+
17
 */
18
19
namespace Plinker\Core;
20
21
/**
22
 * Plinker\Core\Server
23
 */
24
final class Server
25
{
26
    /**
27
     * @var
28
     */
29
    protected $input;
30
31
    /**
32
     * @var
33
     */
34
    protected $config;
35
36
    /**
37
     * @var
38
     */
39
    protected $signer;
40
    
41
    /**
42
     * @var
43
     */
44
    protected $response;
45
    
46
47
    /**
48
     * @const - error strings
49
     */
50
    const ERROR_IP            = "IP not in allowed list (%s)";
51
    const ERROR_TOKEN         = "Plinker token mismatch";
52
    const ERROR_DECODE        = "Failed to decode payload, check secret";
53
    const ERROR_USR_COMPONENT = "User component class (%s) not found";
54
    const ERROR_EXT_COMPONENT = "Component (%s) not implemented";
55
    const ERROR_ACTION        = "Component action (%s) not implemented in: %s";
56
57
    /**
58
     * Class construct
59
     *
60
     * @param  array $config - config array which holds object configuration
61
     * @return void
62
     */
63 2
    public function __construct($config = [])
64
    {
65 2
        $this->config = array_merge([
66 2
            "debug"       => false,
67
            "secret"      => null,
68
            "allowed_ips" => []
69 2
        ], $config);
70
71
        // check and set client timeout
72 2
        if (!isset($this->config["timeout"]) || !is_numeric($this->config["timeout"])) {
73 2
            $this->config["timeout"] = 10;
74
        }
75 2
    }
76
    
77
    /**
78
     * Sets inbound input value into scope
79
     *
80
     * @return void
81
     */
82 1
    private function setInput()
83
    {
84 1
        $this->input = file_get_contents("php://input");
85 1
        if (!empty($this->input)) {
86
            $this->input = gzinflate($this->input);
87
            $this->input = json_decode($this->input, true);
88
        }
89 1
    }
90
    
91
    /**
92
     * Check allowed IPs
93
     *
94
     * @return bool
95
     */
96 1
    private function checkAllowedIp($ip, $allowed_ips = [])
97
    {
98 1
        return !(!empty($allowed_ips) && !in_array($ip, $allowed_ips));
99
    }
100
    
101
    /**
102
     * Execute core component
103
     */
104
    private function executeCoreComponent($component, $action)
105
    {
106
        // component is plinker endpoint
107
        $ns = "\\Plinker\\Core\\Endpoint\\".ucfirst($component);
108
109
        if (class_exists($ns)) {
110
            //
111
            $response = $this->execute($ns, $action);
112
        } else {
113
            if (empty($component) && $action === "info") {
114
                $response = $this->info();
115
            } else {
116
                $response = sprintf(Server::ERROR_EXT_COMPONENT, $component);
117
            }
118
        }
119
        
120
        return $response;
121
    }
122
123
    /**
124
     * Execute user component
125
     */
126
    private function executeUserComponent($component, $action)
127
    {
128
        $ns = null;
129
        
130
        //
131
        if (!empty($this->config["classes"][$component][0])) {
132
            $ns = $this->config["classes"][$component][0];
133
        }
134
135
        //
136
        if (!empty($this->config["classes"][$component][1])) {
137
            $this->config = array_merge(
138
                $this->config,
139
                $this->config["classes"][$component][1]
140
            );
141
        }
142
143
        //
144
        if (!empty($ns) && !file_exists($ns)) {
145
            $this->response = serialize([
146
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
147
                "code"  => 422
148
            ]);
149
            return;
150
        }
151
152
        //
153
        require($ns);
154
155
        //
156
        if (!class_exists($component)) {
157
            $this->response = serialize([
158
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
159
                "code"  => 422
160
            ]);
161
            return;
162
        }
163
164
        //
165
        return $this->execute($component, $action);
166
    }
167
168
    /**
169
     * Listen method
170
     *
171
     * <code>
172
     *  $server->listen();
173
     * </code>
174
     *
175
     * @return void
176
     */
177 1
    public function listen()
178
    {
179 1
        $this->setInput();
180
        
181
        // load signer
182 1
        if (!$this->signer) {
183 1
            $this->signer = new Lib\Signer($this->config);
184
        }
185
186
        // check allowed ips
187 1
        if (!$this->checkAllowedIp($_SERVER["REMOTE_ADDR"], $this->config["allowed_ips"])) {
188
            return json_encode($this->signer->encode([
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_encode($this...Core\JSON_PRETTY_PRINT) returns the type string which is incompatible with the documented return type void.
Loading history...
189
                "error" => sprintf(Server::ERROR_IP, $_SERVER["REMOTE_ADDR"]),
190
                "code" => 403
191
            ]), JSON_PRETTY_PRINT);
192
            return;
0 ignored issues
show
Unused Code introduced by
return is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
193
        }
194
195
        // check header token matches data token
196 1
        if (empty($this->input["token"]) || $_SERVER["HTTP_PLINKER"] != $this->input["token"]) {
197 1
            return json_encode($this->signer->encode([
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_encode($this...Core\JSON_PRETTY_PRINT) returns the type string which is incompatible with the documented return type void.
Loading history...
198 1
                "error" => Server::ERROR_TOKEN,
199
                "code" => 422
200 1
            ]), JSON_PRETTY_PRINT);
201
            return;
202
        }
203
204
        // decode input payload
205
        $this->input = $this->signer->decode($this->input);
206
207
        // could not decode payload
208
        if ($this->input === null) {
209
            return json_encode($this->signer->encode([
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_encode($this...Core\JSON_PRETTY_PRINT) returns the type string which is incompatible with the documented return type void.
Loading history...
210
                "error" => Server::ERROR_DECODE,
211
                "code" => 422
212
            ]), JSON_PRETTY_PRINT);
213
        }
214
215
        // import user config
216
        $this->config = array_merge(
217
            $this->config,
218
            $this->input
219
        );
220
221
        // user component
222
        if (array_key_exists($this->input["component"], $this->config["classes"])) {
223
            //
224
            $response = $this->executeUserComponent($this->input["component"], $this->input["action"]);
225
        }
226
        // core component
227
        else {
228
            $response = $this->executeCoreComponent($this->input["component"], $this->input["action"]);
229
        }
230
231
        // sign response and return
232
        return json_encode($this->signer->encode($response), JSON_PRETTY_PRINT);
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_encode($this...Core\JSON_PRETTY_PRINT) returns the type string which is incompatible with the documented return type void.
Loading history...
233
    }
234
235
    /**
236
     * Return info about available classes
237
     *
238
     * <code>
239
     *  $client->info();
240
     * </code>
241
     *
242
     * @return array
243
     */
244
    private function info()
245
    {
246
        $response = [
247
            "class" => []
248
        ];
249
        foreach ($this->config["classes"] as $key => $val) {
250
            //
251
            require($val[0]);
252
253
            $reflection = new \ReflectionClass($key);
254
255
            foreach ($reflection->getMethods() as $method) {
256
                if (!in_array($method->getName(), ["__construct"])) {
257
                    $param = [];
258
                    foreach ($method->getParameters() as $parameter) {
259
                        $param[] = $parameter->getName();
260
                    }
261
                    $response["class"][$key]["methods"][$method->getName()] = $param;
262
                }
263
            }
264
        }
265
266
        return $response;
267
    }
268
269
    /**
270
     * Execute component
271
     *
272
     * @param  string $ns      component class namespace
273
     * @param  string $action  component action
274
     * @return string
275
     */
276
    private function execute($ns, $action)
277
    {
278
        $component = new $ns($this->config);
279
280
        if (method_exists($component, $action)) {
281
            $response = call_user_func_array(
282
                [
283
                    $component,
284
                    $action
285
                ],
286
                $this->input["params"]
287
            );
288
        } else {
289
            $response = sprintf(Server::ERROR_ACTION, $action, $ns);
290
        }
291
292
        return $response;
293
    }
294
    
295
}
296