Windows服务器上的PHP7 UTF-8文件名,ZipArchive引起的新现象


PHP7 UTF-8 filenames on Windows server, new phenomenon caused by ZipArchive

更新:

准备一份错误报告给那些让PHP7成为可能的伟人,我再次修改了我的研究,并试图将其分解为几行简单的代码。在这样做的时候,我发现PHP本身并不是问题的原因。当我完成后,我会在这里分享我的结果。只是为了让你知道,不可能浪费你的时间或其他什么:)


简介:PHP7现在似乎可以编写UTF-8文件名,但无法访问它们

序言:我在这里读到了大约10-15篇涉及这个主题的文章,但它们并没有帮助我解决问题,而且它们都比PHP7版本旧。在我看来,这可能是一个新问题,我想知道这是否是一个bug。我花了很多时间尝试字符串的en-/解码,并试图找出一种方法来实现它,但没有成功

大家好,来自德国的问候(这里插入害羞而不是我的母语),我希望你们能帮助我解决我遇到的这个新现象。从PHP 7附带的意义上讲,它似乎是"新的"。

我认为大多数在Windows系统上使用PHP的人都非常熟悉文件名的问题,以及PHP的透明包装器,该包装器管理对具有非ASCII文件名的文件的访问(或Windows-1252或任何系统代码页)。

我不太确定如何处理这个问题,正如你所看到的,我在撰写问题方面经验不足,所以请不要马上把我的头扯下来。是的,我会努力保持它的简短。我们开始了:

第一个症状:在更新到PHP7后,我有时会遇到访问软件生成的文件的问题。有时它像往常一样工作,有时则不然。我发现不同之处在于,PHP7现在似乎可以编写UTF-8文件名,但无法访问具有这些名称的文件。

在两个独立的"相同"系统上生成上述文件(仅在PHP版本中不同)后,文件在硬盘上的命名方式如下:

PHP 5.5:Lokaltest_KG_æ¼¢å­--_汉å­_Krâ¼mhold-DEZ1604-140081-complete.zip

PHP 7:Lokaltest_KG_漢字_汉字_Krümhold-DEZ1604-140081-complete.zip

非常棒的是,PHP7能够在HDD上写入unicode文件名,而UTF-16在windowsafaik上使用。现在的缺点是,当我尝试访问这些文件(例如使用is_file())时,PHP 5.5可以工作,但PHP 7不能。

考虑一下这个代码片段(注意:我"入侵"了这个函数,因为这是最简单的方法,它不是为这个目的而写的)。在生成一个zip文件后调用此函数,该文件采用客户的名称和其他值来确定正确的名称。这些来自数据库。PHP的数据库和内部编码都是UTF-8。clearstatcache本身是不必要的,但我把它包括在内是为了让事情更清楚重要:所发生的一切都是用PHP7完成的,没有其他实体负责创建zip文件。确切地说,它是用CCD_ 3完成的。事实上,它是一个zip档案并不重要,关键是文件名和文件内容是由PHP7成功创建的。

public static function downloadFileAsStream( $file )
{
    clearstatcache();
    print $file . "<br/>";
    var_dump(is_file($file));
    die();
}       

输出为:

D:/htdocs/otm/.data/_tmp/Lokaltest_KG_漢字_汉字_Krümhold-DEZ1604-140081-complete.zip
bool(false) 

因此,PHP7能够生成文件——它们确实存在于硬盘上,是合法的、可访问的——但无法访问它们。is_file并不是唯一失败的函数,例如file_exists()也会失败。

一个编码转换的小实验,让你尝一尝我尝试过的东西:

public static function downloadFileAsStream( $file )
{
    clearstatcache();
    print $file . "<br/>";
    print mb_detect_encoding($file, 'ASCII,UTF-16,windows-1252,UTF-8', false) . "<br/>";
    print mb_detect_encoding($file, 'ASCII,UTF-16,windows-1252,UTF-8', true) . "<br/>";
    if (($detectedEncoding = mb_detect_encoding($file, 'ASCII,UTF-16,windows-1252,UTF-8', true)) != 'windows-1252')
    {
        $file = mb_convert_encoding($file, 'UTF-16', $detectedEncoding);
    }
    print $file . "<br/>";
    var_dump(is_file($file));
    die();
}       

输出为:

D:/htdocs/otm/.data/_tmp/Lokaltest_KG_漢字_汉字_Krümhold-DEZ1604-140081-complete.zip
UTF-8
UTF-8
D:/htdocs/otm/.data/_tmp/Lokaltest_KG_o"[W_lI[W_Kr�mhold-DEZ1604-140081-complete.zip
NULL 

因此,从UTF-8(数据库/内部编码)转换为UTF-16(windows文件系统)似乎也不起作用。

我在这里已经到了极限,不幸的是,这个问题对我们来说非常重要,因为我们无法在这个问题迫在眉睫的情况下更新我们的系统。我希望有人能对此有所了解。很抱歉发了这么长的帖子,我不确定我能不能把我的观点表达清楚。


添加:

$file = utf8_decode($file);
var_dump(is_file($file));
die();

为带有日语字母的文件名传递false。当我更改用于创建文件名的输入,使文件名现在为Lokaltest_KG_Krümhold-DEZ1604-140081-complete.zip时,上面的代码为true。所以utf8_decode有帮助,但只是unicode的一小部分,德语元音变音符?

在这里回答我自己的问题:真正的坏家伙是ZipArchive组件,它创建了文件名编码错误的文件。我写了一份很有帮助的错误报告:https://bugs.php.net/bug.php?id=72200

考虑一下这个简短的脚本:

print "php default_charset: ".ini_get('default_charset')."'n"; // just 4 info (UTF-8)
$filename = "bugtest_müller-lüdenscheid.zip"; // just an example
$filename = utf8_encode($filename); // simulating my database delivering utf8-string
$zip = new ZipArchive();
if( $zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true )
{
    $zip->addFile('bugtest.php', 'bugtest.php'); // copy of script file itself
    $zip->close();
}
var_dump( is_file($filename) );  // delivers ?

输出:

output PHP 5.5.35:
    php default_charset: UTF-8
    bool(true)
output PHP 7.0.6:
    php default_charset: UTF-8
    bool(false)