diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml index d864b99..ceb0590 100644 --- a/.github/workflows/composer-require-checker.yml +++ b/.github/workflows/composer-require-checker.yml @@ -28,6 +28,7 @@ jobs: composer-require-checker: uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master with: + config: composer-require-checker.json os: >- ['ubuntu-latest'] php: >- diff --git a/CHANGELOG.md b/CHANGELOG.md index 66cb3a1..f3818c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Enh #57: Simplify code of `HeaderValueHelper::getSortedValueAndParameters()` (@vjik) - Bug #56: Add missed `ext-mbstring` dependency (@vjik) - Enh #58: Add return value to closure for `preg_replace_callback()` in `HeaderValueHelper::getParameters()` (@vjik) +- Enh #59: Implement file name transliteration and remove `yiisoft/strings` dependency (@vjik) ## 1.2.0 November 09, 2021 @@ -14,7 +15,7 @@ ## 1.1.1 February 10, 2021 -- Chg: Update yiisoft/strings dependency (@samdark) +- Chg: Update `yiisoft/strings` dependency (@samdark) ## 1.1.0 December 28, 2020 diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 0000000..4330a18 --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,20 @@ +{ + "symbol-whitelist" : [ + "null", "true", "false", + "static", "self", "parent", + "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "mixed", "never", + "transliterator_transliterate" + ], + "php-core-extensions" : [ + "Core", + "date", + "json", + "hash", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard" + ], + "scan-files" : [] +} diff --git a/composer.json b/composer.json index 0b8d13d..dea0391 100644 --- a/composer.json +++ b/composer.json @@ -32,8 +32,7 @@ ], "require": { "php": "7.4.* || 8.0 - 8.4", - "ext-mbstring": "*", - "yiisoft/strings": "^2.0" + "ext-mbstring": "*" }, "require-dev": { "maglnet/composer-require-checker": "^3.8 || ^4.3", @@ -55,7 +54,6 @@ }, "config": { "sort-packages": true, - "bump-after-update": "dev", "allow-plugins": { "infection/extension-installer": true, "composer/package-versions-deprecated": true diff --git a/src/ContentDispositionHeader.php b/src/ContentDispositionHeader.php index 60cff2a..5a4662a 100644 --- a/src/ContentDispositionHeader.php +++ b/src/ContentDispositionHeader.php @@ -5,7 +5,6 @@ namespace Yiisoft\Http; use InvalidArgumentException; -use Yiisoft\Strings\Inflector; use function in_array; @@ -74,15 +73,7 @@ public static function value(string $type, ?string $fileName = null): string } $fileName = str_replace(['%', '/', '\\'], '_', $fileName); - - $fallbackName = (new Inflector())->toTransliterated($fileName, Inflector::TRANSLITERATE_LOOSE); - $fallbackName = str_replace("\r\n", '_', $fallbackName); - /** - * @var string $fallbackName We use valid regular expression, so `preg_replace()` always returns string. - */ - $fallbackName = preg_replace('/[^\x20-\x7e]/u', '_', $fallbackName); - $fallbackName = str_replace('"', '\\"', $fallbackName); - + $fallbackName = FallbackNameCreator::create($fileName); $utfName = rawurlencode($fileName); $header .= "; filename=\"{$fallbackName}\""; diff --git a/src/FallbackNameCreator.php b/src/FallbackNameCreator.php new file mode 100644 index 0000000..59535ec --- /dev/null +++ b/src/FallbackNameCreator.php @@ -0,0 +1,63 @@ + 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C', + 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', + 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O', + 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH', + 'ß' => 'ss', + 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c', + 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', + 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', + 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', + 'ÿ' => 'y', + ]; + + /** + * The rule is loose, letters will be transliterated with the characters of Basic Latin Unicode Block. + * + * @see https://unicode.org/reports/tr15/#Normalization_Forms_Table + */ + private const TRANSLITERATOR = 'Any-Latin; Latin-ASCII; [\u0080-\uffff] remove'; + + /** + * @param string $fileName The file name to be transliterated. Should be a valid UTF-8 string. + */ + public static function create(string $fileName): string + { + $fallbackName = self::transliterate($fileName); + $fallbackName = str_replace("\r\n", '_', $fallbackName); + /** + * @var string $fallbackName We use valid regular expression, so `preg_replace()` always returns a string. + */ + $fallbackName = preg_replace('/[^\x20-\x7e]/u', '_', $fallbackName); + return str_replace('"', '\\"', $fallbackName); + } + + private static function transliterate(string $fileName): string + { + if (!extension_loaded('intl')) { + return strtr($fileName, self::TRANSLITERATION_MAP); + } + + /** + * @var string We assume that `$fileName` are valid UTF-8 strings and transliterator is valid, so + * `transliterator_transliterate()` never returns `false`. + */ + return transliterator_transliterate(self::TRANSLITERATOR, $fileName); + } +}