@@ -2,9 +2,6 @@ package scan
22
33import (
44 "regexp"
5- "sort"
6- "strconv"
7- "strings"
85
96 "github.com/datafog/datafog-api/internal/models"
107)
@@ -67,78 +64,83 @@ var DefaultEntityConfidences = map[string]float64{
6764 "zip_code" : 0.80 ,
6865}
6966
67+ var defaultScanEntityTypes = []string {
68+ "api_key" ,
69+ "credit_card" ,
70+ "date" ,
71+ "email" ,
72+ "ip_address" ,
73+ "phone" ,
74+ "ssn" ,
75+ "zip_code" ,
76+ }
77+
7078func ScanText (text string , entityFilter []string ) []models.ScanFinding {
71- requested := map [string ]struct {}{}
72- if len (entityFilter ) > 0 {
73- for _ , name := range entityFilter {
74- requested [strings .ToLower (strings .TrimSpace (name ))] = struct {}{}
75- }
76- }
79+ requested := requestedEntitySet (entityFilter )
7780
7881 findings := make ([]models.ScanFinding , 0 )
7982
8083 // Phase 1: Regex engine (fast, always available)
81- entityTypes := make ([]string , 0 , len (DefaultEntityPatterns ))
82- for entityType := range DefaultEntityPatterns {
83- entityTypes = append (entityTypes , entityType )
84- }
85- sort .Strings (entityTypes )
86-
87- for _ , entityType := range entityTypes {
88- pattern := DefaultEntityPatterns [entityType ]
89- if len (requested ) > 0 {
90- if _ , ok := requested [entityType ]; ! ok {
91- continue
84+ for _ , entityType := range defaultScanEntityTypes {
85+ if shouldRunEntityType (requested , entityType ) {
86+ pattern := DefaultEntityPatterns [entityType ]
87+ idxs := pattern .Re .FindAllStringIndex (text , - 1 )
88+ for _ , idx := range idxs {
89+ if len (idx ) != 2 || idx [0 ] < 0 || idx [1 ] < idx [0 ] {
90+ continue
91+ }
92+ value := text [idx [0 ]:idx [1 ]]
93+ if pattern .Validate != nil && ! pattern .Validate (value ) {
94+ continue
95+ }
96+ findings = append (findings , models.ScanFinding {
97+ EntityType : entityType ,
98+ Value : value ,
99+ Start : idx [0 ],
100+ End : idx [1 ],
101+ Confidence : DefaultEntityConfidences [entityType ],
102+ })
92103 }
93104 }
105+ }
94106
95- idxs := pattern .Re .FindAllStringIndex (text , - 1 )
96- for _ , idx := range idxs {
97- if len (idx ) != 2 || idx [0 ] < 0 || idx [1 ] < idx [0 ] {
98- continue
99- }
100- value := text [idx [0 ]:idx [1 ]]
101- if pattern .Validate != nil && ! pattern .Validate (value ) {
102- continue
103- }
104- findings = append (findings , models.ScanFinding {
105- EntityType : entityType ,
106- Value : value ,
107- Start : idx [0 ],
108- End : idx [1 ],
109- Confidence : DefaultEntityConfidences [entityType ],
110- })
111- }
107+ if ! shouldRunNERForFilter (requested ) {
108+ return findings
112109 }
113110
114111 // Phase 2: NER engine (heuristic, when enabled)
115- nerFindings := ScanNER (text , entityFilter )
116- findings = append (findings , nerFindings ... )
112+ findings = append (findings , scanNERWithFilter (text , requested )... )
117113
118114 return findings
119115}
120116
121117// luhnValid implements the Luhn algorithm to validate credit card numbers.
122118// It strips spaces and dashes before checking.
123119func luhnValid (s string ) bool {
124- // Strip spaces and dashes
125- var digits []int
120+ var digits [19 ]int
121+ n := 0
122+
126123 for _ , ch := range s {
127- if ch >= '0' && ch <= '9' {
128- digits = append (digits , int (ch - '0' ))
129- } else if ch == ' ' || ch == '-' {
124+ switch {
125+ case ch >= '0' && ch <= '9' :
126+ if n == len (digits ) {
127+ return false
128+ }
129+ digits [n ] = int (ch - '0' )
130+ n ++
131+ case ch == ' ' || ch == '-' :
130132 continue
131- } else {
133+ default :
132134 return false
133135 }
134136 }
135- if len ( digits ) < 13 || len ( digits ) > 19 {
137+ if n < 13 || n > 19 {
136138 return false
137139 }
138140
139141 sum := 0
140142 double := false
141- for i := len ( digits ) - 1 ; i >= 0 ; i -- {
143+ for i := n - 1 ; i >= 0 ; i -- {
142144 d := digits [i ]
143145 if double {
144146 d *= 2
@@ -154,18 +156,44 @@ func luhnValid(s string) bool {
154156
155157// ipv4Valid checks that each octet is 0-255.
156158func ipv4Valid (s string ) bool {
157- parts := strings .Split (s , "." )
158- if len (parts ) != 4 {
159+ if len (s ) == 0 {
159160 return false
160161 }
161- for _ , part := range parts {
162- n , err := strconv .Atoi (part )
163- if err != nil {
162+
163+ octetCount := 0
164+ value := 0
165+ digitsInOctet := 0
166+
167+ for i := 0 ; i <= len (s ); i ++ {
168+ if i == len (s ) || s [i ] == '.' {
169+ if digitsInOctet == 0 {
170+ return false
171+ }
172+ if value < 0 || value > 255 {
173+ return false
174+ }
175+ octetCount ++
176+ if octetCount > 4 {
177+ return false
178+ }
179+ if i == len (s ) {
180+ break
181+ }
182+ value = 0
183+ digitsInOctet = 0
184+ continue
185+ }
186+
187+ ch := s [i ]
188+ if ch < '0' || ch > '9' {
164189 return false
165190 }
166- if n < 0 || n > 255 {
191+ value = value * 10 + int (ch - '0' )
192+ digitsInOctet ++
193+ if digitsInOctet > 3 {
167194 return false
168195 }
169196 }
170- return true
197+
198+ return octetCount == 4
171199}
0 commit comments