golang 解压带密码的zip包

news2024/9/20 15:02:46

目录

  • Zip文件详解
    • ZIP 文件格式主要特性
    • 常用算法
    • Zip格式结构图总览
    • Zip文件结构详解
      • 数据区
        • 本地文件头
        • 文件数据
        • 文件描述
      • 中央目录记录区(核心目录记录区 )
      • 中央目录记录尾部区
    • 压缩包解压过程
      • 方式1 通过解析中央目录区来解压
      • 方式2 通过读取本地文件头来解压
      • 两种解压方式对比
  • golang解压zip包
    • 官方archive/zip
    • 第三方包github.com/yeka/zip
    • 自己实现解压加密的zip包

Zip文件详解

ZIP 文件格式是一种常用的压缩和归档格式,用于将多个文件和目录打包到一个单独的文件中,同时对其内容进行压缩以减少文件大小。ZIP 文件格式的设计旨在支持多种压缩算法、加密和数据完整性校验。以下是 ZIP 文件格式的主要特性和常用算法:

ZIP 文件格式主要特性

  1. 文件头

    • 每个文件都有一个本地文件头和一个中央目录文件头。文件头包含文件名、压缩方法、时间戳、CRC-32 校验和、压缩前后的大小等信息。
  2. 数据描述符

    • 可选的后缀结构,包含文件的 CRC-32 校验和、压缩大小和未压缩大小。
  3. 中央目录

    • ZIP 文件的末尾包含一个中央目录记录,列出了 ZIP 文件中的所有文件和目录的文件头信息,用于快速定位和访问。
  4. 结束记录

    • ZIP 文件末尾的“中央目录结束记录”标识了中央目录的结束,包含中央目录的偏移量和大小等信息。

一个简单的 ZIP 文件可以包含多个文件的本地文件头、压缩数据、中央目录和结束记录。解压工具通过读取中央目录找到各个文件的偏移量和大小,然后根据这些信息读取和解压文件数据。

以下是一个 ZIP 文件结构的简化示例:

[本地文件头1] [文件数据1] [本地文件头2] [文件数据2] ... [中央目录] [中央目录结束记录]

ZIP 文件格式因其广泛的支持和高效的压缩性能,广泛应用于文件归档和传输。DEFLATE 算法是其最常用的压缩算法,提供了良好的平衡点。

常用算法

  1. 压缩算法

    • DEFLATE:这是 ZIP 文件中最常用的压缩算法,由 Phil Katz 发明。它结合了 LZ77 算法和 Huffman 编码,提供了良好的压缩比和解压速度。
    • STORE:不进行任何压缩,仅用于存储数据。适用于已经被压缩过的数据,如 JPEG 图像或 MP3 音频文件。
    • 其他压缩方法:ZIP 规范还支持其他压缩方法,如 BZIP2、LZMA 和 PPMd,但这些方法在实际使用中较为少见。
  2. 加密算法

    • 传统 ZIP 加密:早期的 ZIP 文件使用一种相对简单的对称加密方法,但这种方法的安全性较弱。
    • AES 加密:一些现代的 ZIP 工具支持使用高级加密标准 (AES) 进行加密,提供了更强的安全性。
  3. 校验算法

    • CRC-32:用于每个文件的数据完整性校验。每个文件都有一个 CRC-32 校验和,用于检测数据传输或存储过程中的错误。

Zip格式结构图总览

在这里插入图片描述

Zip文件结构详解

zip格式压缩包主要由三大部分组成:数据区、中央目录记录区(也有叫核心目录记录)、中央目录记录尾部区。

数据区

数据区是由一系列本地文件记录组成,本地文件记录主要是记录了压缩前后文件的元数据以及存放压缩后的文件,组成部分也分为三大部分:本地文件头文件数据文件描述

本地文件头

在这里插入图片描述

local file header signature     4 bytes  (0x04034b50)
version needed to extract       2 bytes
general purpose bit flag        2 bytes
compression method              2 bytes
last mod file time              2 bytes
last mod file date              2 bytes
crc-32                          4 bytes
compressed size                 4 bytes
uncompressed size               4 bytes
file name length                2 bytes
extra field length              2 bytes

file name (variable size)
extra field (variable size)

本地文件头主要是记录了压缩文件的元数据:

  1. loca file header signature:0~3,4个字节,用来存放本地文件头标识,一般为固定值0x04034b50,用于解压时候,读取判断文件头的开始;

  2. version needed to extract:4~5,2个字节,记录解压缩文件所需的最低支持的ZIP规范版本,apk压缩版本默认是20, 即Deflate压缩方式。该字段值=解压所需的最低ZIP规范版本*10,比如,最低支持的ZIP规范版本是2.0,那么该字段的值就是20。每个版本定义如下。

    当前最低功能版本定义如下:(压缩包记录的解压版本都是需要版本*10,比如:2.0 * 10 = 201.0 – 默认值
    1.1 – 文件是卷标
    2.0 – 文件是一个文件夹(目录)
    2.0 – 使用 Deflate 压缩来压缩文件
    2.0 – 使用传统的 PKWARE 加密对文件进行加密
    2.1 – 使用 Deflate64™ 压缩文件
    2.5 – 使用 PKWARE DCL Implode 压缩文件
    2.7 – 文件是补丁数据集
    4.5 – 文件使用 ZIP64 格式扩展
    4.6 – 使用 BZIP2 压缩文件压缩
    5.0 – 文件使用 DES 加密
    5.0 – 文件使用 3DES 加密
    5.0 – 使用原始 RC2 加密对文件进行加密
    5.0 – 使用 RC4 加密对文件进行加密
    5.1 – 文件使用 AES 加密进行加密
    5.1 – 使用更正的 RC2 加密对文件进行加密
    5.2 – 使用更正的 RC2-64 加密对文件进行加密
    6.1 – 使用非 OAEP 密钥包装对文件进行加密
    6.2 – 中央目录加密
    
  3. genaral purpose bit flag:6~7,2个字节,记录通用标志位,第0位为1时(即二进制:00000000 00000001),表示文件被加密,解压时候需要解密;第3位为1时候(即二进制:00000000 00000100),表示有数据描述部分,本地文件头中的 CRC-32、压缩大小和未压缩大小字段都被设置为0(虽然zip规范是这么定义,但是发现有些压缩包即使声明有数据描述部分,但是本地文件头的CRC-32、压缩大小和未压缩大小依然还是设置为真实值) , 正确的值被放在紧跟在压缩数据之后的数据描述部分,apk的通用标志位默认传0即可,也有传2048、2056,目前第15位是PKWARE保留位。

  4. compression method:8~9,2个字节,记录压缩包所用到的压缩方式,apk默认Deflate压缩,传8即可, 要是传0 ,则是不压缩,各种压缩方式对应数值如下:

    0 – The file is stored (no compression)
    1 – The file is Shrunk
    2 – The file is Reduced with compression factor 1
    3 – The file is Reduced with compression factor 2
    4 – The file is Reduced with compression factor 3
    5 – The file is Reduced with compression factor 4
    6 – The file is Imploded
    7 – Reserved for Tokenizing compression algorithm
    8 – The file is Deflated
    9 – Enhanced Deflating using Deflate64™
    10 – PKWARE Data Compression Library Imploding
    11 – Reserved by PKWARE
    12 – File is compressed using BZIP2 algorithm
    
  5. last mod file time:10~11,2个字节,记录文件最后修改时间,是MS-DOS格式编码的时间
    在这里插入图片描述

  6. last mod date time:12~13,2个字节,记录文件最后修改日期,是MS-DOS格式编码的日期

  7. crc-32:14~17,4个字节,记录文件未压缩时的CRC-32校验码

  8. compressed size:18~21,4个字节,记录文件压缩后的大小

  9. uncompressed size:22~25,4个字节,记录文件未压缩的大小

  10. file name length:26~27,2个字节,记录文件名的长度(假设文件名长度为n)

  11. extra field length:28~29,2个字节,记录扩展区的长度(假设扩展区长度为m)

  12. file name:30~30+n,n个字节,记录文件名

  13. extral field:30+n~30+n+m,m个字节,记录扩展数据

文件数据

文件数据紧跟在本地文件头之后,一般是压缩后的文件数据或压缩方式选择不压缩时候,用来存储未压缩文件数据。

文件描述

文件描述符仅在通用位标志的第 3 位被设置为1时才存在。 它是字节对齐的,紧跟在文件数据的最后一个字节之后。当且仅当无法在 .ZIP 文件中查找时才使用此描述符,例如:当输出 的.ZIP 文件是标准输出或不可查找设备时使用文件描述,换句话说,正常情况下都不需要使用。

在这里插入图片描述
( 数据描述符标识不一定有,因为一开始规范是没有的,后面才加上去的)

中央目录记录区(核心目录记录区 )

中心目录区的结构如下。

[file header 1]
.
.
. 
[file header n]
[digital signature]
central file header signature   4 bytes  (0x02014b50)
version made by                 2 bytes
version needed to extract       2 bytes
general purpose bit flag        2 bytes
compression method              2 bytes
last mod file time              2 bytes
last mod file date              2 bytes
crc-32                          4 bytes
compressed size                 4 bytes
uncompressed size               4 bytes
file name length                2 bytes
extra field length              2 bytes
file comment length             2 bytes
disk number start               2 bytes
internal file attributes        2 bytes
external file attributes        4 bytes
relative offset of local header 4 bytes

file name (variable size)
extra field (variable size)
file comment (variable size)

中央目录记录区是有一系列中央目录记录所组成,一条中央目录记录对应数据区中的一个压缩文件记录,中央目录记录由以下部分构成:(中央目录区通常由多个文件头(file header)组成,每一个被压缩的文件都有一个对应的file header(注意,这里不是local file header),用于标识和定位该文件在ZIP文件中的位置。这个文件头和本地文件头类似,记录了被压缩文件的元数据信息,包括文件原始大小,压缩之后的大小,文件注释等。)

在这里插入图片描述

  1. central file header signature:0~3,4个字节,记录核心目录文件头标识,固定值:0x02014b50,用于解压时候,查找判断是否是中央目录的开始位置
  2. version made by:4~5,2个字节,记录压缩所用的版本,同数据区本地文件头的解压所需版本,apk设置20
  3. 6~7:2个字节,记录解压所需的最小版本,同数据区本地文件头的解压所需版本,apk设置20
  4. 8~9:2个字节,通用位标记,同数据区本地文件头的通用位标记
  5. 压缩方法、文件最后修改时间、文件最后修改日期、CRC-32校验码、压缩后大小、未压缩大小、文件名长度、扩展区长度,这几个字段的含义都等同于数据区本地文件头对应字段的含义
  6. file comment length:32~33,2个字节,记录文件注释的长度
  7. disk number start:34~35,2个字节,记录文件开始位置的磁盘编号,一般传0即可
  8. 36~41:内部文件属性、外部文件属性,一般也是传0即可
  9. 42~45:4个字节,记录数据区本地文件头相对于压缩包开始位置的偏移量

数据签名(digital signature):

header signature                4 bytes  (0x05054b50)
size of data                    2 bytes
signature data (variable size)
  • header signature:数字签名起始标识,固定值为0x05054b50。
  • size of data:数字签名数据大小。
  • signature data :签名数据

中央目录记录尾部区

中央目录记录尾部主要作用是用来定位中央目录记录区的开始位置,同时记录压缩包的注释内容:
在这里插入图片描述

end of central dir signature    4 bytes  (0x06054b50)
number of this disk             2 bytes
number of the disk with the
start of the central directory  2 bytes
total number of entries in the
central directory on this disk  2 bytes
total number of entries in
the central directory           2 bytes
size of the central directory   4 bytes
offset of start of central
directory with respect to
the starting disk number        4 bytes
.ZIP file comment length        2 bytes
.ZIP file comment       (variable size)
  1. end of central dir signature:0~3,4个字节,中央目录记录尾部开头标记,固定值:0x06054b50,用于解压时,查找判断中央目录尾部的起始位置
  2. number of this disk:4~5,2个字节,记录中央目录记录尾部区所在磁盘编号
  3. number of the disk with the start of the central directory:6~7,2个字节,记录中央目录开始位置所在的磁盘编号
  4. total number of entries in the central directory on this disk:8~9,2个字节,该磁盘上所记录的核心目录数量
  5. total number of entries in the central directory:10~11,2个字节,zip压缩包中的文件总数
  6. size of the central directory:12~15,4个字节,整个中央目录的大小(以字节为单位)
  7. offset of start of central directory with respect to the starting disk number:16~19,4个字节,中央目录开始位置相对位移
  8. ZIP file comment length:20~21,2个字节,注释内容的长度(假设长度为n)
  9. ZIP file comment:22~22+n,n个字节,注释内容

中央目录结束标识是ZIP文件解压的入口。通过读取中央目录结束标识,解压缩软件可以快速地找到中央目录,并据此解析整个ZIP文件的结构和内容。通过里面的中央核心目录区的大小可以找到对应的中央目录模块,然后根据中央目录文件头中的本地文件头偏移(relative offset of local header)可以寻址到对应的文件,并进行解压。

每个压缩文件都必须且仅有一个中央目录结束标识。如果ZIP文件损坏或结构不正确,可能会导致中央目录结束标识丢失或损坏,从而使得解压缩软件无法正确读取和解析ZIP文件。

压缩包解压过程

方式1 通过解析中央目录区来解压

通过ZIP文件的结构我们发现,ZIP文件的中央目录区保存了所有的文件信息。所以,可以通过中央目录区拿到所有的文件信息并进行解压,步骤如下所示。

在这里插入图片描述

  1. 首先在 ZIP 文件末尾通过中央目录结束标识 (0x06054b50)找到中央目录结束标识数据块。
  2. 通过中央目录结束标识中的中央目录区开始位置偏移找到中央目录区数据块。
  3. 根据中央目录区的File Header中的 local file header的偏移量找到对应的local file header。
  4. 根据 local file header找到对应的file data
  5. 解密 file data(如果需要);
  6. 解压 file data;

方式2 通过读取本地文件头来解压

根据 ZIP 文件格式标准可知,除了 中央目录区, 本地文件头中也包含了每个文件的相关信息。因此,可以基于本地文件头去解压文件数据,其解压流程就可以变为:

  1. 从头开始,通过本地文件头标识搜索对应的 local file header
  2. 读取 local file header并找到file data;
  3. 解密 file data(如果需要);
  4. 解压 file data;

两种解压方式对比

通过两种解压方式可以明显看出,两种解压方式适用的场景不同。

方式1适用场景:

  • 适用于在解压文件已经存在于磁盘上,并且需要解压压缩包中所有的文件。

方式2适用场景:

  • 当文件不在磁盘上,比如从网络接收的数据,想边接收边解压;

  • 需要顺序解压ZIP文件前面的一小部分文件,可以使用这种方式,因为方式1读中央目录区会带来额外的耗时;

  • ZIP文件中的中央目录区遭到损坏;

golang解压zip包

官方archive/zip

golang zip包的解压有官方的zip包(archive/zip),但是官方给的zip解压包代码只有解压不带密码的zip包。

下面给出解压操作的封装:

func Unzip(src, dst string) error {
	// zip.NewReader() 适合从stream中读取字节序列
	zf, err := zip.OpenReader(src)
	if err != nil {
		return err
	}
	defer zf.Close()

	for _, file := range zf.File {
		// fmt.Println(file.Name)
		path := filepath.Join(dst, file.Name)
		// 如果是目录则创建目录
		if file.FileInfo().IsDir() {
			if err = os.MkdirAll(path, 0o644); err != nil {
				return err
			}
			continue
		}
		f, err := os.Create(path)
		if err != nil {
			return err
		}
		reader, err := file.Open()
		if err != nil {
			return err
		}
		_, err = io.Copy(f, reader)
		if err != nil {
			return err
		}
		_ = f.Close()
		_ = reader.Close()
	}
	return nil
}

func main() {
	log.Println(Unzip("Go.zip", "./static"))
}

在这里插入图片描述
通常情况下,目录权限设为 0755,文件权限设为 0644 更为合适。

在 Unix 和 Unix-like 操作系统(如 Linux)中,文件权限使用三位八进制数来表示,每一位分别表示所有者(Owner)、组(Group)和其他人(Others)的权限。每一位的权限可以是 1(执行权限),2(写入权限),4(读取权限)的组合。它们的组合值表示特定的权限设置。

权限位的含义:

  • 1:执行权限 (Execute)
  • 2:写入权限 (Write)
  • 4:读取权限 (Read)

这些权限可以相加来组合权限。例如:

  • 7 (4+2+1):读取、写入和执行权限 (Read + Write + Execute)
  • 6 (4+2):读取和写入权限 (Read + Write)
  • 5 (4+1):读取和执行权限 (Read + Execute)
  • 4:读取权限 (Read)
  • 3 (2+1):写入和执行权限 (Write + Execute)
  • 2:写入权限 (Write)
  • 1:执行权限 (Execute)
  • 0:无权限 (No permissions)

权限设置示例:

权限设置通常以三位八进制数表示,例如 0755,每一位代表不同用户类别的权限:

  • 第一位(Owner 权限):7,表示所有者有读取、写入和执行权限。
  • 第二位(Group 权限):5,表示组用户有读取和执行权限。
  • 第三位(Others 权限):5,表示其他用户有读取和执行权限。
rwxr-xr-x
  • r 代表读取权限
  • w 代表写入权限
  • x 代表执行权限
  • - 代表没有该权限

示例解释:

  • 0755:

    • 所有者(Owner)权限:7 (rwx) 读取、写入和执行
    • 组(Group)权限:5 (r-x) 读取和执行
    • 其他人(Others)权限:5 (r-x) 读取和执行
  • 0644:

    • 所有者(Owner)权限:6 (rw-) 读取和写入
    • 组(Group)权限:4 (r–) 读取
    • 其他人(Others)权限:4 (r–) 读取

第三方包github.com/yeka/zip

使用go get github.com/yeka/zip安装后,代码都不需要改变,只是导入的zip包替换为第三方即可:
在这里插入图片描述
在这里插入图片描述
https://github.com/yeka/zip,关于这个库里面的整个代码是在官方的zip库的基础上做了一些修改,并且这个库里面的代码抛弃了注册的功能,直接把解密代码写在了open文件里,废弃了一个很好用的功能。

在这里插入图片描述
在这里插入图片描述

自己实现解压加密的zip包

golang官方的zip代码库,https://golang.google.cn/pkg/archive/zip/#pkg-examples有两个注册接口,一个是压缩和一个是解压的:

在这里插入图片描述
在这里插入图片描述
官方注册压缩器方法示例:

package main

import (
	"archive/zip"
	"bytes"
	"compress/flate"
	"io"
)

func main() {
	// Override the default Deflate compressor with a higher compression level.

	// Create a buffer to write our archive to.
	buf := new(bytes.Buffer)

	// Create a new zip archive.
	w := zip.NewWriter(buf)

	// Register a custom Deflate compressor.
	w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
		return flate.NewWriter(out, flate.BestCompression)
	})

	// Proceed to add files to w.
}

类比这个案例可以写出:

func main() {
	zf, _ := zip.OpenReader("")
	zf.RegisterDecompressor(zip.Deflate, func(r io.Reader) io.ReadCloser {
		
		// TODO:解密算法实现

		return flate.NewReader(r)
	})
}

接下来实现解密算法(官方链接:https://support.pkware.com/pkzip/appnote):

在这里插入图片描述
zip标准文件:https://pkwaredownloads.blob.core.windows.net/pem/APPNOTE.txt,温馨提示:不要机器翻译成中文。

在这里插入图片描述

ZIP 文件加密算法通常使用一种简单的流加密方法,称为 ZipCrypto。解密过程包括初始化三个 32 位整数 key0, key1, 和 key2,并根据密码和加密的字节数据更新这些值。加密和解密使用同样的逻辑,只是加密是将明文转换为密文,而解密是将密文转换为明文。

type ZipCrypto struct {
    password []byte
    Keys     [3]uint32
}
 
func NewZipCrypto(passphrase []byte) *ZipCrypto {
    z := &ZipCrypto{}
    z.password = passphrase
    z.init()
    return z
}
 
func (z *ZipCrypto) init() {
    z.Keys[0] = 0x12345678
    z.Keys[1] = 0x23456789
    z.Keys[2] = 0x34567890
 
    for i := 0; i < len(z.password); i++ {
        z.updateKeys(z.password[i])
    }
}
 
func (z *ZipCrypto) updateKeys(byteValue byte) {
    z.Keys[0] = crc32update(z.Keys[0], byteValue)
    z.Keys[1] += z.Keys[0] & 0xff
    z.Keys[1] = z.Keys[1]*134775813 + 1
    z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24))
}
 
func (z *ZipCrypto) magicByte() byte {
    var t uint32 = z.Keys[2] | 2
    return byte((t * (t ^ 1)) >> 8)
}
 
func (z *ZipCrypto) Encrypt(data []byte) []byte {
    length := len(data)
    chiper := make([]byte, length)
    for i := 0; i < length; i++ {
        v := data[i]
        chiper[i] = v ^ z.magicByte()
        z.updateKeys(v)
    }
    return chiper
}
 
func (z *ZipCrypto) Decrypt(chiper []byte) []byte {
    length := len(chiper)
    plain := make([]byte, length)
    for i, c := range chiper {
        v := c ^ z.magicByte()
        z.updateKeys(v)
        plain[i] = v
    }
    return plain
}
 
func crc32update(pCrc32 uint32, bval byte) uint32 {
    return crc32.IEEETable[(pCrc32^uint32(bval))&0xff] ^ (pCrc32 >> 8)
}

实现加密解密算法后写一个小例子来测试下:

func main() {
	password := "generalzy"
	zc := NewZipCrypto(password)

	// 示例数据
	data := []byte("Hello, ZipCrypto!")
	fmt.Printf("Original: %s\n", data)

	// 加密数据
	encrypted := zc.Encrypt(data)
	fmt.Printf("Encrypted: %x\n", encrypted)

	// 初始化解密器
	zcDecrypt := NewZipCrypto(password)

	// 解密数据
	decrypted := zcDecrypt.Decrypt(encrypted)
	fmt.Printf("Decrypted: %s\n", decrypted)
}

在这里插入图片描述
要利用 zip 包提供的注册方法来注册解密函数,可以使用 RegisterDecompressor 方法。首先,需要扩展之前的 ZipCrypto 实现,使其能够解密压缩数据流。然后,可以将这个解密流注册到 zip 包中。

最后给出整体代码:

package main

import (
	"archive/zip"
	"bytes"
	"compress/flate"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

type ZipCrypto struct {
	password []byte
	Keys     [3]uint32
}

func NewZipCrypto(passphrase []byte) *ZipCrypto {
	z := &ZipCrypto{}
	z.password = passphrase
	z.init()
	return z
}

func (z *ZipCrypto) init() {
	z.Keys[0] = 0x12345678
	z.Keys[1] = 0x23456789
	z.Keys[2] = 0x34567890

	for i := 0; i < len(z.password); i++ {
		z.updateKeys(z.password[i])
	}
}

func (z *ZipCrypto) updateKeys(byteValue byte) {
	z.Keys[0] = crc32update(z.Keys[0], byteValue)
	z.Keys[1] += z.Keys[0] & 0xff
	z.Keys[1] = z.Keys[1]*134775813 + 1
	z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24))
}

func (z *ZipCrypto) magicByte() byte {
	var t uint32 = z.Keys[2] | 2
	return byte((t * (t ^ 1)) >> 8)
}

func (z *ZipCrypto) Encrypt(data []byte) []byte {
	length := len(data)
	chiper := make([]byte, length)
	for i := 0; i < length; i++ {
		v := data[i]
		chiper[i] = v ^ z.magicByte()
		z.updateKeys(v)
	}
	return chiper
}

func (z *ZipCrypto) Decrypt(chiper []byte) []byte {
	length := len(chiper)
	plain := make([]byte, length)
	for i, c := range chiper {
		v := c ^ z.magicByte()
		z.updateKeys(v)
		plain[i] = v
	}
	return plain
}

func crc32update(pCrc32 uint32, bval byte) uint32 {
	return crc32.IEEETable[(pCrc32^uint32(bval))&0xff] ^ (pCrc32 >> 8)
}

func ZipCryptoDecryptor(r *io.SectionReader, password []byte) (*io.SectionReader, error) {
	z := NewZipCrypto(password)
	b := make([]byte, r.Size())

	r.Read(b)

	m := z.Decrypt(b)
	return io.NewSectionReader(bytes.NewReader(m), 12, int64(len(m))), nil
}

type unzip struct {
	offset int64
	fp     *os.File
	name   string
}

func (uz *unzip) init() (err error) {
	uz.fp, err = os.Open(uz.name)
	return err
}

func (uz *unzip) close() {
	if uz.fp != nil {
		uz.fp.Close()
	}
}

func (uz *unzip) Size() int64 {
	if uz.fp == nil {
		if err := uz.init(); err != nil {
			return -1
		}
	}

	fi, err := uz.fp.Stat()
	if err != nil {
		return -1
	}

	return fi.Size() - uz.offset
}

func (uz *unzip) ReadAt(p []byte, off int64) (int, error) {
	if uz.fp == nil {
		if err := uz.init(); err != nil {
			return 0, err
		}
	}

	return uz.fp.ReadAt(p, off+uz.offset)
}


// DeCompressZip 解压zip包
func DeCompressZip(zipFile, dest, passwd string, offset int64) error {
	uz := &unzip{offset: offset, name: zipFile}
	defer uz.close()

	zr, err := zip.NewReader(uz, uz.Size())
	if err != nil {
		return err
	}

	if passwd != "" {
		// Register a custom Deflate compressor.
		zr.RegisterDecompressor(zip.Deflate, func(r io.Reader) io.ReadCloser {
			rs := r.(*io.SectionReader)
			r, _ = ZipCryptoDecryptor(rs, []byte(passwd))
			return flate.NewReader(r)
		})

		zr.RegisterDecompressor(zip.Store, func(r io.Reader) io.ReadCloser {
			rs := r.(*io.SectionReader)
			r, _ = ZipCryptoDecryptor(rs, []byte(passwd))
			return ioutil.NopCloser(r)
		})
	}

	for _, f := range zr.File {
		fpath := filepath.Join(dest, f.Name)
		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, os.ModePerm)
			continue
		}

		if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return err
		}

		inFile, err := f.Open()
		if err != nil {
			return err
		}

		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			inFile.Close()
			return err
		}

		_, err = io.Copy(outFile, inFile)
		inFile.Close()
		outFile.Close()
		if err != nil {
			return err
		}
	}

	return nil
}

func main() {
	err := DeCompressZip("Go.zip", "./tmp", "123456", 0)
	if err != nil {
		fmt.Println(err)
	}
	return
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1937408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM常用工具中jmap实现手动进行堆转储(heap dump文件)并使用MAT(Memory Analyzer Tool)进行堆分析-内存消耗分析

场景 JVM-常用工具(jps、jstat、jinfo、jmap、jhat、jstack、jconsole、jvisualvm)使用&#xff1a; JVM-常用工具(jps、jstat、jinfo、jmap、jhat、jstack、jconsole、jvisualvm)使用_jvm分析工具-CSDN博客 上面讲了jmap的简单使用。 下面记录其常用功能&#xff0c;实现堆…

C#+layui+echarts实现动态生成折线图

概要 C#layuiecharts实现动态生成折线图 整体架构流程 后端是c#语言编写的业务流程,前端是layui和echarts 技术细节 1.先看echarts折线图需要什么样子的数据,在想后端怎么处理 2.后端代码 List<ValveTempData> list new List<ValveTempData>(); string …

Spring Cloud中怎么使用Resilience4j RateLimiter对接口进行限流

在微服务架构中&#xff0c;限流是保护系统稳定性的重要手段之一。限流可以防止某个服务因流量过大而过载&#xff0c;影响整个系统的稳定性和性能。Resilience4j 提供了多种限流策略&#xff0c;其中 RateLimiter 是一种常用的限流机制。本文将详细介绍如何在 Spring Cloud 项…

组内第一次会议

会议内容 1、科研平台使用 增删改查对文件 cp -r /root/mmdetection/dataset/ /root/user/wbzExperiment/mmdetection/ rm -r /root/user/yolov5-master tar -czvf test03.tar.gz test03/ unzip abc.zip 上传文件、解压文件&#xff1a;要在自己的目录中&#xff0c;进入…

普中51单片机:LED点阵屏组成结构及实现方法详解(九)

文章目录 引言什么是LED点阵屏&#xff1f;工作原理74HC595移位寄存器基本引脚作用级联工作原理 电路图代码演示——16*16LED点阵屏轮播点亮每行LED代码演示——显示数字0代码演示——16*16游动字幕显示 引言 LED点阵屏作为一种广泛应用于现代显示技术的设备&#xff0c;因其能…

Linux_线程的使用

目录 1、线程与进程的关系 2、线程的优缺点 3、创建线程 4、查看启动的线程 5、验证线程是共享地址空间的 6、pthread_create的重要形参 6.1 线程id 6.2 线程实参 7、线程等待 8、线程退出 9、线程取消 10、线程tcb 10.1 线程栈 11、创建多线程 12、__th…

学生信息管理系统设计

学生信息管理系统的设计是一个综合性的项目&#xff0c;涉及到数据的存储、检索、更新和删除等基本操作&#xff0c;同时也需要考虑系统的易用性、安全性和扩展性。以下是一些关键步骤和要素&#xff0c;用于指导设计这样一个系统&#xff1a; 1. 需求分析 目标用户&#xff…

wls2下的centos使用桥接模式连接宿主机网络独立静态ip

前提&#xff1a;wsl2已安装&#xff0c;可正常更新 1.在控制面板中&#xff0c;打开开启或关闭windows功能&#xff0c;将里面的 Hyper-V功能打开&#xff0c;此处涉及重启 2. 按一下win键&#xff0c;输入hy&#xff0c;上面可以看到Hyper-V Manager,点进去 3.选择右边的 Vi…

Redis系列命令更新--Redis有序集合命令

Redis有序集合&#xff08;sorted set&#xff09; &#xff08;1&#xff09;说明&#xff1a; A、Redis有序集合和集合一样也是string类型元素的集合&#xff0c;且不允许重复的成员&#xff1b;不同的是每个元素都会关联一个double类型的分数&#xff1b;redis正式通过分数…

Java语言程序设计——篇五(1)

数组 概述数组定义实例展示实战演练 二维数组定义数组元素的使用数组初始化器实战演练&#xff1a;矩阵计算 &#x1f4ab;不规则二维数组实战演练&#xff1a;杨辉三角形 概述 ⚡️数组是相同数据类型的元素集合。各元素是有先后顺序的&#xff0c;它们在内存中按照这个先后顺…

【ProtoBuf】通讯录实现(网络版)

Protobuf 还常用于通讯协议、服务端数据交换场景。那么在这个示例中&#xff0c;我们将实现一个网络版本的通讯录&#xff0c;模拟实现客户端与服务端的交互&#xff0c;通过 Protobuf 来实现各端之间的协议序列化。 需求如下&#xff1a; 客户端可以选择对通讯录进行以下操…

电脑文件恢复哪个好?分享四个建议记住常备的方法!

当我们发现电脑误删文件的时候&#xff0c;一定会感到焦虑和困惑&#xff0c;但是一味地焦虑和困惑是没有任何帮助的。我们需要保持冷静&#xff0c;然后通过以下几个方法找回。 电脑文件恢复的方法有很多&#xff0c;选对适合自己的数据恢复软件很重要&#xff0c;本文罗列了几…

引领小模型潮流!OpenAI发布功能强大且成本低的GPT-4o mini

GPT-4o mini的成本比GPT-3.5 Turbo低了超过60%&#xff0c;其聊天表现优于Google的Gemini Flash和Anthropic的Claude Haiku。该模型从周四开始对ChatGPT的免费用户、ChatGPT Plus用户和团队订阅用户开放&#xff0c;并将在下周向企业用户开放。OpenAI计划未来将图像、视频和音频…

Linux——五种IO模型

目录 一、I/O的理解 二、五种IO模型 1.阻塞式IO 2.非阻塞式IO 3.信号驱动IO 4.多路复用IO 5.异步IO 一、I/O的理解 I/O的本质就是输入输出&#xff0c;C语言的stdio&#xff0c;C的iostream&#xff0c;添加了这两个库&#xff0c;我们才能够进行printf、scanf、cin、c…

UDP网口(1)概述

文章目录 1.计算机网络知识在互联网中的应用2.认识FPGA实现UDP网口通信3.FPGA实现UDP网口通信的方案4.FPGA实现UDP网口文章安排5.传送门 1.计算机网络知识在互联网中的应用 以在浏览器中输入淘宝网为例&#xff0c;介绍数据在互联网是如何传输的。我们将要发送的数据包称作A&a…

人工智能AI合集:1、嵌入式LinuxAI开发套件OrangePI AIPRO初体验

前言 随着人工智能技术的飞速发展&#xff0c;AI已经不再是遥不可及的高科技概念&#xff0c;而是逐渐融入到我们的日常生活中。从智能手机的语音助手到家庭中的智能音箱&#xff0c;再到工业自动化和医疗诊断&#xff0c;AI的应用无处不在。然而&#xff0c;要想真正掌握并应用…

数学建模学习(111):改进遗传算法(引入模拟退火、轮盘赌和网格搜索)求解JSP问题

文章目录 一、车间调度问题1.1目前处理方法1.2简单案例 二、基于改进遗传算法求解车间调度2.1车间调度背景介绍2.2遗传算法介绍2.2.1基本流程2.2.2遗传算法的基本操作和公式2.2.3遗传算法的优势2.2.4遗传算法的不足 2.3讲解本文思路及代码2.4算法执行结果&#xff1a; 三、本文…

基于java的设计模式学习

PS &#xff1a;以作者的亲身来看&#xff0c;这东西对于初学者来说有用但不多&#xff0c;这些东西&#xff0c;更像一种经验的总结&#xff0c;在平时开发当中一般是用不到的&#xff0c;因此站在这个角度上用处不大。 1.工厂模式 1.1 简单工厂模式 我们把new 对象逻辑封装…

SpringBoot缓存注解使用

背景 除了 RedisTemplate 外&#xff0c; 自Spring3.1开始&#xff0c;Spring自带了对缓存的支持。我们可以直接使用Spring缓存技术将某些数据放入本机的缓存中&#xff1b;Spring缓存技术也可以搭配其他缓存中间件(如Redis等)进行使用&#xff0c;将某些数据写入到缓存中间件…

【Linux】信号(signal)

目录 一、信号概念&#xff1a; 二、信号的常见状态&#xff1a; 信号递达&#xff1a; 信号未决&#xff1a; 阻塞信号&#xff1a; 忽略信号&#xff1a; 信号在内核中的表示&#xff1a; 三、信号相关函数&#xff1a; sigset_t &#xff08;类型&#xff09;&…