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
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 7

Zipper 这个扩展挺坑的。

6个月前 评论

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

6个月前 评论
JasonG

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

6个月前 评论

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

6个月前 评论
JasonG

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

6个月前 评论

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

1个月前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

社区文档:

将托管在 packagist.org 和 github.com 的扩展包使用国内 CDN 加速
GitHub Laravel 扩展包 TOP 250
速查表方便快速查询框架功能,支持手机访问,支持中英文版本
Laravel 中文文档,由社区用户翻译和维护,将会保持一直更新
此文档的目的,就是为了提高技术团队的凝聚力、一致性和生产效率。
开发环境的部署,开发者工具的选择,适用于 Mac 和 Windows。
浓缩过后的精华
Laravel Nova 后台管理面板文档的中文翻译
Lumen 中文文档,由社区用户翻译和维护,将会保持一直更新
Laravel 下知名扩展包 Dingo API 的中文文档,Laravel API 开发必知必会