Issues (21)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/ServerRequestHandler.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace ByJG\RestServer;
4
5
use ByJG\Cache\Psr16\NoCacheEngine;
6
use ByJG\RestServer\Exception\ClassNotFoundException;
7
use ByJG\RestServer\Exception\Error404Exception;
8
use ByJG\RestServer\Exception\Error405Exception;
9
use ByJG\RestServer\Exception\Error520Exception;
10
use ByJG\RestServer\Exception\InvalidClassException;
11
use ByJG\RestServer\Exception\OperationIdInvalidException;
12
use ByJG\RestServer\Exception\SchemaInvalidException;
13
use ByJG\RestServer\Exception\SchemaNotFoundException;
14
use ByJG\RestServer\HandleOutput\HandleOutputInterface;
15
use ByJG\RestServer\HandleOutput\HtmlHandler;
16
use ByJG\RestServer\HandleOutput\JsonHandler;
17
use ByJG\RestServer\HandleOutput\XmlHandler;
18
use ByJG\Util\Uri;
19
use Closure;
20
use FastRoute\Dispatcher;
21
use FastRoute\RouteCollector;
22
use Psr\SimpleCache\CacheInterface;
23
use Psr\SimpleCache\InvalidArgumentException;
24
25
class ServerRequestHandler
26
{
27
    const OK = "OK";
28
    const METHOD_NOT_ALLOWED = "NOT_ALLOWED";
29
    const NOT_FOUND = "NOT FOUND";
30
31
    protected $routes = null;
32
33
    protected $defaultHandler = null;
34
35
    protected $mimeTypeHandler = [
36
        "text/xml" => XmlHandler::class,
37
        "application/xml" => XmlHandler::class,
38
        "text/html" => HtmlHandler::class,
39
        "application/json" => JsonHandler::class
40
    ];
41
42
    protected $pathHandler = [
43
44
    ];
45
46
    public function getRoutes()
47
    {
48
        return $this->routes;
49
    }
50
51
    /**
52
     * @param RoutePattern[] $routes
53
     */
54
    public function setRoutes($routes)
55
    {
56
        foreach ((array)$routes as $route) {
57
            $this->addRoute($route);
58
        }
59
    }
60
61
    /**
62
     * @param RoutePattern $route
63
     */
64
    public function addRoute(RoutePattern $route)
65
    {
66
        if (is_null($this->routes)) {
67
            $this->routes = [];
68
        }
69
        $this->routes[] = $route;
70
    }
71
72
    /**
73
     * @return HandleOutputInterface
74
     */
75
    public function getDefaultHandler()
76
    {
77
        if (empty($this->defaultHandler)) {
78
            $this->defaultHandler = new JsonHandler();
79
        }
80
        return $this->defaultHandler;
81
    }
82
83
    /**
84
     * @param HandleOutputInterface $defaultHandler
85
     */
86
    public function setDefaultHandler(HandleOutputInterface $defaultHandler)
87
    {
88
        $this->defaultHandler = $defaultHandler;
89
    }
90
91
    /**
92
     * @throws ClassNotFoundException
93
     * @throws Error404Exception
94
     * @throws Error405Exception
95
     * @throws Error520Exception
96
     * @throws InvalidClassException
97
     */
98
    protected function process()
99
    {
100
        // Initialize ErrorHandler with default error handler
101
        ErrorHandler::getInstance()->register();
102
103
        // Get the URL parameters
104
        $httpMethod = $_SERVER['REQUEST_METHOD'];
105
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
106
        parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $queryStr);
107
108
        // Generic Dispatcher for RestServer
109
        $dispatcher = \FastRoute\simpleDispatcher(function (RouteCollector $r) {
110
111
            foreach ($this->getRoutes() as $route) {
112
                $r->addRoute(
113
                    $route->properties('method'),
114
                    $route->properties('pattern'),
115
                    [
116
                        "handler" => $route->properties('handler'),
117
                        "class" => $route->properties('class'),
118
                        "function" => $route->properties('function')
119
                    ]
120
                );
121
            }
122
        });
123
124
        $routeInfo = $dispatcher->dispatch($httpMethod, $uri);
0 ignored issues
show
It seems like $uri defined by parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) on line 105 can also be of type false; however, FastRoute\Dispatcher::dispatch() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
125
126
        // Default Handler for errors
127
        $this->getDefaultHandler()->writeHeader();
128
        ErrorHandler::getInstance()->setHandler($this->getDefaultHandler()->getErrorHandler());
129
130
        // Processing
131
        switch ($routeInfo[0]) {
132
            case Dispatcher::NOT_FOUND:
133
                throw new Error404Exception("404 Route '$uri' Not found");
134
135
            case Dispatcher::METHOD_NOT_ALLOWED:
136
                throw new Error405Exception('405 Method Not Allowed');
137
138
            case Dispatcher::FOUND:
139
                // ... 200 Process:
140
                $vars = array_merge($routeInfo[2], $queryStr);
141
142
                // Instantiate the Service Handler
143
                $handlerRequest = $routeInfo[1];
144
145
                // Execute the request
146
                $handler = !empty($handlerRequest['handler']) ? $handlerRequest['handler'] : $this->getDefaultHandler();
147
                $this->executeRequest(
148
                    new $handler(),
149
                    $handlerRequest['class'],
150
                    $handlerRequest['function'],
151
                    $vars
152
                );
153
154
                break;
155
156
            default:
157
                throw new Error520Exception('Unknown');
158
        }
159
    }
160
161
    /**
162
     * @param HandleOutputInterface $handler
163
     * @param string $class
164
     * @param string $function
165
     * @param array $vars
166
     * @throws ClassNotFoundException
167
     * @throws InvalidClassException
168
     */
169
    protected function executeRequest($handler, $class, $function, $vars)
170
    {
171
        // Setting Default Headers and Error Handler
172
        $handler->writeHeader();
173
        ErrorHandler::getInstance()->setHandler($handler->getErrorHandler());
174
175
        // Set all default values
176
        foreach (array_keys($vars) as $key) {
177
            $_REQUEST[$key] = $_GET[$key] = $vars[$key];
178
        }
179
180
        // Create the Request and Response methods
181
        $request = new HttpRequest($_GET, $_POST, $_SERVER, isset($_SESSION) ? $_SESSION : [], $_COOKIE);
182
        $response = new HttpResponse();
183
184
        // Process Closure
185
        if ($function instanceof Closure) {
186
            $function($response, $request);
187
            echo $handler->processResponse($response);
188
            return;
189
        }
190
191
        // Process Class::Method()
192
        if (!class_exists($class)) {
193
            throw new ClassNotFoundException("Class '$class' defined in the route is not found");
194
        }
195
        $instance = new $class();
196
        if (!method_exists($instance, $function)) {
197
            throw new InvalidClassException("There is no method '$class::$function''");
198
        }
199
        $instance->$function($response, $request);
200
        $handler->processResponse($response);
201
    }
202
203
    /**
204
     * Handle the ROUTE (see web/app-dist.php)
205
     *
206
     * @param RoutePattern[]|null $routePattern
207
     * @param bool $outputBuffer
208
     * @param bool $session
209
     * @return bool|void
210
     * @throws ClassNotFoundException
211
     * @throws Error404Exception
212
     * @throws Error405Exception
213
     * @throws Error520Exception
214
     * @throws InvalidClassException
215
     */
216
    public function handle($routePattern = null, $outputBuffer = true, $session = true)
217
    {
218
        if ($outputBuffer) {
219
            ob_start();
220
        }
221
        if ($session) {
222
            session_start();
223
        }
224
225
        /**
226
         * @var ServerRequestHandler
227
         */
228
        $this->setRoutes($routePattern);
229
230
        // --------------------------------------------------------------------------
231
        // Check if script exists or if is itself
232
        // --------------------------------------------------------------------------
233
234
        $debugBacktrace =  debug_backtrace();
235
        if (!empty($_SERVER['SCRIPT_FILENAME'])
236
            && file_exists($_SERVER['SCRIPT_FILENAME'])
237
            && basename($_SERVER['SCRIPT_FILENAME']) !== basename($debugBacktrace[0]['file'])
238
        ) {
239
            $file = $_SERVER['SCRIPT_FILENAME'];
240
            if (strrchr($file, '.') === ".php") {
241
                require_once($file);
242
            } else {
243
                if (!defined("RESTSERVER_TEST")) {
244
                    header("Content-Type: " . $this->mimeContentType($file));
245
                }
246
247
                echo file_get_contents($file);
248
            }
249
            return true;
250
        }
251
252
        return $this->process();
253
    }
254
255
    /**
256
     * Get the Mime Type based on the filename
257
     *
258
     * @param string $filename
259
     * @return string
260
     * @throws Error404Exception
261
     */
262
    public function mimeContentType($filename)
263
    {
264
265
        $mimeTypes = array(
266
            'txt' => 'text/plain',
267
            'htm' => 'text/html',
268
            'html' => 'text/html',
269
            'php' => 'text/html',
270
            'css' => 'text/css',
271
            'js' => 'application/javascript',
272
            'json' => 'application/json',
273
            'xml' => 'application/xml',
274
            'swf' => 'application/x-shockwave-flash',
275
            'flv' => 'video/x-flv',
276
            // images
277
            'png' => 'image/png',
278
            'jpe' => 'image/jpeg',
279
            'jpeg' => 'image/jpeg',
280
            'jpg' => 'image/jpeg',
281
            'gif' => 'image/gif',
282
            'bmp' => 'image/bmp',
283
            'ico' => 'image/vnd.microsoft.icon',
284
            'tiff' => 'image/tiff',
285
            'tif' => 'image/tiff',
286
            'svg' => 'image/svg+xml',
287
            'svgz' => 'image/svg+xml',
288
            // archives
289
            'zip' => 'application/zip',
290
            'rar' => 'application/x-rar-compressed',
291
            'exe' => 'application/x-msdownload',
292
            'msi' => 'application/x-msdownload',
293
            'cab' => 'application/vnd.ms-cab-compressed',
294
            // audio/video
295
            'mp3' => 'audio/mpeg',
296
            'qt' => 'video/quicktime',
297
            'mov' => 'video/quicktime',
298
            // adobe
299
            'pdf' => 'application/pdf',
300
            'psd' => 'image/vnd.adobe.photoshop',
301
            'ai' => 'application/postscript',
302
            'eps' => 'application/postscript',
303
            'ps' => 'application/postscript',
304
            // ms office
305
            'doc' => 'application/msword',
306
            'rtf' => 'application/rtf',
307
            'xls' => 'application/vnd.ms-excel',
308
            'ppt' => 'application/vnd.ms-powerpoint',
309
            // open office
310
            'odt' => 'application/vnd.oasis.opendocument.text',
311
            'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
312
        );
313
314
        if (!file_exists($filename)) {
315
            throw new Error404Exception();
316
        }
317
318
        $ext = substr(strrchr($filename, "."), 1);
319
        if (array_key_exists($ext, $mimeTypes)) {
320
            return $mimeTypes[$ext];
321
        } elseif (function_exists('finfo_open')) {
322
            $finfo = finfo_open(FILEINFO_MIME);
323
            $mimetype = finfo_file($finfo, $filename);
324
            finfo_close($finfo);
325
            return $mimetype;
326
        } else {
327
            return 'application/octet-stream';
328
        }
329
    }
330
331
    /**
332
     * @param $swaggerJson
333
     * @param CacheInterface|null $cache
334
     * @throws SchemaInvalidException
335
     * @throws SchemaNotFoundException
336
     * @throws OperationIdInvalidException
337
     * @throws InvalidArgumentException
338
     */
339
    public function setRoutesSwagger($swaggerJson, CacheInterface $cache = null)
340
    {
341
        if (!file_exists($swaggerJson)) {
342
            throw new SchemaNotFoundException("Schema '$swaggerJson' not found");
343
        }
344
345
        $schema = json_decode(file_get_contents($swaggerJson), true);
346
        if (!isset($schema['paths'])) {
347
            throw new SchemaInvalidException("Schema '$swaggerJson' is invalid");
348
        }
349
350
        if (is_null($cache)) {
351
            $cache = new NoCacheEngine();
352
        }
353
354
        $routePattern = $cache->get('SERVERHANDLERROUTES', false);
355
        if ($routePattern === false) {
356
            $routePattern = $this->generateRoutes($schema);
357
            $cache->set('SERVERHANDLERROUTES', $routePattern);
358
        }
359
        $this->setRoutes($routePattern);
360
    }
361
362
    /**
363
     * @param $schema
364
     * @return array
365
     * @throws OperationIdInvalidException
366
     */
367
    protected function generateRoutes($schema)
368
    {
369
        $basePath = isset($schema["basePath"]) ? $schema["basePath"] : "";
370
        if (empty($basePath) && isset($schema["servers"])) {
371
            $uri = new Uri($schema["servers"][0]["url"]);
372
            $basePath = $uri->getPath();
373
        }
374
375
        $pathList = $this->sortPaths(array_keys($schema['paths']));
376
377
        $routes = [];
378
        foreach ($pathList as $path) {
379
            foreach ($schema['paths'][$path] as $method => $properties) {
380
                $handler = $this->getMethodHandler($method, $basePath . $path, $properties);
381
                if (!isset($properties['operationId'])) {
382
                    throw new OperationIdInvalidException('OperationId was not found');
383
                }
384
385
                $parts = explode('::', $properties['operationId']);
386
                if (count($parts) !== 2) {
387
                    throw new OperationIdInvalidException(
388
                        'OperationId needs to be in the format Namespace\\class::method'
389
                    );
390
                }
391
392
                $routes[] = new RoutePattern(
393
                    strtoupper($method),
394
                    $basePath . $path,
395
                    $handler,
396
                    $parts[1],
397
                    $parts[0]
398
                );
399
            }
400
        }
401
402
        return $routes;
403
    }
404
405
    protected function sortPaths($pathList)
406
    {
407
        usort($pathList, function ($left, $right) {
408 View Code Duplication
            if (strpos($left, '{') === false && strpos($right, '{') !== false) {
409
                return -16384;
410
            }
411 View Code Duplication
            if (strpos($left, '{') !== false && strpos($right, '{') === false) {
412
                return 16384;
413
            }
414
            if (strpos($left, $right) !== false) {
415
                return -16384;
416
            }
417
            if (strpos($right, $left) !== false) {
418
                return 16384;
419
            }
420
            return strcmp($left, $right);
421
        });
422
423
        return $pathList;
424
    }
425
426
    /**
427
     * @param $method
428
     * @param $path
429
     * @param $properties
430
     * @return string
431
     * @throws OperationIdInvalidException
432
     */
433
    protected function getMethodHandler($method, $path, $properties)
434
    {
435
        $method = strtoupper($method);
436
        if (isset($this->pathHandler["$method::$path"])) {
437
            return $this->pathHandler["$method::$path"];
438
        }
439
        if (!isset($properties['produces'])) {
440
            return get_class($this->getDefaultHandler());
441
        }
442
443
        $produces = $properties['produces'];
444
        if (is_array($produces)) {
445
            $produces = $produces[0];
446
        }
447
448
        if (!isset($this->mimeTypeHandler[$produces])) {
449
            throw new OperationIdInvalidException("There is no handler for $produces");
450
        }
451
452
        return $this->mimeTypeHandler[$produces];
453
    }
454
455
    public function setMimeTypeHandler($mimetype, $handler)
456
    {
457
        $this->mimeTypeHandler[$mimetype] = $handler;
458
    }
459
460
    public function setPathHandler($method, $path, $handler)
461
    {
462
        $method = strtoupper($method);
463
        $this->pathHandler["$method::$path"] = $handler;
464
    }
465
}
466