-
-
Notifications
You must be signed in to change notification settings - Fork 2
LZW feature branch #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,211 @@ | ||||||||||||||||||||||
| package lzw | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import ( | ||||||||||||||||||||||
| "encoding/binary" | ||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||
| "io" | ||||||||||||||||||||||
| "os" | ||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| "file-compressor/constants" | ||||||||||||||||||||||
| "file-compressor/utils" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Zip compresses a single file using LZW algorithm and writes to the output writer. | ||||||||||||||||||||||
| // It writes metadata for the file (name length, name, data length, data). | ||||||||||||||||||||||
| // The progressCallback expects (percentOfCurrentFile float64, messageFromZip string). | ||||||||||||||||||||||
| func Zip(file utils.FileData, output io.Writer, progressCallback func(percentOfCurrentFile float64, messageFromZip string)) error { | ||||||||||||||||||||||
| if progressCallback != nil { | ||||||||||||||||||||||
| // Initial message for this specific file | ||||||||||||||||||||||
| progressCallback(0.0, fmt.Sprintf("Starting LZW for %s", filepath.Base(file.Name))) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if file.Reader == nil { | ||||||||||||||||||||||
| if file.Size > 0 { | ||||||||||||||||||||||
| utils.ColorPrint(utils.YELLOW, fmt.Sprintf("Warning: LZW Zip skipping '%s' due to nil reader.\n", file.Name)) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // If reader is nil, we can't proceed. | ||||||||||||||||||||||
| // Report 100% for this "skipped" file to allow overall progress to advance. | ||||||||||||||||||||||
| if progressCallback != nil { | ||||||||||||||||||||||
| progressCallback(1.0, fmt.Sprintf("Skipped %s (nil reader)", filepath.Base(file.Name))) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| fileReader := file.Reader | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| defer func(r io.Reader) { | ||||||||||||||||||||||
| if c, ok := r.(io.Closer); ok { | ||||||||||||||||||||||
| c.Close() | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }(fileReader) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| fileNameBytes := []byte(filepath.Base(file.Name)) | ||||||||||||||||||||||
| if err := binary.Write(output, binary.LittleEndian, uint32(len(fileNameBytes))); err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("LZW Zip: failed to write file name length for %s: %w", file.Name, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if _, err := output.Write(fileNameBytes); err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("LZW Zip: failed to write file name for %s: %w", file.Name, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| tempCompressedData := utils.NewResizableBuffer() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| var bytesReadForThisFile int64 | ||||||||||||||||||||||
| onProgressUpdate := func(readBytes int64, totalBytes int64) { | ||||||||||||||||||||||
| bytesReadForThisFile = readBytes | ||||||||||||||||||||||
| if progressCallback != nil && totalBytes > 0 { | ||||||||||||||||||||||
| percent := float64(readBytes) / float64(totalBytes) | ||||||||||||||||||||||
| if percent > 1.0 { percent = 1.0 } // Cap percent at 1.0 | ||||||||||||||||||||||
| progressCallback(percent, fmt.Sprintf("Processing %s", filepath.Base(file.Name))) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| trackingReader := utils.NewProgressReader(fileReader, file.Size, onProgressUpdate) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| errCompress := compressData(trackingReader, tempCompressedData) | ||||||||||||||||||||||
| if errCompress != nil { | ||||||||||||||||||||||
| return fmt.Errorf("LZW Zip: failed to compress data for %s: %w", file.Name, errCompress) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // After compression, all bytes of this file should have been read (or an error occurred). | ||||||||||||||||||||||
| // Ensure a final 100% progress for this file is signaled if no error. | ||||||||||||||||||||||
| if progressCallback != nil && file.Size > 0 { | ||||||||||||||||||||||
| // Check if the last report was already 100% | ||||||||||||||||||||||
| // This might be redundant if compressData + ProgressTrackingReader already guarantees a final 100% call, | ||||||||||||||||||||||
| // but it's a safeguard. | ||||||||||||||||||||||
| currentProgress := 0.0 | ||||||||||||||||||||||
| if file.Size > 0 { // Avoid division by zero if file.Size is 0 | ||||||||||||||||||||||
| currentProgress = float64(bytesReadForThisFile) / float64(file.Size) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if currentProgress < 1.0 { | ||||||||||||||||||||||
| progressCallback(1.0, fmt.Sprintf("Finalizing %s", filepath.Base(file.Name))) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| compressedSize := uint64(tempCompressedData.Len()) | ||||||||||||||||||||||
| if err := binary.Write(output, binary.LittleEndian, compressedSize); err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("LZW Zip: failed to write compressed data length for %s: %w", file.Name, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if _, err := io.Copy(output, tempCompressedData.Reader()); err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("LZW Zip: failed to write compressed data for %s: %w", file.Name, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if progressCallback != nil { | ||||||||||||||||||||||
| // Final confirmation for this specific file being done. | ||||||||||||||||||||||
| progressCallback(1.0, fmt.Sprintf("Finished LZW for %s", filepath.Base(file.Name))) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Unzip decompresses data using LZW algorithm from the input reader and writes to outputDir. | ||||||||||||||||||||||
| func Unzip(input io.Reader, outputDir string, progressCallback utils.ProgressCallback) ([]string, error) { | ||||||||||||||||||||||
| if progressCallback != nil { | ||||||||||||||||||||||
| // This is a general progress callback, not the fine-grained one. | ||||||||||||||||||||||
| // We'd need to adapt Unzip similarly to provide detailed progress. | ||||||||||||||||||||||
| // For now, just initial and final messages based on the old system. | ||||||||||||||||||||||
| // Example of how it might be adapted later: | ||||||||||||||||||||||
| // utils.UpdateProgress(utils.ProgressInfo{OriginalMessage: "Starting LZW decompression..."}, progressCallback) | ||||||||||||||||||||||
| progressCallback(0.0, "Starting LZW decompression...") | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| var decompressedFiles []string | ||||||||||||||||||||||
| fileIndex := 0 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for { | ||||||||||||||||||||||
| fileIndex++ | ||||||||||||||||||||||
| var fileNameLen uint32 | ||||||||||||||||||||||
| err := binary.Read(input, binary.LittleEndian, &fileNameLen) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| if err == io.EOF { | ||||||||||||||||||||||
| break | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return decompressedFiles, fmt.Errorf("LZW Unzip: failed to read file name length (file #%d): %w", fileIndex, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if fileNameLen == 0 || fileNameLen > 1024*1024 { | ||||||||||||||||||||||
| return decompressedFiles, fmt.Errorf("LZW Unzip: invalid file name length %d (file #%d), archive might be corrupted", fileNameLen, fileIndex) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| fileNameBytes := make([]byte, fileNameLen) | ||||||||||||||||||||||
| if _, err := io.ReadFull(input, fileNameBytes); err != nil { | ||||||||||||||||||||||
| return decompressedFiles, fmt.Errorf("LZW Unzip: failed to read file name (file #%d): %w", fileIndex, err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| fileName := string(fileNameBytes) | ||||||||||||||||||||||
| cleanFileName := filepath.Clean(fileName) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if strings.HasPrefix(cleanFileName, ".."+string(filepath.Separator)) || cleanFileName == ".." || strings.HasPrefix(cleanFileName, string(filepath.Separator)) || strings.Contains(fileName, "\\") || strings.Contains(fileName, ":") { | ||||||||||||||||||||||
| utils.ColorPrint(utils.RED, fmt.Sprintf("Warning: LZW Unzip potentially unsafe file path '%s' in archive. Skipping.\n", fileName)) | ||||||||||||||||||||||
| var compressedSizeToSkip uint64 | ||||||||||||||||||||||
| if errSize := binary.Read(input, binary.LittleEndian, &compressedSizeToSkip); errSize != nil { | ||||||||||||||||||||||
| return decompressedFiles, fmt.Errorf("LZW Unzip: failed to read size for potentially unsafe file %s: %w", fileName, errSize) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if _, errSeek := io.CopyN(io.Discard, input, int64(compressedSizeToSkip)); errSeek != nil { | ||||||||||||||||||||||
| return decompressedFiles, fmt.Errorf("LZW Unzip: failed to skip data for potentially unsafe file %s: %w", fileName, errSeek) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // TODO: Report progress for skipped file if possible, e.g. | ||||||||||||||||||||||
| // if progressCallback != nil { utils.UpdateProgress(utils.ProgressInfo{ ... CurrentFileName: fileName, OriginalMessage: "Skipped unsafe file" ... }, progressCallback) } | ||||||||||||||||||||||
|
Comment on lines
+148
to
+149
|
||||||||||||||||||||||
| // TODO: Report progress for skipped file if possible, e.g. | |
| // if progressCallback != nil { utils.UpdateProgress(utils.ProgressInfo{ ... CurrentFileName: fileName, OriginalMessage: "Skipped unsafe file" ... }, progressCallback) } | |
| // Report progress for skipped file if progressCallback is provided. | |
| if progressCallback != nil { | |
| utils.UpdateProgress(utils.ProgressInfo{ | |
| CurrentFileName: fileName, | |
| OriginalMessage: "Skipped unsafe file", | |
| PercentComplete: 0, // Skipped files do not contribute to progress percentage. | |
| }, progressCallback) | |
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The path sanitization rejects any backslash or colon, which might block valid Unix filenames; consider using
filepath.Relorfilepath.IsAbsfor a more robust check against directory traversal.