GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (120)

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/Workflow/Composer/Base.php (2 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
 * See class comment
4
 *
5
 * PHP Version 5
6
 *
7
 * @category   Netresearch
8
 * @package    Netresearch\Kite\Workflow
9
 * @subpackage Composer
10
 * @author     Christian Opitz <[email protected]>
11
 * @license    http://www.netresearch.de Netresearch Copyright
12
 * @link       http://www.netresearch.de
13
 */
14
15
namespace Netresearch\Kite\Workflow\Composer;
16
use Netresearch\Kite\Service\Composer\Package;
17
use Netresearch\Kite\Workflow;
18
use Netresearch\Kite\Exception;
19
20
/**
21
 * Abstract for composer workflows
22
 *
23
 * @category   Netresearch
24
 * @package    Netresearch\Kite\Workflow
25
 * @subpackage Composer
26
 * @author     Christian Opitz <[email protected]>
27
 * @license    http://www.netresearch.de Netresearch Copyright
28
 * @link       http://www.netresearch.de
29
 */
30
abstract class Base extends Workflow
31
{
32
    /**
33
     * @var Package[]
34
     */
35
    protected $pushPackages = array();
36
37
    /**
38
     * @var array
39
     */
40
    protected $whitelists;
41
42
    /**
43
     * @var array
44
     */
45
    protected $packageNames;
46
47
    /**
48
     * Configure the variables
49
     *
50
     * @return array
51
     */
52
    protected function configureVariables()
53
    {
54
        $config = $this->getParent()->get('config');
55
        if (!array_key_exists('composer', $config)) {
56
            $config['composer'] = [];
57
        }
58
        foreach (['whitelistNames', 'whitelistPaths', 'whitelistRemotes'] as $key) {
59
            if (!array_key_exists($key, $config['composer'])) {
60
                $config['composer'][ $key ] = null;
61
            }
62
        }
63
64
        return [
65
            'packages' => array(
66
                'type' => 'array',
67
                'label' => 'Package name(s) to limit this operation to',
68
                'shortcut' => 'p',
69
                'option' => true
70
            ),
71
            'whitelistNames' => array(
72
                'default' => '{config["composer"]["whitelistNames"]}',
73
                'type' => 'string',
74
                'label' => 'Regular expression for package names, to limit this operation to',
75
                'option' => true
76
            ),
77
            'whitelistPaths' => array(
78
                'default' => '{config["composer"]["whitelistPaths"]}',
79
                'type' => 'string',
80
                'label' => 'Regular expression for package paths, to limit this operation to',
81
                'option' => true
82
            ),
83
            'whitelistRemotes' => array(
84
                'default' => '{config["composer"]["whitelistRemotes"]}',
85
                'type' => 'string',
86
                'label' => 'Regular expression for package remote urls, to limit this operation to',
87
                'option' => true
88
            ),
89
            '--'
90
        ] + parent::configureVariables();
91
    }
92
93
94
    /**
95
     * Push all packages marked to be pushed
96
     *
97
     * @return void
98
     */
99
    protected function pushPackages()
100
    {
101
        foreach ($this->pushPackages as $i => $package) {
102
            $this->assertPackageIsWhiteListed($package);
103
            $this->console->output("Pushing <comment>$package->name</comment>", false);
104
            $this->git('push', $package->path, array('u' => 'origin', $package->branch));
105
            $this->console->output(
106
                str_repeat(chr(8), strlen($package->name))
107
                . '<info>' . $package->name . '</info>'
108
            );
109
            unset($this->pushPackages[ $i ]);
110
        }
111
    }
112
113
    /**
114
     * doComposerUpdateIfNecessary and possible
115
     *
116
     * @return void
117
     */
118
    protected function doComposerUpdate()
119
    {
120
        try {
121
            $this->composer('update');
122
        } catch (\Exception $e) {
123
            $this->console->output('<warning>Composer update failed</warning>');
124
            $this->console->output('<comment>This might have occured because previous pushes to git did not reache the composer repo yet</comment>');
125
            if ($this->confirm('Retry?')) {
126
                $this->doComposerUpdate();
127
            } else {
128
                $this->doExit('', 1);
129
            }
130
        }
131
    }
132
133
    /**
134
     * Go through all packages and check if packages requiring those packages,
135
     * still require their (likely new) versions.
136
     *
137
     * If not and $autoFix or user agrees, the require-statements in the
138
     * dependent packages are changed accordingly.
139
     *
140
     * @param Package[] $packages Packages
141
     * @param bool      $autoFix  Whether to autofix wrong requirements
142
     *
143
     * @return void
144
     */
145
    protected function rewriteRequirements(array &$packages, $autoFix = false)
146
    {
147
        $checkedOutPackages = array_keys($packages);
148
        $unfixedRequirements = 0;
149
        while ($packageName = array_shift($checkedOutPackages)) {
150
            $branch = $packages[ $packageName ]->branch;
151
            $version = 'dev-' . $branch;
152
            foreach ($this->getPackages(false, false) as $package) {
153
                if (array_key_exists($packageName, $package->requires)) {
154
                    // TODO: Set required version to branch alias, if any
155
                    $requiredVersion = $package->requires[ $packageName ];
156
                    if ($requiredVersion === '@dev') {
157
                        $requiredVersion = 'dev-master';
158
                    }
159
                    if ($requiredVersion !== $version) {
160
                        $this->assertPackageIsWhiteListed($package);
161
                        if (!$package->git) {
162
                            throw new Exception("Package {$package->name} required to be installed from source");
163
                        }
164
                        if ($autoFix) {
165
                            $fix = true;
166
                            $this->output("Changing required version of {$packageName} in {$package->name} from {$requiredVersion} to {$version}");
167
                        } else {
168
                            $this->output("<warning>{$package->name} depends on {$packageName} {$package->requires[$packageName]} and not {$version} as expected</warning>");
169
                            $this->output('<comment>If you don\'t fix that, the branches will probably change with composer update</comment>');
170
                            $fix = $this->confirm('Fix that?');
171
                        }
172
                        if ($fix) {
173
                            if ($this->checkoutPackage($package, $branch, true)) {
174
                                $checkedOutPackages[] = $package->name;
175
                            }
176
                            if (!array_key_exists($package->name, $packages)) {
177
                                $packages[ $package->name ] = $package;
178
                            }
179
                            $this->pushPackages[ $packageName ] = $packages[ $packageName ];
180
                            $this->rewriteRequirement($package, $packageName, $version);
181
                        } else {
182
                            $unfixedRequirements++;
183
                        }
184
                    }
185
                }
186
            }
187
        }
188
        if ($unfixedRequirements) {
189
            $this->doExit(
190
                'It seems like a composer update is required but due to probably incorrect dependencies you have to do that manually', 1
191
            );
192
        }
193
    }
194
195
    /**
196
     * Change the require statement for $requiredPackage to the newVersion in
197
     * $package
198
     *
199
     * @param Package $package         The package
200
     * @param string  $requiredPackage The required package
201
     * @param string  $newVersion      The new version
202
     *
203
     * @return void
204
     */
205
    protected function rewriteRequirement($package, $requiredPackage, $newVersion)
206
    {
207
        $this->assertPackageIsWhiteListed($package);
208
209
        $currentVersion = $package->requires[ $requiredPackage ];
210
        $composerFile = $package->path . '/composer.json';
211
        $composerFileContents = file_get_contents($composerFile);
212
        $newComposerFileContents = preg_replace(
213
            sprintf(
214
                '/(^\s*"require"\s*:\s*\{[^\}]+"%s"\s*:\s*")%s/m',
215
                preg_quote($requiredPackage, '/'),
216
                preg_quote($currentVersion, '/')
217
            ),
218
            '$1' . $newVersion,
219
            $composerFileContents
220
        );
221
        file_put_contents($composerFile, $newComposerFileContents);
222
        $package->reloadRequires();
223
        if ($package->requires[ $requiredPackage ] !== $newVersion) {
224
            file_put_contents($composerFile, $composerFileContents);
225
            $this->output('<error>Could not replace version</error> - generated composer.json was:');
226
            $this->output($newComposerFileContents);
227
            throw new Exception('Replacing version failed');
228
        }
229
230
        $this->git('commit', $package->path, array('n' => true, 'm' => "Change required version of $requiredPackage to $newVersion", 'composer.json'));
231
        if (!isset($package->source)) {
232
            $package->source = new \stdClass();
233
        }
234
        $package->source->reference = $this->git('rev-parse', $package->path, array('HEAD'));
235
236
        $this->console->output("Made <comment>$package->name</comment> require <comment>$requiredPackage $newVersion</comment>");
237
238
        $this->pushPackages[ $package->name ] = $package;
239
    }
240
241
    /**
242
     * Reload the requires from $package composer.json to $package->requires
243
     *
244
     * If $detectChanges, and there are changes on the requirements not in
245
     * $ignorePackages composer update is requested
246
     *
247
     * @param Package $package The package
248
     *
249
     * @deprecated Use $package->reloadRequires()
250
     *
251
     * @return void
252
     */
253
    protected function reloadRequires($package)
254
    {
255
        $package->reloadRequires();
256
    }
257
258
    /**
259
     * Checkout a package at a branch
260
     *
261
     * @param Package $package The package
262
     * @param string  $branch  The branch
263
     * @param bool    $create  Create the branch if it doesn't exist
264
     *
265
     * @return bool|null Whether checkout was successful or null when package is
266
     *                   already at this branch
267
     */
268
    protected function checkoutPackage($package, $branch, $create = false)
269
    {
270
        $this->assertPackageIsWhiteListed($package);
271
272
        if ($package->branch === $branch) {
273
            return null;
274
        }
275
        if (!$package->git) {
276
            throw new Exception('Non git package can not be checked out');
277
        }
278
        $remoteBranch = 'origin/' . $branch;
279
        $isRemote = in_array($remoteBranch, $package->branches, true);
280
        if (in_array($branch, $package->branches, true)) {
281
            $this->git('checkout', $package->path, $branch);
282
        } elseif ($isRemote) {
283
            $this->git('checkout', $package->path, array('b' => $branch, $remoteBranch));
284
        } elseif ($create) {
285
            $branches = array_unique(
286
                array_map(
287
                    function ($el) {
288
                        $parts = explode('/', $el);
289
290
                        return array_pop($parts);
291
                    },
292
                    $package->branches
293
                )
294
            );
295
            sort($branches);
296
            $inferFromBranch = $this->choose(
297
                "Select branch to create new branch '$branch' from in {$package->name}",
298
                $branches, in_array('master', $branches, true) ? 'master' : $branch
299
            );
300
            if ($inferFromBranch !== $package->branch) {
301
                $this->checkoutPackage($package, $inferFromBranch);
302
            }
303
            $this->git('checkout', $package->path, array('b' => $branch));
304
            $package->branches[] = $branch;
305
        } else {
306
            return false;
307
        }
308
309
        if ($isRemote) {
310
            if (!isset($package->upstreams[ $branch ]) || $package->upstreams[ $branch ] !== $remoteBranch) {
311
                $this->git('branch', $package->path, array('u' => $remoteBranch));
312
                $package->upstreams[ $branch ] = $remoteBranch;
313
            }
314
            $this->git('rebase', $package->path);
315
        }
316
317
        $this->console->output("Checked out <comment>{$package->name}</comment> at <comment>$branch</comment>");
318
319
        $package->reloadRequires();
320
        $package->version = 'dev-' . $branch;
321
        $package->branch = $branch;
322
323
        return true;
324
    }
325
326
    /**
327
     * Merge a $branch into $package's current branch
328
     *
329
     * @param Package $package The package
330
     * @param string  $branch  The branch
331
     * @param null    $message The commit message (if any)
332
     * @param bool    $squash  Whether to squash the changes
333
     *
334
     * @return void
335
     */
336
    protected function mergePackage($package, $branch, $message = null, $squash = false)
337
    {
338
        $this->preparePackageForMerge($package, $branch);
339
        $mergeOptions = $this->setMergeOptions($branch, $squash);
340
341
        try {
342
            $this->git('merge', $package->path, $mergeOptions);
343
        } catch (\Exception $e) {
344
            $this->console->output($e->getMessage());
345
            $conflictedFiles = $this->resolveMergeConflicts($package);
346
        }
347
348
        if (isset($conflictedFiles) || $this->git('status', $package->path, array('porcelain' => true))) {
349
            $this->console->output('You are merging package <comment>' . $package->name . '</comment> from <comment>' . $branch . '</comment> into <comment>' . $package->branch . "</comment>.\n");
350
            if (!$message) {
351
                $message = $this->answer(
352
                    'Enter commit message:',
353
                    'Merged ' . $branch . ' into ' . $package->branch
354
                );
355
            }
356
            $this->git('commit', $package->path, array('n' => true, 'm' => $message));
357
        }
358
359
        $this->console->output("Merged with <comment>$branch</comment> in <comment>{$package->name}</comment>");
360
361
        $package->reloadRequires();
362
        $this->pushPackages[ $package->name ] = $package;
363
    }
364
365
    /**
366
     * Check that package is white-listed, is git-package and up-to-date
367
     *
368
     * @param $package
369
     * @param $branch
370
     *
371
     * @return void
372
     *
373
     * @throws Exception
374
     */
375
    private function preparePackageForMerge($package, $branch)
376
    {
377
        $this->assertPackageIsWhiteListed($package);
378
379
        if (!$package->git) {
380
            throw new Exception('Non git package can not be merged');
381
        }
382
383
        $this->git('fetch', $package->path, array('force' => true, 'origin', $branch . ':' . $branch));
384
    }
385
386
    /**
387
     * Set the options for the merge command
388
     *
389
     * @param string $branch branch to merge
390
     * @param bool   $squash is --squash-option set
391
     *
392
     * @return array
393
     */
394
    private function setMergeOptions($branch, $squash)
395
    {
396
        $mergeOptions = array();
397
398
        if ($squash) {
399
            $mergeOptions['squash'] = true;
400
            $mergeOptions['no-commit'] = false;
401
        } else {
402
            $mergeOptions['no-commit'] = true;
403
            $ff = $branch == 'master' ? 'ff' : 'no-ff';
404
            $mergeOptions[ $ff ] = true;
405
        }
406
407
        $mergeOptions[] = $branch;
408
409
        return $mergeOptions;
410
    }
411
412
    /**.
413
     * Try to solve merge conflicts
414
     *
415
     * @param Package $package
416
     *
417
     * @return array
418
     *
419
     * @throws Exception
420
     */
421
    private function resolveMergeConflicts($package)
422
    {
423
        $diff = $this->git('diff', $package->path, array('name-only' => true, 'diff-filter' => 'U'));
424
        $conflictedFiles = array_flip(explode("\n", $diff));
425
        if (array_key_exists('composer.json', $conflictedFiles)) {
426
            try {
427
                $this->resolveRequirementsConflict($package);
428
                $this->git('add', $package->path, 'composer.json');
429
            } catch (Exception $conflictSolvingException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
430
            }
431
        }
432
        if (array_diff(array_keys($conflictedFiles), ['composer.json'])) {
433
            throw new Exception(
434
                'There are unresolved conflicts - please resolve them and then commit the result',
435
                1458307785, isset($conflictSolvingException) ? $conflictSolvingException : null
436
            );
437
        } elseif (isset($conflictSolvingException)) {
438
            throw $conflictSolvingException;
439
        }
440
441
        return $conflictedFiles;
442
    }
443
444
    /**
445
     * Try to solve conflicts inside the require section of the $package
446
     * composer.json
447
     *
448
     * @param Package $package The package
449
     *
450
     * @return void
451
     */
452
    private function resolveRequirementsConflict($package)
453
    {
454
        $contents = file_get_contents($package->path . '/composer.json');
455
        $ours = @json_decode(
456
            preg_replace('/^<{7}.+\n(.+)\n(\|{7}|={7}).+>{7}.+$/smU', '$1', $contents)
457
        );
458
        $theirs = @json_decode(
459
            preg_replace('/^<{7}.+\n={7}\n(.+)\n>{7}.+$/smU', '$1', $contents)
460
        );
461
        if (!is_object($ours) || !is_object($theirs)) {
462
            throw new Exception('Could not regenerate json file from solved conflicts');
463
        }
464
        $diff = array_diff_key($theirs = get_object_vars($theirs), $ours = get_object_vars($ours));
465
        foreach ($ours as $key => $value) {
466
            if ($key !== 'require' && (!array_key_exists($key, $theirs) || serialize($value) !== serialize($theirs[ $key ]))) {
467
                $diff[ $key ] = $value;
468
            }
469
        }
470
        if ($diff !== array()) {
471
            throw new Exception('Can not automerge composer.json due to conflicts outside require object', 1458307516);
472
        }
473
474
        $theirs['require'] = $this->mergeRequirements($package, $ours, $theirs);
475
        file_put_contents($package->path . '/composer.json', $this->jsonEncode($theirs));
476
    }
477
478
    /**
479
     * Encode a variable for JSON file
480
     *
481
     * @param mixed $var The var
482
     *
483
     * @return string
484
     */
485
    protected function jsonEncode($var)
486
    {
487
        return json_encode(
488
            $var, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
489
        ) . "\n";
490
    }
491
492
    /**
493
     * Merge the requirements from different sides of the current $package
494
     *
495
     * @param Package $package The current package
496
     * @param array   $ours    Our composer.json as array
497
     * @param array   $theirs  Their composer.json as array
498
     *
499
     * @return array
500
     */
501
    private function mergeRequirements($package, array $ours, array $theirs)
502
    {
503
        $oursRequire = isset($ours['require']) && is_object($ours['require']) ? get_object_vars($ours['require']) : [];
504
        $theirsRequire = isset($theirs['require']) && is_object($theirs['require']) ? get_object_vars($theirs['require']) : [];
505
        $mergedRequires = array_merge($oursRequire, $theirsRequire);
506
        $packages = $this->getPackages(false, false);
507
        $preferredVersion = 'dev-' . $package->branch;
508
        foreach ($mergedRequires as $packageName => $version) {
509
            $actualVersion = ($version === '@dev') ? 'dev-master' : $version;
510
            if (array_key_exists($packageName, $oursRequire)
511
                && $version !== $oursRequire[ $packageName ]
512
                && $actualVersion !== $oursRequire[ $packageName ]
513
                && $actualVersion !== $preferredVersion
514
                && array_key_exists($packageName, $packages)
515
                && in_array($package->branch, $packages[ $packageName ]->branches, true)
516
            ) {
517
                $mergedRequires[ $packageName ] = $preferredVersion;
518
            }
519
        }
520
521
        return $mergedRequires;
522
    }
523
524
    /**
525
     * Get the allowed packages
526
     *
527
     * @param bool $gitOnly     If git packages should be returned only
528
     * @param bool $allowedOnly If allowed packages should be returned only
529
     *
530
     * @return \Netresearch\Kite\Service\Composer\Package[]
531
     */
532
    protected function getPackages($gitOnly = true, $allowedOnly = true)
533
    {
534
        /* @var $packages \Netresearch\Kite\Service\Composer\Package[] */
535
        /* @var $package \Netresearch\Kite\Service\Composer\Package */
536
        $packages = array();
537
        foreach ($this->get('composer.packages') as $package) {
538
            if ((!$gitOnly || $package->git) && (!$allowedOnly || $this->isPackageAllowed($package))) {
539
                $packages[ $package->name ] = $package;
540
            }
541
        }
542
543
        return $packages;
544
    }
545
546
    /**
547
     * Assert that package is in white lists
548
     *
549
     * @param Package $package The package
550
     *
551
     * @throws Exception
552
     *
553
     * @return void
554
     */
555
    protected function assertPackageIsWhiteListed($package)
556
    {
557
        if ($this->isPackageWhiteListed($package) === false) {
558
            throw new Exception("Package {$package->name} is not in white list");
559
        }
560
    }
561
562
    /**
563
     * Determine if a package is not excluded by the packages option or white lists
564
     *
565
     * @param Package $package The package
566
     *
567
     * @return bool
568
     */
569
    protected function isPackageAllowed(Package $package)
570
    {
571
        if (!is_array($this->packageNames)) {
572
            $this->packageNames = array_fill_keys($this->get('packages') ? : [], null);
573
        }
574
575
        if ($this->packageNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->packageNames of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
576
            if (!array_key_exists($package->name, $this->packageNames)) {
577
                return false;
578
            }
579
            if ($this->isPackageWhiteListed($package) === false) {
580
                if ($this->packageNames[ $package->name ] === null) {
581
                    $this->packageNames[ $package->name ] = $this->confirm("The package $package->name is excluded by your whitelist configuration - are you sure you want to include it anyway?");
582
                }
583
584
                return $this->packageNames[ $package->name ];
585
            }
586
587
            return true;
588
        }
589
590
        return $this->isPackageWhiteListed($package) !== false;
591
    }
592
593
    /**
594
     * Determine if package is white listed
595
     *
596
     * @param Package $package The package
597
     *
598
     * @return bool|null
599
     */
600
    protected function isPackageWhiteListed(Package $package)
601
    {
602
        if (!is_array($this->whitelists)) {
603
            $this->whitelists = [];
604
            foreach (['path', 'remote', 'name'] as $whiteListType) {
605
                $option = $this->get('whitelist' . ucfirst($whiteListType) . 's');
606
                if ($option) {
607
                    $this->whitelists[ $whiteListType ] = '#^' . $option . '$#';
608
                }
609
            }
610
        }
611
612
        foreach ($this->whitelists as $type => $pattern) {
613
            $subject = $package->$type;
614
            if ($type === 'path') {
615
                $subject = rtrim(
616
                    $this->console->getFilesystem()->findShortestPath(
617
                        $this->get('composer.rootPackage.path'),
618
                        $subject,
619
                        true
620
                    ),
621
                    '/'
622
                );
623
            }
624
            if (preg_match($pattern, $subject)) {
625
                return true;
626
            }
627
        }
628
629
        return $this->whitelists ? false : null;
630
    }
631
}
632
633
?>
634