From fdebb502ab7483a0f8fe05f8a03d4448d49c7a16 Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Sat, 25 Jan 2025 22:12:14 +0300
Subject: [PATCH 01/10] unpack zip and version in the window title
---
src/Picocrypt.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 89c9c33..7bcbef8 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -120,6 +120,7 @@ var splitSelected int32 = 1
var recombine bool
var compress bool
var delete bool
+var unpack bool
var keep bool
var kept bool
@@ -552,6 +553,11 @@ func draw() {
giu.Checkbox("Delete volume", &delete),
giu.Tooltip("Delete the volume after a successful decryption"),
).Build()
+
+ giu.Row(
+ giu.Checkbox("Unpack .zip", &unpack),
+ giu.Tooltip("Extract the decrypted .zip (ovewrite files)"),
+ ).Build()
}
}),
@@ -2112,6 +2118,14 @@ func work() {
os.Remove(inputFile)
}
+ if mode == "decrypt" && !kept && unpack {
+ err := unpackArchive(outputFile)
+ if err != nil {
+ mainStatus = "Extraction failed: " + err.Error()
+ mainStatusColor = RED
+ giu.Update()
+ }
+ }
// All done, reset the UI
oldKept := kept
resetUI()
@@ -2330,9 +2344,56 @@ func sizeify(size int64) string {
}
}
+func unpackArchive(zipPath string) error {
+ reader, err := zip.OpenReader(zipPath)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+
+ // Extract files directly into the folder that contains zipPath
+ extractDir := filepath.Dir(zipPath)
+
+ for _, f := range reader.File {
+ outPath := filepath.Join(extractDir, f.Name)
+
+ if f.FileInfo().IsDir() {
+ // Make sure nested directories exist
+ if err := os.MkdirAll(outPath, f.Mode()); err != nil {
+ return err
+ }
+ continue
+ }
+
+ // Create necessary directories for this file
+ if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
+ return err
+ }
+
+ // Extract the file
+ dstFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ return err
+ }
+ fileInArchive, err := f.Open()
+ if err != nil {
+ dstFile.Close()
+ return err
+ }
+ _, copyErr := io.Copy(dstFile, fileInArchive)
+ fileInArchive.Close()
+ dstFile.Close()
+ if copyErr != nil {
+ return copyErr
+ }
+ }
+
+ return nil
+}
+
func main() {
// Create the main window
- window = giu.NewMasterWindow("Picocrypt", 318, 507, giu.MasterWindowFlagsNotResizable)
+ window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable)
// Start the dialog module
dialog.Init()
From fee796845b3b4114003e19ef2aa113e1d53128aa Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Sat, 25 Jan 2025 22:59:41 +0300
Subject: [PATCH 02/10] zip unpacking with status bar
---
src/Picocrypt.go | 90 ++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 76 insertions(+), 14 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 7bcbef8..06896b1 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2119,12 +2119,27 @@ func work() {
}
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()
+
err := unpackArchive(outputFile)
if err != nil {
mainStatus = "Extraction failed: " + err.Error()
mainStatusColor = RED
giu.Update()
}
+
+ // Turn off the progress UI
+ showProgress = false
+ canCancel = false
+ progress = 0
+ progressInfo = ""
+ popupStatus = ""
+ giu.Update()
}
// All done, reset the UI
oldKept := kept
@@ -2223,6 +2238,7 @@ func resetUI() {
recombine = false
compress = false
delete = false
+ unpack = false
keep = false
kept = false
@@ -2345,47 +2361,93 @@ 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()
- // Extract files directly into the folder that contains zipPath
+ // Calculate total unpack size by summing each entry’s UncompressedSize64
+ var totalSize int64
+ for _, f := range reader.File {
+ totalSize += int64(f.UncompressedSize64)
+ }
+
+ // Directory containing the .zip
extractDir := filepath.Dir(zipPath)
- for _, f := range reader.File {
+ // Setup progress tracking
+ var done int64
+ startTime := time.Now()
+
+ // 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
if f.FileInfo().IsDir() {
- // Make sure nested directories exist
if err := os.MkdirAll(outPath, f.Mode()); err != nil {
return err
}
continue
}
- // Create necessary directories for this file
+ // Otherwise create necessary parent directories
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
return err
}
- // Extract the file
+ // Open the file inside the archive
+ fileInArchive, err := f.Open()
+ if err != nil {
+ return err
+ }
+ defer fileInArchive.Close()
+
+ // 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
}
- fileInArchive, err := f.Open()
- if err != nil {
- dstFile.Close()
- return err
+
+ // Read from zip in chunks to update progress
+ 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])
+ if writeErr != nil {
+ dstFile.Close()
+ 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))
+ popupStatus = fmt.Sprintf("Unpacking at %.2f MiB/s (ETA: %s)", speed, eta)
+ giu.Update()
+ }
+ if readErr != nil {
+ if readErr == io.EOF {
+ break
+ }
+ dstFile.Close()
+ return readErr
+ }
}
- _, copyErr := io.Copy(dstFile, fileInArchive)
- fileInArchive.Close()
dstFile.Close()
- if copyErr != nil {
- return copyErr
- }
}
return nil
From af3e1748fb137398343e53bce226f35600f392e8 Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Sun, 26 Jan 2025 00:47:55 +0300
Subject: [PATCH 03/10] Bump to 1.46: zip unpacking and version in title
---
Changelog.md | 6 ++++++
VERSION | 2 +-
src/Picocrypt.go | 6 +++---
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 045c9ba..99ff0c2 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,6 +3,12 @@
Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)
+# v1.46 (Released 01/26/2025)
+
+ - ✓ Added Picocrypt version to the window title
+ - ✓ Added ability to automatically unpack zip archives during decryption
+
+
# v1.45 (Released 12/05/2024)
- ✓ Bumped GitHub Actions Ubuntu 22 -> 24 and macOS 14 -> 15
diff --git a/VERSION b/VERSION
index 24c87c5..7ab887e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.45
+1.46
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 06896b1..f21eb6b 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2,7 +2,7 @@ package main
/*
-Picocrypt v1.45
+Picocrypt v1.46
Copyright (c) Evan Su
Released under a GNU GPL v3 License
https://github.com/Picocrypt/Picocrypt
@@ -60,7 +60,7 @@ var TRANSPARENT = color.RGBA{0x00, 0x00, 0x00, 0x00}
// Generic variables
var window *giu.MasterWindow
-var version = "v1.45"
+var version = "v1.46"
var dpi float32
var mode string
var working bool
@@ -556,7 +556,7 @@ func draw() {
giu.Row(
giu.Checkbox("Unpack .zip", &unpack),
- giu.Tooltip("Extract the decrypted .zip (ovewrite files)"),
+ giu.Tooltip("Extract the decrypted .zip (overwrite files)"),
).Build()
}
}),
From 525ee4d591c6a87dd7e7584044dd5c8210fab859 Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Sun, 26 Jan 2025 01:46:12 +0300
Subject: [PATCH 04/10] flatpak metainfo to 1.46
---
.../io.github.picocrypt.Picocrypt.metainfo.xml | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
index d7b2e59..cad8990 100644
--- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
+++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
@@ -44,11 +44,17 @@
-
- https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v145-released-12052024
+
+ https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01262025
- No code changes; just updated dependencies.
+
+ - Added Picocrypt version to the window title.
+ - Added ability to automatically unpack zip archives during decryption.
+
+
+
+
From 10e8a1af82e4bc95af42481fd3c72795cadf2e96 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Mon, 27 Jan 2025 00:13:11 -0500
Subject: [PATCH 05/10] auto unzip: careful ui state handling
---
src/Picocrypt.go | 45 ++++++++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index f21eb6b..533be8d 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -120,7 +120,8 @@ var splitSelected int32 = 1
var recombine bool
var compress bool
var delete bool
-var unpack bool
+var autoUnzip bool
+var sameLevel bool
var keep bool
var kept bool
@@ -555,8 +556,19 @@ func draw() {
).Build()
giu.Row(
- giu.Checkbox("Unpack .zip", &unpack),
- giu.Tooltip("Extract the decrypted .zip (overwrite files)"),
+ 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"),
+ ),
).Build()
}
}),
@@ -2118,29 +2130,19 @@ func work() {
os.Remove(inputFile)
}
- 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..."
+ if mode == "decrypt" && !kept && autoUnzip {
+ showProgress = true
+ popupStatus = "Unzipping..."
giu.Update()
- err := unpackArchive(outputFile)
- if err != nil {
- mainStatus = "Extraction failed: " + err.Error()
+ if err := unpackArchive(outputFile); err != nil {
+ mainStatus = "Auto unzipping failed!"
mainStatusColor = RED
giu.Update()
+ return
}
-
- // Turn off the progress UI
- showProgress = false
- canCancel = false
- progress = 0
- progressInfo = ""
- popupStatus = ""
- giu.Update()
}
+
// All done, reset the UI
oldKept := kept
resetUI()
@@ -2238,7 +2240,8 @@ func resetUI() {
recombine = false
compress = false
delete = false
- unpack = false
+ autoUnzip = false
+ sameLevel = false
keep = false
kept = false
From bf73698c529ad726edd2f48334f8929b20c289f4 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Mon, 27 Jan 2025 00:32:03 -0500
Subject: [PATCH 06/10] update unpackArchive to use both flags
also remove comments that add no value
---
src/Picocrypt.go | 29 ++++++++++-------------------
1 file changed, 10 insertions(+), 19 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 533be8d..a2e69b9 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2141,6 +2141,8 @@ func work() {
giu.Update()
return
}
+
+ os.Remove(outputFile)
}
// All done, reset the UI
@@ -2364,33 +2366,28 @@ 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)
}
- // Directory containing the .zip
- extractDir := filepath.Dir(zipPath)
+ var extractDir string
+ 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
startTime := time.Now()
- // 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
@@ -2413,20 +2410,14 @@ func unpackArchive(zipPath string) error {
}
defer fileInArchive.Close()
- // Create/overwrite the destination file
- dstFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ dstFile, err := os.Create(outPath)
if err != nil {
return err
}
// Read from zip in chunks to update progress
- buffer := make([]byte, MiB) // e.g., 1 MiB chunk
+ buffer := make([]byte, MiB)
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])
From a929eb10372f0a28cabbada1c491e3e6c9503ed7 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Mon, 27 Jan 2025 00:55:47 -0500
Subject: [PATCH 07/10] final auto unzip code tweaks
---
src/Picocrypt.go | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index a2e69b9..be04bc9 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2387,17 +2387,25 @@ func unpackArchive(zipPath string) error {
var done int64
startTime := time.Now()
- for i, f := range reader.File {
- outPath := filepath.Join(extractDir, f.Name)
+ for _, f := range reader.File {
+ outPath := filepath.Join(extractDir, filepath.Clean(strings.ReplaceAll(f.Name, "\\", "/")))
// Make directory if current entry is a folder
if f.FileInfo().IsDir() {
if err := os.MkdirAll(outPath, f.Mode()); err != nil {
return err
}
+ }
+ }
+
+ for i, f := range reader.File {
+ // Already handled above
+ if f.FileInfo().IsDir() {
continue
}
+ outPath := filepath.Join(extractDir, filepath.Clean(strings.ReplaceAll(f.Name, "\\", "/")))
+
// Otherwise create necessary parent directories
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
return err
@@ -2426,7 +2434,6 @@ 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))
From 47b65d6fe0f97ac4b9cc5935048e34eeb9e1d3bf Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Mon, 27 Jan 2025 01:12:14 -0500
Subject: [PATCH 08/10] 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.
---
src/Picocrypt.go | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index be04bc9..0f953a7 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2388,7 +2388,10 @@ func unpackArchive(zipPath string) error {
startTime := time.Now()
for _, f := range reader.File {
- outPath := filepath.Join(extractDir, filepath.Clean(strings.ReplaceAll(f.Name, "\\", "/")))
+ if strings.Contains(f.Name, "..") {
+ return errors.New("potentially malicious zip item path")
+ }
+ outPath := filepath.Join(extractDir, f.Name)
// Make directory if current entry is a folder
if f.FileInfo().IsDir() {
@@ -2399,12 +2402,16 @@ func unpackArchive(zipPath string) error {
}
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, filepath.Clean(strings.ReplaceAll(f.Name, "\\", "/")))
+ outPath := filepath.Join(extractDir, f.Name)
// Otherwise create necessary parent directories
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
From 46b557e3efc2bb9d40e7ea21c192a42ea151958f Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Tue, 28 Jan 2025 01:50:17 +0300
Subject: [PATCH 09/10] update date
---
Changelog.md | 4 ++--
dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 99ff0c2..58d79e2 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,10 +3,10 @@
- Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)
-# v1.46 (Released 01/26/2025)
+# v1.46 (Released 01/29/2025)
- ✓ Added Picocrypt version to the window title
- - ✓ Added ability to automatically unpack zip archives during decryption
+ - ✓ Added ability to automatically unzip archives upon decryption
# v1.45 (Released 12/05/2024)
diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
index cad8990..b304d69 100644
--- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
+++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
@@ -44,8 +44,8 @@
-
- https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01262025
+
+ https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01292025
- Added Picocrypt version to the window title.
From 445cd48e535912030b61ce1ea05acd580a612887 Mon Sep 17 00:00:00 2001
From: Alex Verner <119082209+Retengart@users.noreply.github.com>
Date: Tue, 28 Jan 2025 12:22:19 +0300
Subject: [PATCH 10/10] fix: delete a partially extracted file if run out of
space
---
src/Picocrypt.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 0f953a7..640d320 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2438,6 +2438,7 @@ func unpackArchive(zipPath string) error {
_, writeErr := dstFile.Write(buffer[:n])
if writeErr != nil {
dstFile.Close()
+ os.Remove(dstFile.Name())
return writeErr
}