diff --git a/src/Application.php b/src/Application.php index 247cad99..3aec2936 100644 --- a/src/Application.php +++ b/src/Application.php @@ -219,13 +219,20 @@ public function run(): int ) ); return 1; - } catch (RuntimeException $e) { + } catch (\Throwable $e) { + $message = $e->getMessage(); + $basePath = canonicalise_path($container->get('basePath')); + + if (strpos($message, $basePath) !== null) { + $message = str_replace($basePath, '', $message); + } + $container ->get(OutputInterface::class) ->printError( sprintf( '%s', - $e->getMessage() + $message ) ); return 1; diff --git a/src/Command/PrintCommand.php b/src/Command/PrintCommand.php index 89a7a429..51150b9d 100644 --- a/src/Command/PrintCommand.php +++ b/src/Command/PrintCommand.php @@ -4,6 +4,8 @@ namespace PhpSchool\PhpWorkshop\Command; +use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException; +use PhpSchool\PhpWorkshop\Exception\ProblemFileDoesNotExistException; use PhpSchool\PhpWorkshop\ExerciseRepository; use PhpSchool\PhpWorkshop\MarkdownRenderer; use PhpSchool\PhpWorkshop\Output\OutputInterface; @@ -68,7 +70,14 @@ public function __invoke(): void $currentExercise = $this->userState->getCurrentExercise(); $exercise = $this->exerciseRepository->findByName($currentExercise); - $markDown = (string) file_get_contents($exercise->getProblem()); + $problemFile = $exercise->getProblem(); + + if (!is_readable($problemFile)) { + throw ProblemFileDoesNotExistException::fromFile($problemFile); + } + + $markDown = (string) file_get_contents($problemFile); + $doc = $this->markdownRenderer->render($markDown); $doc = str_replace('{appname}', $this->appName, $doc); $this->output->write($doc); diff --git a/src/Exception/ProblemFileDoesNotExistException.php b/src/Exception/ProblemFileDoesNotExistException.php new file mode 100644 index 00000000..e26808f3 --- /dev/null +++ b/src/Exception/ProblemFileDoesNotExistException.php @@ -0,0 +1,16 @@ +color->__invoke(" " . $exercise->getName())->yellow()->bold() . "\n"; $output .= $this->color->__invoke(sprintf(" Exercise %d of %d\n\n", $exerciseIndex, $numExercises))->yellow(); - $content = (string) file_get_contents($exercise->getProblem()); + $problemFile = $exercise->getProblem(); + if (!is_readable($problemFile)) { + throw ProblemFileDoesNotExistException::fromFile($problemFile); + } + + $content = (string) file_get_contents($problemFile); $doc = $this->markdownRenderer->render($content); $doc = str_replace('{appname}', $this->appName, $doc); $output .= $doc; diff --git a/src/Utils/StringUtils.php b/src/Utils/StringUtils.php new file mode 100644 index 00000000..f20d7921 --- /dev/null +++ b/src/Utils/StringUtils.php @@ -0,0 +1,33 @@ + 0) { + array_pop($path); + } else { + throw new RuntimeException('Climbing above the root is not permitted.'); + } + } + + return $filename[0] === '/' + ? '/' . implode('/', $path) + : implode('/', $path); + } +} diff --git a/src/functions.php b/src/functions.php index a9eb52a7..58a54126 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use PhpSchool\PhpWorkshop\Utils\StringUtils; + if (!function_exists('mb_str_pad')) { /** @@ -31,3 +33,15 @@ function camel_case_to_kebab_case(string $string): string }, $string); } } + +if (!function_exists('canonicalise_path')) { + + /** + * @param string $path + * @return string + */ + function canonicalise_path(string $path): string + { + return StringUtils::canonicalisePath($path); + } +} diff --git a/test/Util/StringUtilsTest.php b/test/Util/StringUtilsTest.php new file mode 100644 index 00000000..6f6f33eb --- /dev/null +++ b/test/Util/StringUtilsTest.php @@ -0,0 +1,32 @@ +assertEquals('/path/to/file', StringUtils::canonicalisePath('/path/to/file')); + $this->assertEquals('/path/to/file', StringUtils::canonicalisePath('/path/././to/file')); + $this->assertEquals('/path/to/file', StringUtils::canonicalisePath('/path///to/file')); + $this->assertEquals('/path/to/file', StringUtils::canonicalisePath('/path/to/file')); + $this->assertEquals('/', StringUtils::canonicalisePath('/path/to/../../')); + $this->assertEquals('/path', StringUtils::canonicalisePath('/path/to/some/../../')); + $this->assertEquals('/some', StringUtils::canonicalisePath('/path/../some/')); + $this->assertEquals('some', StringUtils::canonicalisePath('path/../some/')); + } + + public function testExceptionIsThrownIfTryingToTraverseUpPastRoom(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Climbing above the root is not permitted.'); + + StringUtils::canonicalisePath('/path/to/../../../'); + } +}