Issues (2)

Security Analysis    no request data  

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/CacheHandler.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 Concat\Http\Handler;
4
5
use Concat\Cache\Adapter\AdapterFactory;
6
use Concat\Cache\CacheInterface;
7
use Doctrine\Common\Cache\FilesystemCache;
8
use GuzzleHttp\MessageFormatter;
9
use GuzzleHttp\Promise\FulfilledPromise;
10
use GuzzleHttp\Promise\PromiseInterface;
11
use GuzzleHttp\Psr7\Request;
12
use GuzzleHttp\Psr7\Response;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
use RuntimeException;
16
17
/**
18
 * Guzzle handler used to cache responses.
19
 */
20
class CacheHandler
21
{
22
23
    /**
24
     * @var \Concat\Cache\CacheInterface Cache provider.
25
     */
26
    protected $cache;
27
28
    /**
29
     * @var callable Default handler used to send response.
30
     */
31
    protected $handler;
32
33
    /**
34
     * @var \Psr\Log\LoggerInterface PSR-3 compliant logger.
35
     */
36
    protected $logger;
37
38
    /**
39
     * @var string|callable Constant or callable that accepts a Response.
40
     */
41
    protected $logLevel;
42
43
    /**
44
     * @var string Log template.
45
     */
46
    protected $logTemplate;
47
48
    /**
49
     * @var array Configuration options.
50
     */
51
    protected $options;
52
53
    /**
54
     *  @var bool Whether the most recent request was fetched from the cache.
55
     */
56
    private $fetchedFromCache = false;
57
58
    /**
59
     * Constructs a new cache handler.
60
     *
61
     * @param object $cache Cache provider.
62
     * @param callable $handler Default handler used to send response.
63
     * @param array $options Configuration options.
64
     */
65
    public function __construct($cache, $handler = null, array $options = [])
66
    {
67
        $handler = $handler ?: $this->getDefaultHandler();
68
69
        $this->setHandler($handler);
70
        $this->setCacheProvider($cache);
71
        $this->setOptions($options);
72
    }
73
74
    /**
75
     * Sets the fallback handler to use when the cache is invalid.
76
     *
77
     * @param callable $handler
78
     *
79
     * @codeCoverageIgnore
80
     */
81
    public function setHandler(callable $handler)
82
    {
83
        $this->handler = $handler;
84
    }
85
86
    /**
87
     * Sets the cache provider.
88
     *
89
     * @param object $cache
90
     */
91
    public function setCacheProvider($cache)
92
    {
93
        $this->cache = AdapterFactory::get($cache);
94
    }
95
96
    /**
97
     * Resets the options, merged with default values.
98
     *
99
     * @param array $options
100
     */
101
    public function setOptions(array $options)
102
    {
103
        $this->options = array_merge($this->getDefaultOptions(), $options);
104
    }
105
106
    /**
107
     * Sets the logger.
108
     *
109
     * @param LoggerInterface $logger
110
     */
111
    public function setLogger(LoggerInterface $logger)
112
    {
113
        $this->logger = $logger;
114
    }
115
116
    /**
117
     * Sets the template to use when logging cache events.
118
     *
119
     * @param string $logTemplate
120
     */
121
    public function setLogTemplate($logTemplate)
122
    {
123
        $this->logTemplate = $logTemplate;
124
    }
125
126
    /**
127
     * Returns a defined log template, or a default template otherwise.
128
     *
129
     * @return string Log template.
130
     */
131
    protected function getLogTemplate()
132
    {
133
        if (is_null($this->logTemplate)) {
134
            return MessageFormatter::SHORT . " {event} (expires in {expires}s)";
135
        }
136
137
        return $this->logTemplate;
138
    }
139
140
    /**
141
     * Returns the default handler, used if a handler is not set.
142
     *
143
     * @return callable
144
     * @codeCoverageIgnore
145
     */
146
    protected function getDefaultHandler()
147
    {
148
        return \GuzzleHttp\choose_handler();
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\choose_handler() has been deprecated with message: choose_handler will be removed in guzzlehttp/guzzle:8.0. Use Utils::chooseHandler instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
149
    }
150
151
    /**
152
     * Returns the default confiration options.
153
     *
154
     * @return array The default configuration options.
155
     */
156
    protected function getDefaultOptions()
157
    {
158
        return [
159
160
            // HTTP methods that should be cached
161
            'methods' => ['GET', 'HEAD', 'OPTIONS'],
162
163
            // Time in seconds to cache the response for
164
            'expire'  => 30,
165
166
            // Accepts a request and returns true if it should be cached
167
            'filter'  => null,
168
        ];
169
    }
170
171
    /**
172
     * Called when a request is made on the client.
173
     *
174
     * @return PromiseInterface
175
     */
176
    public function __invoke(Request $request, array $options)
177
    {
178
        if ($this->shouldCacheRequest($request)) {
179
            return $this->cache($request, $options);
180
        }
181
182
        return $this->invokeDefault($request, $options);
183
    }
184
185
    /**
186
     * Attempts to fetch, otherwise promises to cache a response when the
187
     * default handler fulfills its promise.
188
     *
189
     * @param Request $request The request to cache.
190
     * @param array $options Configuration options.
191
     *
192
     * @return PromiseInterface
193
     */
194
    protected function cache(Request $request, array $options)
195
    {
196
        $key = $this->getKey($request, $options);
197
198
        if ($this->cache->contains($key)) {
199
            $response = $this->fetch($request, $key);
200
201
            // Return the cached response if fetch was successful.
202
            if ($response) {
203
                $this->fetchedFromCache = true;
204
                return new FulfilledPromise($response);
205
            }
206
        }
207
208
        return $this->request($request, $options, $key);
209
    }
210
211
    /**
212
     * Makes the request and stores it in the cache if required to.
213
     *
214
     * @param Request $request
215
     * @param array   $options
216
     * @param string  $key
217
     *
218
     * @return PromiseInterface
219
     */
220
    protected function request(Request $request, array $options, $key)
221
    {
222
        $this->fetchedFromCache = false;
223
224
        // Make the request using the default handler.
225
        $promise = $this->invokeDefault($request, $options);
226
227
        // Don't store if the expire time isn't positive.
228
        if ($this->options['expire'] <= 0) {
229
            return $promise;
230
        }
231
232
        // Promise to store the response once the default promise is fulfilled.
233
        return $promise->then(function ($response) use ($request, $key) {
234
            if ($this->shouldCacheResponse($response)) {
235
236
                // Evaluate the content stream so that it can be cached.
237
                $stream = new CachedStream((string) $response->getBody());
238
                $response = $response->withBody($stream);
239
240
                $this->store($request, $response, $key);
241
            }
242
243
            return $response;
244
        });
245
    }
246
247
    /**
248
     * Returns true if the most recent request was fetched from the cache, or
249
     * false if the request was made (regardless of whether it was then cached).
250
     *
251
     * @return bool
252
     */
253
    public function lastRequestWasFetchedFromCache()
254
    {
255
        return $this->fetchedFromCache;
256
    }
257
258
    /**
259
     * Attempts to fetch a response bundle from the cache for the given key.
260
     *
261
     * @param Request $request
262
     * @param string $key
263
     *
264
     * @return Response|null A response null if invalid.
265
     */
266
    protected function fetch(Request $request, $key)
267
    {
268
        $bundle = $this->fetchBundle($key);
269
270
        if ($bundle) {
271
            $this->logFetchedBundle($request, $bundle);
272
            return $bundle['response'];
273
        }
274
    }
275
276
    /**
277
     * Fetches a response bundle from the cache for a given key.
278
     *
279
     * @param string $key The key to fetch.
280
     *
281
     * @return array|null Bundle from cache or null if expired.
282
     */
283
    protected function fetchBundle($key)
284
    {
285
        $bundle = $this->cache->fetch($key);
286
287
        if ($bundle === false) {
288
            throw new RuntimeException("Failed to fetch response from cache");
289
        }
290
        if ($bundle === null || time() >= $bundle['expires']) {
291
            // Delete expired entries so that they don't trigger 'contains'.
292
            $this->cache->delete($key);
293
294
            return;
295
        }
296
297
        return $bundle;
298
    }
299
300
    /**
301
     * Builds and stores a cache bundle.
302
     *
303
     * @param Request $request
304
     * @param Response $response
305
     * @param string $key
306
     *
307
     * @throws RuntimeException if it fails to store the response in the cache.
308
     */
309
    protected function store(Request $request, Response $response, $key)
310
    {
311
        $bundle = $this->buildCacheBundle($response);
312
313
        // Store the bundle in the cache
314
        $save = $this->cache->store($key, $bundle, $this->options['expire']);
315
316
        if ($save === false) {
317
            throw new RuntimeException("Failed to store response to cache");
318
        }
319
320
        // Log that it has been stored
321
        $this->logStoredBundle($request, $bundle);
322
    }
323
324
    /**
325
     * Builds a cache bundle using a given response.
326
     *
327
     * @param Response $response
328
     *
329
     * @return array The response bundle to cache.
330
     */
331
    protected function buildCacheBundle(Response $response)
332
    {
333
        return [
334
            'response' => $response,
335
            'expires'  => time() + $this->options['expire'],
336
        ];
337
    }
338
339
    /**
340
     * Filters the request using a configured filter to determine if it should
341
     * be cached.
342
     *
343
     * @param Request $request The request to filter.
344
     *
345
     * @return bool true if should be cached, false otherwise.
346
     */
347
    protected function filter(Request $request)
348
    {
349
        $filter = $this->options['filter'];
350
        return ! is_callable($filter) || call_user_func($filter, $request);
351
    }
352
353
    /**
354
     * Checks the method of the request to determine if it should be cached.
355
     *
356
     * @param Request $request The request to check.
357
     *
358
     * @return bool true if should be cached, false otherwise.
359
     */
360
    protected function checkMethod(Request $request)
361
    {
362
        $methods = (array) $this->options['methods'];
363
        return in_array($request->getMethod(), $methods);
364
    }
365
366
    /**
367
     * Returns true if the given request should be cached.
368
     *
369
     * @param Request $request The request to check.
370
     *
371
     * @return bool true if the request should be cached, false otherwise.
372
     */
373
    private function shouldCacheRequest(Request $request)
374
    {
375
        return $this->checkMethod($request) && $this->filter($request);
376
    }
377
378
    /**
379
     * Determines if a response should be cached.
380
     *
381
     * @param Response $response
382
     */
383
    protected function shouldCacheResponse(Response $response)
384
    {
385
        return $response && $response->getStatusCode() < 400;
386
    }
387
388
    /**
389
     * Generates the cache key for the given request and request options. The
390
     * namespace should be set on the cache provider.
391
     *
392
     * @param Request $request The request to generate a key for.
393
     * @param array $options Configuration options.
394
     *
395
     * @return string The cache key
396
     */
397
    protected function getKey(Request $request, array $options)
398
    {
399
        return join(":", [
400
            $request->getMethod(),
401
            $request->getUri(),
402
            md5(json_encode($options)),
403
        ]);
404
    }
405
406
    /**
407
     * Invokes the default handler to produce a promise.
408
     *
409
     * @param Request $request
410
     * @param array $options
411
     *
412
     * @return PromiseInterface
413
     */
414
    protected function invokeDefault(Request $request, array $options)
415
    {
416
        return call_user_func($this->handler, $request, $options);
417
    }
418
419
    /**
420
     * Returns the default log level to use when logging response bundles.
421
     *
422
     * @return string LogLevel
423
     */
424
    protected function getDefaultLogLevel()
425
    {
426
        return LogLevel::DEBUG;
427
    }
428
429
    /**
430
     * Sets the log level to use, which can be either a string or a callable
431
     * that accepts a response (which could be null). A log level could also
432
     * be null, which indicates that the default log level should be used.
433
     *
434
     * @param string|callable|null
435
     */
436
    public function setLogLevel($logLevel)
437
    {
438
        $this->logLevel = $logLevel;
439
    }
440
441
    /**
442
     * Returns a log level for a given response.
443
     *
444
     * @param Response $response The response being logged.
445
     *
446
     * @return string LogLevel
447
     */
448
    protected function getLogLevel(Response $response)
449
    {
450
        if (is_null($this->logLevel)) {
451
            return $this->getDefaultLogLevel();
452
        }
453
454
        if (is_callable($this->logLevel)) {
455
            return call_user_func($this->logLevel, $response);
456
        }
457
458
        return (string) $this->logLevel;
459
    }
460
461
    /**
462
     * Convenient internal logger entry point.
463
     *
464
     * @param string $message
465
     * @param array $bundle
466
     */
467
    private function log($message, array $bundle)
468
    {
469
        if (isset($this->logger)) {
470
            $level = $this->getLogLevel($bundle['response']);
471
            $this->logger->log($level, $message, $bundle);
472
        }
473
    }
474
475
    /**
476
     * Logs that a bundle has been stored in the cache.
477
     *
478
     * @param Request $request The request.
479
     * @param array $bundle The stored response bundle.
480
     */
481
    protected function logStoredBundle(Request $request, array $bundle)
482
    {
483
        $message = $this->getStoredLogMessage($request, $bundle);
484
        $this->log($message, $bundle);
485
    }
486
487
    /**
488
     * Logs that a bundle has been fetched from the cache.
489
     *
490
     * @param Request $request The request that produced the response.
491
     * @param array $bundle The fetched response bundle.
492
     */
493
    protected function logFetchedBundle(Request $request, array $bundle)
494
    {
495
        $message = $this->getFetchedLogMessage($request, $bundle);
496
        $this->log($message, $bundle);
497
    }
498
499
    /**
500
     * Prepares a log template with optional extra fields.
501
     *
502
     * @param array $extras
503
     *
504
     * @return string $template
505
     */
506
    protected function prepareTemplate(array $extras)
507
    {
508
        $template = $this->getLogTemplate();
509
510
        foreach ($extras as $key => $value) {
511
            $template = str_replace('{' . $key . '}', $value, $template);
512
        }
513
514
        return $template;
515
    }
516
517
    /**
518
     * Formats a request and response as a log message.
519
     *
520
     * @param Request $request
521
     * @param array $bundle
522
     * @param string $event
523
     *
524
     * @return string The formatted message.
525
     */
526
    protected function getLogMessage(Request $request, array $bundle, $event)
527
    {
528
        $template = $this->prepareTemplate([
529
            'event'   => $event,
530
            'expires' => $bundle['expires'] - time(),
531
        ]);
532
533
        $response  = $bundle['response'];
534
        $formatter = new MessageFormatter($template);
535
536
        return $formatter->format($request, $response);
537
    }
538
539
    /**
540
     * Returns the log message for when a bundle is stored in the cache.
541
     *
542
     * @param Request $request The request that produced the response.
543
     * @param array $bundle The stored response bundle.
544
     *
545
     * @return string The log message.
546
     */
547
    protected function getStoredLogMessage(Request $request, array $bundle)
548
    {
549
        return $this->getLogMessage($request, $bundle, 'stored in cache');
550
    }
551
552
    /**
553
     * Returns the log message for when a bundle is fetched from the cache.
554
     *
555
     * @param Request $request The request that produced the response.
556
     * @param array $bundle The stored response bundle.
557
     *
558
     * @return string The log message.
559
     */
560
    protected function getFetchedLogMessage(Request $request, array $bundle)
561
    {
562
        return $this->getLogMessage($request, $bundle, 'fetched from cache');
563
    }
564
}
565