Browse Source

Add `StringHelper::matchWildcard()` (#15389)

`StringHelper::matchWildcard()` added
tags/2.0.14
Paul Klimov 7 years ago committed by GitHub
parent
commit
1b8da6d951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      framework/CHANGELOG.md
  2. 5
      framework/base/ActionFilter.php
  3. 3
      framework/filters/AccessRule.php
  4. 3
      framework/filters/HostControl.php
  5. 3
      framework/filters/auth/AuthMethod.php
  6. 14
      framework/helpers/BaseFileHelper.php
  7. 53
      framework/helpers/BaseStringHelper.php
  8. 3
      tests/framework/console/controllers/MigrateControllerTestTrait.php
  9. 87
      tests/framework/helpers/StringHelperTest.php

1
framework/CHANGELOG.md

@ -27,6 +27,7 @@ Yii Framework 2 Change Log
- Enh #8752: Allow specify `$attributeNames` as a string for `yii\base\Model` `validate()` method (developeruz)
- Enh #9137: Added `Access-Control-Allow-Method` header for the OPTIONS request (developeruz)
- Enh #9253: Allow `variations` to be a string for `yii\filters\PageCache` and `yii\widgets\FragmentCache` (schojniak, developeruz)
- Enh #12623: Added `yii\helpers\StringHelper::matchWildcard()` replacing usage of `fnmatch()`, which may be unreliable (klimov-paul)
- Enh #14043: Added `yii\helpers\IpHelper` (silverfire, cebe)
- Enh #7996: Short syntax for verb in GroupUrlRule (schojniak, developeruz)
- Enh #14568: Refactored migration templates to use `safeUp()` and `safeDown()` methods (Kolyunya)

5
framework/base/ActionFilter.php

@ -6,6 +6,7 @@
*/
namespace yii\base;
use yii\helpers\StringHelper;
/**
* ActionFilter is the base class for action filters.
@ -149,7 +150,7 @@ class ActionFilter extends Behavior
} else {
$onlyMatch = false;
foreach ($this->only as $pattern) {
if (fnmatch($pattern, $id)) {
if (StringHelper::matchWildcard($pattern, $id)) {
$onlyMatch = true;
break;
}
@ -158,7 +159,7 @@ class ActionFilter extends Behavior
$exceptMatch = false;
foreach ($this->except as $pattern) {
if (fnmatch($pattern, $id)) {
if (StringHelper::matchWildcard($pattern, $id)) {
$exceptMatch = true;
break;
}

3
framework/filters/AccessRule.php

@ -12,6 +12,7 @@ use yii\base\Action;
use yii\base\Component;
use yii\base\Controller;
use yii\base\InvalidConfigException;
use yii\helpers\StringHelper;
use yii\web\Request;
use yii\web\User;
@ -198,7 +199,7 @@ class AccessRule extends Component
$id = $controller->getUniqueId();
foreach ($this->controllers as $pattern) {
if (fnmatch($pattern, $id)) {
if (StringHelper::matchWildcard($pattern, $id)) {
return true;
}
}

3
framework/filters/HostControl.php

@ -9,6 +9,7 @@ namespace yii\filters;
use Yii;
use yii\base\ActionFilter;
use yii\helpers\StringHelper;
use yii\web\NotFoundHttpException;
/**
@ -135,7 +136,7 @@ class HostControl extends ActionFilter
$currentHost = Yii::$app->getRequest()->getHostName();
foreach ($allowedHosts as $allowedHost) {
if (fnmatch($allowedHost, $currentHost)) {
if (StringHelper::matchWildcard($allowedHost, $currentHost)) {
return true;
}
}

3
framework/filters/auth/AuthMethod.php

@ -10,6 +10,7 @@ namespace yii\filters\auth;
use Yii;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\helpers\StringHelper;
use yii\web\Request;
use yii\web\Response;
use yii\web\UnauthorizedHttpException;
@ -104,7 +105,7 @@ abstract class AuthMethod extends ActionFilter implements AuthInterface
{
$id = $this->getActionId($action);
foreach ($this->optional as $pattern) {
if (fnmatch($pattern, $id)) {
if (StringHelper::matchWildcard($pattern, $id)) {
return true;
}
}

14
framework/helpers/BaseFileHelper.php

@ -591,12 +591,12 @@ class BaseFileHelper
}
}
$fnmatchFlags = 0;
$matchOptions = [];
if ($flags & self::PATTERN_CASE_INSENSITIVE) {
$fnmatchFlags |= FNM_CASEFOLD;
$matchOptions['caseSensitive'] = false;
}
return fnmatch($pattern, $baseName, $fnmatchFlags);
return StringHelper::matchWildcard($pattern, $baseName, $matchOptions);
}
/**
@ -645,12 +645,14 @@ class BaseFileHelper
}
}
$fnmatchFlags = FNM_PATHNAME;
$matchOptions = [
'filePath' => true
];
if ($flags & self::PATTERN_CASE_INSENSITIVE) {
$fnmatchFlags |= FNM_CASEFOLD;
$matchOptions['caseSensitive'] = false;
}
return fnmatch($pattern, $name, $fnmatchFlags);
return StringHelper::matchWildcard($pattern, $name, $matchOptions);
}
/**

53
framework/helpers/BaseStringHelper.php

@ -365,4 +365,57 @@ class BaseStringHelper
// so its safe to call str_replace here
return str_replace(',', '.', (string) $number);
}
/**
* Checks if the passed string would match the given shell wildcard pattern.
* This function emulates [[fnmatch()]], which may be unavailable at certain environment, using PCRE.
* @param string $pattern the shell wildcard pattern.
* @param string $string the tested string.
* @param array $options options for matching. Valid options are:
*
* - caseSensitive: bool, whether pattern should be case sensitive. Defaults to `true`.
* - escape: bool, whether backslash escaping is enabled. Defaults to `true`.
* - filePath: bool, whether slashes in string only matches slashes in the given pattern. Defaults to `false`.
*
* @return bool whether the string matches pattern or not.
* @since 2.0.14
*/
public static function matchWildcard($pattern, $string, $options = [])
{
if ($pattern === '*' && empty($options['filePath'])) {
return true;
}
$replacements = [
'\\\\\\\\' => '\\\\',
'\\\\\\*' => '[*]',
'\\\\\\?' => '[?]',
'\*' => '.*',
'\?' => '.',
'\[\!' => '[^',
'\[' => '[',
'\]' => ']',
'\-' => '-',
];
if (isset($options['escape']) && !$options['escape']) {
unset($replacements['\\\\\\\\']);
unset($replacements['\\\\\\*']);
unset($replacements['\\\\\\?']);
}
if (!empty($options['filePath'])) {
$replacements['\*'] = '[^/\\\\]*';
$replacements['\?'] = '[^/\\\\]';
}
$pattern = strtr(preg_quote($pattern, '#'), $replacements);
$pattern = '#^' . $pattern . '$#us';
if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
$pattern .= 'i';
}
return preg_match($pattern, $string) === 1;
}
}

3
tests/framework/console/controllers/MigrateControllerTestTrait.php

@ -10,6 +10,7 @@ namespace yiiunit\framework\console\controllers;
use Yii;
use yii\console\controllers\BaseMigrateController;
use yii\helpers\FileHelper;
use yii\helpers\StringHelper;
use yiiunit\TestCase;
/**
@ -190,7 +191,7 @@ CODE;
$appliedMigrations = $migrationHistory;
foreach ($expectedMigrations as $expectedMigrationName) {
$appliedMigration = array_shift($appliedMigrations);
if (!fnmatch(strtr($expectedMigrationName, ['\\' => DIRECTORY_SEPARATOR]), strtr($appliedMigration['version'], ['\\' => DIRECTORY_SEPARATOR]))) {
if (!StringHelper::matchWildcard(strtr($expectedMigrationName, ['\\' => DIRECTORY_SEPARATOR]), strtr($appliedMigration['version'], ['\\' => DIRECTORY_SEPARATOR]))) {
$success = false;
break;
}

87
tests/framework/helpers/StringHelperTest.php

@ -312,4 +312,91 @@ class StringHelperTest extends TestCase
['Это закодированная строка', '0K3RgtC-INC30LDQutC-0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw'],
];
}
/**
* Data provider for [[testMatchWildcard()]]
* @return array test data.
*/
public function dataProviderMatchWildcard()
{
return [
// *
['*', 'any', true],
['*', '', true],
['begin*end', 'begin-middle-end', true],
['begin*end', 'beginend', true],
['begin*end', 'begin-d', false],
['*end', 'beginend', true],
['*end', 'begin', false],
['begin*', 'begin-end', true],
['begin*', 'end', false],
['begin*', 'before-begin', false],
// ?
['begin?end', 'begin1end', true],
['begin?end', 'beginend', false],
['begin??end', 'begin12end', true],
['begin??end', 'begin1end', false],
// []
['gr[ae]y', 'gray', true],
['gr[ae]y', 'grey', true],
['gr[ae]y', 'groy', false],
['a[2-8]', 'a1', false],
['a[2-8]', 'a3', true],
['[][!]', ']', true],
['[-1]', '-', true],
// [!]
['gr[!ae]y', 'gray', false],
['gr[!ae]y', 'grey', false],
['gr[!ae]y', 'groy', true],
['a[!2-8]', 'a1', true],
['a[!2-8]', 'a3', false],
// -
['a-z', 'a-z', true],
['a-z', 'a-c', false],
// slashes
['begin/*/end', 'begin/middle/end', true],
['begin/*/end', 'begin/two/steps/end', true],
['begin/*/end', 'begin/end', false],
['begin\\\\*\\\\end', 'begin\middle\end', true],
['begin\\\\*\\\\end', 'begin\two\steps\end', true],
['begin\\\\*\\\\end', 'begin\end', false],
// dots
['begin.*.end', 'begin.middle.end', true],
['begin.*.end', 'begin.two.steps.end', true],
['begin.*.end', 'begin.end', false],
// case
['begin*end', 'BEGIN-middle-END', false],
['begin*end', 'BEGIN-middle-END', true, ['caseSensitive' => false]],
// file path
['begin/*/end', 'begin/middle/end', true, ['filePath' => true]],
['begin/*/end', 'begin/two/steps/end', false, ['filePath' => true]],
['begin\\\\*\\\\end', 'begin\middle\end', true, ['filePath' => true]],
['begin\\\\*\\\\end', 'begin\two\steps\end', false, ['filePath' => true]],
['*', 'any', true, ['filePath' => true]],
['*', 'any/path', false, ['filePath' => true]],
['[.-0]', 'any/path', false, ['filePath' => true]],
['*', '.dotenv', true, ['filePath' => true]],
// escaping
['\*\?', '*?', true],
['\*\?', 'zz', false],
['begin\*\end', 'begin\middle\end', true, ['escape' => false]],
['begin\*\end', 'begin\two\steps\end', true, ['escape' => false]],
['begin\*\end', 'begin\end', false, ['escape' => false]],
['begin\*\end', 'begin\middle\end', true, ['filePath' => true, 'escape' => false]],
['begin\*\end', 'begin\two\steps\end', false, ['filePath' => true, 'escape' => false]],
];
}
/**
* @dataProvider dataProviderMatchWildcard
*
* @param string $pattern
* @param string $string
* @param bool $expectedResult
* @param array $options
*/
public function testMatchWildcard($pattern, $string, $expectedResult, $options = [])
{
$this->assertSame($expectedResult, StringHelper::matchWildcard($pattern, $string, $options));
}
}

Loading…
Cancel
Save