Completed
Push — master ( 094c95...95e0be )
by Lawrence
01:26
created

Server::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
nc 2
nop 1
dl 0
loc 11
ccs 0
cts 9
cp 0
crap 12
rs 9.4285
c 1
b 0
f 0
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
        //
127
        if (!empty($this->config["classes"][$component][0])) {
128
            $ns = $this->config["classes"][$component][0];
129
        }
130
131
        //
132
        if (!empty($this->config["classes"][$component][1])) {
133
            $this->config = array_merge(
134
                $this->config,
135
                $this->config["classes"][$component][1]
136
            );
137
        }
138
139
        //
140
        if (!empty($ns) && !file_exists($ns)) {
141
            $this->response = serialize([
142
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
143
                "code"  => 422
144
            ]);
145
            return;
146
        }
147
148
        //
149
        require($ns);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ns does not seem to be defined for all execution paths leading up to this point.
Loading history...
150
151
        //
152
        if (!class_exists($component)) {
153
            $this->response = serialize([
154
                "error" => sprintf(Server::ERROR_USR_COMPONENT, $component),
155
                "code"  => 422
156
            ]);
157
            return;
158
        }
159
160
        //
161
        return $this->execute($component, $action);
162
    }
163
164
    /**
165
     * Listen method
166
     *
167
     * <code>
168
     *  $server->listen();
169
     * </code>
170
     *
171
     * @return void
172
     */
173
    public function listen()
174
    {
175
        $this->setInput();
176
177
        // check allowed ips
178
        if (!$this->checkAllowedIp($_SERVER["REMOTE_ADDR"], $this->config["allowed_ips"])) {
179
            $this->response = serialize([
180
                "error" => sprintf(Server::ERROR_IP, $_SERVER["REMOTE_ADDR"]),
181
                "code" => 403
182
            ]);
183
            return;
184
        }
185
186
        // check header token matches data token
187
        if ($_SERVER["HTTP_PLINKER"] != $this->input["token"]) {
188
            $this->response = serialize([
189
                "error" => Server::ERROR_TOKEN,
190
                "code" => 422
191
            ]);
192
            return;
193
        }
194
195
        // load signer
196
        if (!$this->signer) {
197
            $this->signer = new Lib\Signer($this->config);
198
        }
199
200
        // decode input payload
201
        $this->input = $this->signer->decode($this->input);
202
203
        // could not decode payload
204
        if ($this->input === null) {
205
            $this->response = serialize([
206
                "error" => Server::ERROR_DECODE,
207
                "code" => 422
208
            ]);
209
            return;
210
        }
211
212
        // import user config
213
        $this->config = array_merge(
214
            $this->config,
215
            $this->input
216
        );
217
218
        // user component
219
        if (array_key_exists($this->input["component"], $this->config["classes"])) {
220
            //
221
            $response = $this->executeUserComponent($this->input["component"], $this->input["action"]);
222
        } 
223
        // core component
224
        else {
225
            $response = $this->executeCoreComponent($this->input["component"], $this->input["action"]);
226
        }
227
228
        $this->response = serialize($response);
229
    }
230
231
    /**
232
     * Return info about available classes
233
     *
234
     * <code>
235
     *  $client->info();
236
     * </code>
237
     *
238
     * @return array
239
     */
240
    private function info()
241
    {
242
        $response = [
243
            "class" => []
244
        ];
245
        foreach ($this->config["classes"] as $key => $val) {
246
            //
247
            require($val[0]);
248
249
            $reflection = new \ReflectionClass($key);
250
251
            foreach ($reflection->getMethods() as $method) {
252
                if (!in_array($method->getName(), ["__construct"])) {
253
                    $param = [];
254
                    foreach ($method->getParameters() as $parameter) {
255
                        $param[] = $parameter->getName();
256
                    }
257
                    $response["class"][$key]["methods"][$method->getName()] = $param;
258
                }
259
            }
260
        }
261
262
        return $response;
263
    }
264
265
    /**
266
     * Execute component
267
     *
268
     * @param  string $ns      component class namespace
269
     * @param  string $action  component action
270
     * @return string
271
     */
272
    private function execute($ns, $action)
273
    {
274
        $response  = null;
275
        $component = new $ns($this->config);
276
277
        if (method_exists($component, $action)) {
278
            $response = call_user_func_array(
279
                [
280
                    $component,
281
                    $action
282
                ],
283
                $this->input["params"]
284
            );
285
        } else {
286
            $response = sprintf(Server::ERROR_ACTION, $action, $ns);
287
        }
288
289
        return $response;
290
    }
291
    
292
    /**
293
     *
294
     */
295
    private function __destruct()
296
    {
297
        header("Content-Type: text/plain; charset=utf-8");
298
        echo $this->response;
299
    }
300
}
301