ZipArchive 解压中文文件乱码解决方案和疑惑

今天接到一个需求,用户上传压缩包,后台解压缩并根据解压后的文件名来新建数据。想想解压缩的功能在网上应该也有现成的轮子了,很快也找到了相应的轮子:Zipper

简单两部操作很快就发现了一个严重的问题,压缩包的内容是中文的,解压缩以后。得到了一堆乱码 :astonished: :astonished: :astonished:
file

踩坑过程(可跳过)

在扩展的 issue 里没有找到合适的解决方案(也可能是英语渣没找着)。不得已,又开始搜查各种资料,发现了如下解决方法:
在扩展源码的 ZipRepository.php

public function each($callback)
{
    for ($i = 0; $i < $this->archive->numFiles; ++$i) {
        //skip if folder
        $stats = $this->archive->statIndex($i, ZipArchive::FL_ENC_RAW);  // 添加这个参数 返回未修改的字符串内容
        $filename = iconv('GBK//IGONRE', 'UTF-8', $stats['name']);  // 再将文件名转码为中文

        if ($stats['size'] === 0 && $stats['crc'] === 0) {
            continue;
        }

        call_user_func_array($callback, [
            'file' => $stats['name'],
        ]);
    }
}

按照这个办法,发现解压出来的文件都是中文了~
然后再仔细看一下,又发现了一个大坑。
file
嗯!?解压出来的文件大小都是 0kb?原来问题出在 $this->archive->getFileStream($fileName),传入中文名以后 getFileStream() 会返回 false。
既然动了源码了,那就再动一点吧 :sweat_smile: :sweat_smile:

最终解决方案

最后把扩展源码的以下部分做了修改,同时传入自动转码的文件名以及手动由GBK转码为 UTF-8 的中文文件名,用自动转码的文件名获取资源,然后以中文名命名。

ZipRepository.php

public function each($callback)
{
    for ($i = 0; $i < $this->archive->numFiles; ++$i) {
        //skip if folder
        $stats = $this->archive->statIndex($i);

        if ($stats['size'] === 0 && $stats['crc'] === 0) {
            continue;
        }
        // 这里做了修改
        $rawName = $this->archive->getNameIndex($i, ZipArchive::FL_ENC_RAW); 
        $rawName = iconv('GBK//IGORNE', 'UTF-8', $rawName);

        call_user_func_array($callback, [
            $stats['name'], $rawName
        ]);
    }
}

Zipper.php

private function extractFilesInternal($path, callable $matchingMethod)
{
    $self = $this;
    // 添加一个中文名参数
    $this->repository->each(function ($fileName, $CNName) use ($path, $matchingMethod, $self) {
        $currentPath = $self->getCurrentFolderWithTrailingSlash();
        if (!empty($currentPath) && !starts_with($fileName, $currentPath)) {
            return;
        }

        $filename = str_replace($self->getInternalPath(), '', $fileName);
        if ($matchingMethod($filename)) {
            $self->extractOneFileInternal($fileName, $CNName, $path);
        }
    });
}

private function extractOneFileInternal($fileName, $CNName, $path)
{

    $tmpPath = str_replace($this->getInternalPath(), '', $CNName);  // 用手动转码的中文文件名命名

    // We need to create the directory first in case it doesn't exist
    $dir = pathinfo($path.DIRECTORY_SEPARATOR.$tmpPath, PATHINFO_DIRNAME);
    if (!$this->file->exists($dir) && !$this->file->makeDirectory($dir, 0755, true, true)) {
        throw new \RuntimeException('Failed to create folders');
    }

    $toPath = $path.DIRECTORY_SEPARATOR.$tmpPath;
    $fileStream = $this->getRepository()->getFileStream($fileName);  // 用系统转码的文件名获取资源
    $this->getFileHandler()->put($toPath, $fileStream);
}

疑惑

为什么 getStream() 传入 ZipArchive::FL_ENC_RAW 返回的 name 会返回 false 呢?
各位大佬有没有遇到类似的情况,有什么更好的解决办法呢?

JasonG
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 8

Zipper 这个扩展挺坑的。

5年前 评论

不过这个解决方案下,Linux / macOS 压缩的中文名文件就会解压出问题了。

5年前 评论
JasonG

@Wi1dcard 是啊 这个转码方案只能转 windows 下压缩的中文压缩包 不太通用 周末试验下你提供的包 感谢!

5年前 评论

一般vendor下的代码不上git ,改源码不方便

5年前 评论
JasonG

@lovecn 是啊 这个只解燃眉之急

5年前 评论

推荐这个:https://github.com/Ne-Lexa/php-zip 这个怎么解压并重命名

4年前 评论

同样遇到这个问题,我发现使用了360压缩的文件,在服务器上解压后是乱码的,使用好压是正常的。。猜测是压缩算法的问题。。

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!