diff --git a/controller/main.php b/controller/main.php index 7ab7bae..dc83197 100644 --- a/controller/main.php +++ b/controller/main.php @@ -104,12 +104,10 @@ public function handle() $this->packager->create_extension($this->data); $filename = $this->packager->create_zip($this->data); - $response = new Response($filename); + $response = new Response(file_get_contents($filename)); $response->headers->set('Content-type', 'application/octet-stream'); $response->headers->set('Content-Disposition', 'attachment; filename="' . basename($filename) . '";'); $response->headers->set('Content-length', filesize($filename)); - $response->sendHeaders(); - $response->setContent(readfile($filename)); return $response; } diff --git a/helper/validator.php b/helper/validator.php index 8153116..dbec69d 100644 --- a/helper/validator.php +++ b/helper/validator.php @@ -75,14 +75,26 @@ public function validate_extension_name($value) */ public function validate_extension_display_name($value) { - if ((string) $value !== '' && strpos($value, '"') === false) + if ((string) $value !== '') { - return htmlspecialchars_decode($value, ENT_NOQUOTES); + return $this->validate_generated_string($value, 'SKELETON_INVALID_DISPLAY_NAME'); } throw new runtime_exception($this->language->lang('SKELETON_INVALID_DISPLAY_NAME')); } + /** + * Validate the extension description for insertion into composer.json + * + * @param string $value The value to validate + * @return string The valid value + * @throws runtime_exception + */ + public function validate_extension_description($value) + { + return $this->validate_generated_string($value, 'SKELETON_INVALID_EXTENSION_DESCRIPTION'); + } + /** * Validate and require the extension date/time * @@ -143,6 +155,8 @@ public function validate_vendor_name($value) */ public function validate_extension_homepage($value) { + $value = $this->validate_generated_string($value, 'SKELETON_INVALID_EXTENSION_URL'); + if ((string) $value !== '' && filter_var($value, FILTER_VALIDATE_URL) === false) { throw new runtime_exception($this->language->lang('SKELETON_INVALID_EXTENSION_URL')); @@ -151,6 +165,18 @@ public function validate_extension_homepage($value) return $value; } + /** + * Validate the author name + * + * @param string $value The value to validate + * @return string The valid value + * @throws runtime_exception + */ + public function validate_author_name($value) + { + return $this->validate_generated_string($value, 'SKELETON_INVALID_AUTHOR_NAME'); + } + /** * Validate the author homepage URL * @@ -160,6 +186,8 @@ public function validate_extension_homepage($value) */ public function validate_author_homepage($value) { + $value = $this->validate_generated_string($value, 'SKELETON_INVALID_AUTHOR_URL'); + if ((string) $value !== '' && filter_var($value, FILTER_VALIDATE_URL) === false) { throw new runtime_exception($this->language->lang('SKELETON_INVALID_AUTHOR_URL')); @@ -177,6 +205,8 @@ public function validate_author_homepage($value) */ public function validate_author_email($value) { + $value = $this->validate_generated_string($value, 'SKELETON_INVALID_AUTHOR_EMAIL'); + if ((string) $value !== '' && filter_var($value, FILTER_VALIDATE_EMAIL) === false) { throw new runtime_exception($this->language->lang('SKELETON_INVALID_AUTHOR_EMAIL')); @@ -185,6 +215,18 @@ public function validate_author_email($value) return $value; } + /** + * Validate the author role + * + * @param string $value The value to validate + * @return string The valid value + * @throws runtime_exception + */ + public function validate_author_role($value) + { + return $this->validate_generated_string($value, 'SKELETON_INVALID_AUTHOR_ROLE'); + } + /** * Validate and require the phpBB minimum version * @@ -239,6 +281,29 @@ public function validate_php_version($value) } + /** + * Validate a string inserted without escaping into generated files. + * Values must be safe inside a JSON string and must not contain a PHP comment + * terminator. Request values are HTML escaped, so inspect their decoded form + * and return safe characters without HTML entities. + * + * @param string $value The value to validate + * @param string $error Language key used for invalid input + * @return string The valid value + * @throws runtime_exception + */ + protected function validate_generated_string($value, $error) + { + $decoded_value = htmlspecialchars_decode((string) $value, ENT_QUOTES); + + if (preg_match('/["\\\\\x00-\x1F]/', $decoded_value) || strpos($decoded_value, '*/') !== false) + { + throw new runtime_exception($this->language->lang($error)); + } + + return htmlspecialchars_decode((string) $value, ENT_NOQUOTES); + } + /** * Version value check. Checks for values like: * 1.0.0-RC1 diff --git a/language/en/common.php b/language/en/common.php index 372b5f4..1309766 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -58,7 +58,6 @@ 'SKELETON_QUESTION_EXTENSION_TIME_EXPLAIN' => 'YYYY-MM-DD, default: today', 'SKELETON_QUESTION_NUM_AUTHORS' => 'How many authors does the extension have', - 'SKELETON_QUESTION_NUM_AUTHORS_EXPLAIN' => 'default: 1', 'SKELETON_QUESTION_AUTHOR_NAME' => 'Please enter the author name', 'SKELETON_QUESTION_AUTHOR_NAME_UI' => 'Author name', @@ -148,8 +147,11 @@ 'SKELETON_TITLE_COMPONENT_INFO' => 'Components', 'SKELETON_INVALID_AUTHOR_EMAIL' => 'An author email is invalid', + 'SKELETON_INVALID_AUTHOR_NAME' => 'An author name is invalid', + 'SKELETON_INVALID_AUTHOR_ROLE' => 'An author role is invalid', 'SKELETON_INVALID_AUTHOR_URL' => 'An author homepage URL is invalid', 'SKELETON_INVALID_DISPLAY_NAME' => 'The display name you provided is invalid', + 'SKELETON_INVALID_EXTENSION_DESCRIPTION'=> 'The extension description you provided is invalid', 'SKELETON_INVALID_EXTENSION_TIME' => 'The extension date you provided is invalid', 'SKELETON_INVALID_EXTENSION_URL' => 'The extension homepage URL is invalid', 'SKELETON_INVALID_EXTENSION_VERSION'=> 'The extension version you provided is invalid', diff --git a/skeleton/console/command/sample.php.twig b/skeleton/console/command/sample.php.twig index 20aea26..ee17340 100644 --- a/skeleton/console/command/sample.php.twig +++ b/skeleton/console/command/sample.php.twig @@ -50,9 +50,13 @@ class sample extends \phpbb\console\command\command * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance + * + * @return int 0 if everything went fine, or an exit code */ protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln($this->user->lang('CLI_{{ EXTENSION.extension_name|upper }}_HELLO')); + + return 0; } } diff --git a/skeleton/notification/type/sample.php.twig b/skeleton/notification/type/sample.php.twig index 9afebe4..666c30f 100644 --- a/skeleton/notification/type/sample.php.twig +++ b/skeleton/notification/type/sample.php.twig @@ -129,7 +129,9 @@ class sample extends \phpbb\notification\type\base */ public function get_url() { - return $this->helper->route('{{ EXTENSION.vendor_name }}_{{ EXTENSION.extension_name }}_controller', $this->get_data('{{ EXTENSION.extension_name|lower }}_sample_name')); + return $this->helper->route('{{ EXTENSION.vendor_name }}_{{ EXTENSION.extension_name }}_controller', [ + 'name' => $this->get_data('{{ EXTENSION.extension_name|lower }}_sample_name'), + ]); } /** diff --git a/tests/controller/main_test.php b/tests/controller/main_test.php index 3a5ab30..620e462 100644 --- a/tests/controller/main_test.php +++ b/tests/controller/main_test.php @@ -324,6 +324,8 @@ public function test_submit_success() $response = $this->get_controller($this->packager_mock)->handle(); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $this->assertSame('', $response->getContent()); + $this->assertSame('0', $response->headers->get('Content-length')); } public function test_submit_exception() diff --git a/tests/helper/validator_test.php b/tests/helper/validator_test.php index b06fa1b..97caffb 100644 --- a/tests/helper/validator_test.php +++ b/tests/helper/validator_test.php @@ -42,6 +42,9 @@ public function valid_data(): array ['extension_display_name', 'Foo bar'], ['extension_display_name', 'Foo >bar\'s< & world'], + ['extension_description', 'Foo >bar\'s< & world'], + ['author_name', 'Foo Bar'], + ['author_role', 'Developer'], ['extension_time', '0000-00-00'], @@ -145,6 +148,13 @@ public function invalid_data(): array ['extension_display_name', '', 'SKELETON_INVALID_DISPLAY_NAME'], ['extension_display_name', null, 'SKELETON_INVALID_DISPLAY_NAME'], ['extension_display_name', 'Foo bar"s world', 'SKELETON_INVALID_DISPLAY_NAME'], + ['extension_display_name', 'Foo */ bar', 'SKELETON_INVALID_DISPLAY_NAME'], + ['extension_display_name', 'Foo \\ bar', 'SKELETON_INVALID_DISPLAY_NAME'], + + ['extension_description', 'Foo "bar"', 'SKELETON_INVALID_EXTENSION_DESCRIPTION'], + ['extension_description', 'Foo \\ bar', 'SKELETON_INVALID_EXTENSION_DESCRIPTION'], + ['extension_description', "Foo\nbar", 'SKELETON_INVALID_EXTENSION_DESCRIPTION'], + ['extension_description', 'Foo */ bar', 'SKELETON_INVALID_EXTENSION_DESCRIPTION'], ['extension_time', '00-00-0000', 'SKELETON_INVALID_EXTENSION_TIME'], ['extension_time', 'FOO', 'SKELETON_INVALID_EXTENSION_TIME'], @@ -175,13 +185,20 @@ public function invalid_data(): array ['extension_homepage', 'www.web.com', 'SKELETON_INVALID_EXTENSION_URL'], ['extension_homepage', 'foo', 'SKELETON_INVALID_EXTENSION_URL'], + ['extension_homepage', 'https://example.com/\\bad', 'SKELETON_INVALID_EXTENSION_URL'], + + ['author_name', 'Foo */ Bar', 'SKELETON_INVALID_AUTHOR_NAME'], + ['author_name', 'Foo "Bar"', 'SKELETON_INVALID_AUTHOR_NAME'], + ['author_role', 'Developer \\ Maintainer', 'SKELETON_INVALID_AUTHOR_ROLE'], ['author_homepage', 'www.web.com', 'SKELETON_INVALID_AUTHOR_URL'], ['author_homepage', 'foo', 'SKELETON_INVALID_AUTHOR_URL'], + ['author_homepage', 'https://example.com/*/', 'SKELETON_INVALID_AUTHOR_URL'], ['author_email', 'mail.com', 'SKELETON_INVALID_AUTHOR_EMAIL'], ['author_email', 'user@mail', 'SKELETON_INVALID_AUTHOR_EMAIL'], ['author_email', 'foo', 'SKELETON_INVALID_AUTHOR_EMAIL'], + ['author_email', 'foo"@example.com', 'SKELETON_INVALID_AUTHOR_EMAIL'], ['phpbb_version_min', '', 'SKELETON_INVALID_PHPBB_MIN_VERSION'], ['phpbb_version_min', null, 'SKELETON_INVALID_PHPBB_MIN_VERSION'],