From 1ec331409affeaabf78390454cee79a4af9b843d Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 29 Jun 2023 17:23:54 +0200 Subject: [PATCH] Add odbc_connection_string_*() functions --- .github/workflows/tests.yml | 2 +- src/Php82/Php82.php | 74 +++++++++++++++++++++++++++++++ src/Php82/README.md | 3 ++ src/Php82/bootstrap.php | 18 ++++++++ tests/Php82/Php82Test.php | 87 +++++++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/Php82/Php82.php create mode 100644 tests/Php82/Php82Test.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f4500264..71a10e1d5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "apcu, intl, mbstring, uuid" + extensions: "apcu, intl, mbstring, odbc, uuid" ini-values: "memory_limit=-1, session.gc_probability=0, apc.enable_cli=1" php-version: "${{ matrix.php }}" tools: "composer:v2" diff --git a/src/Php82/Php82.php b/src/Php82/Php82.php new file mode 100644 index 000000000..21a7c7448 --- /dev/null +++ b/src/Php82/Php82.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @author Alexander M. Turek + * + * @internal + */ +class Php82 +{ + /** + * Determines if a string matches the ODBC quoting rules. + * + * A valid quoted string begins with a '{', ends with a '}', and has no '}' + * inside of the string that aren't repeated (as to be escaped). + * + * These rules are what .NET also follows. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L31-L57 + */ + public static function odbc_connection_string_is_quoted(string $str): bool + { + if ('' === $str || '{' !== $str[0]) { + return false; + } + + /* Check for } that aren't doubled up or at the end of the string */ + $length = \strlen($str) - 1; + for ($i = 0; $i < $length; ++$i) { + if ('}' !== $str[$i]) { + continue; + } + + if ('}' !== $str[++$i]) { + return $i === $length; + } + } + + return true; + } + + /** + * Determines if a value for a connection string should be quoted. + * + * The ODBC specification mentions: + * "Because of connection string and initialization file grammar, keywords and + * attribute values that contain the characters []{}(),;?*=!@ not enclosed + * with braces should be avoided." + * + * Note that it assumes that the string is *not* already quoted. You should + * check beforehand. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L59-L73 + */ + public static function odbc_connection_string_should_quote(string $str): bool + { + return false !== strpbrk($str, '[]{}(),;?*=!@'); + } + + public static function odbc_connection_string_quote(string $str): string + { + return '{'.str_replace('}', '}}', $str).'}'; + } +} diff --git a/src/Php82/README.md b/src/Php82/README.md index 4a4d88175..9038382ae 100644 --- a/src/Php82/README.md +++ b/src/Php82/README.md @@ -9,6 +9,9 @@ This component provides features added to PHP 8.2 core: - [`Random\Engine`](https://wiki.php.net/rfc/rng_extension) - [`Random\Engine\CryptoSafeEngine`](https://wiki.php.net/rfc/rng_extension) - [`Random\Engine\Secure`](https://wiki.php.net/rfc/rng_extension) (check [arokettu/random-polyfill](https://packagist.org/packages/arokettu/random-polyfill) for more engines) +- [`odbc_connection_string_is_quoted()`](https://php.net/odbc_connection_string_is_quoted) +- [`odbc_connection_string_should_quote()`](https://php.net/odbc_connection_string_should_quote) +- [`odbc_connection_string_quote()`](https://php.net/odbc_connection_string_quote) More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). diff --git a/src/Php82/bootstrap.php b/src/Php82/bootstrap.php index 1ebfdba28..fb4169683 100644 --- a/src/Php82/bootstrap.php +++ b/src/Php82/bootstrap.php @@ -9,6 +9,24 @@ * file that was distributed with this source code. */ +use Symfony\Polyfill\Php82 as p; + if (\PHP_VERSION_ID >= 80200) { return; } + +if (!extension_loaded('odbc')) { + return; +} + +if (!function_exists('odbc_connection_string_is_quoted')) { + function odbc_connection_string_is_quoted(string $str): bool { return p\Php82::odbc_connection_string_is_quoted($str); } +} + +if (!function_exists('odbc_connection_string_should_quote')) { + function odbc_connection_string_should_quote(string $str): bool { return p\Php82::odbc_connection_string_should_quote($str); } +} + +if (!function_exists('odbc_connection_string_quote')) { + function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); } +} diff --git a/tests/Php82/Php82Test.php b/tests/Php82/Php82Test.php new file mode 100644 index 000000000..094e2aa4d --- /dev/null +++ b/tests/Php82/Php82Test.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Tests\Php82; + +use PHPUnit\Framework\TestCase; + +/** + * @requires extension odbc + */ +class Php82Test extends TestCase +{ + /** + * @dataProvider provideConnectionStringValuesFromUpstream + * @dataProvider provideMoreConnectionStringValues + */ + public function testConnectionStringIsQuoted(string $value, bool $isQuoted) + { + self::assertSame($isQuoted, odbc_connection_string_is_quoted($value)); + } + + /** + * @dataProvider provideConnectionStringValuesFromUpstream + * @dataProvider provideMoreConnectionStringValues + */ + public function testConnectionStringShouldQuote(string $value, bool $isQuoted, bool $shouldQuote) + { + self::assertSame($shouldQuote, odbc_connection_string_should_quote($value)); + } + + /** + * @dataProvider provideConnectionStringValuesFromUpstream + * @dataProvider provideMoreConnectionStringValues + */ + public function testConnectionStringQuote(string $value, bool $isQuoted, bool $shouldQuote, string $quoted) + { + self::assertSame($quoted, odbc_connection_string_quote($value)); + } + + /** + * Test cases ported from upstream. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/ext/odbc/tests/odbc_utils.phpt + * + * @return \Generator + */ + public static function provideConnectionStringValuesFromUpstream(): \Generator + { + // 1. No, it's not quoted. + // 2. Yes, it should be quoted because of the special character in the middle. + yield 'with_end_curly1' => ['foo}bar', false, true, '{foo}}bar}']; + + // 1. No, the unescaped special character in the middle breaks what would be quoted. + // 2. Yes, it should be quoted because of the special character in the middle. + // Note that should_quote doesn't care about if the string is already quoted. + // That's why you should check if it is quoted first. + yield 'with_end_curly2' => ['{foo}bar}', false, true, '{{foo}}bar}}}']; + + // 1. Yes, the special characters are escaped, so it's quoted. + // 2. See $with_end_curly2; should_quote doesn't care about if the string is already quoted. + yield 'with_end_curly3' => ['{foo}}bar}', true, true, '{{foo}}}}bar}}}']; + + // 1. No, it's not quoted. + // 2. It doesn't need to be quoted because of no s + yield 'with_no_end_curly1' => ['foobar', false, false, '{foobar}']; + + // 1. Yes, it is quoted and any characters are properly escaped. + // 2. See $with_end_curly2. + yield 'with_no_end_curly2' => ['{foobar}', true, true, '{{foobar}}}']; + } + + /** + * @return \Generator + */ + public static function provideMoreConnectionStringValues(): \Generator + { + yield 'double curly at the end' => ['foo}}', false, true, '{foo}}}}}']; + } +}