Skip to content

Commit 1e15435

Browse files
authored
Add scanner for RAR 4/5 archives (#25)
* Add scanner for RAR 4/5 archives * Add merge command. Improve rar scanner.
1 parent 4db7293 commit 1e15435

File tree

8 files changed

+491
-3
lines changed

8 files changed

+491
-3
lines changed

cmd/cmd/merge.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) 2025 Stefano Scafiti
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
package cmd
21+
22+
import (
23+
"bufio"
24+
"crypto/rand"
25+
"fmt"
26+
"io"
27+
mrand "math/rand/v2"
28+
"os"
29+
30+
"github.com/ostafen/digler/internal/logger"
31+
osutils "github.com/ostafen/digler/pkg/util/os"
32+
"github.com/spf13/cobra"
33+
)
34+
35+
func DefineMergeCommand() *cobra.Command {
36+
cmd := &cobra.Command{
37+
Use: "merge <file1> <file2> ...",
38+
Short: "Merge multiple files into a single disk image",
39+
Long: `The 'merge' command combines multiple files into a single flat disk image.
40+
This is useful for testing scanners and plugins with known, reproducible data.
41+
By default, files are concatenated in the order given. You can optionally add zero-byte padding between files to simulate gaps.`,
42+
Args: cobra.MinimumNArgs(1),
43+
SilenceUsage: true,
44+
RunE: RunMerge,
45+
}
46+
47+
cmd.Flags().StringP("output", "o", "", "Path to the output disk image file (required)")
48+
cmd.Flags().Int64("padding", 0, "Number of zero bytes to insert between files (optional)")
49+
cmd.Flags().Int("min-gap", 4*1024, "minimum gap size in bytes between files")
50+
cmd.Flags().Int("max-gap", 512*1024, "maximum gap size in bytes between files")
51+
cmd.Flags().Int("block-size", 512, "block size in bytes")
52+
53+
_ = cmd.MarkFlagRequired("output")
54+
55+
return cmd
56+
}
57+
58+
func RunMerge(cmd *cobra.Command, args []string) error {
59+
filePaths := make([]string, 0, len(args))
60+
for _, arg := range args {
61+
paths, err := osutils.ListFiles(arg)
62+
if err != nil {
63+
return err
64+
}
65+
filePaths = append(filePaths, paths...)
66+
}
67+
68+
out, _ := cmd.Flags().GetString("output")
69+
70+
minGap, _ := cmd.Flags().GetInt("min-gap")
71+
maxGap, _ := cmd.Flags().GetInt("max-gap")
72+
73+
if minGap > maxGap {
74+
return fmt.Errorf("min-gap (%d) cannot be greater than max-gap (%d)", minGap, maxGap)
75+
}
76+
if minGap <= 0 {
77+
return fmt.Errorf("min-gap must be greater than 0")
78+
}
79+
80+
blockSize, _ := cmd.Flags().GetInt("block-size")
81+
if blockSize <= 0 {
82+
return fmt.Errorf("block size must be greater than 0")
83+
}
84+
85+
f, err := os.Create(out)
86+
if err != nil {
87+
return err
88+
}
89+
defer f.Close()
90+
91+
logger := logger.New(os.Stdout, logger.InfoLevel)
92+
93+
logger.Infof("Merging %d files into %s", len(filePaths), out)
94+
95+
w := bufio.NewWriter(f)
96+
97+
gapSize := minGap + mrand.IntN(maxGap-minGap+1)
98+
// Ensure gap size is a multiple of block size
99+
gapSize = min(1, gapSize/blockSize) * blockSize
100+
101+
bytesWritten := int64(0)
102+
for _, path := range filePaths {
103+
_, err := io.CopyN(w, rand.Reader, int64(gapSize))
104+
if err != nil {
105+
return err
106+
}
107+
bytesWritten += int64(gapSize)
108+
109+
nCopied, err := osutils.CopyFile(w, path)
110+
if err != nil {
111+
return err
112+
}
113+
bytesWritten += nCopied
114+
115+
padding := int64(blockSize) - nCopied%int64(blockSize)
116+
117+
gapSize = minGap + mrand.IntN(maxGap-minGap+1)
118+
// Ensure next file starts at a block boundary
119+
gapSize = min(1, gapSize/blockSize) * blockSize
120+
gapSize += int(padding)
121+
}
122+
123+
if err := w.Flush(); err != nil {
124+
return fmt.Errorf("error flushing writer: %w", err)
125+
}
126+
127+
logger.Infof("Merging successfully completed. %d bytes written.", bytesWritten)
128+
return nil
129+
}

cmd/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func Execute() error {
3434
rootCmd.AddCommand(DefineRecoverCommand())
3535
rootCmd.AddCommand(DefineMountCommand())
3636
rootCmd.AddCommand(DefineFormatsCommand())
37+
rootCmd.AddCommand(DefineMergeCommand())
3738

3839
return rootCmd.Execute()
3940
}

internal/format/header.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var fileHeaders = []FileHeader{
5252
tiffFileHeader,
5353
// generic/documents formats
5454
zipFileHeader,
55+
rarFileHeader,
5556
pdfFileHeader,
5657
}
5758

0 commit comments

Comments
 (0)