recog-intrigue 2.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.github/ISSUE_TEMPLATE/fingerprint_request.md +27 -0
- data/.github/PULL_REQUEST_TEMPLATE +24 -0
- data/.gitignore +14 -0
- data/.rbenv-gemset +1 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +25 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +171 -0
- data/COPYING +23 -0
- data/Gemfile +10 -0
- data/LICENSE +7 -0
- data/README.md +85 -0
- data/Rakefile +22 -0
- data/bin/recog_export +81 -0
- data/bin/recog_match +55 -0
- data/bin/recog_standardize +118 -0
- data/bin/recog_verify +64 -0
- data/cpe-remap.yaml +134 -0
- data/features/data/failing_banners_fingerprints.xml +20 -0
- data/features/data/matching_banners_fingerprints.xml +23 -0
- data/features/data/multiple_banners_fingerprints.xml +32 -0
- data/features/data/no_tests.xml +3 -0
- data/features/data/sample_banner.txt +2 -0
- data/features/data/successful_tests.xml +18 -0
- data/features/data/tests_with_failures.xml +20 -0
- data/features/data/tests_with_warnings.xml +17 -0
- data/features/match.feature +36 -0
- data/features/support/aruba.rb +3 -0
- data/features/support/env.rb +6 -0
- data/features/verify.feature +48 -0
- data/identifiers/README.md +47 -0
- data/identifiers/os_architecture.txt +20 -0
- data/identifiers/os_device.txt +52 -0
- data/identifiers/os_family.txt +160 -0
- data/identifiers/os_product.txt +199 -0
- data/identifiers/service_family.txt +185 -0
- data/identifiers/service_product.txt +255 -0
- data/identifiers/software_class.txt +26 -0
- data/identifiers/software_family.txt +91 -0
- data/identifiers/software_product.txt +333 -0
- data/identifiers/vendor.txt +405 -0
- data/lib/recog.rb +4 -0
- data/lib/recog/db.rb +78 -0
- data/lib/recog/db_manager.rb +31 -0
- data/lib/recog/fingerprint.rb +280 -0
- data/lib/recog/fingerprint/regexp_factory.rb +56 -0
- data/lib/recog/fingerprint/test.rb +18 -0
- data/lib/recog/formatter.rb +51 -0
- data/lib/recog/match_reporter.rb +77 -0
- data/lib/recog/matcher.rb +94 -0
- data/lib/recog/matcher_factory.rb +14 -0
- data/lib/recog/nizer.rb +347 -0
- data/lib/recog/verifier.rb +39 -0
- data/lib/recog/verifier_factory.rb +13 -0
- data/lib/recog/verify_reporter.rb +86 -0
- data/lib/recog/version.rb +3 -0
- data/misc/convert_mysql_err +61 -0
- data/misc/order.xsl +17 -0
- data/recog-intrigue.gemspec +45 -0
- data/requirements.txt +2 -0
- data/spec/data/best_os_match_1.yml +17 -0
- data/spec/data/best_os_match_2.yml +17 -0
- data/spec/data/best_service_match_1.yml +17 -0
- data/spec/data/smb_native_os.txt +25 -0
- data/spec/data/test_fingerprints.xml +36 -0
- data/spec/data/verification_fingerprints.xml +86 -0
- data/spec/data/whitespaced_fingerprint.xml +5 -0
- data/spec/lib/fingerprint_self_test_spec.rb +174 -0
- data/spec/lib/recog/db_spec.rb +98 -0
- data/spec/lib/recog/fingerprint/regexp_factory_spec.rb +73 -0
- data/spec/lib/recog/fingerprint_spec.rb +112 -0
- data/spec/lib/recog/formatter_spec.rb +69 -0
- data/spec/lib/recog/match_reporter_spec.rb +91 -0
- data/spec/lib/recog/nizer_spec.rb +330 -0
- data/spec/lib/recog/verify_reporter_spec.rb +113 -0
- data/spec/spec_helper.rb +82 -0
- data/update_cpes.py +186 -0
- data/xml/apache_modules.xml +1911 -0
- data/xml/apache_os.xml +273 -0
- data/xml/architecture.xml +36 -0
- data/xml/dns_versionbind.xml +761 -0
- data/xml/fingerprints.xsd +128 -0
- data/xml/ftp_banners.xml +1553 -0
- data/xml/h323_callresp.xml +603 -0
- data/xml/hp_pjl_id.xml +358 -0
- data/xml/html_title.xml +1630 -0
- data/xml/http_cookies.xml +411 -0
- data/xml/http_servers.xml +3195 -0
- data/xml/http_wwwauth.xml +595 -0
- data/xml/imap_banners.xml +245 -0
- data/xml/ldap_searchresult.xml +711 -0
- data/xml/mdns_device-info_txt.xml +1796 -0
- data/xml/mdns_workstation_txt.xml +15 -0
- data/xml/mysql_banners.xml +1649 -0
- data/xml/mysql_error.xml +871 -0
- data/xml/nntp_banners.xml +82 -0
- data/xml/ntp_banners.xml +1223 -0
- data/xml/operating_system.xml +629 -0
- data/xml/pop_banners.xml +499 -0
- data/xml/rsh_resp.xml +76 -0
- data/xml/rtsp_servers.xml +76 -0
- data/xml/sip_banners.xml +359 -0
- data/xml/sip_user_agents.xml +221 -0
- data/xml/smb_native_lm.xml +62 -0
- data/xml/smb_native_os.xml +662 -0
- data/xml/smtp_banners.xml +1690 -0
- data/xml/smtp_debug.xml +39 -0
- data/xml/smtp_ehlo.xml +49 -0
- data/xml/smtp_expn.xml +82 -0
- data/xml/smtp_help.xml +157 -0
- data/xml/smtp_mailfrom.xml +20 -0
- data/xml/smtp_noop.xml +44 -0
- data/xml/smtp_quit.xml +29 -0
- data/xml/smtp_rcptto.xml +25 -0
- data/xml/smtp_rset.xml +26 -0
- data/xml/smtp_turn.xml +26 -0
- data/xml/smtp_vrfy.xml +89 -0
- data/xml/snmp_sysdescr.xml +6507 -0
- data/xml/snmp_sysobjid.xml +430 -0
- data/xml/ssh_banners.xml +1968 -0
- data/xml/telnet_banners.xml +1595 -0
- data/xml/x11_banners.xml +232 -0
- data/xml/x509_issuers.xml +134 -0
- data/xml/x509_subjects.xml +1268 -0
- metadata +304 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require 'recog/db'
|
|
2
|
+
require 'regexp_parser'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
describe Recog::DB do
|
|
6
|
+
let(:schema) { Nokogiri::XML::Schema(open(File.expand_path(File.join(%w(xml fingerprints.xsd))))) }
|
|
7
|
+
Dir[File.expand_path File.join('xml', '*.xml')].each do |xml_file_name|
|
|
8
|
+
|
|
9
|
+
describe "##{File.basename(xml_file_name)}" do
|
|
10
|
+
|
|
11
|
+
it "is valid XML" do
|
|
12
|
+
doc = Nokogiri::XML(open(xml_file_name))
|
|
13
|
+
errors = schema.validate(doc)
|
|
14
|
+
expect(errors).to be_empty, "#{xml_file_name} is invalid recog XML -- #{errors.inspect}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
db = Recog::DB.new(xml_file_name)
|
|
18
|
+
|
|
19
|
+
it "has a match key" do
|
|
20
|
+
expect(db.match_key).not_to be_nil
|
|
21
|
+
expect(db.match_key).not_to be_empty
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "has valid 'preference' value" do
|
|
25
|
+
# Reserve values below 0.10 and above 0.90 for users
|
|
26
|
+
# See xml/fingerprints.xsd
|
|
27
|
+
expect(db.preference.class).to be ::Float
|
|
28
|
+
expect(db.preference).to be_between(0.10, 0.90)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
fp_descriptions = []
|
|
32
|
+
db.fingerprints.each_index do |i|
|
|
33
|
+
fp = db.fingerprints[i]
|
|
34
|
+
|
|
35
|
+
it "doesn't have a duplicate description" do
|
|
36
|
+
if fp_descriptions.include?(fp.name)
|
|
37
|
+
fail "'#{fp.name}'s description is not unique"
|
|
38
|
+
else
|
|
39
|
+
fp_descriptions << fp.name
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "#{fp.name}" do
|
|
44
|
+
param_names = []
|
|
45
|
+
it "has consistent os.device and hw.device" do
|
|
46
|
+
if fp.params['os.device'] && fp.params['hw.device'] && (fp.params['os.device'] != fp.params['hw.device'])
|
|
47
|
+
fail "#{fp.name} has both hw.device and os.device but with differing values"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
fp.params.each do |param_name, pos_value|
|
|
51
|
+
pos, value = pos_value
|
|
52
|
+
it "has valid looking fingerprint parameter names" do
|
|
53
|
+
unless param_name =~ /^(?:cookie|[^\.]+\..*)$/
|
|
54
|
+
fail "'#{param_name}' is invalid"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "doesn't have param values for capture params" do
|
|
59
|
+
if pos > 0 && !value.to_s.empty?
|
|
60
|
+
fail "'#{fp.name}'s #{param_name} is a non-zero pos but specifies a value of '#{value}'"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "has parameter values other than General, Server or Unknown, which are not helpful" do
|
|
65
|
+
if pos == 0 && value =~ /^(?i:general|server|unknown)$/
|
|
66
|
+
fail "'#{param_name}' has general/server/unknown value '#{value}'"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "doesn't omit values for non-capture params" do
|
|
71
|
+
if pos == 0 && value.to_s.empty?
|
|
72
|
+
fail "'#{fp.name}'s #{param_name} is not a capture (pos=0) but doesn't specify a value"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "doesn't have duplicate params" do
|
|
77
|
+
if param_names.include?(param_name)
|
|
78
|
+
fail "'#{fp.name}'s has duplicate #{param_name}"
|
|
79
|
+
else
|
|
80
|
+
param_names << param_name
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "uses interpolation correctly" do
|
|
85
|
+
if pos == 0 && /\{(?<interpolated>[^\s{}]+)\}/ =~ value
|
|
86
|
+
unless fp.params.key?(interpolated)
|
|
87
|
+
fail "'#{fp.name}' uses interpolated value '#{interpolated}' that does not exist"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "#{fp.regex}" do
|
|
95
|
+
|
|
96
|
+
it "has a valid looking name" do
|
|
97
|
+
expect(fp.name).not_to be_nil
|
|
98
|
+
expect(fp.name).not_to be_empty
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "has a regex" do
|
|
102
|
+
expect(fp.regex).not_to be_nil
|
|
103
|
+
expect(fp.regex.class).to be ::Regexp
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'uses capturing regular expressions properly' do
|
|
107
|
+
# the list of index-based captures that the fingerprint is expecting
|
|
108
|
+
expected_capture_positions = fp.params.values.map(&:first).map(&:to_i).select { |position| position > 0 }
|
|
109
|
+
if fp.params.empty? && expected_capture_positions.size > 0
|
|
110
|
+
fail "Non-asserting fingerprint with regex #{fp.regex} captures #{expected_capture_positions.size} time(s); 0 are needed"
|
|
111
|
+
else
|
|
112
|
+
# parse the regex and count the number of captures
|
|
113
|
+
actual_capture_positions = []
|
|
114
|
+
capture_number = 1
|
|
115
|
+
Regexp::Scanner.scan(fp.regex).each do |token_parts|
|
|
116
|
+
if token_parts.first == :group && ![:close, :passive, :options, :options_switch].include?(token_parts[1])
|
|
117
|
+
actual_capture_positions << capture_number
|
|
118
|
+
capture_number += 1
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
# compare the captures actually performed to those being used and ensure that they contain
|
|
122
|
+
# the same elements regardless of order, preventing, over-, under- and other forms of mis-capturing.
|
|
123
|
+
actual_capture_positions = actual_capture_positions.sort.uniq
|
|
124
|
+
expected_capture_positions = expected_capture_positions.sort.uniq
|
|
125
|
+
expect(actual_capture_positions).to eq(expected_capture_positions),
|
|
126
|
+
"Regex has #{actual_capture_positions.size} capture groups, but the fingerprint expected #{expected_capture_positions.size} extractions."
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Not yet enforced
|
|
131
|
+
# it "has test cases" do
|
|
132
|
+
# expect(fp.tests.length).not_to equal(0)
|
|
133
|
+
# end
|
|
134
|
+
|
|
135
|
+
it "Has a reasonable number (<= 20) of test cases" do
|
|
136
|
+
expect(fp.tests.length).to be <= 20
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
fp_examples = []
|
|
140
|
+
fp.tests.each do |example|
|
|
141
|
+
it "doesn't have a duplicate examples" do
|
|
142
|
+
if fp_examples.include?(example.content)
|
|
143
|
+
fail "'#{fp.name}' has duplicate example '#{example.content}'"
|
|
144
|
+
else
|
|
145
|
+
fp_examples << example.content
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
it "Example '#{example.content}' matches this regex" do
|
|
149
|
+
match = fp.match(example.content)
|
|
150
|
+
expect(match).to_not be_nil, 'Regex did not match'
|
|
151
|
+
# test any extractions specified in the example
|
|
152
|
+
example.attributes.each_pair do |k,v|
|
|
153
|
+
next if k == '_encoding'
|
|
154
|
+
expect(match[k]).to eq(v), "Regex didn't extract expected value for fingerprint attribute #{k} -- got #{match[k]} instead of #{v}"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "Example '#{example.content}' matches this regex first" do
|
|
159
|
+
db.fingerprints.slice(0, i).each_index do |previous_i|
|
|
160
|
+
prev_fp = db.fingerprints[previous_i]
|
|
161
|
+
prev_fp.tests.each do |prev_example|
|
|
162
|
+
match = prev_fp.match(example.content)
|
|
163
|
+
expect(match).to be_nil, "Matched regex ##{previous_i} (#{db.fingerprints[previous_i].regex}) rather than ##{i} (#{db.fingerprints[i].regex})"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'recog/db'
|
|
2
|
+
|
|
3
|
+
describe Recog::DB do
|
|
4
|
+
let(:xml_file) { File.expand_path File.join('spec', 'data', 'test_fingerprints.xml') }
|
|
5
|
+
subject { Recog::DB.new(xml_file) }
|
|
6
|
+
|
|
7
|
+
describe "#fingerprints" do
|
|
8
|
+
subject(:fingerprints) { described_class.new(xml_file).fingerprints }
|
|
9
|
+
|
|
10
|
+
it { is_expected.to be_a(Enumerable) }
|
|
11
|
+
|
|
12
|
+
context "with only a pattern" do
|
|
13
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[0] }
|
|
14
|
+
|
|
15
|
+
it "has a blank name with no description" do
|
|
16
|
+
expect(entry.name).to be_empty
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "has a pattern" do
|
|
20
|
+
expect(entry.regex.source).to eq(".*\\(iSeries\\).*")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "has no params" do
|
|
24
|
+
expect(entry.params).to be_empty
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "has no tests" do
|
|
28
|
+
expect(entry.tests).to be_empty
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "with params" do
|
|
33
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[1] }
|
|
34
|
+
|
|
35
|
+
it "has a name" do
|
|
36
|
+
expect(entry.name).to eq('PalmOS')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "has a pattern" do
|
|
40
|
+
expect(entry.regex.source).to eq(".*\\(PalmOS\\).*")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "has params" do
|
|
44
|
+
expect(entry.params).to eq({"os.vendor"=>[1, "Palm"], "os.device"=>[2, "General"]})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "has no tests" do
|
|
48
|
+
expect(entry.tests).to be_empty
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "with pattern flags" do
|
|
53
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[2] }
|
|
54
|
+
|
|
55
|
+
it "has a name and only uses the first value" do
|
|
56
|
+
expect(entry.name).to eq('HP Designjet printer')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'creates a Regexp with expected flags' do
|
|
60
|
+
expect(entry.regex).to be_a(Regexp)
|
|
61
|
+
expect(entry.regex.options).to eq(Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::IGNORECASE)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "has a pattern" do
|
|
65
|
+
expect(entry.regex).to be_a(Regexp)
|
|
66
|
+
expect(entry.regex.source).to eq("(designjet \\S+)")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "has params" do
|
|
70
|
+
expect(entry.params).to eq({"service.vendor"=>[0, "HP"]})
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "has no tests" do
|
|
74
|
+
expect(entry.tests).to be_empty
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "with test" do
|
|
79
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[3] }
|
|
80
|
+
|
|
81
|
+
it "has a name" do
|
|
82
|
+
expect(entry.name).to eq('HP JetDirect Printer')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "has a pattern" do
|
|
86
|
+
expect(entry.regex.source).to eq("laserjet (.*)(?: series)?")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "has params" do
|
|
90
|
+
expect(entry.params).to eq({"service.vendor"=>[0, "HP"]})
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "has no tests" do
|
|
94
|
+
expect(entry.tests.map(&:content)).to match_array(["HP LaserJet 4100 Series", "HP LaserJet 2200"])
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
require 'recog/fingerprint/regexp_factory'
|
|
3
|
+
|
|
4
|
+
describe Recog::Fingerprint::RegexpFactory do
|
|
5
|
+
|
|
6
|
+
describe 'FLAG_MAP' do
|
|
7
|
+
subject { described_class::FLAG_MAP }
|
|
8
|
+
|
|
9
|
+
it "should have the right number of flags" do
|
|
10
|
+
expect(subject.size).to be 5
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '.build' do
|
|
15
|
+
subject { described_class.build(pattern, options) }
|
|
16
|
+
|
|
17
|
+
let(:pattern) { 'Apache/(\d+)' }
|
|
18
|
+
let(:options) { [ 'REG_ICASE' ] }
|
|
19
|
+
|
|
20
|
+
it { is_expected.to be_a(Regexp) }
|
|
21
|
+
it { is_expected.to match('Apache/2') }
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '.build_options' do
|
|
26
|
+
subject { described_class.build_options(flags) }
|
|
27
|
+
|
|
28
|
+
let(:flags) { [ ] }
|
|
29
|
+
it { is_expected.to be_a(Integer) }
|
|
30
|
+
|
|
31
|
+
context 'without any explicit flags' do
|
|
32
|
+
let(:flags) { [ ] }
|
|
33
|
+
specify "sets default flags" do
|
|
34
|
+
expect(subject).to be Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'with REG_ICASE' do
|
|
39
|
+
let(:flags) { [ 'REG_ICASE' ] }
|
|
40
|
+
specify "sets IGNORECASE" do
|
|
41
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::IGNORECASE)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'with REG_DOT_NEWLINE' do
|
|
46
|
+
let(:flags) { [ 'REG_DOT_NEWLINE' ] }
|
|
47
|
+
specify "sets MULTILINE" do
|
|
48
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context 'with REG_LINE_ANY_CRLF' do
|
|
53
|
+
let(:flags) { [ 'REG_LINE_ANY_CRLF' ] }
|
|
54
|
+
specify "sets MULTILINE" do
|
|
55
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'with multiple flags' do
|
|
60
|
+
let(:flags) { [ 'REG_LINE_ANY_CRLF', 'REG_ICASE' ] }
|
|
61
|
+
specify "sets correct flags" do
|
|
62
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'with invalid flags' do
|
|
67
|
+
let(:flags) { %w(SYN ACK FIN) } # oh, wrong flags!
|
|
68
|
+
specify 'raises and lists supported/unsupported flags' do
|
|
69
|
+
expect { subject }.to raise_error(/SYN,ACK,FIN. Must be one of: .+/)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
require 'recog/fingerprint'
|
|
3
|
+
|
|
4
|
+
describe Recog::Fingerprint do
|
|
5
|
+
context "whitespace" do
|
|
6
|
+
let(:xml) do
|
|
7
|
+
path = File.expand_path(File.join('spec', 'data', 'whitespaced_fingerprint.xml'))
|
|
8
|
+
doc = Nokogiri::XML(IO.read(path))
|
|
9
|
+
doc.xpath("//fingerprint").first
|
|
10
|
+
end
|
|
11
|
+
subject { Recog::Fingerprint.new(xml) }
|
|
12
|
+
|
|
13
|
+
describe "#name" do
|
|
14
|
+
it "properly squashes whitespace" do
|
|
15
|
+
expect(subject.name).to eq('I love whitespace!')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#verification" do
|
|
21
|
+
let(:xml_file) { File.expand_path(File.join('spec', 'data', 'verification_fingerprints.xml')) }
|
|
22
|
+
let(:doc) { Nokogiri::XML(IO.read(xml_file)) }
|
|
23
|
+
|
|
24
|
+
context "0 params" do
|
|
25
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[0]) }
|
|
26
|
+
|
|
27
|
+
it "does not yield if a fingerprint has 0 parameters" do
|
|
28
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "0 capture groups" do
|
|
33
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[1]) }
|
|
34
|
+
|
|
35
|
+
it "does not yield if a fingerprint has parameters, but 0 are defined by a capture group " do
|
|
36
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "0 examples" do
|
|
41
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[2]) }
|
|
42
|
+
|
|
43
|
+
it "does not yield if a fingerprint has 0 examples" do
|
|
44
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "1 capture group, 1 example" do
|
|
49
|
+
|
|
50
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[3]) }
|
|
51
|
+
|
|
52
|
+
it "does not yield when one capture group parameter is tested for in one example" do
|
|
53
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "2 capture groups, 1 example" do
|
|
58
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[4]) }
|
|
59
|
+
|
|
60
|
+
it "does not yield when two capture group parameters are tested for in one example" do
|
|
61
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "2 capture groups, 2 examples, 1 in each" do
|
|
66
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[5]) }
|
|
67
|
+
|
|
68
|
+
it "does not yield when two capture group parameters are tested for in two examples, one parameter in each" do
|
|
69
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "1 missing capture group, 1 example" do
|
|
74
|
+
|
|
75
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[6]) }
|
|
76
|
+
|
|
77
|
+
it "identifies when a parameter defined by a capture group is not included in one example" do
|
|
78
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String])
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context "2 missing capture groups, 1 example" do
|
|
83
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[7]) }
|
|
84
|
+
|
|
85
|
+
it "identifies when two parameters defined by a capture groups are not included in one example" do
|
|
86
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String], [:warn, String])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "1 missing capture group, 2 examples" do
|
|
91
|
+
|
|
92
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[8]) }
|
|
93
|
+
|
|
94
|
+
it "identifies when a parameter defined by a capture group is not included in one example" do
|
|
95
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "2 missing capture groups, 2 examples" do
|
|
100
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[9]) }
|
|
101
|
+
|
|
102
|
+
it "identifies when two parameters defined by a capture groups are not included in one example" do
|
|
103
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String], [:warn, String])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
skip "value interpolation" do
|
|
110
|
+
# TODO
|
|
111
|
+
end
|
|
112
|
+
end
|