-
Notifications
You must be signed in to change notification settings - Fork 118
/
patterns.go
134 lines (117 loc) · 3.33 KB
/
patterns.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package wappalyzer
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// ParsedPattern encapsulates a regular expression with
// additional metadata for confidence and version extraction.
type ParsedPattern struct {
regex *regexp.Regexp
Confidence int
Version string
SkipRegex bool
}
// ParsePattern extracts information from a pattern, supporting both regex and simple patterns
func ParsePattern(pattern string) (*ParsedPattern, error) {
parts := strings.Split(pattern, "\\;")
p := &ParsedPattern{Confidence: 100}
if parts[0] == "" {
p.SkipRegex = true
}
for i, part := range parts {
if i == 0 {
if p.SkipRegex {
continue
}
regexPattern := part
regexPattern = strings.ReplaceAll(regexPattern, "/", "\\/")
regexPattern = strings.ReplaceAll(regexPattern, "\\+", "__escapedPlus__")
regexPattern = strings.ReplaceAll(regexPattern, "+", "{1,250}")
regexPattern = strings.ReplaceAll(regexPattern, "*", "{0,250}")
regexPattern = strings.ReplaceAll(regexPattern, "__escapedPlus__", "\\+")
var err error
p.regex, err = regexp.Compile("(?i)" + regexPattern)
if err != nil {
return nil, err
}
} else {
keyValue := strings.SplitN(part, ":", 2)
if len(keyValue) < 2 {
continue
}
switch keyValue[0] {
case "confidence":
conf, err := strconv.Atoi(keyValue[1])
if err != nil {
// If conversion fails, keep default confidence
p.Confidence = 100
} else {
p.Confidence = conf
}
case "version":
p.Version = keyValue[1]
}
}
}
return p, nil
}
func (p *ParsedPattern) Evaluate(target string) (bool, string) {
if p.SkipRegex {
return true, ""
}
if p.regex == nil {
return false, ""
}
submatches := p.regex.FindStringSubmatch(target)
if len(submatches) == 0 {
return false, ""
}
extractedVersion, _ := p.extractVersion(submatches)
return true, extractedVersion
}
// extractVersion uses the provided pattern to extract version information from a target string.
func (p *ParsedPattern) extractVersion(submatches []string) (string, error) {
if len(submatches) == 0 {
return "", nil // No matches found
}
result := p.Version
for i, match := range submatches[1:] { // Start from 1 to skip the entire match
placeholder := fmt.Sprintf("\\%d", i+1)
result = strings.ReplaceAll(result, placeholder, match)
}
// Evaluate any ternary expressions in the result
result, err := evaluateVersionExpression(result, submatches[1:])
if err != nil {
return "", err
}
return strings.TrimSpace(result), nil
}
// evaluateVersionExpression handles ternary expressions in version strings.
func evaluateVersionExpression(expression string, submatches []string) (string, error) {
if strings.Contains(expression, "?") {
parts := strings.Split(expression, "?")
if len(parts) != 2 {
return "", fmt.Errorf("invalid ternary expression: %s", expression)
}
trueFalseParts := strings.Split(parts[1], ":")
if len(trueFalseParts) != 2 {
return "", fmt.Errorf("invalid true/false parts in ternary expression: %s", expression)
}
if trueFalseParts[0] != "" { // Simple existence check
if len(submatches) == 0 {
return trueFalseParts[1], nil
}
return trueFalseParts[0], nil
}
if trueFalseParts[1] == "" {
if len(submatches) == 0 {
return "", nil
}
return trueFalseParts[0], nil
}
return trueFalseParts[1], nil
}
return expression, nil
}