Compare commits

..

4 commits

Author SHA1 Message Date
Evan Su
47b65d6fe0 return err on ".." in zip item file path
Unlikely to happen since go stdlib zip doesn't do it, so if it does happen, better safe than sorry.
2025-01-27 01:12:14 -05:00
Evan Su
a929eb1037 final auto unzip code tweaks 2025-01-27 00:55:47 -05:00
Evan Su
bf73698c52 update unpackArchive to use both flags
also remove comments that add no value
2025-01-27 00:32:03 -05:00
Evan Su
10e8a1af82 auto unzip: careful ui state handling 2025-01-27 00:13:11 -05:00

View file

@ -120,7 +120,8 @@ var splitSelected int32 = 1
var recombine bool var recombine bool
var compress bool var compress bool
var delete bool var delete bool
var unpack bool var autoUnzip bool
var sameLevel bool
var keep bool var keep bool
var kept bool var kept bool
@ -555,8 +556,19 @@ func draw() {
).Build() ).Build()
giu.Row( giu.Row(
giu.Checkbox("Unpack .zip", &unpack), giu.Style().SetDisabled(!strings.HasSuffix(inputFile, ".zip.pcv")).To(
giu.Tooltip("Extract the decrypted .zip (overwrite files)"), giu.Checkbox("Auto unzip", &autoUnzip).OnChange(func() {
if !autoUnzip {
sameLevel = false
}
}),
giu.Tooltip("Extract .zip upon decryption (may overwrite)"),
),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(!autoUnzip).To(
giu.Checkbox("Same level", &sameLevel),
giu.Tooltip("Extract .zip contents to same folder as volume"),
),
).Build() ).Build()
} }
}), }),
@ -2118,29 +2130,21 @@ func work() {
os.Remove(inputFile) os.Remove(inputFile)
} }
if mode == "decrypt" && !kept && unpack { if mode == "decrypt" && !kept && autoUnzip {
showProgress = true // Turn on the progress popup showProgress = true
canCancel = true // Allow the user to cancel popupStatus = "Unzipping..."
progress = 0
progressInfo = ""
popupStatus = "Preparing to unpack..."
giu.Update() giu.Update()
err := unpackArchive(outputFile) if err := unpackArchive(outputFile); err != nil {
if err != nil { mainStatus = "Auto unzipping failed!"
mainStatus = "Extraction failed: " + err.Error()
mainStatusColor = RED mainStatusColor = RED
giu.Update() giu.Update()
return
} }
// Turn off the progress UI os.Remove(outputFile)
showProgress = false
canCancel = false
progress = 0
progressInfo = ""
popupStatus = ""
giu.Update()
} }
// All done, reset the UI // All done, reset the UI
oldKept := kept oldKept := kept
resetUI() resetUI()
@ -2238,7 +2242,8 @@ func resetUI() {
recombine = false recombine = false
compress = false compress = false
delete = false delete = false
unpack = false autoUnzip = false
sameLevel = false
keep = false keep = false
kept = false kept = false
@ -2361,33 +2366,31 @@ func sizeify(size int64) string {
} }
func unpackArchive(zipPath string) error { func unpackArchive(zipPath string) error {
// Open the .zip
reader, err := zip.OpenReader(zipPath) reader, err := zip.OpenReader(zipPath)
if err != nil { if err != nil {
return err return err
} }
defer reader.Close() defer reader.Close()
// Calculate total unpack size by summing each entrys UncompressedSize64
var totalSize int64 var totalSize int64
for _, f := range reader.File { for _, f := range reader.File {
totalSize += int64(f.UncompressedSize64) totalSize += int64(f.UncompressedSize64)
} }
// Directory containing the .zip var extractDir string
extractDir := filepath.Dir(zipPath) if sameLevel {
extractDir = filepath.Dir(zipPath)
} else {
extractDir = filepath.Join(filepath.Dir(zipPath), strings.TrimSuffix(filepath.Base(zipPath), ".zip"))
}
// Setup progress tracking
var done int64 var done int64
startTime := time.Now() startTime := time.Now()
// Iterate over each file in the archive for _, f := range reader.File {
for i, f := range reader.File { if strings.Contains(f.Name, "..") {
// If user clicked "Cancel" elsewhere, stop return errors.New("potentially malicious zip item path")
if !working {
return errors.New("operation canceled by user")
} }
outPath := filepath.Join(extractDir, f.Name) outPath := filepath.Join(extractDir, f.Name)
// Make directory if current entry is a folder // Make directory if current entry is a folder
@ -2395,9 +2398,21 @@ func unpackArchive(zipPath string) error {
if err := os.MkdirAll(outPath, f.Mode()); err != nil { if err := os.MkdirAll(outPath, f.Mode()); err != nil {
return err return err
} }
}
}
for i, f := range reader.File {
if strings.Contains(f.Name, "..") {
return errors.New("potentially malicious zip item path")
}
// Already handled above
if f.FileInfo().IsDir() {
continue continue
} }
outPath := filepath.Join(extractDir, f.Name)
// Otherwise create necessary parent directories // Otherwise create necessary parent directories
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
return err return err
@ -2410,20 +2425,14 @@ func unpackArchive(zipPath string) error {
} }
defer fileInArchive.Close() defer fileInArchive.Close()
// Create/overwrite the destination file dstFile, err := os.Create(outPath)
dstFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil { if err != nil {
return err return err
} }
// Read from zip in chunks to update progress // Read from zip in chunks to update progress
buffer := make([]byte, MiB) // e.g., 1 MiB chunk buffer := make([]byte, MiB)
for { for {
if !working {
dstFile.Close()
os.Remove(outPath) // remove partial extraction if canceled
return errors.New("operation canceled by user")
}
n, readErr := fileInArchive.Read(buffer) n, readErr := fileInArchive.Read(buffer)
if n > 0 { if n > 0 {
_, writeErr := dstFile.Write(buffer[:n]) _, writeErr := dstFile.Write(buffer[:n])
@ -2432,7 +2441,6 @@ func unpackArchive(zipPath string) error {
return writeErr return writeErr
} }
// Update "done" and recalc progress, speed, eta
done += int64(n) done += int64(n)
progress, speed, eta = statify(done, totalSize, startTime) progress, speed, eta = statify(done, totalSize, startTime)
progressInfo = fmt.Sprintf("%d/%d", i+1, len(reader.File)) progressInfo = fmt.Sprintf("%d/%d", i+1, len(reader.File))