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