php 解压有密码的zip文件_在没有临时文件的PHP中流式传输ZIP文件

介绍

当我尝试在我的自定义Web应用程序中实现“下载目录”功能时,我找到的所有解决方案都是基于先创建zip文件然后发送它。在我的情况下,这可能导致大的临时文件(无论如何大多数图像)都无法被压缩。

所以我提出了一个想法,即在原始数据周围即时创建一个未压缩的ZIP存档 - 而且我发现这很容易。

背景

为此,它足以考虑ZIP存档的最小nessessary结构:我们不需要多部分文件,我们不需要为每个文件存储额外的信息 - 因为我们不需要任何压缩知识算法。

ZIP存档的基本结构使您可以轻松地即时组装它:

文件输入1文件头1档案数据1文件输入2文件头2档案数据2...文件输入文件标题文件数据目录条目1目录条目2...目录条目目录结束

详细地说,让我们深入研究一些字节。这是一个包含未压缩文本文件“test.txt”的ZIP文件,其中包含“快速棕色狐狸跳过懒狗”的文字。我从上面和每个值着色区域,并包含每个值的含义的摘要:

822422cc76fb842c635de9fa8fd3c487.png

一般数据类型

  • UInt16 - 以小端字节顺序排列的2字节,16位数(例如0x1234 = [34,12])
  • UInt32 - 以小端字节顺序的4字节,32位数字(例如0x12345678 = [78,56,34,12]
  • DateTime - 具有两秒精度的时间戳,位格式为YYYYYYYmmmmddddd HHHHHiiiiiisssss以小端字节顺序排列,例如2019-01-23 22:33:44: 值二进制到位- 1980年390b001001110b 0100111 0001 10111 10110 100001 1011010b000000010b 0100111 0001 10111 10110 100001 10110230b000101110b 0100111 0001 10111 10110 100001 10110小时220b000101100b 0100111 0001 10111 10110 100001 10110分钟330b001000010b 0100111 0001 10111 10110 100001 10110二/ 2220b000101100b 0100111 0001 10111 10110 100001 101100b 0100111 0001 10111 10110 100001 10110 = 0x4E37B436 => [36,B4,37,4E]
  • CRC-32 - 使用神奇的数字0xdebb20e3对文件数据进行4字节CRC-32校验和。在PHP中,这是名为“crc32b”的哈希算法。

文件输入

文件条目是描述文件并包含其数据的部分。文件条目一个接一个地堆叠。

名称长度数据类型描述签名4签名文件条目签名,由“PK”后跟字节03和04组成2UINT16主机系统和兼容版本 - 为此目的我只使用0x000A来表示Windows / NTFS,但它真的没关系那么多2UINT16关于如何读取此文件的选项 - 为此我使用0x0800,意味着UFT-8编码的文件名和注释,没有别的压缩方法2UINT16数据被压缩的方法 - 为此目的使用0x0000,意思是“未压缩”FILETIME4UInt32的文件的最后一次修改时间,没有保存其他时间,格式见上文校验4UInt32的文件数据的CRC-32 cecksum,格式见上文压缩尺寸4UInt32的压缩文件数据的大小 - 为此目的与文件大小相同未压缩的尺寸4UInt32的未压缩文件的大小文件名长度2UINT16文件名的长度额外数据长度2UINT16额外数据的长度 - 为此目的,不使用额外数据,因此总是0x0000文件名*串UTF-8编码的文件名文件数据*字节文件数据 - 通常是压缩的,但在这种情况下只是原始数据额外数据*特别额外数据,例如创建时间,属性等 - 为此目的不使用

中央目录录入

中央目录条目包含有关文件条目的更详细数据。中央目录条目堆叠在另一个上,并构建一种内容表。

名称长度数据类型描述签名4签名中央目录条目签名,由“PK”后跟字节01和02组成操作系统版本2UINT16存档的版本由 - 为此我只使用0x003F2UINT16提取所需的最低版本 - 为此我只使用0x000A2UINT16关于如何读取此文件的选项 - 为此我使用0x0800,意味着UFT-8编码的文件名和注释,没有别的压缩方法2UINT16数据被压缩的方法 - 为此目的使用0x0000,意思是“未压缩”FILETIME4约会时间文件的最后一次修改时间,没有保存其他时间,格式见上文校验4CRC32文件数据的CRC-32 cecksum,格式见上文压缩尺寸4UInt32的压缩文件数据的大小 - 为此目的与文件大小相同未压缩的尺寸4UInt32的未压缩文件的大小文件名长度2UINT16文件名的长度额外数据长度2UINT16额外数据的长度 - 为此目的,不使用额外数据,因此总是0x0000评论长度2UINT16文件注释的长度磁盘2UINT16文件所在的磁盘编号 - 为此,我只使用一个文件,因此始终为0x0000内部属性2UINT16内部使用的属性 - 为此目的,这不使用,始终为0x0000外部属性4UInt32的归因于外部使用 - 为此目的,这不使用,始终为0x00000000文件输入的偏移量4UInt32的文件内部偏移到此中心目录条目的文件文件名*串UTF-8编码的文件名额外数据*特别额外数据,例如创建时间,属性等 - 为此目的不使用评论*串对所描述文件的评论

中央目录条目结束

此条目仅发生一次 - 至少为此目的 - 直接堆叠在最后一个中央目录条目上。

名称长度数据类型描述签名4签名中央目录条目签名,由“PK”后跟字节05和06组成磁盘索引2UINT16该磁盘的索引 - 为此我不使用多磁盘,所以这总是0x0000启动磁盘2UINT16这个中心目录启动的磁盘索引 - 为此我不使用多磁盘,所以这总是0x0000文件计数,磁盘2UINT16此磁盘上的文件数 - 为此目的,这始终是包含文件的总数文件计数,中央目录2UINT16此中央目录中的文件数 - 为此目的,这始终是包含文件的总数尺寸4UInt32的中心目录的大小,不包括此条目抵消4UInt32的此磁盘上第一个中央目录条目的偏移量 - 为此目的,这始终是此文件中第一个中央目录条目的偏移量评论长度2UINT16归档评论的长度评论*串档案评论

使用代码

代码是一个名为BjSZipper的PHP类,它包含静态和实例功能,具体取决于您选择使用的方法。在这两种情况下,只有文件信息存储在内存中,文件数据是及时流式传输的。

1.收集信息然后发送(实例)

此方法使用类的实例,收集要发送的每个文件的信息(包括计算CRC-32校验和),然后开始发送存档。用户的利润是他获得了进度条,因为客户可以提前知道存档大小。缺点是在请求后稍晚开始下载 - 特别是如果要处理很多或大文件的话。

方法

__construct($zipName = "download.zip", $comment = "")

BjSZipper的构造函数。采用两个参数:

  • $zipName - 发送到客户端的ZIP存档的文件名,可选,默认为“download.zip”
  • $comment - 归档注释,可选,默认为空
AddDir($path, $recursive = true, $filter = null)

准备路径及其内容以包含在zip存档中。路径相对于$path存档根存储。采取三个参数:

  • $path - 从中​​获取文件的目录路径
  • $recursive - bool,如果为true,则递归扫描目录,可选,默认为true
  • $filter - 包含文件的Reglular表达式,可选,默认情况下包括所有文件
AddFile($file, $name = null, $relativePath = "", $comment = "")

准备要包含在存档中的单个文件。采取四个论点:

  • $file - 完整的文件路径
  • $name - 存档中文件的名称,可选,默认为文件的基本名称
  • $relativePath - 归档文件的路径,可选,默认为归档根,使用斜杠'/'作为路径分隔符
  • $comment - 文件注释,可选,默认为空
AddData($data, $name, $relativePath = '', $comment = '', $filetime = null)

准备从原始数据发送的单个文件。需要五个参数:

  • $data - 文件的原始数据,存储在内存中
  • $name - 存档中文件的名称
  • $relativePath - 归档文件的路径,可选,默认为归档根,使用斜杠'/'作为路径分隔符
  • $comment - 文件注释,可选,默认为空
  • $filetime - 文件的最后修改时间,可选,默认为当前时间
Clear()

将实例重置为从头开始。

Send()

将收集的文件以已组合的ZIP存档发送到客户端。

require_once('BjSZipper.php');

// Create a new instance
$zip = new BjSZipper('images.zip');

// Add files and data to send
$zip->AddDir(dirname(__FILE__), true, '/.(jpg|jpeg)/i'); // All JPEGs recursively
$zip->AddFile('/var/www/html/testdata.bin');              // Just a normal file
$zip->AddData('All the JPEG images.', 'desc.txt');        // A raw text file

// Start sending the archive
$zip->Send();

2.立即开始发送(静态)

此方法使用静态方法。每个文件在收集其数据后直接发送,文件信息存储在内存中用于最终的中心指令。利润是客户端更快的反应时间,因为下载在第一个文件处理后立即开始,内存使用情况稍微好一点,因为只存储了存档相关数据,并且如果添加的原始数据没有保留以供以后发送。缺点是脚本无法知道生成的归档大小,因此客户端将无法显示进度。

方法

static Begin($zipName = 'downlaod.zip', $unlimitedTime = true)

将下载标头发送到客户端。采用两个参数:

  • $zipName - 呈现给客户端的存档文件名,可选,默认为'download.zip'
  • $unlimitedTime - 如果为true,则set_time_limit(0)用于禁用PHP执行时间限制,可选,默认为true
static SendFile($file, $name = null, $relativePath = '', $comment = '')

将单个文件附加到客户端的归档流。采取四个参数:

  • $file - 文件的完整路径
  • $name - 存档中文件的名称,可选,默认为文件的基本名称
  • $relativePath - 文件相对于归档根的路径,分隔符是斜杠'/',可选,默认是归档根目录
  • $comment - 此文件的注释,可选,默认为
static SendDir($path, $recursive, $filter = null)

将目录中的所有指定文件追加到存档流到客户端。所有文件都相对于$path存档根目录添加。采取三个参数:

  • $path - 从中​​获取文件的目录的完整路径
  • $recursive - 如果为true,则还搜索子目录,可选,默认为true
  • $filter - 要添加的正则表达式过滤文件,可选,默认是找到的所有文件
static SendData($data, $name, $relativePath = '', $comment = '', $filetime = null)

将原始数据中的文件追加到存档流到客户端。需要五个参数:

  • $data - 要追加的文件的原始数据
  • $name - 存档中文件的名称
  • $relativePath - 归档中文件相对于归档根目录的路径,可选,默认为归档根目录
  • $comment - 此文件的注释,可选,默认为空
  • $filetime - 归档中文件的文件修改时间,可选,默认为当前时间
static End($comment = '')

将中心目录和结束部分发送到客户端,从而结束存档。采用一个参数:

  • $comment - 对档案的评论

require_once('BjSZipper.php');

// Send the HTTP headers
BjSZipper::Begin('images.zip');

// Add files and data to send
BjSZipper::SendDir(dirname(__FILE__), true, '/.(jpg|jpeg)/i'); // All JPEGs recursively
BjSZipper::SendFile('/var/www/html/testdata.bin');              // Just a normal file
BjSZipper::SendData('All the JPEG images.', 'desc.txt');        // A raw text file

// Send the archive directory and end the archive
BjSZipper::End();

兴趣点

我编写此代码的目的是让它工作 - 基本上没有包含安全措施,几乎没有异常处理。使用时请注意这一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值