55package cmd
66
77import (
8+ "fmt"
89 "log"
910 "os"
1011 "path/filepath"
11- "runtime"
1212 "time"
1313
14- "github.com/gammazero/workerpool "
15- "github.com/saferwall/cli/internal/util "
14+ tea "github.com/charmbracelet/bubbletea "
15+ "github.com/saferwall/cli/internal/entity "
1616 "github.com/saferwall/cli/internal/webapi"
1717 "github.com/spf13/cobra"
1818)
1919
20+ const (
21+ statusQueued = 1
22+ statusScanning = 2
23+ statusCompleted = 3
24+
25+ pollInterval = 5 * time .Second
26+ )
27+
2028// Used for flags.
21- var filePath string
2229var forceRescanFlag bool
23- var asyncScanFlag bool
30+ var parallelFlag int
2431var enableDetonationFlag bool
2532var timeoutFlag int
2633var osFlag string
2734
2835func init () {
29- scanCmd .Flags ().StringVarP (& filePath , "path" , "p" , "" ,
30- "File name or path to scan (required)" )
3136 scanCmd .Flags ().BoolVarP (& forceRescanFlag , "force" , "f" , false ,
3237 "Force rescan the file if it exists" )
33- scanCmd .Flags ().BoolVarP ( & asyncScanFlag , "async " , "a " , false ,
34- "Scan files in parallel" )
38+ scanCmd .Flags ().IntVarP ( & parallelFlag , "parallel " , "p " , 1 ,
39+ "Number of files to scan in parallel" )
3540 scanCmd .Flags ().BoolVarP (& enableDetonationFlag , "enableDetonation" , "d" , false ,
3641 "Skip detonation" )
3742 scanCmd .Flags ().IntVarP (& timeoutFlag , "timeout" , "t" , 15 ,
3843 "Detonation duration in seconds" )
3944 scanCmd .Flags ().StringVarP (& osFlag , "os" , "o" , "win-10" ,
4045 "Preferred OS for detonation, choice(win-7 | win-10)" )
41- scanCmd .MarkFlagRequired ("path" )
46+ }
47+
48+ type scanSummary struct {
49+ SHA256 string `json:"sha256"`
50+ Classification string `json:"classification"`
51+ FileFormat string `json:"file_format"`
52+ FileExtension string `json:"file_extension"`
53+ MultiAV * avSummary `json:"multiav,omitempty"`
54+ }
55+
56+ type avSummary struct {
57+ Positives int `json:"positives"`
58+ EnginesCount int `json:"engines_count"`
59+ }
4260
61+ func buildScanSummary (file entity.File ) scanSummary {
62+ s := scanSummary {
63+ SHA256 : file .SHA256 ,
64+ Classification : file .Classification ,
65+ FileFormat : file .Format ,
66+ FileExtension : file .Extension ,
67+ }
68+
69+ if lastScan , ok := file .MultiAV ["last_scan" ].(map [string ]any ); ok {
70+ if stats , ok := lastScan ["stats" ].(map [string ]any ); ok {
71+ av := & avSummary {}
72+ if v , ok := stats ["positives" ].(float64 ); ok {
73+ av .Positives = int (v )
74+ }
75+ if v , ok := stats ["engines_count" ].(float64 ); ok {
76+ av .EnginesCount = int (v )
77+ }
78+ s .MultiAV = av
79+ }
80+ }
81+
82+ return s
4383}
4484
4585// scanFile scans an individual file or a directory.
4686func scanFile (web webapi.Service , filePath , token string ) error {
47-
4887 _ , err := os .Stat (filePath )
4988 if os .IsNotExist (err ) {
5089 log .Printf ("file path [%s] does not exists" , filePath )
@@ -60,98 +99,20 @@ func scanFile(web webapi.Service, filePath, token string) error {
6099 return nil
61100 })
62101
63- if asyncScanFlag {
64-
65- // Create a worker pool
66- maxWorkers := runtime .GOMAXPROCS (0 )
67- wp := workerpool .New (maxWorkers )
68-
69- // Upload files
70- for _ , filename := range fileList {
71- filename := filename
72- wp .Submit (func () {
73-
74- // Get sha256
75- data , err := os .ReadFile (filename )
76- if err != nil {
77- log .Fatalf ("failed to read file: %v" , filename )
78- }
79- sha256 := util .GetSha256 (data )
80-
81- // Check if we the file exists in the DB.
82- exists , err := web .FileExists (sha256 )
83- if err != nil {
84- log .Fatalf ("failed to check existence of file: %v" , filename )
85- }
86-
87- // Upload the file to be scanned, this will automatically trigger a scan request.
88- if ! exists {
89- _ , err = web .Scan (filename , token , osFlag , enableDetonationFlag , timeoutFlag )
90- if err != nil {
91- log .Fatalf ("failed to upload file: %v" , filename )
92- }
93- } else {
94- // Force rescan the file
95- if forceRescanFlag {
96- err = web .Rescan (sha256 , token , osFlag , enableDetonationFlag , timeoutFlag )
97- if err != nil {
98- log .Fatalf ("failed to rescan file: %v" , filename )
99- }
100- }
101- }
102-
103- time .Sleep (2 * time .Second )
104- })
105- }
106- wp .StopWait ()
107- return nil
108- }
109-
110- // Sequentially scan the files.
111- for _ , filename := range fileList {
112- // Get sha256
113- data , err := os .ReadFile (filename )
114- if err != nil {
115- log .Fatalf ("failed to read file: %v" , filename )
116- }
117- sha256 := util .GetSha256 (data )
118-
119- log .Printf ("processing %s" , sha256 )
120-
121- // Check if we the file exists in the DB.
122- exists , err := web .FileExists (sha256 )
123- if err != nil {
124- log .Fatalf ("failed to check existence of file: %s, error: %v" , filename , err )
125- }
126-
127- // Upload the file to be scanned, this will automatically
128- // trigger a scan request.
129- if ! exists {
130- body , err := web .Scan (filename , token , osFlag , enableDetonationFlag , timeoutFlag )
131- if err != nil {
132- log .Fatalf ("failed to upload file: %s, error: %v" , filename , err )
133- }
134- log .Print (body )
135- time .Sleep (10 * time .Second )
136- } else {
137- // Force re-scan the file
138- if forceRescanFlag {
139- err = web .Rescan (sha256 , token , osFlag , enableDetonationFlag , timeoutFlag )
140- if err != nil {
141- log .Fatalf ("failed to re-scan file: %v" , filename )
142- }
143- }
144- }
145-
102+ // Launch TUI scan with the configured parallelism.
103+ model := newScanModel (fileList , web , token , parallelFlag )
104+ p := tea .NewProgram (model )
105+ if _ , err := p .Run (); err != nil {
106+ return fmt .Errorf ("TUI error: %w" , err )
146107 }
147-
148108 return nil
149109}
150110
151111var scanCmd = & cobra.Command {
152- Use : "scan" ,
112+ Use : "scan <path> " ,
153113 Short : "Submit a scan request of a file using its hash" ,
154114 Long : `Scans the file` ,
115+ Args : cobra .ExactArgs (1 ),
155116 Run : func (cmd * cobra.Command , args []string ) {
156117
157118 // login to saferwall web service
@@ -161,6 +122,6 @@ var scanCmd = &cobra.Command{
161122 log .Fatalf ("failed to login to saferwall web service" )
162123 }
163124
164- scanFile (webSvc , filePath , token )
125+ scanFile (webSvc , args [ 0 ] , token )
165126 },
166127}
0 commit comments