Passed
Push — master ( 399a37...aa154b )
by Lawrence
01:34
created

Server::setInput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 5
cp 0
crap 2
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
    public function __construct($config = [])
64
    {
65
        $this->config = array_merge([
66
            "debug"       => false,
67
            "secret"      => null,
68
            "allowed_ips" => []
69
        ], $config);
70
71
        // check and set client timeout
72
        if (!isset($this->config["timeout"]) || !is_numeric($this->config["timeout"])) {
73
            $this->config["timeout"] = 10;
74
        }
75
    }
76
    
77
    /**
78
     * Sets inbound input value into scope
79
     *
80
     * @return void
81
     */
82
    private function setInput()
83
    {
84
        $this->input = file_get_contents("php://input");
85
        $this->input = gzinflate($this->input);
86
        $this->input = json_decode($this->input, true);
87
    }
88
    
89
    /**
90
     * Check allowed IPs
91
     *
92
     * @return bool
93
     */
94
    private function checkAllowedIp($ip, $allowed_ips = [])
95
    {
96
        return !(!empty($allowed_ips) && !in_array($ip, $allowed_ips));
97
    }
98
    
99
    /**
100
     * Execute core component
101
     */
102
    private function executeCoreComponent($component, $action)
103
    {
104
        // component is plinker endpoint
105
        $ns = "\\Plinker\\Core\\Endpoint\\".ucfirst($component);
106
107
        if (class_exists($ns)) {
108
            //
109
            $response = $this->execute($ns, $action);
110
        } else {
111
            if (empty($component) && $action === "info") {
112
                $response = $this->info();
113
            } else {
114
                $response = sprintf(Server::ERROR_EXT_COMPONENT, $component);
115
            }
116
        }
117
        
118
        return $response;
119
    }
120
121
    /**
122
     * Execute user component
123
     */
124
    private function executeUserComponent($component, $action)
125
    {
126
        $ns = null;
127
        
128
        //
129
        if (!empty($this->config["classes"][$component][0])) {
130
            $ns = $this->config["classes"][$component][0];
131
        }
132
133
        //
134
        if (!empty($this->config["classes"][$component][1])) {
135
            $this->config = array_merge(
136
                $this->config,
137
                $this->config["classes"][$component][1]
138
            );
139
        }
140
141
        //
142
        if (!empty($ns) && !file_exists($ns)) {
143
            $this->response = serialize([
144
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
145
                "code"  => 422
146
            ]);
147
            return;
148
        }
149
150
        //
151
        require($ns);
152
153
        //
154
        if (!class_exists($component)) {
155
            $this->response = serialize([
156
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
157
                "code"  => 422
158
            ]);
159
            return;
160
        }
161
162
        //
163
        return $this->execute($component, $action);
164
    }
165
166
    /**
167
     * Listen method
168
     *
169
     * <code>
170
     *  $server->listen();
171
     * </code>
172
     *
173
     * @return void
174
     */
175
    public function listen()
176
    {
177
        $this->setInput();
178
        
179
        // load signer
180
        if (!$this->signer) {
181
            $this->signer = new Lib\Signer($this->config);
182
        }
183
184
        // check allowed ips
185
        if (!$this->checkAllowedIp($_SERVER["REMOTE_ADDR"], $this->config["allowed_ips"])) {
186
            $this->response = [
187
                "error" => sprintf(Server::ERROR_IP, $_SERVER["REMOTE_ADDR"]),
188
                "code" => 403
189
            ];
190
            return;
191
        }
192
193
        // check header token matches data token
194
        if ($_SERVER["HTTP_PLINKER"] != $this->input["token"]) {
195
            $this->response = [
196
                "error" => Server::ERROR_TOKEN,
197
                "code" => 422
198
            ];
199
            return;
200
        }
201
202
        // decode input payload
203
        $this->input = $this->signer->decode($this->input);
204
205
        // could not decode payload
206
        if ($this->input === null) {
207
            $this->response = [
208
                "error" => Server::ERROR_DECODE,
209
                "code" => 422
210
            ];
211
            return;
212
        }
213
214
        // import user config
215
        $this->config = array_merge(
216
            $this->config,
217
            $this->input
218
        );
219
220
        // user component
221
        if (array_key_exists($this->input["component"], $this->config["classes"])) {
222
            //
223
            $response = $this->executeUserComponent($this->input["component"], $this->input["action"]);
224
        }
225
        // core component
226
        else {
227
            $response = $this->executeCoreComponent($this->input["component"], $this->input["action"]);
228
        }
229
230
        $this->response = $response;
231
    }
232
233
    /**
234
     * Return info about available classes
235
     *
236
     * <code>
237
     *  $client->info();
238
     * </code>
239
     *
240
     * @return array
241
     */
242
    private function info()
243
    {
244
        $response = [
245
            "class" => []
246
        ];
247
        foreach ($this->config["classes"] as $key => $val) {
248
            //
249
            require($val[0]);
250
251
            $reflection = new \ReflectionClass($key);
252
253
            foreach ($reflection->getMethods() as $method) {
254
                if (!in_array($method->getName(), ["__construct"])) {
255
                    $param = [];
256
                    foreach ($method->getParameters() as $parameter) {
257
                        $param[] = $parameter->getName();
258
                    }
259
                    $response["class"][$key]["methods"][$method->getName()] = $param;
260
                }
261
            }
262
        }
263
264
        return $response;
265
    }
266
267
    /**
268
     * Execute component
269
     *
270
     * @param  string $ns      component class namespace
271
     * @param  string $action  component action
272
     * @return string
273
     */
274
    private function execute($ns, $action)
275
    {
276
        $component = new $ns($this->config);
277
278
        if (method_exists($component, $action)) {
279
            $response = call_user_func_array(
280
                [
281
                    $component,
282
                    $action
283
                ],
284
                $this->input["params"]
285
            );
286
        } else {
287
            $response = sprintf(Server::ERROR_ACTION, $action, $ns);
288
        }
289
290
        return $response;
291
    }
292
    
293
    /**
294
     *
295
     */
296
    private function __destruct()
297
    {
298
        header("Content-Type: text/plain; charset=utf-8");
299
        echo json_encode($this->signer->encode($this->response), JSON_PRETTY_PRINT);
300
    }
301
}
302