Issues (32)

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/Whoops/Handler/PrettyPageHandler.php (6 issues)

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
 * Whoops - php errors for cool kids
4
 * @author Filipe Dobreira <http://github.com/filp>
5
 */
6
7
namespace Whoops\Handler;
8
9
use InvalidArgumentException;
10
use RuntimeException;
11
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
12
use Symfony\Component\VarDumper\Cloner\VarCloner;
13
use UnexpectedValueException;
14
use Whoops\Exception\Formatter;
15
use Whoops\Util\Misc;
16
use Whoops\Util\TemplateHelper;
17
18
class PrettyPageHandler extends Handler
19
{
20
    const EDITOR_SUBLIME = "sublime";
21
    const EDITOR_TEXTMATE = "textmate";
22
    const EDITOR_EMACS = "emacs";
23
    const EDITOR_MACVIM = "macvim";
24
    const EDITOR_PHPSTORM = "phpstorm";
25
    const EDITOR_IDEA = "idea";
26
    const EDITOR_VSCODE = "vscode";
27
    const EDITOR_ATOM = "atom";
28
    const EDITOR_ESPRESSO = "espresso";
29
    const EDITOR_XDEBUG = "xdebug";
30
    const EDITOR_NETBEANS = "netbeans";
31
32
    /**
33
     * Search paths to be scanned for resources.
34
     *
35
     * Stored in the reverse order they're declared.
36
     *
37
     * @var array
38
     */
39
    private $searchPaths = [];
40
41
    /**
42
     * Fast lookup cache for known resource locations.
43
     *
44
     * @var array
45
     */
46
    private $resourceCache = [];
47
48
    /**
49
     * The name of the custom css file.
50
     *
51
     * @var string|null
52
     */
53
    private $customCss = null;
54
55
    /**
56
     * The name of the custom js file.
57
     *
58
     * @var string|null
59
     */
60
    private $customJs = null;
61
62
    /**
63
     * @var array[]
64
     */
65
    private $extraTables = [];
66
67
    /**
68
     * @var bool
69
     */
70
    private $handleUnconditionally = false;
71
72
    /**
73
     * @var string
74
     */
75
    private $pageTitle = "Whoops! There was an error.";
76
77
    /**
78
     * @var array[]
79
     */
80
    private $applicationPaths;
81
82
    /**
83
     * @var array[]
84
     */
85
    private $blacklist = [
86
        '_GET' => [],
87
        '_POST' => [],
88
        '_FILES' => [],
89
        '_COOKIE' => [],
90
        '_SESSION' => [],
91
        '_SERVER' => [],
92
        '_ENV' => [],
93
    ];
94
95
    /**
96
     * An identifier for a known IDE/text editor.
97
     *
98
     * Either a string, or a calalble that resolves a string, that can be used
99
     * to open a given file in an editor. If the string contains the special
100
     * substrings %file or %line, they will be replaced with the correct data.
101
     *
102
     * @example
103
     *   "txmt://open?url=%file&line=%line"
104
     *
105
     * @var callable|string $editor
106
     */
107
    protected $editor;
108
109
    /**
110
     * A list of known editor strings.
111
     *
112
     * @var array
113
     */
114
    protected $editors = [
115
        "sublime"  => "subl://open?url=file://%file&line=%line",
116
        "textmate" => "txmt://open?url=file://%file&line=%line",
117
        "emacs"    => "emacs://open?url=file://%file&line=%line",
118
        "macvim"   => "mvim://open/?url=file://%file&line=%line",
119
        "phpstorm" => "phpstorm://open?file=%file&line=%line",
120
        "idea"     => "idea://open?file=%file&line=%line",
121
        "vscode"   => "vscode://file/%file:%line",
122
        "atom"     => "atom://core/open/file?filename=%file&line=%line",
123
        "espresso" => "x-espresso://open?filepath=%file&lines=%line",
124
        "netbeans" => "netbeans://open/?f=%file:%line",
125
    ];
126
127
    /**
128
     * @var TemplateHelper
129
     */
130
    protected $templateHelper;
131
132
    /**
133
     * Constructor.
134
     *
135
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
136
     */
137 1
    public function __construct()
138
    {
139 1
        if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
140
            // Register editor using xdebug's file_link_format option.
141
            $this->editors['xdebug'] = function ($file, $line) {
142 1
                return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
143
            };
144
145
            // If xdebug is available, use it as default editor.
146 1
            $this->setEditor('xdebug');
147 1
        }
148
149
        // Add the default, local resource search path:
150 1
        $this->searchPaths[] = __DIR__ . "/../Resources";
151
152
        // blacklist php provided auth based values
153 1
        $this->blacklist('_SERVER', 'PHP_AUTH_PW');
154
155 1
        $this->templateHelper = new TemplateHelper();
156
157 1
        if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
158 1
            $cloner = new VarCloner();
159
            // Only dump object internals if a custom caster exists for performance reasons
160
            // https://github.com/filp/whoops/pull/404
161
            $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
0 ignored issues
show
The parameter $isNested is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $filter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
                $class = $stub->class;
163
                $classes = [$class => $class] + class_parents($obj) + class_implements($obj);
164
165
                foreach ($classes as $class) {
166
                    if (isset(AbstractCloner::$defaultCasters[$class])) {
167
                        return $a;
168
                    }
169
                }
170
171
                // Remove all internals
172
                return [];
173 1
            }]);
174 1
            $this->templateHelper->setCloner($cloner);
175 1
        }
176 1
    }
177
178
    /**
179
     * @return int|null
180
     *
181
     * @throws \Exception
182
     */
183 1
    public function handle()
184
    {
185 1
        if (!$this->handleUnconditionally()) {
186
            // Check conditions for outputting HTML:
187
            // @todo: Make this more robust
188 1
            if (PHP_SAPI === 'cli') {
189
                // Help users who have been relying on an internal test value
190
                // fix their code to the proper method
191 1
                if (isset($_ENV['whoops-test'])) {
192
                    throw new \Exception(
193
                        'Use handleUnconditionally instead of whoops-test'
194
                        .' environment variable'
195
                    );
196
                }
197
198 1
                return Handler::DONE;
199
            }
200
        }
201
202
        $templateFile = $this->getResource("views/layout.html.php");
203
        $cssFile      = $this->getResource("css/whoops.base.css");
204
        $zeptoFile    = $this->getResource("js/zepto.min.js");
205
        $prettifyFile = $this->getResource("js/prettify.min.js");
206
        $clipboard    = $this->getResource("js/clipboard.min.js");
207
        $jsFile       = $this->getResource("js/whoops.base.js");
208
209
        if ($this->customCss) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customCss of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
210
            $customCssFile = $this->getResource($this->customCss);
211
        }
212
213
        if ($this->customJs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customJs of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
214
            $customJsFile = $this->getResource($this->customJs);
215
        }
216
217
        $inspector = $this->getInspector();
218
        $frames = $this->getExceptionFrames();
219
        $code = $this->getExceptionCode();
220
221
        // List of variables that will be passed to the layout template.
222
        $vars = [
223
            "page_title" => $this->getPageTitle(),
224
225
            // @todo: Asset compiler
226
            "stylesheet" => file_get_contents($cssFile),
227
            "zepto"      => file_get_contents($zeptoFile),
228
            "prettify"   => file_get_contents($prettifyFile),
229
            "clipboard"  => file_get_contents($clipboard),
230
            "javascript" => file_get_contents($jsFile),
231
232
            // Template paths:
233
            "header"                     => $this->getResource("views/header.html.php"),
234
            "header_outer"               => $this->getResource("views/header_outer.html.php"),
235
            "frame_list"                 => $this->getResource("views/frame_list.html.php"),
236
            "frames_description"         => $this->getResource("views/frames_description.html.php"),
237
            "frames_container"           => $this->getResource("views/frames_container.html.php"),
238
            "panel_details"              => $this->getResource("views/panel_details.html.php"),
239
            "panel_details_outer"        => $this->getResource("views/panel_details_outer.html.php"),
240
            "panel_left"                 => $this->getResource("views/panel_left.html.php"),
241
            "panel_left_outer"           => $this->getResource("views/panel_left_outer.html.php"),
242
            "frame_code"                 => $this->getResource("views/frame_code.html.php"),
243
            "env_details"                => $this->getResource("views/env_details.html.php"),
244
245
            "title"            => $this->getPageTitle(),
246
            "name"             => explode("\\", $inspector->getExceptionName()),
247
            "message"          => $inspector->getExceptionMessage(),
248
            "previousMessages" => $inspector->getPreviousExceptionMessages(),
249
            "docref_url"       => $inspector->getExceptionDocrefUrl(),
250
            "code"             => $code,
251
            "previousCodes"    => $inspector->getPreviousExceptionCodes(),
252
            "plain_exception"  => Formatter::formatExceptionPlain($inspector),
253
            "frames"           => $frames,
254
            "has_frames"       => !!count($frames),
255
            "handler"          => $this,
256
            "handlers"         => $this->getRun()->getHandlers(),
257
258
            "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ?  'application' : 'all',
0 ignored issues
show
The method isApplication cannot be called on $frames->offsetGet(0) (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
259
            "has_frames_tabs"   => $this->getApplicationPaths(),
260
261
            "tables"      => [
262
                "GET Data"              => $this->masked($_GET, '_GET'),
263
                "POST Data"             => $this->masked($_POST, '_POST'),
264
                "Files"                 => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
265
                "Cookies"               => $this->masked($_COOKIE, '_COOKIE'),
266
                "Session"               => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') :  [],
267
                "Server/Request Data"   => $this->masked($_SERVER, '_SERVER'),
268
                "Environment Variables" => $this->masked($_ENV, '_ENV'),
269
            ],
270
        ];
271
272
        if (isset($customCssFile)) {
273
            $vars["stylesheet"] .= file_get_contents($customCssFile);
274
        }
275
276
        if (isset($customJsFile)) {
277
            $vars["javascript"] .= file_get_contents($customJsFile);
278
        }
279
280
        // Add extra entries list of data tables:
281
        // @todo: Consolidate addDataTable and addDataTableCallback
282
        $extraTables = array_map(function ($table) use ($inspector) {
283
            return $table instanceof \Closure ? $table($inspector) : $table;
284
        }, $this->getDataTables());
285
        $vars["tables"] = array_merge($extraTables, $vars["tables"]);
286
287
        $plainTextHandler = new PlainTextHandler();
288
        $plainTextHandler->setException($this->getException());
289
        $plainTextHandler->setInspector($this->getInspector());
290
        $vars["preface"] = "<!--\n\n\n" .  $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
291
292
        $this->templateHelper->setVariables($vars);
293
        $this->templateHelper->render($templateFile);
294
295
        return Handler::QUIT;
296
    }
297
298
    /**
299
     * Get the stack trace frames of the exception currently being handled.
300
     *
301
     * @return \Whoops\Exception\FrameCollection
302
     */
303
    protected function getExceptionFrames()
304
    {
305
        $frames = $this->getInspector()->getFrames();
306
307
        if ($this->getApplicationPaths()) {
308
            foreach ($frames as $frame) {
309
                foreach ($this->getApplicationPaths() as $path) {
310
                    if (strpos($frame->getFile(), $path) === 0) {
311
                        $frame->setApplication(true);
312
                        break;
313
                    }
314
                }
315
            }
316
        }
317
318
        return $frames;
319
    }
320
321
    /**
322
     * Get the code of the exception currently being handled.
323
     *
324
     * @return string
325
     */
326
    protected function getExceptionCode()
327
    {
328
        $exception = $this->getException();
329
330
        $code = $exception->getCode();
331
        if ($exception instanceof \ErrorException) {
332
            // ErrorExceptions wrap the php-error types within the 'severity' property
333
            $code = Misc::translateErrorCode($exception->getSeverity());
334
        }
335
336
        return (string) $code;
337
    }
338
339
    /**
340
     * @return string
341
     */
342
    public function contentType()
343
    {
344
        return 'text/html';
345
    }
346
347
    /**
348
     * Adds an entry to the list of tables displayed in the template.
349
     *
350
     * The expected data is a simple associative array. Any nested arrays
351
     * will be flattened with `print_r`.
352
     *
353
     * @param string $label
354
     * @param array  $data
355
     *
356
     * @return static
357
     */
358 1
    public function addDataTable($label, array $data)
359
    {
360 1
        $this->extraTables[$label] = $data;
361 1
        return $this;
362
    }
363
364
    /**
365
     * Lazily adds an entry to the list of tables displayed in the table.
366
     *
367
     * The supplied callback argument will be called when the error is
368
     * rendered, it should produce a simple associative array. Any nested
369
     * arrays will be flattened with `print_r`.
370
     *
371
     * @param string   $label
372
     * @param callable $callback Callable returning an associative array
373
     *
374
     * @throws InvalidArgumentException If $callback is not callable
375
     *
376
     * @return static
377
     */
378 1
    public function addDataTableCallback($label, /* callable */ $callback)
379
    {
380 1
        if (!is_callable($callback)) {
381
            throw new InvalidArgumentException('Expecting callback argument to be callable');
382
        }
383
384 1
        $this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = null) use ($callback) {
385
            try {
386 1
                $result = call_user_func($callback, $inspector);
387
388
                // Only return the result if it can be iterated over by foreach().
389 1
                return is_array($result) || $result instanceof \Traversable ? $result : [];
390
            } catch (\Exception $e) {
391
                // Don't allow failure to break the rendering of the original exception.
392
                return [];
393
            }
394
        };
395
396 1
        return $this;
397
    }
398
399
    /**
400
     * Returns all the extra data tables registered with this handler.
401
     *
402
     * Optionally accepts a 'label' parameter, to only return the data table
403
     * under that label.
404
     *
405
     * @param string|null $label
406
     *
407
     * @return array[]|callable
408
     */
409 2
    public function getDataTables($label = null)
410
    {
411 2
        if ($label !== null) {
412 2
            return isset($this->extraTables[$label]) ?
413 2
                   $this->extraTables[$label] : [];
414
        }
415
416 2
        return $this->extraTables;
417
    }
418
419
    /**
420
     * Set whether to handle unconditionally.
421
     *
422
     * Allows to disable all attempts to dynamically decide whether to handle
423
     * or return prematurely. Set this to ensure that the handler will perform,
424
     * no matter what.
425
     *
426
     * @param bool|null $value
427
     *
428
     * @return bool|static
429
     */
430 1
    public function handleUnconditionally($value = null)
431
    {
432 1
        if (func_num_args() == 0) {
433 1
            return $this->handleUnconditionally;
434
        }
435
436
        $this->handleUnconditionally = (bool) $value;
437
        return $this;
438
    }
439
440
    /**
441
     * Adds an editor resolver.
442
     *
443
     * Either a string, or a closure that resolves a string, that can be used
444
     * to open a given file in an editor. If the string contains the special
445
     * substrings %file or %line, they will be replaced with the correct data.
446
     *
447
     * @example
448
     *  $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
449
     * @example
450
     *   $run->addEditor('remove-it', function($file, $line) {
451
     *       unlink($file);
452
     *       return "http://stackoverflow.com";
453
     *   });
454
     *
455
     * @param string          $identifier
456
     * @param string|callable $resolver
457
     *
458
     * @return static
459
     */
460 1
    public function addEditor($identifier, $resolver)
461
    {
462 1
        $this->editors[$identifier] = $resolver;
463 1
        return $this;
464
    }
465
466
    /**
467
     * Set the editor to use to open referenced files.
468
     *
469
     * Pass either the name of a configured editor, or a closure that directly
470
     * resolves an editor string.
471
     *
472
     * @example
473
     *   $run->setEditor(function($file, $line) { return "file:///{$file}"; });
474
     * @example
475
     *   $run->setEditor('sublime');
476
     *
477
     * @param string|callable $editor
478
     *
479
     * @throws InvalidArgumentException If invalid argument identifier provided
480
     *
481
     * @return static
482
     */
483 4
    public function setEditor($editor)
484
    {
485 4
        if (!is_callable($editor) && !isset($this->editors[$editor])) {
486
            throw new InvalidArgumentException(
487
                "Unknown editor identifier: $editor. Known editors:" .
488
                implode(",", array_keys($this->editors))
489
            );
490
        }
491
492 4
        $this->editor = $editor;
493 4
        return $this;
494
    }
495
496
    /**
497
     * Get the editor href for a given file and line, if available.
498
     *
499
     * @param string $filePath
500
     * @param int    $line
501
     *
502
     * @throws InvalidArgumentException If editor resolver does not return a string
503
     *
504
     * @return string|bool
505
     */
506 4
    public function getEditorHref($filePath, $line)
507
    {
508 4
        $editor = $this->getEditor($filePath, $line);
509
510 4
        if (empty($editor)) {
511 1
            return false;
512
        }
513
514
        // Check that the editor is a string, and replace the
515
        // %line and %file placeholders:
516 4
        if (!isset($editor['url']) || !is_string($editor['url'])) {
517
            throw new UnexpectedValueException(
518
                __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
519
            );
520
        }
521
522 4
        $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
523 4
        $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
524
525 4
        return $editor['url'];
526
    }
527
528
    /**
529
     * Determine if the editor link should act as an Ajax request.
530
     *
531
     * @param string $filePath
532
     * @param int    $line
533
     *
534
     * @throws UnexpectedValueException If editor resolver does not return a boolean
535
     *
536
     * @return bool
537
     */
538 1
    public function getEditorAjax($filePath, $line)
539
    {
540 1
        $editor = $this->getEditor($filePath, $line);
541
542
        // Check that the ajax is a bool
543 1
        if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
544
            throw new UnexpectedValueException(
545
                __METHOD__ . " should always resolve to a bool; got something else instead."
546
            );
547
        }
548 1
        return $editor['ajax'];
549
    }
550
551
    /**
552
     * Determines both the editor and if ajax should be used.
553
     *
554
     * @param string $filePath
555
     * @param int    $line
556
     *
557
     * @return array
558
     */
559 1
    protected function getEditor($filePath, $line)
560
    {
561 1
        if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
562
            return [];
563
        }
564
565 1
        if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
566
            return [
567
                'ajax' => false,
568
                'url' => $this->editors[$this->editor],
569
            ];
570
        }
571
572 1
        if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
573 1
            if (is_callable($this->editor)) {
574
                $callback = call_user_func($this->editor, $filePath, $line);
575
            } else {
576 1
                $callback = call_user_func($this->editors[$this->editor], $filePath, $line);
577
            }
578
579 1
            if (empty($callback)) {
580
                return [];
581
            }
582
583 1
            if (is_string($callback)) {
584
                return [
585 1
                    'ajax' => false,
586 1
                    'url' => $callback,
587 1
                ];
588
            }
589
590
            return [
591
                'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
592
                'url' => isset($callback['url']) ? $callback['url'] : $callback,
593
            ];
594
        }
595
596
        return [];
597
    }
598
599
    /**
600
     * Set the page title.
601
     *
602
     * @param string $title
603
     *
604
     * @return static
605
     */
606 1
    public function setPageTitle($title)
607
    {
608 1
        $this->pageTitle = (string) $title;
609 1
        return $this;
610
    }
611
612
    /**
613
     * Get the page title.
614
     *
615
     * @return string
616
     */
617 1
    public function getPageTitle()
618
    {
619 1
        return $this->pageTitle;
620
    }
621
622
    /**
623
     * Adds a path to the list of paths to be searched for resources.
624
     *
625
     * @param string $path
626
     *
627
     * @throws InvalidArgumentException If $path is not a valid directory
628
     *
629
     * @return static
630
     */
631 2
    public function addResourcePath($path)
632
    {
633 2
        if (!is_dir($path)) {
634 1
            throw new InvalidArgumentException(
635 1
                "'$path' is not a valid directory"
636 1
            );
637
        }
638
639 1
        array_unshift($this->searchPaths, $path);
640 1
        return $this;
641
    }
642
643
    /**
644
     * Adds a custom css file to be loaded.
645
     *
646
     * @param string|null $name
647
     *
648
     * @return static
649
     */
650
    public function addCustomCss($name)
651
    {
652
        $this->customCss = $name;
653
        return $this;
654
    }
655
656
    /**
657
     * Adds a custom js file to be loaded.
658
     *
659
     * @param string|null $name
660
     *
661
     * @return static
662
     */
663
    public function addCustomJs($name)
664
    {
665
        $this->customJs = $name;
666
        return $this;
667
    }
668
669
    /**
670
     * @return array
671
     */
672 1
    public function getResourcePaths()
673
    {
674 1
        return $this->searchPaths;
675
    }
676
677
    /**
678
     * Finds a resource, by its relative path, in all available search paths.
679
     *
680
     * The search is performed starting at the last search path, and all the
681
     * way back to the first, enabling a cascading-type system of overrides for
682
     * all resources.
683
     *
684
     * @param string $resource
685
     *
686
     * @throws RuntimeException If resource cannot be found in any of the available paths
687
     *
688
     * @return string
689
     */
690
    protected function getResource($resource)
691
    {
692
        // If the resource was found before, we can speed things up
693
        // by caching its absolute, resolved path:
694
        if (isset($this->resourceCache[$resource])) {
695
            return $this->resourceCache[$resource];
696
        }
697
698
        // Search through available search paths, until we find the
699
        // resource we're after:
700
        foreach ($this->searchPaths as $path) {
701
            $fullPath = $path . "/$resource";
702
703
            if (is_file($fullPath)) {
704
                // Cache the result:
705
                $this->resourceCache[$resource] = $fullPath;
706
                return $fullPath;
707
            }
708
        }
709
710
        // If we got this far, nothing was found.
711
        throw new RuntimeException(
712
            "Could not find resource '$resource' in any resource paths."
713
            . "(searched: " . join(", ", $this->searchPaths). ")"
714
        );
715
    }
716
717
    /**
718
     * @deprecated
719
     *
720
     * @return string
721
     */
722
    public function getResourcesPath()
723
    {
724
        $allPaths = $this->getResourcePaths();
725
726
        // Compat: return only the first path added
727
        return end($allPaths) ?: null;
728
    }
729
730
    /**
731
     * @deprecated
732
     *
733
     * @param string $resourcesPath
734
     *
735
     * @return static
736
     */
737
    public function setResourcesPath($resourcesPath)
738
    {
739
        $this->addResourcePath($resourcesPath);
740
        return $this;
741
    }
742
743
    /**
744
     * Return the application paths.
745
     *
746
     * @return array
747
     */
748
    public function getApplicationPaths()
749
    {
750
        return $this->applicationPaths;
751
    }
752
753
    /**
754
     * Set the application paths.
755
     *
756
     * @param array $applicationPaths
757
     *
758
     * @return void
759
     */
760
    public function setApplicationPaths($applicationPaths)
761
    {
762
        $this->applicationPaths = $applicationPaths;
763
    }
764
765
    /**
766
     * Set the application root path.
767
     *
768
     * @param string $applicationRootPath
769
     *
770
     * @return void
771
     */
772
    public function setApplicationRootPath($applicationRootPath)
773
    {
774
        $this->templateHelper->setApplicationRootPath($applicationRootPath);
775
    }
776
777
    /**
778
     * blacklist a sensitive value within one of the superglobal arrays.
779
     * Alias for the hideSuperglobalKey method.
780
     *
781
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
782
     * @param string $key             The key within the superglobal
783
     * @see hideSuperglobalKey
784
     *
785
     * @return static
786
     */
787 1
    public function blacklist($superGlobalName, $key)
788
    {
789 1
        $this->blacklist[$superGlobalName][] = $key;
790 1
        return $this;
791
    }
792
793
    /**
794
     * Hide a sensitive value within one of the superglobal arrays.
795
     *
796
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
797
     * @param string $key             The key within the superglobal
798
     * @return static
799
     */
800
    public function hideSuperglobalKey($superGlobalName, $key)
801
    {
802
        return $this->blacklist($superGlobalName, $key);
803
    }
804
805
    /**
806
     * Checks all values within the given superGlobal array.
807
     *
808
     * Blacklisted values will be replaced by a equal length string containing
809
     * only '*' characters for string values.
810
     * Non-string values will be replaced with a fixed asterisk count.
811
     * We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting.
812
     *
813
     * @param array  $superGlobal     One of the superglobal arrays
814
     * @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
815
     *
816
     * @return array $values without sensitive data
817
     */
818
    private function masked(array $superGlobal, $superGlobalName)
819
    {
820
        $blacklisted = $this->blacklist[$superGlobalName];
821
822
        $values = $superGlobal;
823
824
        foreach ($blacklisted as $key) {
825
            if (isset($superGlobal[$key])) {
826
                $values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3);
827
            }
828
        }
829
830
        return $values;
831
    }
832
}
833