Issues (191)

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.

lib/ILess/Importer.php (1 issue)

Labels
Severity

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
 * This file is part of the ILess
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace ILess;
10
11
use ILess\Cache\CacheInterface;
12
use ILess\Exception\Exception;
13
use ILess\Exception\ImportException;
14
use ILess\Exception\ParserException;
15
use ILess\Importer\ImporterInterface;
16
use ILess\Node\RulesetNode;
17
use ILess\Parser\Core;
18
19
/**
20
 * Import.
21
 */
22
class Importer
23
{
24
    /**
25
     * The context.
26
     *
27
     * @var Context
28
     */
29
    protected $context;
30
31
    /**
32
     * The cache.
33
     *
34
     * @var CacheInterface
35
     */
36
    protected $cache;
37
38
    /**
39
     * Array of importers.
40
     *
41
     * @var array
42
     */
43
    protected $importers = [];
44
45
    /**
46
     * Array of imported files.
47
     *
48
     * @var array
49
     */
50
    protected $importedFiles = [];
51
52
    /**
53
     * @var PluginManager
54
     */
55
    protected $pluginManager;
56
57
    /**
58
     * Constructor.
59
     *
60
     * @param Context $context The context
61
     * @param array $importers Array of importers
62
     * @param CacheInterface $cache The cache
63
     * @param PluginManager $manager
64
     */
65
    public function __construct(Context $context, array $importers, CacheInterface $cache, PluginManager $manager = null)
66
    {
67
        $this->context = $context;
68
        $this->pluginManager = $manager;
69
        $this->registerImporters($importers);
70
        $this->cache = $cache;
71
    }
72
73
    /**
74
     * Sets The context.
75
     *
76
     * @param Context $context
77
     *
78
     * @return Importer
79
     */
80
    public function setEnvironment(Context $context)
81
    {
82
        $this->context = $context;
83
84
        return $this;
85
    }
86
87
    /**
88
     * Sets the cache.
89
     *
90
     * @param CacheInterface $cache
91
     *
92
     * @return Importer
93
     */
94
    public function setCache(CacheInterface $cache)
95
    {
96
        $this->cache = $cache;
97
98
        return $this;
99
    }
100
101
    /**
102
     * Returns the cache.
103
     *
104
     * @return CacheInterface
105
     */
106
    public function getCache()
107
    {
108
        return $this->cache;
109
    }
110
111
    /**
112
     * Returns the context.
113
     *
114
     * @return Context
115
     */
116
    public function getContext()
117
    {
118
        return $this->context;
119
    }
120
121
    /**
122
     * Imports the file.
123
     *
124
     * @param string $path The path to import. Path will be searched by the importers
125
     * @param bool $tryAppendLessExtension Whether to try appending the less extension (if the path has no extension)
126
     * @param array $importOptions Import options
127
     * @param int $index Current index
128
     *
129
     * @return array
130
     *
131
     * @throws ImportException If the $path could not be imported
132
     */
133
    public function import(
134
        $path,
135
        $tryAppendLessExtension = false,
136
        FileInfo $currentFileInfo,
137
        array $importOptions = [],
138
        $index = 0
139
    ) {
140
        $plugin = isset($importOptions['plugin']) && $importOptions['plugin'];
141
        $inline = isset($importOptions['inline']) && $importOptions['inline'];
142
        $css = isset($importOptions['css']) && $importOptions['css'];
143
144
        if ($tryAppendLessExtension && !pathinfo($path, PATHINFO_EXTENSION)) {
145
            if ($plugin) {
146
                $path .= '.php';
147
            } else {
148
                $path .= '.less';
149
            }
150
        }
151
152
        // we must generate separate cache for inline and css files to avoid problems
153
        // when someone wants to import the less file first as inline import and than normally
154
        $cacheKey = $this->generateCacheKey(
155
            (Util::isPathAbsolute($path) ? $path : $currentFileInfo->currentDirectory . $path)
156
            . 'css-' . (int) $inline . 'inline-' . (int) $css
157
        );
158
159
        // do we have a file in the cache?
160
        if ($this->cache->has($cacheKey)) {
161
            // check the modified timestamp
162
            $file = $this->cache->get($cacheKey);
163
            // search
164
            foreach ($this->importers as $importer) {
165
                /* @var $file ImportedFile */
166
                /* @var $importer ImporterInterface */
167
                $lastModified = $importer->getLastModified($path, $currentFileInfo);
168
                if ($lastModified !== false && $lastModified === $file->getLastModified()) {
169
                    // the modification time is the same, take the one from cache
170
                    return $this->doImport($file, $path, $currentFileInfo, $importOptions, true);
171
                }
172
            }
173
        }
174
175
        foreach ($this->importers as $importer) {
176
            /* @var $importer ImporterInterface */
177
            $file = $importer->import($path, $currentFileInfo);
178
179
            // import is handled by the importer
180
            if ($file instanceof ImportedFile) {
181
                if ($plugin) {
182
183
                    // create dummy ruleset which will hold the functions
184
                    $ruleset = new RulesetNode([], []);
185
                    $ruleset->root = false;
186
                    $ruleset->functions[] = $file->getPath();
187
188
                    $file->setRuleset($ruleset);
189
190
                    return [
191
                        true,
192
                        $file,
193
                    ];
194
                } else {
195
                    $result = $this->doImport($file, $path, $currentFileInfo, $importOptions);
196
                    /* @var $file ImportedFile */
197
                    list(, $file) = $result;
198
                    // save the cache
199
                    $this->cache->set($cacheKey, $file);
200
201
                    return $result;
202
                }
203
            }
204
        }
205
206
        throw new ImportException(sprintf("'%s' wasn't found.", $path), $index, $currentFileInfo);
207
    }
208
209
    /**
210
     * Does the import.
211
     *
212
     * @param ImportedFile $file The imported file
213
     * @param string $path The original path
214
     * @param FileInfo $currentFileInfo Current file info
215
     * @param array $importOptions Import options
216
     * @param bool $fromCache Is the imported file coming from cache?
217
     *
218
     * @throws ParserException
219
     * @throws Exception
220
     *
221
     * @return array
222
     */
223
    protected function doImport(
224
        ImportedFile $file,
225
        $path,
226
        FileInfo $currentFileInfo,
227
        array $importOptions = [],
228
        $fromCache = false
229
    ) {
230
        $newEnv = Context::createCopyForCompilation($this->context, $this->context->frames);
231
232
        $newFileInfo = clone $currentFileInfo;
233
234
        if ($this->context->relativeUrls) {
235
            // Pass on an updated rootPath if path of imported file is relative and file
236
            // is in a (sub|sup) directory
237
            //
238
            // Examples:
239
            // - If path of imported file is 'module/nav/nav.less' and rootPath is 'less/',
240
            //   then rootPath should become 'less/module/nav/'
241
            // - If path of imported file is '../mixins.less' and rootPath is 'less/',
242
            //   then rootPath should become 'less/../'
243
            if (!Util::isPathAbsolute($path) && (($lastSlash = strrpos($path, '/')) !== false)) {
244
                $relativeSubDirectory = substr($path, 0, $lastSlash + 1);
245
                $newFileInfo->rootPath = $newFileInfo->rootPath . $relativeSubDirectory;
246
            }
247
        }
248
249
        // we need to clone here, to prevent modification of node current info object
250
        $newEnv->currentFileInfo = $newFileInfo;
251
        $newEnv->processImports = false;
252
253
        if ($currentFileInfo->reference
254
            || (isset($importOptions['reference']) && $importOptions['reference'])
255
        ) {
256
            $newEnv->currentFileInfo->reference = true;
257
        }
258
259
        $key = $file->getPath();
260
        $root = null;
261
        $alreadyImported = false;
262
263
        // check for already imported file
264
        if (isset($this->importedFiles[$key])) {
265
            $alreadyImported = true;
266
        } elseif (!$file->getRuleset()) {
267
            try {
268
                // we do not parse the root but load the file as is
269
                if (isset($importOptions['inline']) && $importOptions['inline']) {
270
                    $root = $file->getContent();
271
                } else {
272
                    $parser = new Core($newEnv, $this, $this->pluginManager);
273
                    $root = $parser->parseFile($file, true);
274
                    $root->root = false;
275
                    $root->firstRoot = false;
276
                }
277
                $file->setRuleset($root);
0 ignored issues
show
It seems like $root defined by $parser->parseFile($file, true) on line 273 can also be of type object<ILess\Parser\Core>; however, ILess\ImportedFile::setRuleset() does only seem to accept string|object<ILess\Node\RulesetNode>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
278
                // we need to catch parse exceptions
279
            } catch (Exception $e) {
280
                // rethrow
281
                throw $e;
282
            } catch (\Exception $error) {
283
                $file->setError($error);
284
            }
285
286
            $this->setImportedFile($key, $file, $path, $currentFileInfo);
287
        } else {
288
            $this->setImportedFile($key, $file, $path, $currentFileInfo);
289
        }
290
291
        if ($fromCache) {
292
            $ruleset = $this->importedFiles[$key][0]->getRuleset();
293
            if ($ruleset instanceof Node) {
294
                // this is a workaround for reference and import one issues when taken cache
295
                $this->updateReferenceInCurrentFileInfo($ruleset, $newEnv->currentFileInfo);
296
            }
297
        }
298
299
        return [
300
            $alreadyImported,
301
            $this->importedFiles[$key][0],
302
        ];
303
    }
304
305
    /**
306
     * Updates the currentFileInfo object to the $value.
307
     *
308
     * @param Node $node The node to update
309
     * @param FileInfo $newInfo The new file info
310
     */
311
    protected function updateReferenceInCurrentFileInfo(Node $node, FileInfo $newInfo)
312
    {
313
        if (isset($node->currentFileInfo)) {
314
            $node->currentFileInfo->reference = $newInfo->reference;
315
            $node->currentFileInfo->rootPath = $newInfo->rootPath;
316
        }
317
318
        if (Node::propertyExists($node, 'rules') &&
319
            is_array($node->rules) && $node->rules
320
        ) {
321
            foreach ($node->rules as $rule) {
322
                $this->updateReferenceInCurrentFileInfo($rule, $newInfo);
323
            }
324
        }
325
    }
326
327
    /**
328
     * Returns the last modification time of the file.
329
     *
330
     * @param string $path
331
     * @param FileInfo $currentFileInfo
332
     *
333
     * @return int
334
     *
335
     * @throws Exception If there was an error
336
     */
337
    public function getLastModified($path, FileInfo $currentFileInfo)
338
    {
339
        foreach ($this->importers as $importer) {
340
            /* @var $importer ImporterInterface */
341
            $result = $importer->getLastModified($path, $currentFileInfo);
342
            if ($result !== null) {
343
                return $result;
344
            }
345
        }
346
347
        throw new ImportException(sprintf('Error getting last modification time of the file "%s".', $path));
348
    }
349
350
    /**
351
     * Registers an importer.
352
     *
353
     * @param ImporterInterface $importer
354
     * @param string $name The importer name (only for developer reference)
355
     * @param bool $prepend Prepend before current importers?
356
     *
357
     * @return Importer
358
     */
359
    public function registerImporter(ImporterInterface $importer, $name = null, $prepend = false)
360
    {
361
        // FIXME: what about more than one importer with the same class?
362
        $name = !is_null($name) ? $name : get_class($importer);
363
364
        if ($prepend) {
365
            // array unshift with preservation of keys
366
            $importers = array_reverse($this->importers, true);
367
            $importers[$name] = $importer;
368
            $this->importers = array_reverse($importers, true);
369
        } else {
370
            $this->importers[$name] = $importer;
371
        }
372
373
        return $this;
374
    }
375
376
    /**
377
     * Returns the importer with given name.
378
     *
379
     * @param string $name
380
     *
381
     * @return ImporterInterface
382
     */
383
    public function getImporter($name)
384
    {
385
        return isset($this->importers[$name]) ? $this->importers[$name] : null;
386
    }
387
388
    /**
389
     * Returns registered importers.
390
     *
391
     * @return array
392
     */
393
    public function getImporters()
394
    {
395
        return $this->importers;
396
    }
397
398
    /**
399
     * Registers an array of importers.
400
     *
401
     * @param array $importers
402
     *
403
     * @return Importer
404
     */
405
    public function registerImporters(array $importers)
406
    {
407
        foreach ($importers as $name => $importer) {
408
            $this->registerImporter($importer, is_numeric($name) ? null : $name);
409
        }
410
411
        return $this;
412
    }
413
414
    /**
415
     * Clears all importers.
416
     *
417
     * @return Importer
418
     */
419
    public function clearImporters()
420
    {
421
        $this->importers = [];
422
423
        return $this;
424
    }
425
426
    /**
427
     * Returns a list of imported files.
428
     *
429
     * @return array
430
     */
431
    public function getImportedFiles()
432
    {
433
        return $this->importedFiles;
434
    }
435
436
    /**
437
     * Sets the imported file.
438
     *
439
     * @param string $pathAbsolute The absolute path
440
     * @param ImportedFile $file The imported file
441
     * @param string $path The original path to import
442
     * @param FileInfo $currentFileInfo
443
     *
444
     * @return Importer
445
     */
446
    public function setImportedFile($pathAbsolute, ImportedFile $file, $path, FileInfo $currentFileInfo)
447
    {
448
        $this->importedFiles[$pathAbsolute] = [$file, $path, $currentFileInfo];
449
        // save for source map generation
450
        $this->context->setFileContent($pathAbsolute, $file->getContent());
451
452
        return $this;
453
    }
454
455
    /**
456
     * Returns the imported file.
457
     *
458
     * @param string $absolutePath The absolute path of the file
459
     * @param mixed $default The default when no file with given $path is already imported
460
     *
461
     * @return array Array(ImportedFile, $originalPath, CurrentFileInfo)
462
     */
463
    public function getImportedFile($absolutePath, $default = null)
464
    {
465
        return isset($this->importedFiles[$absolutePath]) ? $this->importedFiles[$absolutePath] : $default;
466
    }
467
468
    /**
469
     * Generates unique cache key for given $filename.
470
     *
471
     * @param string $filename
472
     *
473
     * @return string
474
     */
475
    protected function generateCacheKey($filename)
476
    {
477
        return Util::generateCacheKey($filename);
478
    }
479
}
480