Skip to content

Commit

Permalink
Add odbc_connection_string_*() functions
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Jul 4, 2023
1 parent 05dfdff commit 5d0f408
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
72 changes: 72 additions & 0 deletions src/Php82/Php82.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Symfony\Polyfill\Php82;

/**
* @author Alexander M. Turek <me@derrabus.de>
*
* @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);
for ($i = 0; $i < $length; $i++) {
if ($str[$i] !== '}') {
continue;
}

if ($i + 2 < $length && $str[$i + 1] === '}') {
/* Skip over so we don't count it again */
$i++;

continue;
}


/* If not at the end, not quoted */
return $i + 1 === $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 strpbrk($str, '[]{}(),;?*=!@') !== false;
}

public static function odbc_connection_string_quote(string $str): string
{
return '{' . str_replace('}', '}}', $str) . '}';
}
}
3 changes: 3 additions & 0 deletions src/Php82/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
14 changes: 14 additions & 0 deletions src/Php82/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
* file that was distributed with this source code.
*/

use Symfony\Polyfill\Php82 as p;

if (\PHP_VERSION_ID >= 80200) {
return;
}

if (extension_loaded('odbc') && !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 (extension_loaded('odbc') && !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 (extension_loaded('odbc') && !function_exists('odbc_connection_string_quote')) {
function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); }
}
75 changes: 75 additions & 0 deletions tests/Php82/Php82Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Symfony\Polyfill\Tests\Php82;

use PHPUnit\Framework\TestCase;

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<string, array{string, bool, bool, string}>
*/
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<string, array{string, bool, bool, string}>
*/
public static function provideMoreConnectionStringValues(): \Generator
{
yield 'double curly at the end' => ['foo}}', false, true, '{foo}}}}}'];
}
}

0 comments on commit 5d0f408

Please sign in to comment.