Issues (1)

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/Badoo/LiveProfiler/LiveProfiler.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
/**
4
 * @maintainer Timur Shagiakhmetov <[email protected]>
5
 */
6
7
namespace Badoo\LiveProfiler;
8
9
use Doctrine\DBAL\Connection;
10
use Doctrine\DBAL\DBALException;
11
use Psr\Log\LoggerInterface;
12
13
class LiveProfiler
14
{
15
    CONST MODE_DB = 'db';
16
    CONST MODE_FILES = 'files';
17
    CONST MODE_API = 'api';
18
19
    /** @var LiveProfiler */
20
    protected static $instance;
21
    /** @var string */
22
    protected $mode = self::MODE_DB;
23
    /** @var string */
24
    protected $path = '';
25
    /** @var string */
26
    protected $api_key = '';
27
    /** @var string */
28
    protected $url = 'http://liveprof.org/api';
29
    /** @var Connection */
30
    protected $Conn;
31
    /** @var LoggerInterface */
32
    protected $Logger;
33
    /** @var DataPackerInterface */
34
    protected $DataPacker;
35
    /** @var string */
36
    protected $connection_string;
37
    /** @var string */
38
    protected $app;
39
    /** @var string */
40
    protected $label;
41
    /** @var string */
42
    protected $datetime;
43
    /** @var int */
44
    protected $divider = 1000;
45
    /** @var int */
46
    protected $total_divider = 10000;
47
    /** @var callable callback to start profiling */
48
    protected $start_callback;
49
    /** @var callable callback to end profiling */
50
    protected $end_callback;
51
    /** @var bool */
52
    protected $is_enabled = false;
53
    /** @var array */
54
    protected $last_profile_data = [];
55
56
    /**
57
     * LiveProfiler constructor.
58
     * @param string $connection_string_or_path
59
     * @param string $mode
60
     */
61 17
    public function __construct($connection_string_or_path = '', $mode = self::MODE_DB)
62
    {
63 17
        $this->mode = $mode;
64
65 17
        $this->app = 'Default';
66 17
        $this->label = $this->getAutoLabel();
67 17
        $this->datetime = date('Y-m-d H:i:s');
68
69 17
        $this->detectProfiler();
70 17
        $this->Logger = new Logger();
71 17
        $this->DataPacker = new DataPacker();
72
73 17
        if ($mode === self::MODE_DB) {
74 15
            $this->connection_string = $connection_string_or_path ?: getenv('LIVE_PROFILER_CONNECTION_URL');
75 2
        } elseif ($mode === self::MODE_API) {
76 1
            if ($connection_string_or_path) {
77
                $this->url = $connection_string_or_path;
78 1
            } elseif (getenv('LIVE_PROFILER_API_URL')) {
79 1
                $this->url = getenv('LIVE_PROFILER_API_URL');
80
            }
81
        } else {
82 1
            $this->setPath($connection_string_or_path ?: getenv('LIVE_PROFILER_PATH'));
83
        }
84 17
    }
85
86 1
    public static function getInstance($connection_string = '', $mode = self::MODE_DB)
87
    {
88 1
        if (self::$instance === null) {
89 1
            self::$instance = new static($connection_string, $mode);
90
        }
91
92 1
        return self::$instance;
93
    }
94
95 7
    public function start()
96
    {
97 7
        if ($this->is_enabled) {
98 1
            return true;
99
        }
100
101 6
        if (null === $this->start_callback) {
102 1
            return true;
103
        }
104
105 5
        if ($this->needToStart($this->divider)) {
106 4
            $this->is_enabled = true;
107 1
        } elseif ($this->needToStart($this->total_divider)) {
108 1
            $this->is_enabled = true;
109 1
            $this->label = 'All';
110
        }
111
112 5
        if ($this->is_enabled) {
113 5
            register_shutdown_function([$this, 'end']);
114 5
            call_user_func($this->start_callback);
115
        }
116
117 5
        return true;
118
    }
119
120
    /**
121
     * @return bool
122
     */
123 6
    public function end()
124
    {
125 6
        if (!$this->is_enabled) {
126 1
            return true;
127
        }
128
129 5
        $this->is_enabled = false;
130
131 5
        if (null === $this->end_callback) {
132 1
            return true;
133
        }
134
135 4
        $data = call_user_func($this->end_callback);
136 4
        if (!is_array($data)) {
137 1
            $this->Logger->warning('Invalid profiler data: ' . var_export($data, true));
138 1
            return false;
139
        }
140
141 3
        if (empty($data)) {
142
            return false;
143
        }
144
145 3
        $this->last_profile_data = $data;
146 3
        $result = $this->save($this->app, $this->label, $this->datetime, $data);
147
148 3
        if (!$result) {
149 2
            $this->Logger->warning('Can\'t insert profile data');
150
        }
151
152 3
        return $result;
153
    }
154
155 18
    public function detectProfiler()
156
    {
157 18
        if (function_exists('xhprof_enable')) {
158
            return $this->useXhprof();
159
        }
160
161 18
        if (function_exists('tideways_xhprof_enable')) {
162
            return $this->useTidyWays();
163
        }
164
165 18
        if (function_exists('uprofiler_enable')) {
166
            return $this->useUprofiler();
167
        }
168
169 18
        return $this->useSimpleProfiler();
170
    }
171
172 2
    public function useXhprof()
173
    {
174 2
        if ($this->is_enabled) {
175 1
            $this->Logger->warning('can\'t change profiler after profiling started');
176 1
            return $this;
177
        }
178
179
        $this->start_callback = function () {
180
            xhprof_enable(XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU);
181
        };
182
183
        $this->end_callback = function () {
184
            return xhprof_disable();
185
        };
186
187 1
        return $this;
188
    }
189
190 2
    public function useXhprofSample()
191
    {
192 2
        if ($this->is_enabled) {
193 1
            $this->Logger->warning('can\'t change profiler after profiling started');
194 1
            return $this;
195
        }
196
197 1
        if (!ini_get('xhprof.sampling_interval')) {
198 1
            ini_set('xhprof.sampling_interval', 10000);
199
        }
200
201 1
        if (!ini_get('xhprof.sampling_depth')) {
202 1
            ini_set('xhprof.sampling_depth', 200);
203
        }
204
205
        $this->start_callback = function () {
206
            define('XHPROF_SAMPLING_BEGIN', microtime(true));
207
            xhprof_sample_enable();
208
        };
209
210
        $this->end_callback = function () {
211
            return $this->convertSampleDataToCommonFormat(xhprof_sample_disable());
212
        };
213
214 1
        return $this;
215
    }
216
217 1
    protected function convertSampleDataToCommonFormat(array $sampling_data)
218
    {
219 1
        $result_data = [];
220 1
        $prev_time = XHPROF_SAMPLING_BEGIN;
221 1
        foreach ($sampling_data as $time => $callstack) {
222 1
            $wt = (int)(($time - $prev_time) * 1000000);
223 1
            $functions = explode('==>', $callstack);
224 1
            $prev_i = 0;
225 1
            $main_key = $functions[$prev_i];
226 1
            if (!isset($result_data[$main_key])) {
227 1
                $result_data[$main_key] = [
228
                    'ct' => 0,
229
                    'wt' => 0,
230
                ];
231
            }
232 1
            $result_data[$main_key]['ct'] ++;
233 1
            $result_data[$main_key]['wt'] += $wt;
234
235 1
            $func_cnt = count($functions);
236 1
            for ($i = 1; $i < $func_cnt; $i++) {
237 1
                $key = $functions[$prev_i] . '==>' . $functions[$i];
238
239 1
                if (!isset($result_data[$key])) {
240 1
                    $result_data[$key] = [
241
                        'ct' => 0,
242
                        'wt' => 0,
243
                    ];
244
                }
245
246 1
                $result_data[$key]['wt'] += $wt;
247 1
                $result_data[$key]['ct']++;
248
249 1
                $prev_i = $i;
250
            }
251
252 1
            $prev_time = $time;
253
        }
254
255 1
        return $result_data;
256
    }
257
258 2
    public function useTidyWays()
259
    {
260 2
        if ($this->is_enabled) {
261 1
            $this->Logger->warning('can\'t change profiler after profiling started');
262 1
            return $this;
263
        }
264
265
        $this->start_callback = function () {
266
            tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_CPU);
267
        };
268
269
        $this->end_callback = function () {
270
            return tideways_xhprof_disable();
271
        };
272
273 1
        return $this;
274
    }
275
276 2
    public function useUprofiler()
277
    {
278 2
        if ($this->is_enabled) {
279 1
            $this->Logger->warning('can\'t change profiler after profiling started');
280 1
            return $this;
281
        }
282
283
        $this->start_callback = function () {
284
            uprofiler_enable(UPROFILER_FLAGS_CPU | UPROFILER_FLAGS_MEMORY);
285
        };
286
287
        $this->end_callback = function () {
288
            return uprofiler_disable();
289
        };
290
291 1
        return $this;
292
    }
293
294 18
    public function useSimpleProfiler()
295
    {
296 18
        if ($this->is_enabled) {
297
            $this->Logger->warning('can\'t change profiler after profiling started');
298
            return $this;
299
        }
300
301
        $this->start_callback = function () {
302
            \Badoo\LiveProfiler\SimpleProfiler::getInstance()->enable();
303
        };
304
305
        $this->end_callback = function () {
306
            return \Badoo\LiveProfiler\SimpleProfiler::getInstance()->disable();
307
        };
308
309 18
        return $this;
310
    }
311
312
    /**
313
     * @return bool
314
     */
315 1
    public function reset()
316
    {
317 1
        if ($this->is_enabled) {
318 1
            call_user_func($this->end_callback);
319 1
            $this->is_enabled = false;
320
        }
321
322 1
        return true;
323
    }
324
325
    /**
326
     * @param string $mode
327
     * @return $this
328
     */
329 1
    public function setMode($mode)
330
    {
331 1
        $this->mode = $mode;
332 1
        return $this;
333
    }
334
335
    /**
336
     * @return string
337
     */
338 1
    public function getMode()
339
    {
340 1
        return $this->mode;
341
    }
342
343
    /**
344
     * @param string $path
345
     * @return $this
346
     */
347 2
    public function setPath($path)
348
    {
349 2
        if (!is_dir($path)) {
350 1
            $this->Logger->error('Directory ' . $path . ' does not exists');
351
        }
352
353 2
        $this->path = $path;
354 2
        return $this;
355
    }
356
357
    /**
358
     * @return string
359
     */
360 1
    public function getPath()
361
    {
362 1
        return $this->path;
363
    }
364
365
    /**
366
     * @param string $api_key
367
     * @return $this
368
     */
369 2
    public function setApiKey($api_key)
370
    {
371 2
        $this->api_key = $api_key;
372 2
        return $this;
373
    }
374
375
    /**
376
     * @return string
377
     */
378 1
    public function getApiKey()
379
    {
380 1
        return $this->api_key;
381
    }
382
383
    /**
384
     * @param string $app
385
     * @return $this
386
     */
387 1
    public function setApp($app)
388
    {
389 1
        $this->app = $app;
390 1
        return $this;
391
    }
392
393
    /**
394
     * @return string
395
     */
396 1
    public function getApp()
397
    {
398 1
        return $this->app;
399
    }
400
401
    /**
402
     * @param string $label
403
     * @return $this
404
     */
405 1
    public function setLabel($label)
406
    {
407 1
        $this->label = $label;
408 1
        return $this;
409
    }
410
411
    /**
412
     * @return string
413
     */
414 1
    public function getLabel()
415
    {
416 1
        return $this->label;
417
    }
418
419
    /**
420
     * @param string $datetime
421
     * @return $this
422
     */
423 1
    public function setDateTime($datetime)
424
    {
425 1
        $this->datetime = $datetime;
426 1
        return $this;
427
    }
428
429
    /**
430
     * @return string
431
     */
432 1
    public function getDateTime()
433
    {
434 1
        return $this->datetime;
435
    }
436
437
    /**
438
     * @param int $divider
439
     * @return $this
440
     */
441 5
    public function setDivider($divider)
442
    {
443 5
        $this->divider = $divider;
444 5
        return $this;
445
    }
446
447
    /**
448
     * @param int $total_divider
449
     * @return $this
450
     */
451 2
    public function setTotalDivider($total_divider)
452
    {
453 2
        $this->total_divider = $total_divider;
454 2
        return $this;
455
    }
456
457
    /**
458
     * @param \Closure $start_callback
459
     * @return $this
460
     */
461 5
    public function setStartCallback(\Closure $start_callback)
462
    {
463 5
        $this->start_callback = $start_callback;
464 5
        return $this;
465
    }
466
467
    /**
468
     * @param \Closure $end_callback
469
     * @return $this
470
     */
471 5
    public function setEndCallback(\Closure $end_callback)
472
    {
473 5
        $this->end_callback = $end_callback;
474 5
        return $this;
475
    }
476
477
    /**
478
     * @param LoggerInterface $Logger
479
     * @return $this
480
     */
481 9
    public function setLogger(LoggerInterface $Logger)
482
    {
483 9
        $this->Logger = $Logger;
484 9
        return $this;
485
    }
486
487
    /**
488
     * @param DataPackerInterface $DataPacker
489
     * @return $this
490
     */
491 2
    public function setDataPacker($DataPacker)
492
    {
493 2
        $this->DataPacker = $DataPacker;
494 2
        return $this;
495
    }
496
497
    /**
498
     * @return array
499
     */
500 1
    public function getLastProfileData()
501
    {
502 1
        return $this->last_profile_data;
503
    }
504
505
    /**
506
     * @return Connection
507
     * @throws DBALException
508
     */
509 3
    protected function getConnection()
510
    {
511 3
        if (null === $this->Conn) {
512 3
            $config = new \Doctrine\DBAL\Configuration();
513 3
            $connectionParams = ['url' => $this->connection_string];
514 3
            $this->Conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
515
        }
516
517 3
        return $this->Conn;
518
    }
519
520
    /**
521
     * @param Connection $Conn
522
     * @return $this
523
     */
524 1
    public function setConnection(Connection $Conn)
525
    {
526 1
        $this->Conn = $Conn;
527 1
        return $this;
528
    }
529
530
    /**
531
     * @param string $connection_string
532
     * @return $this
533
     */
534 1
    public function setConnectionString($connection_string)
535
    {
536 1
        $this->connection_string = $connection_string;
537 1
        return $this;
538
    }
539
540
    /**
541
     * @param string $app
542
     * @param string $label
543
     * @param string $datetime
544
     * @param array $data
545
     * @return bool
546
     */
547 3
    protected function save($app, $label, $datetime, $data)
548
    {
549 3
        if ($this->mode === self::MODE_DB) {
550 1
            return $this->saveToDB($app, $label, $datetime, $data);
551
        }
552
553 2
        if ($this->mode === self::MODE_API) {
554 1
            return $this->sendToAPI($app, $label, $datetime, $data);
555
        }
556
557 1
        return $this->saveToFile($app, $label, $datetime, $data);
558
    }
559
560
    /**
561
     * @param string $app
562
     * @param string $label
563
     * @param string $datetime
564
     * @param array $data
565
     * @return bool
566
     */
567 1
    protected function sendToAPI($app, $label, $datetime, $data)
568
    {
569 1
        $data = $this->DataPacker->pack($data);
570 1
        $api_key = $this->api_key;
571 1
        $curl_handle = curl_init();
572 1
        curl_setopt($curl_handle,CURLOPT_URL,$this->url);
573 1
        curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER, true);
574 1
        curl_setopt($curl_handle, CURLOPT_POST, 1);
575 1
        curl_setopt($curl_handle, CURLOPT_POSTFIELDS, http_build_query(compact('api_key', 'app', 'label', 'datetime', 'data')));
576 1
        curl_exec($curl_handle);
577 1
        $http_code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
578 1
        curl_close($curl_handle);
579
580 1
        return $http_code === 200;
581
    }
582
583
    /**
584
     * @param string $app
585
     * @param string $label
586
     * @param string $datetime
587
     * @param array $data
588
     * @return bool
589
     */
590 1
    protected function saveToDB($app, $label, $datetime, $data)
591
    {
592 1
        $packed_data = $this->DataPacker->pack($data);
593
594
        try {
595 1
            return (bool)$this->getConnection()->insert(
596 1
                'details',
597
                [
598 1
                    'app' => $app,
599 1
                    'label' => $label,
600 1
                    'perfdata' => $packed_data,
601 1
                    'timestamp' => $datetime
602
                ]
603
            );
604
        } catch (DBALException $Ex) {
605
            $this->Logger->error('Error in insertion profile data: ' . $Ex->getMessage());
606
            return false;
607
        }
608
    }
609
610
    /**
611
     * @param string $app
612
     * @param string $label
613
     * @param string $datetime
614
     * @param array $data
615
     * @return bool
616
     */
617 1
    private function saveToFile($app, $label, $datetime, $data)
618
    {
619 1
        $path = sprintf('%s/%s/%s', $this->path, $app, base64_encode($label));
620
621 1
        if (!is_dir($path) && !mkdir($path, 0755, true) && !is_dir($path)) {
622
            $this->Logger->error('Directory "'. $path .'" was not created');
623
            return false;
624
        }
625
626 1
        $filename = sprintf('%s/%s.json', $path, strtotime($datetime));
627 1
        $packed_data = $this->DataPacker->pack($data);
628 1
        return (bool)file_put_contents($filename, $packed_data);
629
    }
630
631
    /**
632
     * @throws DBALException
633
     */
634 3
    public function createTable()
635
    {
636 3
        $driver_name = $this->getConnection()->getDriver()->getName();
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\DBAL\Driver::getName() has been deprecated.

This method has been deprecated.

Loading history...
637 3
        $sql_path = __DIR__ . '/../../../bin/install_data/' . $driver_name . '/source.sql';
638 3
        if (!file_exists($sql_path)) {
639 1
            $this->Logger->error('Invalid sql path:' . $sql_path);
640 1
            return false;
641
        }
642
643 2
        $sql = file_get_contents($sql_path);
644
645 2
        $this->getConnection()->exec($sql);
646 2
        return true;
647
    }
648
649
    /**
650
     * @return string
651
     */
652 17
    protected function getAutoLabel()
653
    {
654 17
        if (!empty($_SERVER['REQUEST_URI'])) {
655 16
            $label = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
656 16
            return $label ?: $_SERVER['REQUEST_URI'];
657
        }
658
659 1
        return $_SERVER['SCRIPT_NAME'];
660
    }
661
662
    /**
663
     * @param int $divider
664
     * @return bool
665
     */
666 5
    protected function needToStart($divider)
667
    {
668 5
        return mt_rand(1, $divider) === 1;
669
    }
670
}
671