Repository: AlistGo/alist (language: go)

Issue:
能否支持zip、tar等压缩包的在线内容预览
### Please make sure of the following things

- [X] I have read the [documentation](https://alist.nn.ci).
- [X] I'm sure there are no duplicate issues or discussions.
- [X] I'm sure this feature is not implemented.
- [X] I'm sure it's a reasonable and popular requirement.

### Description of the feature / 需求描述

实现类似于tar -tvf的效果

### Suggested solution / 实现思路

_No response_

### Additional context / 附件

_No response_


Discussion / hints:
https://pkg.go.dev/archive/zip#NewReader
zip来说似乎只要有io.ReaderAt实现就可以快速列目录和文件，似乎代码改动不大
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

强烈支持！
这个想做的话得前端做吧？因为不管302还是proxy最终给到前段的都是一个url，后端做的话必须下载下来，读取文件
> https://pkg.go.dev/archive/zip#NewReader zip来说似乎只要有io.ReaderAt实现就可以快速列目录和文件，似乎代码改动不大

zip 格式 好像有通过range header 实现io.ReadAt 的https://github.com/snabb/httpreaderat
https://github.com/mholt/archiver 可以实现rar，7z，zip，tar的解压，压缩方式涵盖了，bz2，gzip，xz，等多种。
https://github.com/snabb/httpreaderat 可以通过http range 头实现 `io.ReadAt`接口
目前实验下来只有zip和7z支持快速列目录和文件

目前实验性的写了几个接口可以做到，平铺所有层级目录文件，或者像文件系统一样一级一级查看，实现在服务器上只下载压缩包内指定文件。

但是目前不知道alist想怎么设计这个功能，比如权限设计，接口参数设计，缓存设计等。我觉得这个功能甚至可以单独作为一个插件，通过提供下载链接（不论302还是代理），在线解析压缩包目录，下载指定文件（如果要支持在线播放，可能还要能支持（range header）。
@xhofe 
解压部分可参考实验性代码
https://github.com/SheltonZhu/alist/commit/ebee35df2f9ba878c1a43badcb68070c76b51838?diff=unified&w=0

> [mholt/archiver](https://github.com/mholt/archiver) 可以实现rar，7z，zip，tar的解压，压缩方式涵盖了，bz2，gzip，xz，等多种。 [snabb/httpreaderat](https://github.com/snabb/httpreaderat) 可以通过http range 头实现 `io.ReadAt`接口 目前实验下来只有zip和7z支持快速列目录和文件
> 
> 目前实验性的写了几个接口可以做到，平铺所有层级目录文件，或者像文件系统一样一级一级查看，实现在服务器上只下载压缩包内指定文件。
> 
> 但是目前不知道alist想怎么设计这个功能，比如权限设计，接口参数设计，缓存设计等。我觉得这个功能甚至可以单独作为一个插件，通过提供下载链接（不论302还是代理），在线解析压缩包目录，下载指定文件（如果要支持在线播放，可能还要能支持（range header）。 @xhofe

因为临近毕业，最近在忙着写毕业论文，所以暂时不会思考添加新的feature
@xhofe 求merge，这个功能太实用了！
现在有考虑支持吗？
等一个支持……
如果在线列出压缩包内目录树实现有难度，是否可以先解压到当前目录？
安卓的MiXplorer软件已经实现了压缩包预览，说明是可以实现的

Fix patch:
diff --git a/cmd/kill.go b/cmd/kill.go
new file mode 100644
index 00000000000..3378fd70988
--- /dev/null
+++ b/cmd/kill.go
@@ -0,0 +1,54 @@
+package cmd
+
+import (
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+	"os"
+)
+
+// KillCmd represents the kill command
+var KillCmd = &cobra.Command{
+	Use:   "kill",
+	Short: "Force kill alist server process by daemon/pid file",
+	Run: func(cmd *cobra.Command, args []string) {
+		kill()
+	},
+}
+
+func kill() {
+	initDaemon()
+	if pid == -1 {
+		log.Info("Seems not have been started. Try use `alist start` to start server.")
+		return
+	}
+	process, err := os.FindProcess(pid)
+	if err != nil {
+		log.Errorf("failed to find process by pid: %d, reason: %v", pid, process)
+		return
+	}
+	err = process.Kill()
+	if err != nil {
+		log.Errorf("failed to kill process %d: %v", pid, err)
+	} else {
+		log.Info("killed process: ", pid)
+	}
+	err = os.Remove(pidFile)
+	if err != nil {
+		log.Errorf("failed to remove pid file")
+	}
+	pid = -1
+}
+
+func init() {
+	RootCmd.AddCommand(KillCmd)
+
+	// Here you will define your flags and configuration settings.
+
+	// Cobra supports Persistent Flags which will work for this command
+	// and all subcommands, e.g.:
+	// stopCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+	// Cobra supports local flags which will only run when this command
+	// is called directly, e.g.:
+	// stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/cmd/root.go b/cmd/root.go
index 6bd82b7a4a3..59eb989c3a0 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/alist-org/alist/v3/cmd/flags"
 	_ "github.com/alist-org/alist/v3/drivers"
+	_ "github.com/alist-org/alist/v3/internal/archive"
 	_ "github.com/alist-org/alist/v3/internal/offline_download"
 	"github.com/spf13/cobra"
 )
diff --git a/cmd/server.go b/cmd/server.go
index 3112a6a9905..d9206cfeb18 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	ftpserver "github.com/KirCute/ftpserverlib-pasvportmap"
 	"github.com/KirCute/sftpd-alist"
+	"github.com/alist-org/alist/v3/internal/fs"
 	"net"
 	"net/http"
 	"os"
@@ -159,6 +160,7 @@ the address is defined in config file`,
 		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 		<-quit
 		utils.Log.Println("Shutdown server...")
+		fs.ArchiveContentUploadTaskManager.RemoveAll()
 		Release()
 		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 		defer cancel()
diff --git a/cmd/stop.go b/cmd/stop_default.go
similarity index 87%
rename from cmd/stop.go
rename to cmd/stop_default.go
index 09fba7b759d..8f133940a29 100644
--- a/cmd/stop.go
+++ b/cmd/stop_default.go
@@ -1,10 +1,10 @@
-/*
-Copyright © 2022 NAME HERE <EMAIL ADDRESS>
-*/
+//go:build !windows
+
 package cmd
 
 import (
 	"os"
+	"syscall"
 
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -30,11 +30,11 @@ func stop() {
 		log.Errorf("failed to find process by pid: %d, reason: %v", pid, process)
 		return
 	}
-	err = process.Kill()
+	err = process.Signal(syscall.SIGTERM)
 	if err != nil {
-		log.Errorf("failed to kill process %d: %v", pid, err)
+		log.Errorf("failed to terminate process %d: %v", pid, err)
 	} else {
-		log.Info("killed process: ", pid)
+		log.Info("terminated process: ", pid)
 	}
 	err = os.Remove(pidFile)
 	if err != nil {
diff --git a/cmd/stop_windows.go b/cmd/stop_windows.go
new file mode 100644
index 00000000000..e086eab1ea8
--- /dev/null
+++ b/cmd/stop_windows.go
@@ -0,0 +1,34 @@
+//go:build windows
+
+package cmd
+
+import (
+	"github.com/spf13/cobra"
+)
+
+// StopCmd represents the stop command
+var StopCmd = &cobra.Command{
+	Use:   "stop",
+	Short: "Same as the kill command",
+	Run: func(cmd *cobra.Command, args []string) {
+		stop()
+	},
+}
+
+func stop() {
+	kill()
+}
+
+func init() {
+	RootCmd.AddCommand(StopCmd)
+
+	// Here you will define your flags and configuration settings.
+
+	// Cobra supports Persistent Flags which will work for this command
+	// and all subcommands, e.g.:
+	// stopCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+	// Cobra supports local flags which will only run when this command
+	// is called directly, e.g.:
+	// stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/go.mod b/go.mod
index 7ca66e155ff..0693dcd32c3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,8 @@
 module github.com/alist-org/alist/v3
 
-go 1.22.4
+go 1.23
+
+toolchain go1.23.1
 
 require (
 	github.com/KirCute/ftpserverlib-pasvportmap v1.25.0
@@ -40,17 +42,20 @@ require (
 	github.com/ipfs/go-ipfs-api v0.7.0
 	github.com/jlaffaye/ftp v0.2.0
 	github.com/json-iterator/go v1.1.12
+	github.com/kdomanski/iso9660 v0.4.0
 	github.com/larksuite/oapi-sdk-go/v3 v3.3.1
 	github.com/maruel/natural v1.1.1
 	github.com/meilisearch/meilisearch-go v0.27.2
+	github.com/mholt/archives v0.1.0
 	github.com/minio/sio v0.4.0
 	github.com/natefinch/lumberjack v2.0.0+incompatible
 	github.com/ncw/swift/v2 v2.0.3
-	github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831
+	github.com/orzogc/fake115uploader v0.6.2
 	github.com/pkg/errors v0.9.1
 	github.com/pkg/sftp v1.13.6
 	github.com/pquerna/otp v1.4.0
 	github.com/rclone/rclone v1.67.0
+	github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/afero v1.11.0
 	github.com/spf13/cobra v1.8.1
@@ -61,6 +66,7 @@ require (
 	github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
 	github.com/xhofe/tache v0.1.3
 	github.com/xhofe/wopan-sdk-go v0.1.3
+	github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9
 	github.com/zzzhr1990/go-common-entity v0.0.0-20221216044934-fd1c571e3a22
 	golang.org/x/crypto v0.31.0
 	golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e
@@ -77,21 +83,32 @@ require (
 )
 
 require (
-	github.com/BurntSushi/toml v0.3.1 // indirect
+	github.com/STARRY-S/zip v0.2.1 // indirect
 	github.com/blevesearch/go-faiss v1.0.20 // indirect
 	github.com/blevesearch/zapx/v16 v16.1.5 // indirect
+	github.com/bodgit/plumbing v1.3.0 // indirect
+	github.com/bodgit/sevenzip v1.6.0 // indirect
+	github.com/bodgit/windows v1.0.1 // indirect
 	github.com/bytedance/sonic/loader v0.1.1 // indirect
 	github.com/charmbracelet/x/ansi v0.2.3 // indirect
 	github.com/charmbracelet/x/term v0.2.0 // indirect
 	github.com/cloudwego/base64x v0.1.4 // indirect
 	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
 	github.com/fclairamb/go-log v0.5.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
 	github.com/hekmon/cunits/v2 v2.1.0 // indirect
 	github.com/ipfs/boxo v0.12.0 // indirect
 	github.com/jackc/puddle/v2 v2.2.1 // indirect
+	github.com/klauspost/pgzip v1.2.6 // indirect
+	github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect
+	github.com/sorairolake/lzip-go v0.3.5 // indirect
 	github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543 // indirect
+	github.com/therootcompany/xz v1.0.1 // indirect
+	github.com/ulikunitz/xz v0.5.12 // indirect
+	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
 )
 
 require (
@@ -99,8 +116,8 @@ require (
 	github.com/RoaringBitmap/roaring v1.9.3 // indirect
 	github.com/abbot/go-http-auth v0.4.0 // indirect
 	github.com/aead/ecdh v0.2.0 // indirect
-	github.com/andreburgaud/crypt2go v1.2.0 // indirect
-	github.com/andybalholm/brotli v1.0.4 // indirect
+	github.com/andreburgaud/crypt2go v1.8.0 // indirect
+	github.com/andybalholm/brotli v1.1.1 // indirect
 	github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/benbjohnson/clock v1.3.0 // indirect
@@ -161,7 +178,7 @@ require (
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
-	github.com/klauspost/compress v1.17.8 // indirect
+	github.com/klauspost/compress v1.17.11 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 	github.com/kr/fs v0.1.0 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
@@ -196,7 +213,7 @@ require (
 	github.com/multiformats/go-varint v0.0.7 // indirect
 	github.com/otiai10/copy v1.14.0
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
-	github.com/pierrec/lz4/v4 v4.1.18 // indirect
+	github.com/pierrec/lz4/v4 v4.1.21 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
 	github.com/pquerna/cachecontrol v0.1.0 // indirect
@@ -228,7 +245,7 @@ require (
 	golang.org/x/sync v0.10.0 // indirect
 	golang.org/x/sys v0.28.0 // indirect
 	golang.org/x/term v0.27.0 // indirect
-	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/text v0.21.0
 	golang.org/x/tools v0.24.0 // indirect
 	google.golang.org/api v0.169.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
diff --git a/go.sum b/go.sum
index 101a0bea063..9d92a935f4d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,27 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
 cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw=
 cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
 cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/KirCute/ftpserverlib-pasvportmap v1.25.0 h1:ikwCzeqoqN6wvBHOB9OI6dde/jbV7EoTMpUcxtYl5Po=
 github.com/KirCute/ftpserverlib-pasvportmap v1.25.0/go.mod h1:v0NgMtKDDi/6CM6r4P+daCljCW3eO9yS+Z+pZDTKo1E=
 github.com/KirCute/sftpd-alist v0.0.12 h1:GNVM5QLbQLAfXP4wGUlXFA2IO6fVek0n0IsGnOuISdg=
@@ -12,6 +30,8 @@ github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9
 github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
 github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
 github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
+github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
+github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
 github.com/SheltonZhu/115driver v1.0.32 h1:Taw1bnfcPJZW0xTdhDvEbBS1tccif7J7DslRp2NkDyQ=
 github.com/SheltonZhu/115driver v1.0.32/go.mod h1:XXFi23pyhAgzUE8dUEKdGvIdUQKi3wv6zR7C1Do40D8=
 github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
@@ -30,10 +50,11 @@ github.com/alist-org/times v0.0.0-20240721124654-efa0c7d3ad92 h1:pIEI87zhv8ZzQcu
 github.com/alist-org/times v0.0.0-20240721124654-efa0c7d3ad92/go.mod h1:oPJwGY3sLmGgcJamGumz//0A35f4BwQRacyqLNcJTOU=
 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
-github.com/andreburgaud/crypt2go v1.2.0 h1:oly/ENAodeqTYpUafgd4r3v+VKLQnmOKUyfpj+TxHbE=
-github.com/andreburgaud/crypt2go v1.2.0/go.mod h1:kKRqlrX/3Q9Ki7HdUsoh0cX1Urq14/Hcta4l4VrIXrI=
-github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
+github.com/andreburgaud/crypt2go v1.8.0 h1:J73vGTb1P6XL69SSuumbKs0DWn3ulbl9L92ZXBjw6pc=
+github.com/andreburgaud/crypt2go v1.8.0/go.mod h1:L5nfShQ91W78hOWhUH2tlGRPO+POAPJAF5fKOLB9SXg=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
+github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
 github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
 github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
@@ -91,6 +112,12 @@ github.com/blevesearch/zapx/v16 v16.1.5 h1:b0sMcarqNFxuXvjoXsF8WtwVahnxyhEvBSRJi
 github.com/blevesearch/zapx/v16 v16.1.5/go.mod h1:J4mSF39w1QELc11EWRSBFkPeZuO7r/NPKkHzDCoiaI8=
 github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
 github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
+github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
+github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
+github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
+github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
+github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
+github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
@@ -99,6 +126,7 @@ github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3z
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
 github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
@@ -115,8 +143,12 @@ github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4h
 github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
 github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
 github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/city404/v6-public-rpc-proto/go v0.0.0-20240817070657-90f8e24b653e h1:GLC8iDDcbt1H8+RkNao2nRGjyNTIo81e1rAJT9/uWYA=
 github.com/city404/v6-public-rpc-proto/go v0.0.0-20240817070657-90f8e24b653e/go.mod h1:ln9Whp+wVY/FTbn2SK0ag+SKD2fC0yQCF/Lqowc1LmU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
 github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@@ -145,8 +177,13 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
 github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
 github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
+github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
+github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
 github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
 github.com/fclairamb/go-log v0.5.0 h1:Gz9wSamEaA6lta4IU2cjJc2xSq5sV5VYSB5w/SUHhVc=
@@ -175,6 +212,8 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
 github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
 github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
@@ -220,14 +259,32 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
 github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -236,6 +293,11 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
 github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
 github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
 github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -243,6 +305,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
 github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
 github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -257,13 +321,18 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI0=
 github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
 github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
 github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
 github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
 github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ=
@@ -297,18 +366,26 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
 github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
+github.com/kdomanski/iso9660 v0.4.0 h1:BPKKdcINz3m0MdjIMwS0wx1nofsOjxOq8TOr45WGHFg=
+github.com/kdomanski/iso9660 v0.4.0/go.mod h1:OxUSupHsO9ceI8lBLPJKWBTphLemjrCQY8LPXM7qSzU=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
-github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
-github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
+github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
+github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -355,6 +432,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/meilisearch/meilisearch-go v0.27.2 h1:3G21dJ5i208shnLPDsIEZ0L0Geg/5oeXABFV7nlK94k=
 github.com/meilisearch/meilisearch-go v0.27.2/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
+github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q=
+github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
 github.com/minio/sio v0.4.0 h1:u4SWVEm5lXSqU42ZWawV0D9I5AZ5YMmo2RXpEQ/kRhc=
@@ -400,8 +479,10 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4
 github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
 github.com/ncw/swift/v2 v2.0.3 h1:8R9dmgFIWs+RiVlisCEfiQiik1hjuR0JnOkLxaP9ihg=
 github.com/ncw/swift/v2 v2.0.3/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
-github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831 h1:K3T3eu4h5aYIOzUtLjN08L4Qt4WGaJONMgcaD0ayBJQ=
-github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831/go.mod h1:lSHD4lC4zlMl+zcoysdJcd5KFzsWwOD8BJbyg1Ws9Ng=
+github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 h1:MYzLheyVx1tJVDqfu3YnN4jtnyALNzLvwl+f58TcvQY=
+github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
+github.com/orzogc/fake115uploader v0.6.2 h1:f4LzqeeXpmY7DjOMnzmAnnPTPMA/f/BUclq4ecffTvU=
+github.com/orzogc/fake115uploader v0.6.2/go.mod h1:Mqqwv1+gUEjJhUfIQanco3DCTKp+7lSx8DJ3AoRwMoE=
 github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
 github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
 github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
@@ -410,8 +491,8 @@ github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OI
 github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
-github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
-github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -432,6 +513,7 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
 github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
 github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
 github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
@@ -445,13 +527,17 @@ github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Ny
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
+github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
+github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
 github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
 github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
 github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM=
@@ -469,6 +555,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
+github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -501,6 +589,8 @@ github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7 h1:Jtcrb09q0AVWe3
 github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
 github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543 h1:6Y51mutOvRGRx6KqyMNo//xk8B8o6zW9/RVmy1VamOs=
 github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543/go.mod h1:jpwqYA8KUVEvSUJHkCXsnBRJCSKP1BMa81QZ6kvRpow=
+github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
+github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
 github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
 github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
 github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
@@ -517,6 +607,9 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
+github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
 github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -536,6 +629,10 @@ github.com/xhofe/tache v0.1.3 h1:MipxzlljYX29E1YI/SLC7hVomVF+51iP1OUzlsuq1wE=
 github.com/xhofe/tache v0.1.3/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
 github.com/xhofe/wopan-sdk-go v0.1.3 h1:J58X6v+n25ewBZjb05pKOr7AWGohb+Rdll4CThGh6+A=
 github.com/xhofe/wopan-sdk-go v0.1.3/go.mod h1:dcY9yA28fnaoZPnXZiVTFSkcd7GnIPTpTIIlfSI5z5Q=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
+github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9 h1:K8gF0eekWPEX+57l30ixxzGhHH/qscI3JCnuhbN6V4M=
+github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9/go.mod h1:9BnoKCcgJ/+SLhfAXj15352hTOuVmG5Gzo8xNRINfqI=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -545,6 +642,10 @@ github.com/zzzhr1990/go-common-entity v0.0.0-20221216044934-fd1c571e3a22 h1:X+lH
 github.com/zzzhr1990/go-common-entity v0.0.0-20221216044934-fd1c571e3a22/go.mod h1:1zGRDJd8zlG6P8azG96+uywfh6udYWwhOmUivw+xsuM=
 go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
 go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
@@ -555,12 +656,16 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
 go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
 go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
 go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
+go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
+go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
 gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -576,11 +681,35 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
 golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
 golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
 golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -588,8 +717,19 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -600,6 +740,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
@@ -607,8 +748,17 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
 golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
 golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -619,10 +769,20 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -664,7 +824,9 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
 golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -679,6 +841,7 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
@@ -686,8 +849,30 @@ golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -700,12 +885,45 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
 google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
 google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
 google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
@@ -744,8 +962,16 @@ gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDa
 gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
 gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
 gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/archive/all.go b/internal/archive/all.go
new file mode 100644
index 00000000000..18167933b1c
--- /dev/null
+++ b/internal/archive/all.go
@@ -0,0 +1,7 @@
+package archive
+
+import (
+	_ "github.com/alist-org/alist/v3/internal/archive/archives"
+	_ "github.com/alist-org/alist/v3/internal/archive/iso9660"
+	_ "github.com/alist-org/alist/v3/internal/archive/zip"
+)
diff --git a/internal/archive/archives/archives.go b/internal/archive/archives/archives.go
new file mode 100644
index 00000000000..b70ba95bc86
--- /dev/null
+++ b/internal/archive/archives/archives.go
@@ -0,0 +1,126 @@
+package archives
+
+import (
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"io"
+	"io/fs"
+	"os"
+	stdpath "path"
+	"strings"
+)
+
+type Archives struct {
+}
+
+func (_ *Archives) AcceptedExtensions() []string {
+	return []string{
+		".br", ".bz2", ".gz", ".lz4", ".lz", ".sz", ".s2", ".xz", ".zz", ".zst", ".tar", ".rar", ".7z",
+	}
+}
+
+func (_ *Archives) GetMeta(ss *stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error) {
+	fsys, err := getFs(ss, args)
+	if err != nil {
+		return nil, err
+	}
+	_, err = fsys.ReadDir(".")
+	if err != nil {
+		return nil, filterPassword(err)
+	}
+	return &model.ArchiveMetaInfo{
+		Comment:   "",
+		Encrypted: false,
+	}, nil
+}
+
+func (_ *Archives) List(ss *stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error) {
+	fsys, err := getFs(ss, args.ArchiveArgs)
+	if err != nil {
+		return nil, err
+	}
+	innerPath := strings.TrimPrefix(args.InnerPath, "/")
+	if innerPath == "" {
+		innerPath = "."
+	}
+	obj, err := fsys.ReadDir(innerPath)
+	if err != nil {
+		return nil, filterPassword(err)
+	}
+	return utils.SliceConvert(obj, func(src os.DirEntry) (model.Obj, error) {
+		info, err := src.Info()
+		if err != nil {
+			return nil, err
+		}
+		return toModelObj(info), nil
+	})
+}
+
+func (_ *Archives) Extract(ss *stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	fsys, err := getFs(ss, args.ArchiveArgs)
+	if err != nil {
+		return nil, 0, err
+	}
+	file, err := fsys.Open(strings.TrimPrefix(args.InnerPath, "/"))
+	if err != nil {
+		return nil, 0, filterPassword(err)
+	}
+	stat, err := file.Stat()
+	if err != nil {
+		return nil, 0, filterPassword(err)
+	}
+	return file, stat.Size(), nil
+}
+
+func (_ *Archives) Decompress(ss *stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
+	fsys, err := getFs(ss, args.ArchiveArgs)
+	if err != nil {
+		return err
+	}
+	isDir := false
+	path := strings.TrimPrefix(args.InnerPath, "/")
+	if path == "" {
+		isDir = true
+		path = "."
+	} else {
+		stat, err := fsys.Stat(path)
+		if err != nil {
+			return filterPassword(err)
+		}
+		if stat.IsDir() {
+			isDir = true
+			outputPath = stdpath.Join(outputPath, stat.Name())
+			err = os.Mkdir(outputPath, 0700)
+			if err != nil {
+				return filterPassword(err)
+			}
+		}
+	}
+	if isDir {
+		err = fs.WalkDir(fsys, path, func(p string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+			relPath := strings.TrimPrefix(p, path+"/")
+			dstPath := stdpath.Join(outputPath, relPath)
+			if d.IsDir() {
+				err = os.MkdirAll(dstPath, 0700)
+			} else {
+				dir := stdpath.Dir(dstPath)
+				err = decompress(fsys, p, dir, func(_ float64) {})
+			}
+			return err
+		})
+	} else {
+		err = decompress(fsys, path, outputPath, up)
+	}
+	return filterPassword(err)
+}
+
+var _ tool.Tool = (*Archives)(nil)
+
+func init() {
+	tool.RegisterTool(&Archives{})
+}
diff --git a/internal/archive/archives/utils.go b/internal/archive/archives/utils.go
new file mode 100644
index 00000000000..b72e6bc6a00
--- /dev/null
+++ b/internal/archive/archives/utils.go
@@ -0,0 +1,80 @@
+package archives
+
+import (
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/mholt/archives"
+	"io"
+	fs2 "io/fs"
+	"os"
+	stdpath "path"
+	"strings"
+)
+
+func getFs(ss *stream.SeekableStream, args model.ArchiveArgs) (*archives.ArchiveFS, error) {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return nil, err
+	}
+	format, _, err := archives.Identify(ss.Ctx, ss.GetName(), reader)
+	if err != nil {
+		return nil, errs.UnknownArchiveFormat
+	}
+	extractor, ok := format.(archives.Extractor)
+	if !ok {
+		return nil, errs.UnknownArchiveFormat
+	}
+	switch f := format.(type) {
+	case archives.SevenZip:
+		f.Password = args.Password
+	case archives.Rar:
+		f.Password = args.Password
+	}
+	return &archives.ArchiveFS{
+		Stream:  io.NewSectionReader(reader, 0, ss.GetSize()),
+		Format:  extractor,
+		Context: ss.Ctx,
+	}, nil
+}
+
+func toModelObj(file os.FileInfo) *model.Object {
+	return &model.Object{
+		Name:     file.Name(),
+		Size:     file.Size(),
+		Modified: file.ModTime(),
+		IsFolder: file.IsDir(),
+	}
+}
+
+func filterPassword(err error) error {
+	if err != nil && strings.Contains(err.Error(), "password") {
+		return errs.WrongArchivePassword
+	}
+	return err
+}
+
+func decompress(fsys fs2.FS, filePath, targetPath string, up model.UpdateProgress) error {
+	rc, err := fsys.Open(filePath)
+	if err != nil {
+		return err
+	}
+	defer rc.Close()
+	stat, err := rc.Stat()
+	if err != nil {
+		return err
+	}
+	f, err := os.OpenFile(stdpath.Join(targetPath, stat.Name()), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = io.Copy(f, &stream.ReaderUpdatingProgress{
+		Reader: &stream.SimpleReaderWithSize{
+			Reader: rc,
+			Size:   stat.Size(),
+		},
+		UpdateProgress: up,
+	})
+	return err
+}
diff --git a/internal/archive/iso9660/iso9660.go b/internal/archive/iso9660/iso9660.go
new file mode 100644
index 00000000000..e9cb3f538ec
--- /dev/null
+++ b/internal/archive/iso9660/iso9660.go
@@ -0,0 +1,96 @@
+package iso9660
+
+import (
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/kdomanski/iso9660"
+	"io"
+	"os"
+	stdpath "path"
+)
+
+type ISO9660 struct {
+}
+
+func (t *ISO9660) AcceptedExtensions() []string {
+	return []string{".iso"}
+}
+
+func (t *ISO9660) GetMeta(ss *stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error) {
+	return &model.ArchiveMetaInfo{
+		Comment:   "",
+		Encrypted: false,
+	}, nil
+}
+
+func (t *ISO9660) List(ss *stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error) {
+	img, err := getImage(ss)
+	if err != nil {
+		return nil, err
+	}
+	dir, err := getObj(img, args.InnerPath)
+	if err != nil {
+		return nil, err
+	}
+	if !dir.IsDir() {
+		return nil, errs.NotFolder
+	}
+	children, err := dir.GetChildren()
+	if err != nil {
+		return nil, err
+	}
+	ret := make([]model.Obj, 0, len(children))
+	for _, child := range children {
+		ret = append(ret, toModelObj(child))
+	}
+	return ret, nil
+}
+
+func (t *ISO9660) Extract(ss *stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	img, err := getImage(ss)
+	if err != nil {
+		return nil, 0, err
+	}
+	obj, err := getObj(img, args.InnerPath)
+	if err != nil {
+		return nil, 0, err
+	}
+	if obj.IsDir() {
+		return nil, 0, errs.NotFile
+	}
+	return io.NopCloser(obj.Reader()), obj.Size(), nil
+}
+
+func (t *ISO9660) Decompress(ss *stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
+	img, err := getImage(ss)
+	if err != nil {
+		return err
+	}
+	obj, err := getObj(img, args.InnerPath)
+	if err != nil {
+		return err
+	}
+	if obj.IsDir() {
+		if args.InnerPath != "/" {
+			outputPath = stdpath.Join(outputPath, obj.Name())
+			if err = os.MkdirAll(outputPath, 0700); err != nil {
+				return err
+			}
+		}
+		var children []*iso9660.File
+		if children, err = obj.GetChildren(); err == nil {
+			err = decompressAll(children, outputPath)
+		}
+	} else {
+		err = decompress(obj, outputPath, up)
+	}
+	return err
+}
+
+var _ tool.Tool = (*ISO9660)(nil)
+
+func init() {
+	tool.RegisterTool(&ISO9660{})
+}
diff --git a/internal/archive/iso9660/utils.go b/internal/archive/iso9660/utils.go
new file mode 100644
index 00000000000..12de8e6ea28
--- /dev/null
+++ b/internal/archive/iso9660/utils.go
@@ -0,0 +1,100 @@
+package iso9660
+
+import (
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/kdomanski/iso9660"
+	"io"
+	"os"
+	stdpath "path"
+	"strings"
+)
+
+func getImage(ss *stream.SeekableStream) (*iso9660.Image, error) {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return nil, err
+	}
+	return iso9660.OpenImage(reader)
+}
+
+func getObj(img *iso9660.Image, path string) (*iso9660.File, error) {
+	obj, err := img.RootDir()
+	if err != nil {
+		return nil, err
+	}
+	if path == "/" {
+		return obj, nil
+	}
+	paths := strings.Split(strings.TrimPrefix(path, "/"), "/")
+	for _, p := range paths {
+		if !obj.IsDir() {
+			return nil, errs.ObjectNotFound
+		}
+		children, err := obj.GetChildren()
+		if err != nil {
+			return nil, err
+		}
+		exist := false
+		for _, child := range children {
+			if child.Name() == p {
+				obj = child
+				exist = true
+				break
+			}
+		}
+		if !exist {
+			return nil, errs.ObjectNotFound
+		}
+	}
+	return obj, nil
+}
+
+func toModelObj(file *iso9660.File) model.Obj {
+	return &model.Object{
+		Name:     file.Name(),
+		Size:     file.Size(),
+		Modified: file.ModTime(),
+		IsFolder: file.IsDir(),
+	}
+}
+
+func decompress(f *iso9660.File, path string, up model.UpdateProgress) error {
+	file, err := os.OpenFile(stdpath.Join(path, f.Name()), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	_, err = io.Copy(file, &stream.ReaderUpdatingProgress{
+		Reader: &stream.SimpleReaderWithSize{
+			Reader: f.Reader(),
+			Size:   f.Size(),
+		},
+		UpdateProgress: up,
+	})
+	return err
+}
+
+func decompressAll(children []*iso9660.File, path string) error {
+	for _, child := range children {
+		if child.IsDir() {
+			nextChildren, err := child.GetChildren()
+			if err != nil {
+				return err
+			}
+			nextPath := stdpath.Join(path, child.Name())
+			if err = os.MkdirAll(nextPath, 0700); err != nil {
+				return err
+			}
+			if err = decompressAll(nextChildren, nextPath); err != nil {
+				return err
+			}
+		} else {
+			if err := decompress(child, path, func(_ float64) {}); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/internal/archive/tool/base.go b/internal/archive/tool/base.go
new file mode 100644
index 00000000000..08e96614f51
--- /dev/null
+++ b/internal/archive/tool/base.go
@@ -0,0 +1,15 @@
+package tool
+
+import (
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"io"
+)
+
+type Tool interface {
+	AcceptedExtensions() []string
+	GetMeta(ss *stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error)
+	List(ss *stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error)
+	Extract(ss *stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error)
+	Decompress(ss *stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error
+}
diff --git a/internal/archive/tool/utils.go b/internal/archive/tool/utils.go
new file mode 100644
index 00000000000..822ee894fd1
--- /dev/null
+++ b/internal/archive/tool/utils.go
@@ -0,0 +1,23 @@
+package tool
+
+import (
+	"github.com/alist-org/alist/v3/internal/errs"
+)
+
+var (
+	Tools = make(map[string]Tool)
+)
+
+func RegisterTool(tool Tool) {
+	for _, ext := range tool.AcceptedExtensions() {
+		Tools[ext] = tool
+	}
+}
+
+func GetArchiveTool(ext string) (Tool, error) {
+	t, ok := Tools[ext]
+	if !ok {
+		return nil, errs.UnknownArchiveFormat
+	}
+	return t, nil
+}
diff --git a/internal/archive/zip/utils.go b/internal/archive/zip/utils.go
new file mode 100644
index 00000000000..81b47782840
--- /dev/null
+++ b/internal/archive/zip/utils.go
@@ -0,0 +1,156 @@
+package zip
+
+import (
+	"bytes"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/saintfish/chardet"
+	"github.com/yeka/zip"
+	"golang.org/x/text/encoding"
+	"golang.org/x/text/encoding/charmap"
+	"golang.org/x/text/encoding/japanese"
+	"golang.org/x/text/encoding/korean"
+	"golang.org/x/text/encoding/simplifiedchinese"
+	"golang.org/x/text/encoding/traditionalchinese"
+	"golang.org/x/text/encoding/unicode"
+	"golang.org/x/text/encoding/unicode/utf32"
+	"golang.org/x/text/transform"
+	"io"
+	"os"
+	stdpath "path"
+	"strings"
+)
+
+func toModelObj(file os.FileInfo) *model.Object {
+	return &model.Object{
+		Name:     decodeName(file.Name()),
+		Size:     file.Size(),
+		Modified: file.ModTime(),
+		IsFolder: file.IsDir(),
+	}
+}
+
+func decompress(file *zip.File, filePath, outputPath, password string) error {
+	targetPath := outputPath
+	dir, base := stdpath.Split(filePath)
+	if dir != "" {
+		targetPath = stdpath.Join(targetPath, dir)
+		err := os.MkdirAll(targetPath, 0700)
+		if err != nil {
+			return err
+		}
+	}
+	if base != "" {
+		err := _decompress(file, targetPath, password, func(_ float64) {})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func _decompress(file *zip.File, targetPath, password string, up model.UpdateProgress) error {
+	if file.IsEncrypted() {
+		file.SetPassword(password)
+	}
+	rc, err := file.Open()
+	if err != nil {
+		return err
+	}
+	defer rc.Close()
+	f, err := os.OpenFile(stdpath.Join(targetPath, file.FileInfo().Name()), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = io.Copy(f, &stream.ReaderUpdatingProgress{
+		Reader: &stream.SimpleReaderWithSize{
+			Reader: rc,
+			Size:   file.FileInfo().Size(),
+		},
+		UpdateProgress: up,
+	})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func filterPassword(err error) error {
+	if err != nil && strings.Contains(err.Error(), "password") {
+		return errs.WrongArchivePassword
+	}
+	return err
+}
+
+func decodeName(name string) string {
+	b := []byte(name)
+	detector := chardet.NewTextDetector()
+	result, err := detector.DetectBest(b)
+	if err != nil {
+		return name
+	}
+	enc := getEncoding(result.Charset)
+	if enc == nil {
+		return name
+	}
+	i := bytes.NewReader(b)
+	decoder := transform.NewReader(i, enc.NewDecoder())
+	content, _ := io.ReadAll(decoder)
+	return string(content)
+}
+
+func getEncoding(name string) (enc encoding.Encoding) {
+	switch name {
+	case "UTF-16BE":
+		enc = unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)
+	case "UTF-16LE":
+		enc = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
+	case "UTF-32BE":
+		enc = utf32.UTF32(utf32.BigEndian, utf32.IgnoreBOM)
+	case "UTF-32LE":
+		enc = utf32.UTF32(utf32.LittleEndian, utf32.IgnoreBOM)
+	case "ISO-8859-1":
+		enc = charmap.ISO8859_1
+	case "ISO-8859-2":
+		enc = charmap.ISO8859_2
+	case "ISO-8859-3":
+		enc = charmap.ISO8859_3
+	case "ISO-8859-4":
+		enc = charmap.ISO8859_4
+	case "ISO-8859-5":
+		enc = charmap.ISO8859_5
+	case "ISO-8859-6":
+		enc = charmap.ISO8859_6
+	case "ISO-8859-7":
+		enc = charmap.ISO8859_7
+	case "ISO-8859-8":
+		enc = charmap.ISO8859_8
+	case "ISO-8859-8-I":
+		enc = charmap.ISO8859_8I
+	case "ISO-8859-9":
+		enc = charmap.ISO8859_9
+	case "windows-1251":
+		enc = charmap.Windows1251
+	case "windows-1256":
+		enc = charmap.Windows1256
+	case "KOI8-R":
+		enc = charmap.KOI8R
+	case "Shift_JIS":
+		enc = japanese.ShiftJIS
+	case "GB-18030":
+		enc = simplifiedchinese.GB18030
+	case "EUC-JP":
+		enc = japanese.EUCJP
+	case "EUC-KR":
+		enc = korean.EUCKR
+	case "Big5":
+		enc = traditionalchinese.Big5
+	case "ISO-2022-JP":
+		enc = japanese.ISO2022JP
+	default:
+		enc = nil
+	}
+	return
+}
diff --git a/internal/archive/zip/zip.go b/internal/archive/zip/zip.go
new file mode 100644
index 00000000000..ccb70e65996
--- /dev/null
+++ b/internal/archive/zip/zip.go
@@ -0,0 +1,174 @@
+package zip
+
+import (
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/yeka/zip"
+	"io"
+	"os"
+	stdpath "path"
+	"strings"
+)
+
+type Zip struct {
+}
+
+func (_ *Zip) AcceptedExtensions() []string {
+	return []string{".zip"}
+}
+
+func (_ *Zip) GetMeta(ss *stream.SeekableStream, args model.ArchiveArgs) (model.ArchiveMeta, error) {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return nil, err
+	}
+	zipReader, err := zip.NewReader(reader, ss.GetSize())
+	if err != nil {
+		return nil, err
+	}
+	encrypted := false
+	for _, file := range zipReader.File {
+		if file.IsEncrypted() {
+			encrypted = true
+			break
+		}
+	}
+	return &model.ArchiveMetaInfo{
+		Comment:   zipReader.Comment,
+		Encrypted: encrypted,
+	}, nil
+}
+
+func (_ *Zip) List(ss *stream.SeekableStream, args model.ArchiveInnerArgs) ([]model.Obj, error) {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return nil, err
+	}
+	zipReader, err := zip.NewReader(reader, ss.GetSize())
+	if err != nil {
+		return nil, err
+	}
+	if args.InnerPath == "/" {
+		ret := make([]model.Obj, 0)
+		passVerified := false
+		for _, file := range zipReader.File {
+			if !passVerified && file.IsEncrypted() {
+				file.SetPassword(args.Password)
+				rc, e := file.Open()
+				if e != nil {
+					return nil, filterPassword(e)
+				}
+				_ = rc.Close()
+				passVerified = true
+			}
+			name := decodeName(file.Name)
+			if strings.Contains(strings.TrimSuffix(name, "/"), "/") {
+				continue
+			}
+			ret = append(ret, toModelObj(file.FileInfo()))
+		}
+		return ret, nil
+	} else {
+		innerPath := strings.TrimPrefix(args.InnerPath, "/") + "/"
+		ret := make([]model.Obj, 0)
+		exist := false
+		for _, file := range zipReader.File {
+			name := decodeName(file.Name)
+			if name == innerPath {
+				exist = true
+			}
+			dir := stdpath.Dir(strings.TrimSuffix(name, "/")) + "/"
+			if dir != innerPath {
+				continue
+			}
+			ret = append(ret, toModelObj(file.FileInfo()))
+		}
+		if !exist {
+			return nil, errs.ObjectNotFound
+		}
+		return ret, nil
+	}
+}
+
+func (_ *Zip) Extract(ss *stream.SeekableStream, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return nil, 0, err
+	}
+	zipReader, err := zip.NewReader(reader, ss.GetSize())
+	if err != nil {
+		return nil, 0, err
+	}
+	innerPath := strings.TrimPrefix(args.InnerPath, "/")
+	for _, file := range zipReader.File {
+		if decodeName(file.Name) == innerPath {
+			if file.IsEncrypted() {
+				file.SetPassword(args.Password)
+			}
+			r, e := file.Open()
+			if e != nil {
+				return nil, 0, e
+			}
+			return r, file.FileInfo().Size(), nil
+		}
+	}
+	return nil, 0, errs.ObjectNotFound
+}
+
+func (_ *Zip) Decompress(ss *stream.SeekableStream, outputPath string, args model.ArchiveInnerArgs, up model.UpdateProgress) error {
+	reader, err := stream.NewReadAtSeeker(ss, 0)
+	if err != nil {
+		return err
+	}
+	zipReader, err := zip.NewReader(reader, ss.GetSize())
+	if err != nil {
+		return err
+	}
+	if args.InnerPath == "/" {
+		for i, file := range zipReader.File {
+			name := decodeName(file.Name)
+			err = decompress(file, name, outputPath, args.Password)
+			if err != nil {
+				return err
+			}
+			up(float64(i+1) * 100.0 / float64(len(zipReader.File)))
+		}
+	} else {
+		innerPath := strings.TrimPrefix(args.InnerPath, "/")
+		innerBase := stdpath.Base(innerPath)
+		createdBaseDir := false
+		for _, file := range zipReader.File {
+			name := decodeName(file.Name)
+			if name == innerPath {
+				err = _decompress(file, outputPath, args.Password, up)
+				if err != nil {
+					return err
+				}
+				break
+			} else if strings.HasPrefix(name, innerPath+"/") {
+				targetPath := stdpath.Join(outputPath, innerBase)
+				if !createdBaseDir {
+					err = os.Mkdir(targetPath, 0700)
+					if err != nil {
+						return err
+					}
+					createdBaseDir = true
+				}
+				restPath := strings.TrimPrefix(name, innerPath+"/")
+				err = decompress(file, restPath, targetPath, args.Password)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+var _ tool.Tool = (*Zip)(nil)
+
+func init() {
+	tool.RegisterTool(&Zip{})
+}
diff --git a/internal/bootstrap/data/user.go b/internal/bootstrap/data/user.go
index 5b596a85852..9c3f8962ad3 100644
--- a/internal/bootstrap/data/user.go
+++ b/internal/bootstrap/data/user.go
@@ -26,13 +26,14 @@ func initUser() {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			salt := random.String(16)
 			admin = &model.User{
-				Username:   "admin",
-				Salt:       salt,
-				PwdHash:    model.TwoHashPwd(adminPassword, salt),
-				Role:       model.ADMIN,
-				BasePath:   "/",
-				Authn:      "[]",
-				Permission: 0xFF, // 0(can see hidden) - 7(can remove)
+				Username: "admin",
+				Salt:     salt,
+				PwdHash:  model.TwoHashPwd(adminPassword, salt),
+				Role:     model.ADMIN,
+				BasePath: "/",
+				Authn:    "[]",
+				// 0(can see hidden) - 7(can remove) & 12(can read archives) - 13(can decompress archives)
+				Permission: 0x30FF,
 			}
 			if err := op.CreateUser(admin); err != nil {
 				panic(err)
diff --git a/internal/bootstrap/patch/v3_41_0/grant_permission.go b/internal/bootstrap/patch/v3_41_0/grant_permission.go
index d658d184d4d..e62d1e8fa90 100644
--- a/internal/bootstrap/patch/v3_41_0/grant_permission.go
+++ b/internal/bootstrap/patch/v3_41_0/grant_permission.go
@@ -5,18 +5,20 @@ import (
 	"github.com/alist-org/alist/v3/pkg/utils"
 )
 
-// GrantAdminPermissions gives admin Permission 0(can see hidden) - 9(webdav manage)
-// This patch is written to help users upgrading from older version better adapt to PR AlistGo/alist#7705.
+// GrantAdminPermissions gives admin Permission 0(can see hidden) - 9(webdav manage) and
+// 12(can read archives) - 13(can decompress archives)
+// This patch is written to help users upgrading from older version better adapt to PR AlistGo/alist#7705 and
+// PR AlistGo/alist#7817.
 func GrantAdminPermissions() {
 	admin, err := op.GetAdmin()
 	if err != nil {
 		utils.Log.Errorf("Cannot grant permissions to admin: %v", err)
 	}
-	if (admin.Permission & 0x3FF) == 0 {
-		admin.Permission |= 0x3FF
-	}
-	err = op.UpdateUser(admin)
-	if err != nil {
-		utils.Log.Errorf("Cannot grant permissions to admin: %v", err)
+	if (admin.Permission & 0x33FF) == 0 {
+		admin.Permission |= 0x33FF
+		err = op.UpdateUser(admin)
+		if err != nil {
+			utils.Log.Errorf("Cannot grant permissions to admin: %v", err)
+		}
 	}
 }
diff --git a/internal/bootstrap/task.go b/internal/bootstrap/task.go
index 3390235320c..9c30c3926b5 100644
--- a/internal/bootstrap/task.go
+++ b/internal/bootstrap/task.go
@@ -16,4 +16,6 @@ func InitTaskManager() {
 	if len(tool.TransferTaskManager.GetAll()) == 0 { //prevent offline downloaded files from being deleted
 		CleanTempDir()
 	}
+	fs.ArchiveDownloadTaskManager = tache.NewManager[*fs.ArchiveDownloadTask](tache.WithWorks(conf.Conf.Tasks.Decompress.Workers), tache.WithPersistFunction(db.GetTaskDataFunc("decompress", conf.Conf.Tasks.Decompress.TaskPersistant), db.UpdateTaskDataFunc("decompress", conf.Conf.Tasks.Decompress.TaskPersistant)), tache.WithMaxRetry(conf.Conf.Tasks.Decompress.MaxRetry))
+	fs.ArchiveContentUploadTaskManager.Manager = tache.NewManager[*fs.ArchiveContentUploadTask](tache.WithWorks(conf.Conf.Tasks.DecompressUpload.Workers), tache.WithMaxRetry(conf.Conf.Tasks.DecompressUpload.MaxRetry)) //decompress upload will not support persist
 }
diff --git a/internal/conf/config.go b/internal/conf/config.go
index d015cda0a91..4f5c2ae0e35 100644
--- a/internal/conf/config.go
+++ b/internal/conf/config.go
@@ -53,10 +53,12 @@ type TaskConfig struct {
 }
 
 type TasksConfig struct {
-	Download TaskConfig `json:"download" envPrefix:"DOWNLOAD_"`
-	Transfer TaskConfig `json:"transfer" envPrefix:"TRANSFER_"`
-	Upload   TaskConfig `json:"upload" envPrefix:"UPLOAD_"`
-	Copy     TaskConfig `json:"copy" envPrefix:"COPY_"`
+	Download         TaskConfig `json:"download" envPrefix:"DOWNLOAD_"`
+	Transfer         TaskConfig `json:"transfer" envPrefix:"TRANSFER_"`
+	Upload           TaskConfig `json:"upload" envPrefix:"UPLOAD_"`
+	Copy             TaskConfig `json:"copy" envPrefix:"COPY_"`
+	Decompress       TaskConfig `json:"decompress" envPrefix:"DECOMPRESS_"`
+	DecompressUpload TaskConfig `json:"decompress_upload" envPrefix:"DECOMPRESS_UPLOAD_"`
 }
 
 type Cors struct {
@@ -169,6 +171,15 @@ func DefaultConfig() *Config {
 				MaxRetry: 2,
 				// TaskPersistant: true,
 			},
+			Decompress: TaskConfig{
+				Workers:  5,
+				MaxRetry: 2,
+				// TaskPersistant: true,
+			},
+			DecompressUpload: TaskConfig{
+				Workers:  5,
+				MaxRetry: 2,
+			},
 		},
 		Cors: Cors{
 			AllowOrigins: []string{"*"},
diff --git a/internal/driver/driver.go b/internal/driver/driver.go
index 6fd5e8d6f90..a1c64144af4 100644
--- a/internal/driver/driver.go
+++ b/internal/driver/driver.go
@@ -123,7 +123,43 @@ type PutURLResult interface {
 	PutURL(ctx context.Context, dstDir model.Obj, name, url string) (model.Obj, error)
 }
 
-type UpdateProgress func(percentage float64)
+type ArchiveReader interface {
+	// GetArchiveMeta get the meta-info of an archive
+	// return errs.WrongArchivePassword if the meta-info is also encrypted but provided password is wrong or empty
+	// return errs.NotImplement to use internal archive tools to get the meta-info, such as the following cases:
+	// 1. the driver do not support the format of the archive but there may be an internal tool do
+	// 2. handling archives is a VIP feature, but the driver does not have VIP access
+	GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error)
+	// ListArchive list the children of model.ArchiveArgs.InnerPath in the archive
+	// return errs.NotImplement to use internal archive tools to list the children
+	// return errs.NotSupport if the folder structure should be acquired from model.ArchiveMeta.GetTree
+	ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error)
+	// Extract get url/filepath/reader of a file in the archive
+	// return errs.NotImplement to use internal archive tools to extract
+	Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error)
+}
+
+type ArchiveGetter interface {
+	// ArchiveGet get file by inner path
+	// return errs.NotImplement to use internal archive tools to get the children
+	// return errs.NotSupport if the folder structure should be acquired from model.ArchiveMeta.GetTree
+	ArchiveGet(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (model.Obj, error)
+}
+
+type ArchiveDecompress interface {
+	ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) error
+}
+
+type ArchiveDecompressResult interface {
+	// ArchiveDecompress decompress an archive
+	// when args.PutIntoNewDir, the new sub-folder should be named the same to the archive but without the extension
+	// return each decompressed obj from the root path of the archive when args.PutIntoNewDir is false
+	// return only the newly created folder when args.PutIntoNewDir is true
+	// return errs.NotImplement to use internal archive tools to decompress
+	ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error)
+}
+
+type UpdateProgress model.UpdateProgress
 
 type Progress struct {
 	Total int64
diff --git a/internal/errs/errors.go b/internal/errs/errors.go
index ecfe43e3dc0..2a22dca1e6f 100644
--- a/internal/errs/errors.go
+++ b/internal/errs/errors.go
@@ -19,6 +19,10 @@ var (
 	StorageNotFound  = errors.New("storage not found")
 	StreamIncomplete = errors.New("upload/download stream incomplete, possible network issue")
 	StreamPeekFail   = errors.New("StreamPeekFail")
+
+	UnknownArchiveFormat      = errors.New("unknown archive format")
+	WrongArchivePassword      = errors.New("wrong archive password")
+	DriverExtractNotSupported = errors.New("driver extraction not supported")
 )
 
 // NewErr wrap constant error with an extra message
diff --git a/internal/fs/archive.go b/internal/fs/archive.go
new file mode 100644
index 00000000000..f3e05926e88
--- /dev/null
+++ b/internal/fs/archive.go
@@ -0,0 +1,395 @@
+package fs
+
+import (
+	"context"
+	stderrors "errors"
+	"fmt"
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/conf"
+	"github.com/alist-org/alist/v3/internal/driver"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"github.com/alist-org/alist/v3/internal/task"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	"github.com/xhofe/tache"
+	"io"
+	"math/rand"
+	"mime"
+	"net/http"
+	"os"
+	stdpath "path"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type ArchiveDownloadTask struct {
+	task.TaskExtension
+	model.ArchiveDecompressArgs
+	status       string
+	SrcObjPath   string
+	DstDirPath   string
+	srcStorage   driver.Driver
+	dstStorage   driver.Driver
+	SrcStorageMp string
+	DstStorageMp string
+	Tool         tool.Tool
+}
+
+func (t *ArchiveDownloadTask) GetName() string {
+	return fmt.Sprintf("decompress [%s](%s)[%s] to [%s](%s) with password <%s>", t.SrcStorageMp, t.SrcObjPath,
+		t.InnerPath, t.DstStorageMp, t.DstDirPath, t.Password)
+}
+
+func (t *ArchiveDownloadTask) GetStatus() string {
+	return t.status
+}
+
+func (t *ArchiveDownloadTask) Run() error {
+	t.ClearEndTime()
+	t.SetStartTime(time.Now())
+	defer func() { t.SetEndTime(time.Now()) }()
+	uploadTask, err := t.RunWithoutPushUploadTask()
+	if err != nil {
+		return err
+	}
+	ArchiveContentUploadTaskManager.Add(uploadTask)
+	return nil
+}
+
+func (t *ArchiveDownloadTask) RunWithoutPushUploadTask() (*ArchiveContentUploadTask, error) {
+	var err error
+	if t.srcStorage == nil {
+		t.srcStorage, err = op.GetStorageByMountPath(t.SrcStorageMp)
+	}
+	l, srcObj, err := op.Link(t.Ctx(), t.srcStorage, t.SrcObjPath, model.LinkArgs{
+		Header: http.Header{},
+	})
+	if err != nil {
+		return nil, err
+	}
+	fs := stream.FileStream{
+		Obj: srcObj,
+		Ctx: t.Ctx(),
+	}
+	ss, err := stream.NewSeekableStream(fs, l)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		if err := ss.Close(); err != nil {
+			log.Errorf("failed to close file streamer, %v", err)
+		}
+	}()
+	var decompressUp model.UpdateProgress
+	if t.CacheFull {
+		t.SetTotalBytes(srcObj.GetSize())
+		t.status = "getting src object"
+		_, err = ss.CacheFullInTempFileAndUpdateProgress(t.SetProgress)
+		if err != nil {
+			return nil, err
+		}
+		decompressUp = func(_ float64) {}
+	} else {
+		decompressUp = t.SetProgress
+	}
+	t.status = "walking and decompressing"
+	dir, err := os.MkdirTemp(conf.Conf.TempDir, "dir-*")
+	if err != nil {
+		return nil, err
+	}
+	err = t.Tool.Decompress(ss, dir, t.ArchiveInnerArgs, decompressUp)
+	if err != nil {
+		return nil, err
+	}
+	baseName := strings.TrimSuffix(srcObj.GetName(), stdpath.Ext(srcObj.GetName()))
+	uploadTask := &ArchiveContentUploadTask{
+		TaskExtension: task.TaskExtension{
+			Creator: t.GetCreator(),
+		},
+		ObjName:      baseName,
+		InPlace:      !t.PutIntoNewDir,
+		FilePath:     dir,
+		DstDirPath:   t.DstDirPath,
+		dstStorage:   t.dstStorage,
+		DstStorageMp: t.DstStorageMp,
+	}
+	return uploadTask, nil
+}
+
+var ArchiveDownloadTaskManager *tache.Manager[*ArchiveDownloadTask]
+
+type ArchiveContentUploadTask struct {
+	task.TaskExtension
+	status       string
+	ObjName      string
+	InPlace      bool
+	FilePath     string
+	DstDirPath   string
+	dstStorage   driver.Driver
+	DstStorageMp string
+	finalized    bool
+}
+
+func (t *ArchiveContentUploadTask) GetName() string {
+	return fmt.Sprintf("upload %s to [%s](%s)", t.ObjName, t.DstStorageMp, t.DstDirPath)
+}
+
+func (t *ArchiveContentUploadTask) GetStatus() string {
+	return t.status
+}
+
+func (t *ArchiveContentUploadTask) Run() error {
+	t.ClearEndTime()
+	t.SetStartTime(time.Now())
+	defer func() { t.SetEndTime(time.Now()) }()
+	return t.RunWithNextTaskCallback(func(nextTsk *ArchiveContentUploadTask) error {
+		ArchiveContentUploadTaskManager.Add(nextTsk)
+		return nil
+	})
+}
+
+func (t *ArchiveContentUploadTask) RunWithNextTaskCallback(f func(nextTsk *ArchiveContentUploadTask) error) error {
+	var err error
+	if t.dstStorage == nil {
+		t.dstStorage, err = op.GetStorageByMountPath(t.DstStorageMp)
+	}
+	info, err := os.Stat(t.FilePath)
+	if err != nil {
+		return err
+	}
+	if info.IsDir() {
+		t.status = "src object is dir, listing objs"
+		nextDstPath := t.DstDirPath
+		if !t.InPlace {
+			nextDstPath = stdpath.Join(nextDstPath, t.ObjName)
+			err = op.MakeDir(t.Ctx(), t.dstStorage, nextDstPath)
+			if err != nil {
+				return err
+			}
+		}
+		entries, err := os.ReadDir(t.FilePath)
+		if err != nil {
+			return err
+		}
+		var es error
+		for _, entry := range entries {
+			var nextFilePath string
+			if entry.IsDir() {
+				nextFilePath, err = moveToTempPath(stdpath.Join(t.FilePath, entry.Name()), "dir-")
+			} else {
+				nextFilePath, err = moveToTempPath(stdpath.Join(t.FilePath, entry.Name()), "file-")
+			}
+			if err != nil {
+				es = stderrors.Join(es, err)
+				continue
+			}
+			err = f(&ArchiveContentUploadTask{
+				TaskExtension: task.TaskExtension{
+					Creator: t.GetCreator(),
+				},
+				ObjName:      entry.Name(),
+				InPlace:      false,
+				FilePath:     nextFilePath,
+				DstDirPath:   nextDstPath,
+				dstStorage:   t.dstStorage,
+				DstStorageMp: t.DstStorageMp,
+			})
+			if err != nil {
+				es = stderrors.Join(es, err)
+			}
+		}
+		if es != nil {
+			return es
+		}
+	} else {
+		t.SetTotalBytes(info.Size())
+		file, err := os.Open(t.FilePath)
+		if err != nil {
+			return err
+		}
+		fs := &stream.FileStream{
+			Obj: &model.Object{
+				Name:     t.ObjName,
+				Size:     info.Size(),
+				Modified: time.Now(),
+			},
+			Mimetype:     mime.TypeByExtension(filepath.Ext(t.ObjName)),
+			WebPutAsTask: true,
+			Reader:       file,
+		}
+		fs.Closers.Add(file)
+		t.status = "uploading"
+		err = op.Put(t.Ctx(), t.dstStorage, t.DstDirPath, fs, t.SetProgress, true)
+		if err != nil {
+			return err
+		}
+	}
+	t.deleteSrcFile()
+	return nil
+}
+
+func (t *ArchiveContentUploadTask) Cancel() {
+	t.TaskExtension.Cancel()
+	t.deleteSrcFile()
+}
+
+func (t *ArchiveContentUploadTask) deleteSrcFile() {
+	if !t.finalized {
+		_ = os.RemoveAll(t.FilePath)
+		t.finalized = true
+	}
+}
+
+func moveToTempPath(path, prefix string) (string, error) {
+	newPath, err := genTempFileName(prefix)
+	if err != nil {
+		return "", err
+	}
+	err = os.Rename(path, newPath)
+	if err != nil {
+		return "", err
+	}
+	return newPath, nil
+}
+
+func genTempFileName(prefix string) (string, error) {
+	retry := 0
+	for retry < 10000 {
+		newPath := stdpath.Join(conf.Conf.TempDir, prefix+strconv.FormatUint(uint64(rand.Uint32()), 10))
+		if _, err := os.Stat(newPath); err != nil {
+			if os.IsNotExist(err) {
+				return newPath, nil
+			} else {
+				return "", err
+			}
+		}
+		retry++
+	}
+	return "", errors.New("failed to generate temp-file name: too many retries")
+}
+
+type archiveContentUploadTaskManagerType struct {
+	*tache.Manager[*ArchiveContentUploadTask]
+}
+
+func (m *archiveContentUploadTaskManagerType) Remove(id string) {
+	if t, ok := m.GetByID(id); ok {
+		t.deleteSrcFile()
+		m.Manager.Remove(id)
+	}
+}
+
+func (m *archiveContentUploadTaskManagerType) RemoveAll() {
+	tasks := m.GetAll()
+	for _, t := range tasks {
+		m.Remove(t.GetID())
+	}
+}
+
+func (m *archiveContentUploadTaskManagerType) RemoveByState(state ...tache.State) {
+	tasks := m.GetByState(state...)
+	for _, t := range tasks {
+		m.Remove(t.GetID())
+	}
+}
+
+func (m *archiveContentUploadTaskManagerType) RemoveByCondition(condition func(task *ArchiveContentUploadTask) bool) {
+	tasks := m.GetByCondition(condition)
+	for _, t := range tasks {
+		m.Remove(t.GetID())
+	}
+}
+
+var ArchiveContentUploadTaskManager = &archiveContentUploadTaskManagerType{
+	Manager: nil,
+}
+
+func archiveMeta(ctx context.Context, path string, args model.ArchiveMetaArgs) (*model.ArchiveMetaProvider, error) {
+	storage, actualPath, err := op.GetStorageAndActualPath(path)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed get storage")
+	}
+	return op.GetArchiveMeta(ctx, storage, actualPath, args)
+}
+
+func archiveList(ctx context.Context, path string, args model.ArchiveListArgs) ([]model.Obj, error) {
+	storage, actualPath, err := op.GetStorageAndActualPath(path)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed get storage")
+	}
+	return op.ListArchive(ctx, storage, actualPath, args)
+}
+
+func archiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) (task.TaskExtensionInfo, error) {
+	srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed get src storage")
+	}
+	dstStorage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed get dst storage")
+	}
+	if srcStorage.GetStorage() == dstStorage.GetStorage() {
+		err = op.ArchiveDecompress(ctx, srcStorage, srcObjActualPath, dstDirActualPath, args, lazyCache...)
+		if !errors.Is(err, errs.NotImplement) {
+			return nil, err
+		}
+	}
+	ext := stdpath.Ext(srcObjActualPath)
+	t, err := tool.GetArchiveTool(ext)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "failed get [%s] archive tool", ext)
+	}
+	taskCreator, _ := ctx.Value("user").(*model.User)
+	tsk := &ArchiveDownloadTask{
+		TaskExtension: task.TaskExtension{
+			Creator: taskCreator,
+		},
+		ArchiveDecompressArgs: args,
+		srcStorage:            srcStorage,
+		dstStorage:            dstStorage,
+		SrcObjPath:            srcObjActualPath,
+		DstDirPath:            dstDirActualPath,
+		SrcStorageMp:          srcStorage.GetStorage().MountPath,
+		DstStorageMp:          dstStorage.GetStorage().MountPath,
+		Tool:                  t,
+	}
+	if ctx.Value(conf.NoTaskKey) != nil {
+		uploadTask, err := tsk.RunWithoutPushUploadTask()
+		if err != nil {
+			return nil, errors.WithMessagef(err, "failed download [%s]", srcObjPath)
+		}
+		defer uploadTask.deleteSrcFile()
+		var callback func(t *ArchiveContentUploadTask) error
+		callback = func(t *ArchiveContentUploadTask) error {
+			e := t.RunWithNextTaskCallback(callback)
+			t.deleteSrcFile()
+			return e
+		}
+		return nil, uploadTask.RunWithNextTaskCallback(callback)
+	} else {
+		ArchiveDownloadTaskManager.Add(tsk)
+		return tsk, nil
+	}
+}
+
+func archiveDriverExtract(ctx context.Context, path string, args model.ArchiveInnerArgs) (*model.Link, model.Obj, error) {
+	storage, actualPath, err := op.GetStorageAndActualPath(path)
+	if err != nil {
+		return nil, nil, errors.WithMessage(err, "failed get storage")
+	}
+	return op.DriverExtract(ctx, storage, actualPath, args)
+}
+
+func archiveInternalExtract(ctx context.Context, path string, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	storage, actualPath, err := op.GetStorageAndActualPath(path)
+	if err != nil {
+		return nil, 0, errors.WithMessage(err, "failed get storage")
+	}
+	return op.InternalExtract(ctx, storage, actualPath, args)
+}
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 24f1d47fa5e..a873f917301 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -7,6 +7,7 @@ import (
 	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/task"
 	log "github.com/sirupsen/logrus"
+	"io"
 )
 
 // the param named path of functions in this package is a mount path
@@ -109,6 +110,46 @@ func PutAsTask(ctx context.Context, dstDirPath string, file model.FileStreamer)
 	return t, err
 }
 
+func ArchiveMeta(ctx context.Context, path string, args model.ArchiveMetaArgs) (*model.ArchiveMetaProvider, error) {
+	meta, err := archiveMeta(ctx, path, args)
+	if err != nil {
+		log.Errorf("failed get archive meta %s: %+v", path, err)
+	}
+	return meta, err
+}
+
+func ArchiveList(ctx context.Context, path string, args model.ArchiveListArgs) ([]model.Obj, error) {
+	objs, err := archiveList(ctx, path, args)
+	if err != nil {
+		log.Errorf("failed list archive [%s]%s: %+v", path, args.InnerPath, err)
+	}
+	return objs, err
+}
+
+func ArchiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) (task.TaskExtensionInfo, error) {
+	t, err := archiveDecompress(ctx, srcObjPath, dstDirPath, args, lazyCache...)
+	if err != nil {
+		log.Errorf("failed decompress [%s]%s: %+v", srcObjPath, args.InnerPath, err)
+	}
+	return t, err
+}
+
+func ArchiveDriverExtract(ctx context.Context, path string, args model.ArchiveInnerArgs) (*model.Link, model.Obj, error) {
+	l, obj, err := archiveDriverExtract(ctx, path, args)
+	if err != nil {
+		log.Errorf("failed extract [%s]%s: %+v", path, args.InnerPath, err)
+	}
+	return l, obj, err
+}
+
+func ArchiveInternalExtract(ctx context.Context, path string, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	l, obj, err := archiveInternalExtract(ctx, path, args)
+	if err != nil {
+		log.Errorf("failed extract [%s]%s: %+v", path, args.InnerPath, err)
+	}
+	return l, obj, err
+}
+
 type GetStoragesArgs struct {
 }
 
diff --git a/internal/model/archive.go b/internal/model/archive.go
new file mode 100644
index 00000000000..03ac7c360a5
--- /dev/null
+++ b/internal/model/archive.go
@@ -0,0 +1,49 @@
+package model
+
+type ObjTree interface {
+	Obj
+	GetChildren() []ObjTree
+}
+
+type ObjectTree struct {
+	Object
+	Children []ObjTree
+}
+
+func (t *ObjectTree) GetChildren() []ObjTree {
+	return t.Children
+}
+
+type ArchiveMeta interface {
+	GetComment() string
+	// IsEncrypted means if the content of the archive requires a password to access
+	// GetArchiveMeta should return errs.WrongArchivePassword if the meta-info is also encrypted,
+	// and the provided password is empty.
+	IsEncrypted() bool
+	// GetTree directly returns the full folder structure
+	// returns nil if the folder structure should be acquired by calling driver.ArchiveReader.ListArchive
+	GetTree() []ObjTree
+}
+
+type ArchiveMetaInfo struct {
+	Comment   string
+	Encrypted bool
+	Tree      []ObjTree
+}
+
+func (m *ArchiveMetaInfo) GetComment() string {
+	return m.Comment
+}
+
+func (m *ArchiveMetaInfo) IsEncrypted() bool {
+	return m.Encrypted
+}
+
+func (m *ArchiveMetaInfo) GetTree() []ObjTree {
+	return m.Tree
+}
+
+type ArchiveMetaProvider struct {
+	ArchiveMeta
+	DriverProviding bool
+}
diff --git a/internal/model/args.go b/internal/model/args.go
index 613699b95b4..a9feeb206ef 100644
--- a/internal/model/args.go
+++ b/internal/model/args.go
@@ -48,6 +48,33 @@ type FsOtherArgs struct {
 	Method string      `json:"method" form:"method"`
 	Data   interface{} `json:"data" form:"data"`
 }
+
+type ArchiveArgs struct {
+	Password string
+	LinkArgs
+}
+
+type ArchiveInnerArgs struct {
+	ArchiveArgs
+	InnerPath string
+}
+
+type ArchiveMetaArgs struct {
+	ArchiveArgs
+	Refresh bool
+}
+
+type ArchiveListArgs struct {
+	ArchiveInnerArgs
+	Refresh bool
+}
+
+type ArchiveDecompressArgs struct {
+	ArchiveInnerArgs
+	CacheFull     bool
+	PutIntoNewDir bool
+}
+
 type RangeReadCloserIF interface {
 	RangeRead(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error)
 	utils.ClosersIF
diff --git a/internal/model/obj.go b/internal/model/obj.go
index 122fb546278..2a72ca9eff4 100644
--- a/internal/model/obj.go
+++ b/internal/model/obj.go
@@ -48,8 +48,11 @@ type FileStreamer interface {
 	RangeRead(http_range.Range) (io.Reader, error)
 	//for a non-seekable Stream, if Read is called, this function won't work
 	CacheFullInTempFile() (File, error)
+	CacheFullInTempFileAndUpdateProgress(up UpdateProgress) (File, error)
 }
 
+type UpdateProgress func(percentage float64)
+
 type URL interface {
 	URL() string
 }
diff --git a/internal/model/user.go b/internal/model/user.go
index f75fc6875c5..eaa0fed9d09 100644
--- a/internal/model/user.go
+++ b/internal/model/user.go
@@ -44,6 +44,8 @@ type User struct {
 	//   9:  webdav write
 	//   10: ftp/sftp login and read
 	//   11: ftp/sftp write
+	//   12: can read archives
+	//   13: can decompress archives
 	Permission int32  `json:"permission"`
 	OtpSecret  string `json:"-"`
 	SsoID      string `json:"sso_id"` // unique by sso platform
@@ -127,6 +129,14 @@ func (u *User) CanFTPManage() bool {
 	return (u.Permission>>11)&1 == 1
 }
 
+func (u *User) CanReadArchives() bool {
+	return (u.Permission>>12)&1 == 1
+}
+
+func (u *User) CanDecompress() bool {
+	return (u.Permission>>13)&1 == 1
+}
+
 func (u *User) JoinPath(reqPath string) (string, error) {
 	return utils.JoinBasePath(u.BasePath, reqPath)
 }
diff --git a/internal/op/archive.go b/internal/op/archive.go
new file mode 100644
index 00000000000..6a9fa084778
--- /dev/null
+++ b/internal/op/archive.go
@@ -0,0 +1,424 @@
+package op
+
+import (
+	"context"
+	stderrors "errors"
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/stream"
+	"io"
+	stdpath "path"
+	"strings"
+	"time"
+
+	"github.com/Xhofe/go-cache"
+	"github.com/alist-org/alist/v3/internal/driver"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/pkg/singleflight"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+)
+
+var archiveMetaCache = cache.NewMemCache(cache.WithShards[*model.ArchiveMetaProvider](64))
+var archiveMetaG singleflight.Group[*model.ArchiveMetaProvider]
+
+func GetArchiveMeta(ctx context.Context, storage driver.Driver, path string, args model.ArchiveMetaArgs) (*model.ArchiveMetaProvider, error) {
+	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
+		return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
+	}
+	path = utils.FixAndCleanPath(path)
+	key := Key(storage, path)
+	if !args.Refresh {
+		if meta, ok := archiveMetaCache.Get(key); ok {
+			log.Debugf("use cache when get %s archive meta", path)
+			return meta, nil
+		}
+	}
+	fn := func() (*model.ArchiveMetaProvider, error) {
+		_, m, err := getArchiveMeta(ctx, storage, path, args)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to get %s archive met: %+v", path, err)
+		}
+		if !storage.Config().NoCache {
+			archiveMetaCache.Set(key, m, cache.WithEx[*model.ArchiveMetaProvider](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
+		}
+		return m, nil
+	}
+	if storage.Config().OnlyLocal {
+		meta, err := fn()
+		return meta, err
+	}
+	meta, err, _ := archiveMetaG.Do(key, fn)
+	return meta, err
+}
+
+func getArchiveToolAndStream(ctx context.Context, storage driver.Driver, path string, args model.LinkArgs) (model.Obj, tool.Tool, *stream.SeekableStream, error) {
+	l, obj, err := Link(ctx, storage, path, args)
+	if err != nil {
+		return nil, nil, nil, errors.WithMessagef(err, "failed get [%s] link", path)
+	}
+	ext := stdpath.Ext(obj.GetName())
+	t, err := tool.GetArchiveTool(ext)
+	if err != nil {
+		return nil, nil, nil, errors.WithMessagef(err, "failed get [%s] archive tool", ext)
+	}
+	ss, err := stream.NewSeekableStream(stream.FileStream{Ctx: ctx, Obj: obj}, l)
+	if err != nil {
+		return nil, nil, nil, errors.WithMessagef(err, "failed get [%s] stream", path)
+	}
+	return obj, t, ss, nil
+}
+
+func getArchiveMeta(ctx context.Context, storage driver.Driver, path string, args model.ArchiveMetaArgs) (model.Obj, *model.ArchiveMetaProvider, error) {
+	storageAr, ok := storage.(driver.ArchiveReader)
+	if ok {
+		obj, err := GetUnwrap(ctx, storage, path)
+		if err != nil {
+			return nil, nil, errors.WithMessage(err, "failed to get file")
+		}
+		if obj.IsDir() {
+			return nil, nil, errors.WithStack(errs.NotFile)
+		}
+		meta, err := storageAr.GetArchiveMeta(ctx, obj, args.ArchiveArgs)
+		if !errors.Is(err, errs.NotImplement) {
+			return obj, &model.ArchiveMetaProvider{ArchiveMeta: meta, DriverProviding: true}, err
+		}
+	}
+	obj, t, ss, err := getArchiveToolAndStream(ctx, storage, path, args.LinkArgs)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer func() {
+		if err := ss.Close(); err != nil {
+			log.Errorf("failed to close file streamer, %v", err)
+		}
+	}()
+	meta, err := t.GetMeta(ss, args.ArchiveArgs)
+	return obj, &model.ArchiveMetaProvider{ArchiveMeta: meta, DriverProviding: false}, err
+}
+
+var archiveListCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
+var archiveListG singleflight.Group[[]model.Obj]
+
+func ListArchive(ctx context.Context, storage driver.Driver, path string, args model.ArchiveListArgs) ([]model.Obj, error) {
+	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
+		return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
+	}
+	path = utils.FixAndCleanPath(path)
+	metaKey := Key(storage, path)
+	key := stdpath.Join(metaKey, args.InnerPath)
+	if !args.Refresh {
+		if files, ok := archiveListCache.Get(key); ok {
+			log.Debugf("use cache when list archive [%s]%s", path, args.InnerPath)
+			return files, nil
+		}
+		if meta, ok := archiveMetaCache.Get(metaKey); ok {
+			log.Debugf("use meta cache when list archive [%s]%s", path, args.InnerPath)
+			return getChildrenFromArchiveMeta(meta, args.InnerPath)
+		}
+	}
+	objs, err, _ := archiveListG.Do(key, func() ([]model.Obj, error) {
+		obj, files, err := listArchive(ctx, storage, path, args)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to list archive [%s]%s: %+v", path, args.InnerPath, err)
+		}
+		// set path
+		for _, f := range files {
+			if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && obj.GetPath() != "" {
+				s.SetPath(stdpath.Join(obj.GetPath(), args.InnerPath, f.GetName()))
+			}
+		}
+		// warp obj name
+		model.WrapObjsName(files)
+		// sort objs
+		if storage.Config().LocalSort {
+			model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
+		}
+		model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
+		if !storage.Config().NoCache {
+			if len(files) > 0 {
+				log.Debugf("set cache: %s => %+v", key, files)
+				archiveListCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
+			} else {
+				log.Debugf("del cache: %s", key)
+				archiveListCache.Del(key)
+			}
+		}
+		return files, nil
+	})
+	return objs, err
+}
+
+func _listArchive(ctx context.Context, storage driver.Driver, path string, args model.ArchiveListArgs) (model.Obj, []model.Obj, error) {
+	storageAr, ok := storage.(driver.ArchiveReader)
+	if ok {
+		obj, err := GetUnwrap(ctx, storage, path)
+		if err != nil {
+			return nil, nil, errors.WithMessage(err, "failed to get file")
+		}
+		if obj.IsDir() {
+			return nil, nil, errors.WithStack(errs.NotFile)
+		}
+		files, err := storageAr.ListArchive(ctx, obj, args.ArchiveInnerArgs)
+		if !errors.Is(err, errs.NotImplement) {
+			return obj, files, err
+		}
+	}
+	obj, t, ss, err := getArchiveToolAndStream(ctx, storage, path, args.LinkArgs)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer func() {
+		if err := ss.Close(); err != nil {
+			log.Errorf("failed to close file streamer, %v", err)
+		}
+	}()
+	files, err := t.List(ss, args.ArchiveInnerArgs)
+	return obj, files, err
+}
+
+func listArchive(ctx context.Context, storage driver.Driver, path string, args model.ArchiveListArgs) (model.Obj, []model.Obj, error) {
+	obj, files, err := _listArchive(ctx, storage, path, args)
+	if errors.Is(err, errs.NotSupport) {
+		var meta model.ArchiveMeta
+		meta, err = GetArchiveMeta(ctx, storage, path, model.ArchiveMetaArgs{
+			ArchiveArgs: args.ArchiveArgs,
+			Refresh:     args.Refresh,
+		})
+		if err != nil {
+			return nil, nil, err
+		}
+		files, err = getChildrenFromArchiveMeta(meta, args.InnerPath)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+	if err == nil && obj == nil {
+		obj, err = GetUnwrap(ctx, storage, path)
+	}
+	if err != nil {
+		return nil, nil, err
+	}
+	return obj, files, err
+}
+
+func getChildrenFromArchiveMeta(meta model.ArchiveMeta, innerPath string) ([]model.Obj, error) {
+	obj := meta.GetTree()
+	if obj == nil {
+		return nil, errors.WithStack(errs.NotImplement)
+	}
+	dirs := splitPath(innerPath)
+	for _, dir := range dirs {
+		var next model.ObjTree
+		for _, c := range obj {
+			if c.GetName() == dir {
+				next = c
+				break
+			}
+		}
+		if next == nil {
+			return nil, errors.WithStack(errs.ObjectNotFound)
+		}
+		if !next.IsDir() || next.GetChildren() == nil {
+			return nil, errors.WithStack(errs.NotFolder)
+		}
+		obj = next.GetChildren()
+	}
+	return utils.SliceConvert(obj, func(src model.ObjTree) (model.Obj, error) {
+		return src, nil
+	})
+}
+
+func splitPath(path string) []string {
+	var parts []string
+	for {
+		dir, file := stdpath.Split(path)
+		if file == "" {
+			break
+		}
+		parts = append([]string{file}, parts...)
+		path = strings.TrimSuffix(dir, "/")
+	}
+	return parts
+}
+
+func ArchiveGet(ctx context.Context, storage driver.Driver, path string, args model.ArchiveListArgs) (model.Obj, model.Obj, error) {
+	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
+		return nil, nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
+	}
+	path = utils.FixAndCleanPath(path)
+	af, err := GetUnwrap(ctx, storage, path)
+	if err != nil {
+		return nil, nil, errors.WithMessage(err, "failed to get file")
+	}
+	if af.IsDir() {
+		return nil, nil, errors.WithStack(errs.NotFile)
+	}
+	if g, ok := storage.(driver.ArchiveGetter); ok {
+		obj, err := g.ArchiveGet(ctx, af, args.ArchiveInnerArgs)
+		if err == nil {
+			return af, model.WrapObjName(obj), nil
+		}
+	}
+
+	if utils.PathEqual(args.InnerPath, "/") {
+		return af, &model.ObjWrapName{
+			Name: RootName,
+			Obj: &model.Object{
+				Name:     af.GetName(),
+				Path:     af.GetPath(),
+				ID:       af.GetID(),
+				Size:     af.GetSize(),
+				Modified: af.ModTime(),
+				IsFolder: true,
+			},
+		}, nil
+	}
+
+	innerDir, name := stdpath.Split(args.InnerPath)
+	args.InnerPath = strings.TrimSuffix(innerDir, "/")
+	files, err := ListArchive(ctx, storage, path, args)
+	if err != nil {
+		return nil, nil, errors.WithMessage(err, "failed get parent list")
+	}
+	for _, f := range files {
+		if f.GetName() == name {
+			return af, f, nil
+		}
+	}
+	return nil, nil, errors.WithStack(errs.ObjectNotFound)
+}
+
+type extractLink struct {
+	Link *model.Link
+	Obj  model.Obj
+}
+
+var extractCache = cache.NewMemCache(cache.WithShards[*extractLink](16))
+var extractG singleflight.Group[*extractLink]
+
+func DriverExtract(ctx context.Context, storage driver.Driver, path string, args model.ArchiveInnerArgs) (*model.Link, model.Obj, error) {
+	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
+		return nil, nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
+	}
+	key := stdpath.Join(Key(storage, path), args.InnerPath)
+	if link, ok := extractCache.Get(key); ok {
+		return link.Link, link.Obj, nil
+	} else if link, ok := extractCache.Get(key + ":" + args.IP); ok {
+		return link.Link, link.Obj, nil
+	}
+	fn := func() (*extractLink, error) {
+		link, err := driverExtract(ctx, storage, path, args)
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed extract archive")
+		}
+		if link.Link.Expiration != nil {
+			if link.Link.IPCacheKey {
+				key = key + ":" + args.IP
+			}
+			extractCache.Set(key, link, cache.WithEx[*extractLink](*link.Link.Expiration))
+		}
+		return link, nil
+	}
+	if storage.Config().OnlyLocal {
+		link, err := fn()
+		if err != nil {
+			return nil, nil, err
+		}
+		return link.Link, link.Obj, nil
+	}
+	link, err, _ := extractG.Do(key, fn)
+	if err != nil {
+		return nil, nil, err
+	}
+	return link.Link, link.Obj, err
+}
+
+func driverExtract(ctx context.Context, storage driver.Driver, path string, args model.ArchiveInnerArgs) (*extractLink, error) {
+	storageAr, ok := storage.(driver.ArchiveReader)
+	if !ok {
+		return nil, errs.DriverExtractNotSupported
+	}
+	archiveFile, extracted, err := ArchiveGet(ctx, storage, path, model.ArchiveListArgs{
+		ArchiveInnerArgs: args,
+		Refresh:          false,
+	})
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed to get file")
+	}
+	if extracted.IsDir() {
+		return nil, errors.WithStack(errs.NotFile)
+	}
+	link, err := storageAr.Extract(ctx, archiveFile, args)
+	return &extractLink{Link: link, Obj: extracted}, err
+}
+
+type streamWithParent struct {
+	rc     io.ReadCloser
+	parent *stream.SeekableStream
+}
+
+func (s *streamWithParent) Read(p []byte) (int, error) {
+	return s.rc.Read(p)
+}
+
+func (s *streamWithParent) Close() error {
+	err1 := s.rc.Close()
+	err2 := s.parent.Close()
+	return stderrors.Join(err1, err2)
+}
+
+func InternalExtract(ctx context.Context, storage driver.Driver, path string, args model.ArchiveInnerArgs) (io.ReadCloser, int64, error) {
+	_, t, ss, err := getArchiveToolAndStream(ctx, storage, path, args.LinkArgs)
+	if err != nil {
+		return nil, 0, err
+	}
+	rc, size, err := t.Extract(ss, args)
+	if err != nil {
+		if e := ss.Close(); e != nil {
+			log.Errorf("failed to close file streamer, %v", e)
+		}
+		return nil, 0, err
+	}
+	return &streamWithParent{rc: rc, parent: ss}, size, nil
+}
+
+func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) error {
+	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
+		return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
+	}
+	srcPath = utils.FixAndCleanPath(srcPath)
+	dstDirPath = utils.FixAndCleanPath(dstDirPath)
+	srcObj, err := GetUnwrap(ctx, storage, srcPath)
+	if err != nil {
+		return errors.WithMessage(err, "failed to get src object")
+	}
+	dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
+	if err != nil {
+		return errors.WithMessage(err, "failed to get dst dir")
+	}
+
+	switch s := storage.(type) {
+	case driver.ArchiveDecompressResult:
+		var newObjs []model.Obj
+		newObjs, err = s.ArchiveDecompress(ctx, srcObj, dstDir, args)
+		if err == nil {
+			if newObjs != nil && len(newObjs) > 0 {
+				for _, newObj := range newObjs {
+					addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
+				}
+			} else if !utils.IsBool(lazyCache...) {
+				ClearCache(storage, dstDirPath)
+			}
+		}
+	case driver.ArchiveDecompress:
+		err = s.ArchiveDecompress(ctx, srcObj, dstDir, args)
+		if err == nil && !utils.IsBool(lazyCache...) {
+			ClearCache(storage, dstDirPath)
+		}
+	default:
+		return errs.NotImplement
+	}
+	return errors.WithStack(err)
+}
diff --git a/internal/stream/stream.go b/internal/stream/stream.go
index 2c9543c1d94..b19eb07753d 100644
--- a/internal/stream/stream.go
+++ b/internal/stream/stream.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"os"
 
 	"github.com/alist-org/alist/v3/internal/errs"
@@ -60,6 +61,8 @@ func (f *FileStream) Close() error {
 		err2 = os.RemoveAll(f.tmpFile.Name())
 		if err2 != nil {
 			err2 = errs.NewErr(err2, "failed to remove tmpFile [%s]", f.tmpFile.Name())
+		} else {
+			f.tmpFile = nil
 		}
 	}
 
@@ -92,6 +95,26 @@ func (f *FileStream) CacheFullInTempFile() (model.File, error) {
 	return f.tmpFile, nil
 }
 
+func (f *FileStream) CacheFullInTempFileAndUpdateProgress(up model.UpdateProgress) (model.File, error) {
+	if f.tmpFile != nil {
+		return f.tmpFile, nil
+	}
+	if file, ok := f.Reader.(model.File); ok {
+		return file, nil
+	}
+	tmpF, err := utils.CreateTempFile(&ReaderUpdatingProgress{
+		Reader:         f,
+		UpdateProgress: up,
+	}, f.GetSize())
+	if err != nil {
+		return nil, err
+	}
+	f.Add(tmpF)
+	f.tmpFile = tmpF
+	f.Reader = tmpF
+	return f.tmpFile, nil
+}
+
 const InMemoryBufMaxSize = 10 // Megabytes
 const InMemoryBufMaxSizeBytes = InMemoryBufMaxSize * 1024 * 1024
 
@@ -247,7 +270,202 @@ func (ss *SeekableStream) CacheFullInTempFile() (model.File, error) {
 	return ss.tmpFile, nil
 }
 
+func (ss *SeekableStream) CacheFullInTempFileAndUpdateProgress(up model.UpdateProgress) (model.File, error) {
+	if ss.tmpFile != nil {
+		return ss.tmpFile, nil
+	}
+	if ss.mFile != nil {
+		return ss.mFile, nil
+	}
+	tmpF, err := utils.CreateTempFile(&ReaderUpdatingProgress{
+		Reader:         ss,
+		UpdateProgress: up,
+	}, ss.GetSize())
+	if err != nil {
+		return nil, err
+	}
+	ss.Add(tmpF)
+	ss.tmpFile = tmpF
+	ss.Reader = tmpF
+	return ss.tmpFile, nil
+}
+
 func (f *FileStream) SetTmpFile(r *os.File) {
 	f.Reader = r
 	f.tmpFile = r
 }
+
+type ReaderWithSize interface {
+	io.Reader
+	GetSize() int64
+}
+
+type SimpleReaderWithSize struct {
+	io.Reader
+	Size int64
+}
+
+func (r *SimpleReaderWithSize) GetSize() int64 {
+	return r.Size
+}
+
+type ReaderUpdatingProgress struct {
+	Reader ReaderWithSize
+	model.UpdateProgress
+	offset int
+}
+
+func (r *ReaderUpdatingProgress) Read(p []byte) (n int, err error) {
+	n, err = r.Reader.Read(p)
+	r.offset += n
+	r.UpdateProgress(math.Min(100.0, float64(r.offset)/float64(r.Reader.GetSize())*100.0))
+	return n, err
+}
+
+type SStreamReadAtSeeker interface {
+	model.File
+	GetRawStream() *SeekableStream
+}
+
+type readerCur struct {
+	reader io.Reader
+	cur    int64
+}
+
+type RangeReadReadAtSeeker struct {
+	ss        *SeekableStream
+	masterOff int64
+	readers   []*readerCur
+}
+
+type FileReadAtSeeker struct {
+	ss *SeekableStream
+}
+
+func NewReadAtSeeker(ss *SeekableStream, offset int64, forceRange ...bool) (SStreamReadAtSeeker, error) {
+	if ss.mFile != nil {
+		_, err := ss.mFile.Seek(offset, io.SeekStart)
+		if err != nil {
+			return nil, err
+		}
+		return &FileReadAtSeeker{ss: ss}, nil
+	}
+	var r io.Reader
+	var err error
+	if offset != 0 || utils.IsBool(forceRange...) {
+		if offset < 0 || offset > ss.GetSize() {
+			return nil, errors.New("offset out of range")
+		}
+		r, err = ss.RangeRead(http_range.Range{Start: offset, Length: -1})
+		if err != nil {
+			return nil, err
+		}
+		if rc, ok := r.(io.Closer); ok {
+			ss.Closers.Add(rc)
+		}
+	} else {
+		r = ss
+	}
+	return &RangeReadReadAtSeeker{
+		ss:        ss,
+		masterOff: offset,
+		readers:   []*readerCur{{reader: r, cur: offset}},
+	}, nil
+}
+
+func (r *RangeReadReadAtSeeker) GetRawStream() *SeekableStream {
+	return r.ss
+}
+
+func (r *RangeReadReadAtSeeker) getReaderAtOffset(off int64) (*readerCur, error) {
+	for _, reader := range r.readers {
+		if reader.cur == off {
+			return reader, nil
+		}
+	}
+	reader, err := r.ss.RangeRead(http_range.Range{Start: off, Length: -1})
+	if err != nil {
+		return nil, err
+	}
+	if c, ok := reader.(io.Closer); ok {
+		r.ss.Closers.Add(c)
+	}
+	rc := &readerCur{reader: reader, cur: off}
+	r.readers = append(r.readers, rc)
+	return rc, nil
+}
+
+func (r *RangeReadReadAtSeeker) ReadAt(p []byte, off int64) (int, error) {
+	rc, err := r.getReaderAtOffset(off)
+	if err != nil {
+		return 0, err
+	}
+	num := 0
+	for num < len(p) {
+		n, err := rc.reader.Read(p[num:])
+		rc.cur += int64(n)
+		num += n
+		if err != nil {
+			return num, err
+		}
+	}
+	return num, nil
+}
+
+func (r *RangeReadReadAtSeeker) Seek(offset int64, whence int) (int64, error) {
+	switch whence {
+	case io.SeekStart:
+	case io.SeekCurrent:
+		if offset == 0 {
+			return r.masterOff, nil
+		}
+		offset += r.masterOff
+	case io.SeekEnd:
+		offset += r.ss.GetSize()
+	default:
+		return 0, errs.NotSupport
+	}
+	if offset < 0 {
+		return r.masterOff, errors.New("invalid seek: negative position")
+	}
+	if offset > r.ss.GetSize() {
+		return r.masterOff, io.EOF
+	}
+	r.masterOff = offset
+	return offset, nil
+}
+
+func (r *RangeReadReadAtSeeker) Read(p []byte) (n int, err error) {
+	rc, err := r.getReaderAtOffset(r.masterOff)
+	if err != nil {
+		return 0, err
+	}
+	n, err = rc.reader.Read(p)
+	rc.cur += int64(n)
+	r.masterOff += int64(n)
+	return n, err
+}
+
+func (r *RangeReadReadAtSeeker) Close() error {
+	return r.ss.Close()
+}
+
+func (f *FileReadAtSeeker) GetRawStream() *SeekableStream {
+	return f.ss
+}
+
+func (f *FileReadAtSeeker) Read(p []byte) (n int, err error) {
+	return f.ss.mFile.Read(p)
+}
+
+func (f *FileReadAtSeeker) ReadAt(p []byte, off int64) (n int, err error) {
+	return f.ss.mFile.ReadAt(p, off)
+}
+
+func (f *FileReadAtSeeker) Seek(offset int64, whence int) (int64, error) {
+	return f.ss.mFile.Seek(offset, whence)
+}
+
+func (f *FileReadAtSeeker) Close() error {
+	return f.ss.Close()
+}
diff --git a/internal/task/manager.go b/internal/task/manager.go
new file mode 100644
index 00000000000..3caa685a9c9
--- /dev/null
+++ b/internal/task/manager.go
@@ -0,0 +1,20 @@
+package task
+
+import "github.com/xhofe/tache"
+
+type Manager[T tache.Task] interface {
+	Add(task T)
+	Cancel(id string)
+	CancelAll()
+	CancelByCondition(condition func(task T) bool)
+	GetAll() []T
+	GetByID(id string) (T, bool)
+	GetByState(state ...tache.State) []T
+	GetByCondition(condition func(task T) bool) []T
+	Remove(id string)
+	RemoveAll()
+	RemoveByState(state ...tache.State)
+	RemoveByCondition(condition func(task T) bool)
+	Retry(id string)
+	RetryAllFailed()
+}
diff --git a/server/ftp/fsread.go b/server/ftp/fsread.go
index 257d2ec838a..f7e018e0f5f 100644
--- a/server/ftp/fsread.go
+++ b/server/ftp/fsread.go
@@ -8,10 +8,8 @@ import (
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/stream"
-	"github.com/alist-org/alist/v3/pkg/http_range"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/pkg/errors"
-	"io"
 	fs2 "io/fs"
 	"net/http"
 	"os"
@@ -20,9 +18,7 @@ import (
 
 type FileDownloadProxy struct {
 	ftpserver.FileTransfer
-	ss     *stream.SeekableStream
-	reader io.Reader
-	cur    int64
+	reader stream.SStreamReadAtSeeker
 }
 
 func OpenDownload(ctx context.Context, reqPath string, offset int64) (*FileDownloadProxy, error) {
@@ -55,22 +51,16 @@ func OpenDownload(ctx context.Context, reqPath string, offset int64) (*FileDownl
 	if err != nil {
 		return nil, err
 	}
-	var reader io.Reader
-	if offset != 0 {
-		reader, err = ss.RangeRead(http_range.Range{Start: offset, Length: -1})
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		reader = ss
+	reader, err := stream.NewReadAtSeeker(ss, offset)
+	if err != nil {
+		_ = ss.Close()
+		return nil, err
 	}
-	return &FileDownloadProxy{ss: ss, reader: reader}, nil
+	return &FileDownloadProxy{reader: reader}, nil
 }
 
 func (f *FileDownloadProxy) Read(p []byte) (n int, err error) {
-	n, err = f.reader.Read(p)
-	f.cur += int64(n)
-	return n, err
+	return f.reader.Read(p)
 }
 
 func (f *FileDownloadProxy) Write(p []byte) (n int, err error) {
@@ -78,32 +68,11 @@ func (f *FileDownloadProxy) Write(p []byte) (n int, err error) {
 }
 
 func (f *FileDownloadProxy) Seek(offset int64, whence int) (int64, error) {
-	switch whence {
-	case io.SeekStart:
-		break
-	case io.SeekCurrent:
-		offset += f.cur
-		break
-	case io.SeekEnd:
-		offset += f.ss.GetSize()
-		break
-	default:
-		return 0, errs.NotSupport
-	}
-	if offset < 0 {
-		return 0, errors.New("Seek: negative position")
-	}
-	reader, err := f.ss.RangeRead(http_range.Range{Start: offset, Length: -1})
-	if err != nil {
-		return f.cur, err
-	}
-	f.cur = offset
-	f.reader = reader
-	return offset, nil
+	return f.reader.Seek(offset, whence)
 }
 
 func (f *FileDownloadProxy) Close() error {
-	return f.ss.Close()
+	return f.reader.Close()
 }
 
 type OsFileInfoAdapter struct {
diff --git a/server/handles/archive.go b/server/handles/archive.go
new file mode 100644
index 00000000000..29dbf3c2d34
--- /dev/null
+++ b/server/handles/archive.go
@@ -0,0 +1,381 @@
+package handles
+
+import (
+	"fmt"
+	"github.com/alist-org/alist/v3/internal/archive/tool"
+	"github.com/alist-org/alist/v3/internal/conf"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/fs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
+	"github.com/alist-org/alist/v3/internal/setting"
+	"github.com/alist-org/alist/v3/internal/sign"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/alist-org/alist/v3/server/common"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	"mime"
+	stdpath "path"
+	"strings"
+)
+
+type ArchiveMetaReq struct {
+	Path        string `json:"path" form:"path"`
+	Password    string `json:"password" form:"password"`
+	Refresh     bool   `json:"refresh" form:"refresh"`
+	ArchivePass string `json:"archive_pass" form:"archive_pass"`
+}
+
+type ArchiveMetaResp struct {
+	Comment     string               `json:"comment"`
+	IsEncrypted bool                 `json:"encrypted"`
+	Content     []ArchiveContentResp `json:"content"`
+	RawURL      string               `json:"raw_url"`
+	Sign        string               `json:"sign"`
+}
+
+type ArchiveContentResp struct {
+	ObjResp
+	Children []ArchiveContentResp `json:"children,omitempty"`
+}
+
+func toObjsRespWithoutSignAndThumb(obj model.Obj) ObjResp {
+	return ObjResp{
+		Name:        obj.GetName(),
+		Size:        obj.GetSize(),
+		IsDir:       obj.IsDir(),
+		Modified:    obj.ModTime(),
+		Created:     obj.CreateTime(),
+		HashInfoStr: obj.GetHash().String(),
+		HashInfo:    obj.GetHash().Export(),
+		Sign:        "",
+		Thumb:       "",
+		Type:        utils.GetObjType(obj.GetName(), obj.IsDir()),
+	}
+}
+
+func toContentResp(objs []model.ObjTree) []ArchiveContentResp {
+	if objs == nil {
+		return nil
+	}
+	ret, _ := utils.SliceConvert(objs, func(src model.ObjTree) (ArchiveContentResp, error) {
+		return ArchiveContentResp{
+			ObjResp:  toObjsRespWithoutSignAndThumb(src),
+			Children: toContentResp(src.GetChildren()),
+		}, nil
+	})
+	return ret
+}
+
+func FsArchiveMeta(c *gin.Context) {
+	var req ArchiveMetaReq
+	if err := c.ShouldBind(&req); err != nil {
+		common.ErrorResp(c, err, 400)
+		return
+	}
+	user := c.MustGet("user").(*model.User)
+	if !user.CanReadArchives() {
+		common.ErrorResp(c, errs.PermissionDenied, 403)
+		return
+	}
+	reqPath, err := user.JoinPath(req.Path)
+	if err != nil {
+		common.ErrorResp(c, err, 403)
+		return
+	}
+	meta, err := op.GetNearestMeta(reqPath)
+	if err != nil {
+		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
+			common.ErrorResp(c, err, 500, true)
+			return
+		}
+	}
+	c.Set("meta", meta)
+	if !common.CanAccess(user, meta, reqPath, req.Password) {
+		common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
+		return
+	}
+	archiveArgs := model.ArchiveArgs{
+		LinkArgs: model.LinkArgs{
+			Header:  c.Request.Header,
+			Type:    c.Query("type"),
+			HttpReq: c.Request,
+		},
+		Password: req.ArchivePass,
+	}
+	ret, err := fs.ArchiveMeta(c, reqPath, model.ArchiveMetaArgs{
+		ArchiveArgs: archiveArgs,
+		Refresh:     req.Refresh,
+	})
+	if err != nil {
+		if errors.Is(err, errs.WrongArchivePassword) {
+			common.ErrorResp(c, err, 202)
+		} else {
+			common.ErrorResp(c, err, 500)
+		}
+		return
+	}
+	s := ""
+	if isEncrypt(meta, reqPath) || setting.GetBool(conf.SignAll) {
+		s = sign.Sign(reqPath)
+	}
+	api := "/ae"
+	if ret.DriverProviding {
+		api = "/ad"
+	}
+	common.SuccessResp(c, ArchiveMetaResp{
+		Comment:     ret.GetComment(),
+		IsEncrypted: ret.IsEncrypted(),
+		Content:     toContentResp(ret.GetTree()),
+		RawURL:      fmt.Sprintf("%s%s%s", common.GetApiUrl(c.Request), api, utils.EncodePath(reqPath, true)),
+		Sign:        s,
+	})
+}
+
+type ArchiveListReq struct {
+	ArchiveMetaReq
+	model.PageReq
+	InnerPath string `json:"inner_path" form:"inner_path"`
+}
+
+type ArchiveListResp struct {
+	Content []ObjResp `json:"content"`
+	Total   int64     `json:"total"`
+}
+
+func FsArchiveList(c *gin.Context) {
+	var req ArchiveListReq
+	if err := c.ShouldBind(&req); err != nil {
+		common.ErrorResp(c, err, 400)
+		return
+	}
+	req.Validate()
+	user := c.MustGet("user").(*model.User)
+	if !user.CanReadArchives() {
+		common.ErrorResp(c, errs.PermissionDenied, 403)
+		return
+	}
+	reqPath, err := user.JoinPath(req.Path)
+	if err != nil {
+		common.ErrorResp(c, err, 403)
+		return
+	}
+	meta, err := op.GetNearestMeta(reqPath)
+	if err != nil {
+		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
+			common.ErrorResp(c, err, 500, true)
+			return
+		}
+	}
+	c.Set("meta", meta)
+	if !common.CanAccess(user, meta, reqPath, req.Password) {
+		common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
+		return
+	}
+	objs, err := fs.ArchiveList(c, reqPath, model.ArchiveListArgs{
+		ArchiveInnerArgs: model.ArchiveInnerArgs{
+			ArchiveArgs: model.ArchiveArgs{
+				LinkArgs: model.LinkArgs{
+					Header:  c.Request.Header,
+					Type:    c.Query("type"),
+					HttpReq: c.Request,
+				},
+				Password: req.ArchivePass,
+			},
+			InnerPath: utils.FixAndCleanPath(req.InnerPath),
+		},
+		Refresh: req.Refresh,
+	})
+	if err != nil {
+		if errors.Is(err, errs.WrongArchivePassword) {
+			common.ErrorResp(c, err, 202)
+		} else {
+			common.ErrorResp(c, err, 500)
+		}
+		return
+	}
+	total, objs := pagination(objs, &req.PageReq)
+	ret, _ := utils.SliceConvert(objs, func(src model.Obj) (ObjResp, error) {
+		return toObjsRespWithoutSignAndThumb(src), nil
+	})
+	common.SuccessResp(c, ArchiveListResp{
+		Content: ret,
+		Total:   int64(total),
+	})
+}
+
+type ArchiveDecompressReq struct {
+	SrcDir        string `json:"src_dir" form:"src_dir"`
+	DstDir        string `json:"dst_dir" form:"dst_dir"`
+	Name          string `json:"name" form:"name"`
+	ArchivePass   string `json:"archive_pass" form:"archive_pass"`
+	InnerPath     string `json:"inner_path" form:"inner_path"`
+	CacheFull     bool   `json:"cache_full" form:"cache_full"`
+	PutIntoNewDir bool   `json:"put_into_new_dir" form:"put_into_new_dir"`
+}
+
+func FsArchiveDecompress(c *gin.Context) {
+	var req ArchiveDecompressReq
+	if err := c.ShouldBind(&req); err != nil {
+		common.ErrorResp(c, err, 400)
+		return
+	}
+	user := c.MustGet("user").(*model.User)
+	if !user.CanDecompress() {
+		common.ErrorResp(c, errs.PermissionDenied, 403)
+		return
+	}
+	srcPath, err := user.JoinPath(stdpath.Join(req.SrcDir, req.Name))
+	if err != nil {
+		common.ErrorResp(c, err, 403)
+		return
+	}
+	dstDir, err := user.JoinPath(req.DstDir)
+	if err != nil {
+		common.ErrorResp(c, err, 403)
+		return
+	}
+	t, err := fs.ArchiveDecompress(c, srcPath, dstDir, model.ArchiveDecompressArgs{
+		ArchiveInnerArgs: model.ArchiveInnerArgs{
+			ArchiveArgs: model.ArchiveArgs{
+				LinkArgs: model.LinkArgs{
+					Header:  c.Request.Header,
+					Type:    c.Query("type"),
+					HttpReq: c.Request,
+				},
+				Password: req.ArchivePass,
+			},
+			InnerPath: utils.FixAndCleanPath(req.InnerPath),
+		},
+		CacheFull:     req.CacheFull,
+		PutIntoNewDir: req.PutIntoNewDir,
+	})
+	if err != nil {
+		if errors.Is(err, errs.WrongArchivePassword) {
+			common.ErrorResp(c, err, 202)
+		} else {
+			common.ErrorResp(c, err, 500)
+		}
+		return
+	}
+	common.SuccessResp(c, gin.H{
+		"task": getTaskInfo(t),
+	})
+}
+
+func ArchiveDown(c *gin.Context) {
+	archiveRawPath := c.MustGet("path").(string)
+	innerPath := utils.FixAndCleanPath(c.Query("inner"))
+	password := c.Query("pass")
+	filename := stdpath.Base(innerPath)
+	storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
+	if err != nil {
+		common.ErrorResp(c, err, 500)
+		return
+	}
+	if common.ShouldProxy(storage, filename) {
+		ArchiveProxy(c)
+		return
+	} else {
+		link, _, err := fs.ArchiveDriverExtract(c, archiveRawPath, model.ArchiveInnerArgs{
+			ArchiveArgs: model.ArchiveArgs{
+				LinkArgs: model.LinkArgs{
+					IP:      c.ClientIP(),
+					Header:  c.Request.Header,
+					Type:    c.Query("type"),
+					HttpReq: c.Request,
+				},
+				Password: password,
+			},
+			InnerPath: innerPath,
+		})
+		if err != nil {
+			common.ErrorResp(c, err, 500)
+			return
+		}
+		down(c, link)
+	}
+}
+
+func ArchiveProxy(c *gin.Context) {
+	archiveRawPath := c.MustGet("path").(string)
+	innerPath := utils.FixAndCleanPath(c.Query("inner"))
+	password := c.Query("pass")
+	filename := stdpath.Base(innerPath)
+	storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
+	if err != nil {
+		common.ErrorResp(c, err, 500)
+		return
+	}
+	if canProxy(storage, filename) {
+		// TODO: Support external download proxy URL
+		link, file, err := fs.ArchiveDriverExtract(c, archiveRawPath, model.ArchiveInnerArgs{
+			ArchiveArgs: model.ArchiveArgs{
+				LinkArgs: model.LinkArgs{
+					Header:  c.Request.Header,
+					Type:    c.Query("type"),
+					HttpReq: c.Request,
+				},
+				Password: password,
+			},
+			InnerPath: innerPath,
+		})
+		if err != nil {
+			common.ErrorResp(c, err, 500)
+			return
+		}
+		localProxy(c, link, file, storage.GetStorage().ProxyRange)
+	} else {
+		common.ErrorStrResp(c, "proxy not allowed", 403)
+		return
+	}
+}
+
+func ArchiveInternalExtract(c *gin.Context) {
+	archiveRawPath := c.MustGet("path").(string)
+	innerPath := utils.FixAndCleanPath(c.Query("inner"))
+	password := c.Query("pass")
+	rc, size, err := fs.ArchiveInternalExtract(c, archiveRawPath, model.ArchiveInnerArgs{
+		ArchiveArgs: model.ArchiveArgs{
+			LinkArgs: model.LinkArgs{
+				Header:  c.Request.Header,
+				Type:    c.Query("type"),
+				HttpReq: c.Request,
+			},
+			Password: password,
+		},
+		InnerPath: innerPath,
+	})
+	if err != nil {
+		common.ErrorResp(c, err, 500)
+		return
+	}
+	defer func() {
+		if err := rc.Close(); err != nil {
+			log.Errorf("failed to close file streamer, %v", err)
+		}
+	}()
+	headers := map[string]string{
+		"Referrer-Policy": "no-referrer",
+		"Cache-Control":   "max-age=0, no-cache, no-store, must-revalidate",
+	}
+	if c.Query("attachment") == "true" {
+		filename := stdpath.Base(innerPath)
+		headers["Content-Disposition"] = fmt.Sprintf("attachment; filename=\"%s\"", filename)
+	}
+	contentType := c.Request.Header.Get("Content-Type")
+	if contentType == "" {
+		fileExt := stdpath.Ext(innerPath)
+		contentType = mime.TypeByExtension(fileExt)
+	}
+	c.DataFromReader(200, size, contentType, rc, headers)
+}
+
+func ArchiveExtensions(c *gin.Context) {
+	var ext []string
+	for key := range tool.Tools {
+		ext = append(ext, strings.TrimPrefix(key, "."))
+	}
+	common.SuccessResp(c, ext)
+}
diff --git a/server/handles/down.go b/server/handles/down.go
index 0020ed1453e..f01c9d6683b 100644
--- a/server/handles/down.go
+++ b/server/handles/down.go
@@ -40,28 +40,7 @@ func Down(c *gin.Context) {
 			common.ErrorResp(c, err, 500)
 			return
 		}
-		if link.MFile != nil {
-			defer func(ReadSeekCloser io.ReadCloser) {
-				err := ReadSeekCloser.Close()
-				if err != nil {
-					log.Errorf("close data error: %s", err)
-				}
-			}(link.MFile)
-		}
-		c.Header("Referrer-Policy", "no-referrer")
-		c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
-		if setting.GetBool(conf.ForwardDirectLinkParams) {
-			query := c.Request.URL.Query()
-			for _, v := range conf.SlicesMap[conf.IgnoreDirectLinkParams] {
-				query.Del(v)
-			}
-			link.URL, err = utils.InjectQuery(link.URL, query)
-			if err != nil {
-				common.ErrorResp(c, err, 500)
-				return
-			}
-		}
-		c.Redirect(302, link.URL)
+		down(c, link)
 	}
 }
 
@@ -95,27 +74,58 @@ func Proxy(c *gin.Context) {
 			common.ErrorResp(c, err, 500)
 			return
 		}
-		if link.URL != "" && setting.GetBool(conf.ForwardDirectLinkParams) {
-			query := c.Request.URL.Query()
-			for _, v := range conf.SlicesMap[conf.IgnoreDirectLinkParams] {
-				query.Del(v)
-			}
-			link.URL, err = utils.InjectQuery(link.URL, query)
+		localProxy(c, link, file, storage.GetStorage().ProxyRange)
+	} else {
+		common.ErrorStrResp(c, "proxy not allowed", 403)
+		return
+	}
+}
+
+func down(c *gin.Context, link *model.Link) {
+	var err error
+	if link.MFile != nil {
+		defer func(ReadSeekCloser io.ReadCloser) {
+			err := ReadSeekCloser.Close()
 			if err != nil {
-				common.ErrorResp(c, err, 500)
-				return
+				log.Errorf("close data error: %s", err)
 			}
+		}(link.MFile)
+	}
+	c.Header("Referrer-Policy", "no-referrer")
+	c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
+	if setting.GetBool(conf.ForwardDirectLinkParams) {
+		query := c.Request.URL.Query()
+		for _, v := range conf.SlicesMap[conf.IgnoreDirectLinkParams] {
+			query.Del(v)
 		}
-		if storage.GetStorage().ProxyRange {
-			common.ProxyRange(link, file.GetSize())
+		link.URL, err = utils.InjectQuery(link.URL, query)
+		if err != nil {
+			common.ErrorResp(c, err, 500)
+			return
+		}
+	}
+	c.Redirect(302, link.URL)
+}
+
+func localProxy(c *gin.Context, link *model.Link, file model.Obj, proxyRange bool) {
+	var err error
+	if link.URL != "" && setting.GetBool(conf.ForwardDirectLinkParams) {
+		query := c.Request.URL.Query()
+		for _, v := range conf.SlicesMap[conf.IgnoreDirectLinkParams] {
+			query.Del(v)
 		}
-		err = common.Proxy(c.Writer, c.Request, link, file)
+		link.URL, err = utils.InjectQuery(link.URL, query)
 		if err != nil {
-			common.ErrorResp(c, err, 500, true)
+			common.ErrorResp(c, err, 500)
 			return
 		}
-	} else {
-		common.ErrorStrResp(c, "proxy not allowed", 403)
+	}
+	if proxyRange {
+		common.ProxyRange(link, file.GetSize())
+	}
+	err = common.Proxy(c.Writer, c.Request, link, file)
+	if err != nil {
+		common.ErrorResp(c, err, 500, true)
 		return
 	}
 }
diff --git a/server/handles/task.go b/server/handles/task.go
index c7d9ef4842d..af7974a9c29 100644
--- a/server/handles/task.go
+++ b/server/handles/task.go
@@ -75,7 +75,7 @@ func getUserInfo(c *gin.Context) (bool, uint, bool) {
 	}
 }
 
-func getTargetedHandler[T task.TaskExtensionInfo](manager *tache.Manager[T], callback func(c *gin.Context, task T)) gin.HandlerFunc {
+func getTargetedHandler[T task.TaskExtensionInfo](manager task.Manager[T], callback func(c *gin.Context, task T)) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		isAdmin, uid, ok := getUserInfo(c)
 		if !ok {
@@ -97,7 +97,7 @@ func getTargetedHandler[T task.TaskExtensionInfo](manager *tache.Manager[T], cal
 	}
 }
 
-func getBatchHandler[T task.TaskExtensionInfo](manager *tache.Manager[T], callback func(task T)) gin.HandlerFunc {
+func getBatchHandler[T task.TaskExtensionInfo](manager task.Manager[T], callback func(task T)) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		isAdmin, uid, ok := getUserInfo(c)
 		if !ok {
@@ -122,7 +122,7 @@ func getBatchHandler[T task.TaskExtensionInfo](manager *tache.Manager[T], callba
 	}
 }
 
-func taskRoute[T task.TaskExtensionInfo](g *gin.RouterGroup, manager *tache.Manager[T]) {
+func taskRoute[T task.TaskExtensionInfo](g *gin.RouterGroup, manager task.Manager[T]) {
 	g.GET("/undone", func(c *gin.Context) {
 		isAdmin, uid, ok := getUserInfo(c)
 		if !ok {
@@ -220,4 +220,6 @@ func SetupTaskRoute(g *gin.RouterGroup) {
 	taskRoute(g.Group("/copy"), fs.CopyTaskManager)
 	taskRoute(g.Group("/offline_download"), tool.DownloadTaskManager)
 	taskRoute(g.Group("/offline_download_transfer"), tool.TransferTaskManager)
+	taskRoute(g.Group("/decompress"), fs.ArchiveDownloadTaskManager)
+	taskRoute(g.Group("/decompress_upload"), fs.ArchiveContentUploadTaskManager)
 }
diff --git a/server/router.go b/server/router.go
index 184de51e46a..63bad60f03f 100644
--- a/server/router.go
+++ b/server/router.go
@@ -42,6 +42,12 @@ func Init(e *gin.Engine) {
 	g.GET("/p/*path", middlewares.Down, handles.Proxy)
 	g.HEAD("/d/*path", middlewares.Down, handles.Down)
 	g.HEAD("/p/*path", middlewares.Down, handles.Proxy)
+	g.GET("/ad/*path", middlewares.Down, handles.ArchiveDown)
+	g.GET("/ap/*path", middlewares.Down, handles.ArchiveProxy)
+	g.GET("/ae/*path", middlewares.Down, handles.ArchiveInternalExtract)
+	g.HEAD("/ad/*path", middlewares.Down, handles.ArchiveDown)
+	g.HEAD("/ap/*path", middlewares.Down, handles.ArchiveProxy)
+	g.HEAD("/ae/*path", middlewares.Down, handles.ArchiveInternalExtract)
 
 	api := g.Group("/api")
 	auth := api.Group("", middlewares.Auth)
@@ -77,6 +83,7 @@ func Init(e *gin.Engine) {
 	public := api.Group("/public")
 	public.Any("/settings", handles.PublicSettings)
 	public.Any("/offline_download_tools", handles.OfflineDownloadTools)
+	public.Any("/archive_extensions", handles.ArchiveExtensions)
 
 	_fs(auth.Group("/fs"))
 	_task(auth.Group("/task", middlewares.AuthNotGuest))
@@ -173,6 +180,10 @@ func _fs(g *gin.RouterGroup) {
 	// g.POST("/add_qbit", handles.AddQbittorrent)
 	// g.POST("/add_transmission", handles.SetTransmission)
 	g.POST("/add_offline_download", handles.AddOfflineDownload)
+	a := g.Group("/archive")
+	a.Any("/meta", handles.FsArchiveMeta)
+	a.Any("/list", handles.FsArchiveList)
+	a.POST("/decompress", handles.FsArchiveDecompress)
 }
 
 func _task(g *gin.RouterGroup) {


Test patch: