From dcbb3a5ee54bc31f032a9c67b83708c635a5dab0 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 12 May 2025 15:40:23 +0300 Subject: [PATCH 1/5] Implement file name transliteration and remove `yiisoft/strings` dependency --- CHANGELOG.md | 3 +- composer-require-checker.json | 17 +++++++++ composer.json | 15 ++++---- src/ContentDispositionHeader.php | 11 +----- src/FallbackNameCreator.php | 64 ++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 composer-require-checker.json create mode 100644 src/FallbackNameCreator.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..a3859d1 --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,17 @@ +{ + "symbol-whitelist" : [], + "php-core-extensions" : [ + "Core", + "date", + "json", + "hash", + "pcre", + "Phar", + "Reflection", + "SPL", + "random", + "standard", + "intl" + ], + "scan-files" : [] +} diff --git a/composer.json b/composer.json index 0b8d13d..ca73062 100644 --- a/composer.json +++ b/composer.json @@ -32,16 +32,15 @@ ], "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", - "phpunit/phpunit": "^9.6.22", - "rector/rector": "^2.0.10", - "roave/infection-static-analysis-plugin": "^1.18", - "spatie/phpunit-watcher": "^1.23.6", - "vimeo/psalm": "^4.30 || ^5.26.1 || ^6.8.8" + "maglnet/composer-require-checker": "^3.8 || ^4.16.1", + "phpunit/phpunit": "^9.6.23", + "rector/rector": "^2.0.15", + "roave/infection-static-analysis-plugin": "^1.37", + "spatie/phpunit-watcher": "^1.24.0", + "vimeo/psalm": "^4.30 || ^5.26.1 || ^6.10.3" }, "autoload": { "psr-4": { 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..65e90cb --- /dev/null +++ b/src/FallbackNameCreator.php @@ -0,0 +1,64 @@ + '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); + $fallbackName = str_replace('"', '\\"', $fallbackName); + return $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); + } +} From 9f46cbca07137424512a552356ae3aff3f252692 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 12 May 2025 12:40:43 +0000 Subject: [PATCH 2/5] Apply fixes from StyleCI --- src/FallbackNameCreator.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/FallbackNameCreator.php b/src/FallbackNameCreator.php index 65e90cb..59535ec 100644 --- a/src/FallbackNameCreator.php +++ b/src/FallbackNameCreator.php @@ -45,8 +45,7 @@ public static function create(string $fileName): string * @var string $fallbackName We use valid regular expression, so `preg_replace()` always returns a string. */ $fallbackName = preg_replace('/[^\x20-\x7e]/u', '_', $fallbackName); - $fallbackName = str_replace('"', '\\"', $fallbackName); - return $fallbackName; + return str_replace('"', '\\"', $fallbackName); } private static function transliterate(string $fileName): string From 40202f7664867ffcb7691a64929ca6f062aa1a64 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 12 May 2025 15:41:47 +0300 Subject: [PATCH 3/5] fix --- composer.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index ca73062..dea0391 100644 --- a/composer.json +++ b/composer.json @@ -35,12 +35,12 @@ "ext-mbstring": "*" }, "require-dev": { - "maglnet/composer-require-checker": "^3.8 || ^4.16.1", - "phpunit/phpunit": "^9.6.23", - "rector/rector": "^2.0.15", - "roave/infection-static-analysis-plugin": "^1.37", - "spatie/phpunit-watcher": "^1.24.0", - "vimeo/psalm": "^4.30 || ^5.26.1 || ^6.10.3" + "maglnet/composer-require-checker": "^3.8 || ^4.3", + "phpunit/phpunit": "^9.6.22", + "rector/rector": "^2.0.10", + "roave/infection-static-analysis-plugin": "^1.18", + "spatie/phpunit-watcher": "^1.23.6", + "vimeo/psalm": "^4.30 || ^5.26.1 || ^6.8.8" }, "autoload": { "psr-4": { @@ -54,7 +54,6 @@ }, "config": { "sort-packages": true, - "bump-after-update": "dev", "allow-plugins": { "infection/extension-installer": true, "composer/package-versions-deprecated": true From 9a8bc13f7d8c596d146ee6f3aa2c6f5ad5c912c4 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 12 May 2025 15:43:16 +0300 Subject: [PATCH 4/5] fix --- composer-require-checker.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer-require-checker.json b/composer-require-checker.json index a3859d1..6028bf9 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -1,5 +1,7 @@ { - "symbol-whitelist" : [], + "symbol-whitelist" : [ + "transliterator_transliterate" + ], "php-core-extensions" : [ "Core", "date", @@ -10,8 +12,7 @@ "Reflection", "SPL", "random", - "standard", - "intl" + "standard" ], "scan-files" : [] } From 44ba536b3e89a0e72c551d1e3a940691b147a82c Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 12 May 2025 15:50:56 +0300 Subject: [PATCH 5/5] fix --- .github/workflows/composer-require-checker.yml | 1 + composer-require-checker.json | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) 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/composer-require-checker.json b/composer-require-checker.json index 6028bf9..4330a18 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -1,5 +1,8 @@ { "symbol-whitelist" : [ + "null", "true", "false", + "static", "self", "parent", + "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "mixed", "never", "transliterator_transliterate" ], "php-core-extensions" : [ @@ -11,7 +14,6 @@ "Phar", "Reflection", "SPL", - "random", "standard" ], "scan-files" : []