1
1
package validation
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"crypto/sha256"
7
+ "errors"
6
8
"fmt"
7
9
"io"
8
10
"log/slog"
@@ -26,11 +28,8 @@ func InitializeVirusTotal() {
26
28
27
29
type AnalysisResults struct {
28
30
Attributes struct {
29
- Stats * struct {
30
- Suspicious * int `json:"suspicious,omitempty"`
31
- Malicious * int `json:"malicious,omitempty"`
32
- } `json:"stats,omitempty"`
33
- Status string `json:"status"`
31
+ Stats * AnalysisStats `json:"stats,omitempty"`
32
+ Status string `json:"status"`
34
33
} `json:"attributes,omitempty"`
35
34
Meta struct {
36
35
FileInfo struct {
@@ -41,16 +40,18 @@ type AnalysisResults struct {
41
40
42
41
type PreviousAnalysisResults struct {
43
42
Attributes struct {
44
- Stats * struct {
45
- Suspicious * int `json:"suspicious,omitempty"`
46
- Malicious * int `json:"malicious,omitempty"`
47
- } `json:"last_analysis_stats,omitempty"`
43
+ Stats * AnalysisStats `json:"last_analysis_stats,omitempty"`
48
44
} `json:"attributes,omitempty"`
49
45
}
50
46
47
+ type AnalysisStats struct {
48
+ Suspicious * int `json:"suspicious,omitempty"`
49
+ Malicious * int `json:"malicious,omitempty"`
50
+ }
51
+
51
52
type ScanResult struct {
52
53
Safe bool
53
- Hash * string
54
+ Hash string
54
55
FileName string
55
56
}
56
57
@@ -67,11 +68,7 @@ func ScanFiles(ctx context.Context, files []io.Reader, names []string) ([]ScanRe
67
68
if err != nil {
68
69
return fmt .Errorf ("failed to scan %s: %w" , names [count ], err )
69
70
}
70
- select {
71
- case c <- * scanResult :
72
- case <- gctx .Done ():
73
- return gctx .Err ()
74
- }
71
+ c <- * scanResult
75
72
return nil
76
73
})
77
74
}
@@ -94,85 +91,89 @@ func ScanFiles(ctx context.Context, files []io.Reader, names []string) ([]ScanRe
94
91
}
95
92
96
93
func scanFile (ctx context.Context , file io.Reader , name string ) (* ScanResult , error ) {
97
- scanResult := ScanResult {
98
- Safe : false ,
99
- FileName : name ,
94
+ hash := sha256 .New ()
95
+ fileBytes , err := io .ReadAll (file )
96
+ if err != nil {
97
+ return nil , fmt .Errorf ("failed read file: %w" , err )
100
98
}
101
99
102
- hash := sha256 . New ( )
103
- if _ , err := io . Copy ( hash , file ); err != nil {
104
- return nil , fmt .Errorf ("failed to generate hash for file %w" , err )
100
+ _ , err = hash . Write ( fileBytes )
101
+ if err != nil {
102
+ return nil , fmt .Errorf ("failed to hash file: %w" , err )
105
103
}
106
104
107
105
checksum := hash .Sum (nil )
108
- var previousAnalysisResults PreviousAnalysisResults
106
+ hashStr := fmt . Sprintf ( "%x" , checksum )
109
107
110
- _ , err := client . GetData ( vt . URL ( "files/%x" , checksum ), & previousAnalysisResults )
108
+ var previousAnalysisResults PreviousAnalysisResults
111
109
112
- alreadyScanned := false
113
- analysisID := ""
110
+ hasPreviousAnalysis := true
111
+ _ , err = client .GetData (vt .URL ("files/%x" , checksum ), & previousAnalysisResults )
112
+ if err != nil {
113
+ var vtErr vt.Error
114
+ if errors .As (err , & vtErr ) {
115
+ if vtErr .Code != "NotFoundError" {
116
+ return nil , fmt .Errorf ("failed to get previous analysis results: %w" , err )
117
+ }
118
+ } else {
119
+ return nil , fmt .Errorf ("failed to get previous analysis results: %w" , err )
120
+ }
121
+ hasPreviousAnalysis = false
122
+ }
114
123
115
- if err == nil {
116
- alreadyScanned = true
117
- slox .Info (ctx , "file already scanned, skipping upload" , slog .String ("file" , name ))
118
- hash := fmt .Sprintf ("%x" , checksum )
119
- scanResult .Hash = & hash
120
- } else {
121
- scan , err := client .NewFileScanner ().Scan (file , name , nil )
124
+ if hasPreviousAnalysis {
125
+ slox .Info (ctx , "file already scanned" , slog .String ("file" , name ), slog .String ("hash" , hashStr ))
126
+ if previousAnalysisResults .Attributes .Stats == nil {
127
+ return nil , fmt .Errorf ("no stats available on previous analysis" )
128
+ }
129
+ safe , err := isResultSafe (* previousAnalysisResults .Attributes .Stats )
122
130
if err != nil {
123
- return & scanResult , fmt .Errorf ("failed to scan file: %w" , err )
131
+ return nil , fmt .Errorf ("failed to determine if file is safe : %w" , err )
124
132
}
125
- analysisID := scan .ID ()
126
- slox .Info (ctx , "uploaded virus scan" , slog .String ("file" , name ), slog .String ("analysis_id" , analysisID ))
133
+ return & ScanResult {
134
+ Safe : safe ,
135
+ Hash : hashStr ,
136
+ FileName : name ,
137
+ }, nil
127
138
}
128
139
129
- for {
130
- var analysisResults AnalysisResults
131
- var malicious int
132
- var suspicious int
133
-
134
- if ! alreadyScanned {
135
- time .Sleep (time .Second * 15 )
136
-
137
- _ , err = client .GetData (vt .URL ("analyses/%s" , analysisID ), & analysisResults )
138
- if err != nil {
139
- scanResult .Safe = false
140
- return nil , fmt .Errorf ("failed to get analysis results: %w" , err )
141
- }
142
- scanResult .Hash = & analysisResults .Meta .FileInfo .SHA256
143
-
144
- if ! alreadyScanned && analysisResults .Attributes .Status != "completed" {
145
- continue
146
- }
140
+ scan , err := client .NewFileScanner ().Scan (bytes .NewReader (fileBytes ), name , nil )
141
+ if err != nil {
142
+ return nil , fmt .Errorf ("failed to scan file: %w" , err )
143
+ }
144
+ analysisID := scan .ID ()
145
+ slox .Info (ctx , "uploaded virus scan" , slog .String ("file" , name ), slog .String ("analysis_id" , analysisID ))
147
146
148
- if analysisResults .Attributes .Stats == nil {
149
- slox .Error (ctx , "no stats available" , slog .Any ("err" , err ), slog .String ("file" , name ))
150
- scanResult .Safe = false
151
- return & scanResult , nil
152
- }
147
+ var analysisResults AnalysisResults
148
+ for analysisResults .Attributes .Status != "completed" {
149
+ time .Sleep (time .Second * 15 )
153
150
154
- if analysisResults .Attributes .Stats .Malicious == nil || analysisResults .Attributes .Stats .Suspicious == nil {
155
- slox .Error (ctx , "unable to determine malicious or suspicious file" , slog .Any ("err" , err ), slog .String ("file" , name ))
156
- scanResult .Safe = false
157
- return & scanResult , nil
158
- }
159
- malicious = * analysisResults .Attributes .Stats .Malicious
160
- suspicious = * analysisResults .Attributes .Stats .Suspicious
161
- } else {
162
- malicious = * previousAnalysisResults .Attributes .Stats .Malicious
163
- suspicious = * previousAnalysisResults .Attributes .Stats .Suspicious
151
+ _ , err := client .GetData (vt .URL ("analyses/%s" , analysisID ), & analysisResults )
152
+ if err != nil {
153
+ return nil , fmt .Errorf ("failed to get analysis results: %w" , err )
164
154
}
155
+ }
165
156
166
- // Why 1? Well because some company made a shitty AI and it flags random mods.
167
- if malicious > 1 || suspicious > 1 {
168
- slox .Error (ctx , "suspicious or malicious file found" , slog .Any ("err" , err ), slog .String ("file" , name ))
169
- scanResult .Safe = false
170
- return & scanResult , nil
171
- }
157
+ if analysisResults .Attributes .Stats == nil {
158
+ return nil , fmt .Errorf ("no stats available" )
159
+ }
160
+
161
+ safe , err := isResultSafe (* analysisResults .Attributes .Stats )
162
+ if err != nil {
163
+ return nil , fmt .Errorf ("failed to determine if file is safe: %w" , err )
164
+ }
165
+ return & ScanResult {
166
+ Safe : safe ,
167
+ Hash : hashStr ,
168
+ FileName : name ,
169
+ }, nil
170
+ }
172
171
173
- scanResult .Safe = true
174
- break
172
+ func isResultSafe (stats AnalysisStats ) (bool , error ) {
173
+ if stats .Malicious == nil || stats .Suspicious == nil {
174
+ return false , fmt .Errorf ("missing malicious or suspicious stats" )
175
175
}
176
176
177
- return & scanResult , nil
177
+ // Why 1? Well because some company made a shitty AI and it flags random mods.
178
+ return * stats .Malicious <= 1 && * stats .Suspicious <= 1 , nil
178
179
}
0 commit comments