This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Mouf\NodeJsInstaller; |
||
3 | |||
4 | use Composer\Composer; |
||
5 | use Composer\IO\IOInterface; |
||
6 | use Composer\Util\RemoteFilesystem; |
||
7 | |||
8 | class NodeJsInstaller |
||
9 | { |
||
10 | |||
11 | /** |
||
12 | * @var IOInterface |
||
13 | */ |
||
14 | private $io; |
||
15 | |||
16 | protected $rfs; |
||
17 | |||
18 | public function __construct(IOInterface $io, Composer $composer) |
||
19 | { |
||
20 | $this->io = $io; |
||
21 | $this->rfs = new RemoteFilesystem($io, $composer->getConfig()); |
||
22 | } |
||
23 | |||
24 | /** |
||
25 | * Checks if NodeJS is installed globally. |
||
26 | * If yes, will return the version number. |
||
27 | * If no, will return null. |
||
28 | * |
||
29 | * Note: trailing "v" will be removed from version string. |
||
30 | * |
||
31 | * @return null|string |
||
32 | */ |
||
33 | public function getNodeJsGlobalInstallVersion() |
||
34 | { |
||
35 | $returnCode = 0; |
||
36 | $output = ""; |
||
37 | |||
38 | ob_start(); |
||
39 | $version = exec("nodejs -v 2>&1", $output, $returnCode); |
||
40 | ob_end_clean(); |
||
41 | |||
42 | if ($returnCode !== 0) { |
||
43 | ob_start(); |
||
44 | $version = exec("node -v 2>&1", $output, $returnCode); |
||
45 | ob_end_clean(); |
||
46 | |||
47 | if ($returnCode !== 0) { |
||
48 | return; |
||
49 | } |
||
50 | } |
||
51 | |||
52 | return ltrim($version, "v"); |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Returns the full path to NodeJS global install (if available). |
||
57 | */ |
||
58 | public function getNodeJsGlobalInstallPath() |
||
59 | { |
||
60 | $pathToNodeJS = $this->getGlobalInstallPath("nodejs"); |
||
61 | if (!$pathToNodeJS) { |
||
62 | $pathToNodeJS = $this->getGlobalInstallPath("node"); |
||
63 | } |
||
64 | |||
65 | return $pathToNodeJS; |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * Returns the full install path to a command |
||
70 | * @param string $command |
||
71 | */ |
||
72 | public function getGlobalInstallPath($command) |
||
73 | { |
||
74 | if (Environment::isWindows()) { |
||
75 | $result = trim(shell_exec("where /F ".escapeshellarg($command)), "\n\r"); |
||
76 | |||
77 | // "Where" can return several lines. |
||
78 | $lines = explode("\n", $result); |
||
79 | |||
80 | return $lines[0]; |
||
81 | } else { |
||
82 | // We want to get output from stdout, not from stderr. |
||
83 | // Therefore, we use proc_open. |
||
84 | $descriptorspec = array( |
||
85 | 0 => array("pipe", "r"), // stdin |
||
86 | 1 => array("pipe", "w"), // stdout |
||
87 | 2 => array("pipe", "w"), // stderr |
||
88 | ); |
||
89 | $pipes = array(); |
||
90 | |||
91 | $process = proc_open("which ".escapeshellarg($command), $descriptorspec, $pipes); |
||
92 | |||
93 | $stdout = stream_get_contents($pipes[1]); |
||
94 | fclose($pipes[1]); |
||
95 | |||
96 | // Let's ignore stderr (it is possible we do not find anything and depending on the OS, stderr will |
||
97 | // return things or not) |
||
98 | fclose($pipes[2]); |
||
99 | |||
100 | proc_close($process); |
||
101 | |||
102 | return trim($stdout, "\n\r"); |
||
103 | } |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Checks if NodeJS is installed locally. |
||
108 | * If yes, will return the version number. |
||
109 | * If no, will return null. |
||
110 | * |
||
111 | * Note: trailing "v" will be removed from version string. |
||
112 | * |
||
113 | * @return null|string |
||
114 | */ |
||
115 | public function getNodeJsLocalInstallVersion($binDir) |
||
116 | { |
||
117 | $returnCode = 0; |
||
118 | $output = ""; |
||
119 | |||
120 | $cwd = getcwd(); |
||
121 | chdir(__DIR__.'/../../../../'); |
||
122 | |||
123 | ob_start(); |
||
124 | |||
125 | $version = exec($binDir.DIRECTORY_SEPARATOR.'node -v 2>&1', $output, $returnCode); |
||
126 | |||
127 | ob_end_clean(); |
||
128 | |||
129 | chdir($cwd); |
||
130 | |||
131 | if ($returnCode !== 0) { |
||
132 | return; |
||
133 | } else { |
||
134 | return ltrim($version, "v"); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Returns URL based on version. |
||
140 | * URL is dependent on environment |
||
141 | * @param string $version |
||
142 | * @return string |
||
143 | * @throws NodeJsInstallerException |
||
144 | */ |
||
145 | public function getNodeJSUrl($version) |
||
146 | { |
||
147 | if (Environment::isWindows() && Environment::getArchitecture() == 32) { |
||
148 | View Code Duplication | if (version_compare($version, '4.0.0') >= 0) { |
|
0 ignored issues
–
show
|
|||
149 | return "https://nodejs.org/dist/v".$version."/win-x86/node.exe"; |
||
150 | } else { |
||
151 | return "https://nodejs.org/dist/v".$version."/node.exe"; |
||
152 | } |
||
153 | } elseif (Environment::isWindows() && Environment::getArchitecture() == 64) { |
||
154 | View Code Duplication | if (version_compare($version, '4.0.0') >= 0) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
155 | return "https://nodejs.org/dist/v" . $version . "/win-x64/node.exe"; |
||
156 | } else { |
||
157 | return "https://nodejs.org/dist/v" . $version . "/x64/node.exe"; |
||
158 | } |
||
159 | } elseif (Environment::isMacOS() && Environment::getArchitecture() == 32) { |
||
160 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-darwin-x86.tar.gz"; |
||
161 | } elseif (Environment::isMacOS() && Environment::getArchitecture() == 64) { |
||
162 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-darwin-x64.tar.gz"; |
||
163 | } elseif (Environment::isSunOS() && Environment::getArchitecture() == 32) { |
||
164 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-sunos-x86.tar.gz"; |
||
165 | } elseif (Environment::isSunOS() && Environment::getArchitecture() == 64) { |
||
166 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-sunos-x64.tar.gz"; |
||
167 | } elseif (Environment::isLinux() && Environment::isArm()) { |
||
168 | if (version_compare($version, '4.0.0') >= 0) { |
||
169 | if (Environment::isArmV6l()) { |
||
170 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-linux-armv6l.tar.gz"; |
||
171 | } elseif (Environment::isArmV7l()) { |
||
172 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-linux-armv7l.tar.gz"; |
||
173 | } elseif (Environment::getArchitecture() == 64) { |
||
174 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-linux-arm64.tar.gz"; |
||
175 | } else { |
||
176 | throw new NodeJsInstallerException('NodeJS-installer cannot install Node on computers with ARM 32bits processors that are not v6l or v7l. Please install NodeJS globally on your machine first, then run composer again.'); |
||
177 | } |
||
178 | } else { |
||
179 | throw new NodeJsInstallerException('NodeJS-installer cannot install Node <4.0 on computers with ARM processors. Please install NodeJS globally on your machine first, then run composer again, or consider installing a version of NodeJS >=4.0.'); |
||
180 | } |
||
181 | } elseif (Environment::isLinux() && Environment::getArchitecture() == 32) { |
||
182 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-linux-x86.tar.gz"; |
||
183 | } elseif (Environment::isLinux() && Environment::getArchitecture() == 64) { |
||
184 | return "https://nodejs.org/dist/v".$version."/node-v".$version."-linux-x64.tar.gz"; |
||
185 | } else { |
||
186 | throw new NodeJsInstallerException('Unsupported architecture: '.PHP_OS.' - '.Environment::getArchitecture().' bits'); |
||
187 | } |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Installs NodeJS |
||
192 | * @param string $version |
||
193 | * @param string $targetDirectory |
||
194 | * @throws NodeJsInstallerException |
||
195 | */ |
||
196 | public function install($version, $targetDirectory) |
||
197 | { |
||
198 | $this->io->write("Installing <info>NodeJS v".$version."</info>"); |
||
199 | $url = $this->getNodeJSUrl($version); |
||
200 | $this->io->write(" Downloading from $url"); |
||
201 | |||
202 | $cwd = getcwd(); |
||
203 | |||
204 | $fileName = 'vendor/'.pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_BASENAME); |
||
205 | |||
206 | $this->rfs->copy(parse_url($url, PHP_URL_HOST), $url, $fileName); |
||
0 ignored issues
–
show
|
|||
207 | |||
208 | if (!file_exists($fileName)) { |
||
209 | throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' |
||
210 | .' directory is writable and you have internet connectivity'); |
||
211 | } |
||
212 | |||
213 | if (!file_exists($targetDirectory)) { |
||
214 | mkdir($targetDirectory, 0775, true); |
||
215 | } |
||
216 | |||
217 | if (!is_writable($targetDirectory)) { |
||
218 | throw new NodeJsInstallerException("'$targetDirectory' is not writable"); |
||
219 | } |
||
220 | |||
221 | if (!Environment::isWindows()) { |
||
222 | // Now, if we are not in Windows, let's untar. |
||
223 | $this->extractTo($fileName, $targetDirectory); |
||
224 | |||
225 | // Let's delete the downloaded file. |
||
226 | unlink($fileName); |
||
227 | } else { |
||
228 | // If we are in Windows, let's move and install NPM. |
||
229 | rename($fileName, $targetDirectory.'/'.basename($fileName)); |
||
230 | |||
231 | // We have to download the latest available version in a bin for Windows, then upgrade it: |
||
232 | $url = "https://nodejs.org/dist/npm/npm-1.4.12.zip"; |
||
233 | $npmFileName = "vendor/npm-1.4.12.zip"; |
||
234 | $this->rfs->copy(parse_url($url, PHP_URL_HOST), $url, $npmFileName); |
||
0 ignored issues
–
show
|
|||
235 | |||
236 | $this->unzip($npmFileName, $targetDirectory); |
||
237 | |||
238 | unlink($npmFileName); |
||
239 | |||
240 | // Let's update NPM |
||
241 | $highestNpmVersion = "latest"; |
||
242 | if ($version < 10) { |
||
243 | $highestNpmVersion = "6.14.8"; |
||
244 | } |
||
245 | |||
246 | // 1- Update PATH to run npm. |
||
247 | $path = getenv('PATH'); |
||
248 | $newPath = realpath($targetDirectory).";".$path; |
||
249 | putenv('PATH='.$newPath); |
||
250 | |||
251 | // 2- Run npm |
||
252 | $cwd2 = getcwd(); |
||
253 | chdir($targetDirectory); |
||
254 | |||
255 | $returnCode = 0; |
||
256 | passthru("npm update npm@{$highestNpmVersion}", $returnCode); |
||
257 | if ($returnCode !== 0) { |
||
258 | throw new NodeJsInstallerException("An error occurred while updating NPM to latest version."); |
||
259 | } |
||
260 | |||
261 | // Finally, let's copy the base npm file for Cygwin |
||
262 | if (file_exists('node_modules/npm/bin/npm')) { |
||
263 | copy('node_modules/npm/bin/npm', 'npm'); |
||
264 | } |
||
265 | |||
266 | chdir($cwd2); |
||
267 | } |
||
268 | |||
269 | chdir($cwd); |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * Extract tar.gz file to target directory. |
||
274 | * |
||
275 | * @param string $tarGzFile |
||
276 | * @param string $targetDir |
||
277 | */ |
||
278 | private function extractTo($tarGzFile, $targetDir) |
||
279 | { |
||
280 | // Note: we cannot use PharData class because it does not keeps symbolic links. |
||
281 | // Also, --strip 1 allows us to remove the first directory. |
||
282 | |||
283 | $output = $return_var = null; |
||
284 | |||
285 | exec("tar -xvf ".$tarGzFile." -C ".escapeshellarg($targetDir)." --strip 1", $output, $return_var); |
||
286 | |||
287 | if ($return_var !== 0) { |
||
288 | throw new NodeJsInstallerException("An error occurred while untaring NodeJS ($tarGzFile) to $targetDir"); |
||
289 | } |
||
290 | } |
||
291 | |||
292 | public function createBinScripts($binDir, $targetDir, $isLocal) |
||
293 | { |
||
294 | if (!file_exists($binDir)) { |
||
295 | $result = mkdir($binDir, 0775, true); |
||
296 | if ($result === false) { |
||
297 | throw new NodeJsInstallerException("Unable to create directory ".$binDir); |
||
298 | } |
||
299 | } |
||
300 | |||
301 | $fullTargetDir = realpath($targetDir); |
||
302 | $binDir = realpath($binDir); |
||
303 | |||
304 | if (!Environment::isWindows()) { |
||
305 | $this->createBinScript($binDir, $fullTargetDir, 'node', 'node', $isLocal); |
||
306 | $this->createBinScript($binDir, $fullTargetDir, 'npm', 'npm', $isLocal); |
||
307 | } else { |
||
308 | $this->createBinScript($binDir, $fullTargetDir, 'node.bat', 'node', $isLocal); |
||
309 | $this->createBinScript($binDir, $fullTargetDir, 'npm.bat', 'npm', $isLocal); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Copy script into $binDir, replacing PATH with $fullTargetDir |
||
315 | * @param string $binDir |
||
316 | * @param string $fullTargetDir |
||
317 | * @param string $scriptName |
||
318 | * @param bool $isLocal |
||
319 | */ |
||
320 | private function createBinScript($binDir, $fullTargetDir, $scriptName, $target, $isLocal) |
||
321 | { |
||
322 | $content = file_get_contents(__DIR__.'/../bin/'.($isLocal ? "local/" : "global/").$scriptName); |
||
323 | if ($isLocal) { |
||
324 | $path = $this->makePathRelative($fullTargetDir, $binDir); |
||
325 | } else { |
||
326 | if ($scriptName == "node") { |
||
327 | $path = $this->getNodeJsGlobalInstallPath(); |
||
328 | } else { |
||
329 | $path = $this->getGlobalInstallPath($target); |
||
330 | } |
||
331 | |||
332 | if (strpos($path, $binDir) === 0) { |
||
333 | // we found the local installation that already exists. |
||
334 | |||
335 | return; |
||
336 | } |
||
337 | } |
||
338 | |||
339 | |||
340 | file_put_contents($binDir.'/'.$scriptName, sprintf($content, $path)); |
||
341 | chmod($binDir.'/'.$scriptName, 0755); |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Shamelessly stolen from Symfony's FileSystem. Thanks guys! |
||
346 | * Given an existing path, convert it to a path relative to a given starting path. |
||
347 | * |
||
348 | * @param string $endPath Absolute path of target |
||
349 | * @param string $startPath Absolute path where traversal begins |
||
350 | * |
||
351 | * @return string Path of target relative to starting path |
||
352 | */ |
||
353 | private function makePathRelative($endPath, $startPath) |
||
354 | { |
||
355 | // Normalize separators on Windows |
||
356 | if ('\\' === DIRECTORY_SEPARATOR) { |
||
357 | $endPath = strtr($endPath, '\\', '/'); |
||
358 | $startPath = strtr($startPath, '\\', '/'); |
||
359 | } |
||
360 | // Split the paths into arrays |
||
361 | $startPathArr = explode('/', trim($startPath, '/')); |
||
362 | $endPathArr = explode('/', trim($endPath, '/')); |
||
363 | // Find for which directory the common path stops |
||
364 | $index = 0; |
||
365 | while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { |
||
366 | $index++; |
||
367 | } |
||
368 | // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) |
||
369 | $depth = count($startPathArr) - $index; |
||
370 | // Repeated "../" for each level need to reach the common path |
||
371 | $traverser = str_repeat('../', $depth); |
||
372 | $endPathRemainder = implode('/', array_slice($endPathArr, $index)); |
||
373 | // Construct $endPath from traversing to the common path, then to the remaining $endPath |
||
374 | $relativePath = $traverser.(strlen($endPathRemainder) > 0 ? $endPathRemainder.'/' : ''); |
||
375 | |||
376 | return (strlen($relativePath) === 0) ? './' : $relativePath; |
||
377 | } |
||
378 | |||
379 | private function unzip($zipFileName, $targetDir) |
||
380 | { |
||
381 | $zip = new \ZipArchive(); |
||
382 | $res = $zip->open($zipFileName); |
||
383 | if ($res === true) { |
||
384 | // extract it to the path we determined above |
||
385 | $zip->extractTo($targetDir); |
||
386 | $zip->close(); |
||
387 | } else { |
||
388 | throw new NodeJsInstallerException("Unable to extract file $zipFileName"); |
||
389 | } |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Adds the vendor/bin directory into the path. |
||
394 | * Note: the vendor/bin is prepended in order to be applied BEFORE an existing install of node. |
||
395 | * |
||
396 | * @param string $binDir |
||
397 | */ |
||
398 | public function registerPath($binDir) |
||
399 | { |
||
400 | $path = getenv('PATH'); |
||
401 | if (Environment::isWindows()) { |
||
402 | putenv('PATH='.realpath($binDir).';'.$path); |
||
403 | } else { |
||
404 | putenv('PATH='.realpath($binDir).':'.$path); |
||
405 | } |
||
406 | } |
||
407 | } |
||
408 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.