selenium_tor 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -2
- data/CHANGELOG.md +21 -0
- data/README.md +45 -24
- data/fonts/000_README.txt +7 -0
- data/lib/tor/driver.rb +53 -28
- data/lib/tor/options.rb +16 -13
- data/lib/tor/system_tor.rb +4 -71
- data/lib/tor/tor_prefs.rb +2 -1
- data/lib/tor/tor_process.rb +78 -0
- data/lib/tor/torrc.rb +3 -1
- data/lib/tor/version.rb +1 -1
- data/selenium_tor.gemspec +1 -1
- metadata +9 -8
- data/lib/tor/driver_not_yet_connected_to_tor_network.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6aeb8724ddcdf0d7260330ca616651d54b94ce33c6a635c71ef5c5f3577de220
|
4
|
+
data.tar.gz: 12b5d264f6a774dab990148feb15179d0b7873e42eb5b1c4bb99e8383f5e5577
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: deafbf4cbbc646e2fef5dc1800bc009edceb72906d65dc071942d0c742a49dd6aa17ac4fe2f88fc8d36f909445b31378996073f7d44b8cff473db2e9e3f5d403
|
7
|
+
data.tar.gz: 71c615ce16eb80f696aaa4dc26d73459df4ce8c4b7d994998580be1d93405baa5dc9ee94f321e7494f1567f3bed1f48481e53149829c2e1671268a6069d9d91f
|
data/.rubocop.yml
CHANGED
@@ -10,8 +10,9 @@ AllCops:
|
|
10
10
|
Style/HashSyntax:
|
11
11
|
Enabled: false # yuk Ruby 3.1
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
Naming/MethodName:
|
14
|
+
Exclude:
|
15
|
+
- !ruby/regexp /test_.*\.rb$/ # for test_FOO constant tests
|
15
16
|
|
16
17
|
Security/Eval:
|
17
18
|
Exclude:
|
@@ -27,3 +28,10 @@ Style/RegexpLiteral:
|
|
27
28
|
|
28
29
|
Minitest/TestMethodName:
|
29
30
|
Enabled: true
|
31
|
+
|
32
|
+
Minitest/AssertTruthy:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Minitest/TestFileName:
|
36
|
+
Exclude:
|
37
|
+
- test/features/fingerprinting/vglrun_test_fingerprintjs.rb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## [1.2.0] - 2024-07-31
|
4
|
+
|
5
|
+
### Bug fixes
|
6
|
+
|
7
|
+
* [#11](https://gitlab.com/matzfan/selenium-tor/-/issues/11)
|
8
|
+
|
9
|
+
### New features
|
10
|
+
|
11
|
+
* add ability to change security level
|
12
|
+
* add ability to get new circuit for page ([#10](https://gitlab.com/matzfan/selenium-tor/-/issues/10))
|
13
|
+
|
14
|
+
## [1.1.0] - 2024-07-28
|
15
|
+
|
16
|
+
### Bug fixes
|
17
|
+
|
18
|
+
* [#9](https://gitlab.com/matzfan/selenium-tor/-/issues/9)
|
19
|
+
|
20
|
+
### New features
|
21
|
+
|
22
|
+
* deprecate SystemTor options IFO tor_opts
|
23
|
+
|
3
24
|
## [1.0.0] - 2024-07-15
|
4
25
|
|
5
26
|
### New features
|
data/README.md
CHANGED
@@ -17,6 +17,10 @@ The above approach will hide your IP, but there is a good chance your browser's
|
|
17
17
|
|
18
18
|
## Known issues
|
19
19
|
|
20
|
+
Known issues are recorded [here](https://gitlab.com/matzfan/selenium-tor/-/issues).
|
21
|
+
|
22
|
+
### Fingerprinting
|
23
|
+
|
20
24
|
The gem uses Xvfb to allow Tor Browser to be manipulated headlessly. Xvfb in turn uses `llvmpipe` software acceleration which may lead to issues with WebGL fingerprinting - see [issue #7](https://gitlab.com/matzfan/selenium-tor/-/issues/7). If this is a problem we recommend using [VirtualGL](https://www.virtualgl.org) to force Xvfb to use your hardware driver instead. This is done by prepending your executable code with the command `vglrun`. For an example see the section below on testing. VirtualGL can be installed from a [package repo](https://virtualgl.org/Downloads/YUM).
|
21
25
|
|
22
26
|
## Installation
|
@@ -44,39 +48,56 @@ options = Selenium::WebDriver::Tor::Options.new
|
|
44
48
|
@driver.title # => Congratulations. This browser is configured to use Tor.
|
45
49
|
@driver.quit
|
46
50
|
```
|
47
|
-
|
48
|
-
|
49
|
-
### Multiple instances using the system tor
|
51
|
+
If the network is inaccessible for any reason a `TorNetworkError` will result.
|
50
52
|
|
51
|
-
|
53
|
+
### Multiple driver instances
|
52
54
|
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
system_tor = Selenium::WebDriver::Tor::SystemTor.new # writes a driver-specific torrc file
|
56
|
-
options = Selenium::WebDriver::Tor::Options.new system_tor: system_tor
|
57
|
-
@driver = Selenium::WebDriver.for :tor, options: options # new tor process started when :system_tor option passed to driver
|
58
|
-
@driver.get 'https://example.com'
|
59
|
-
@driver.quit # stops the new system tor process and deletes torrc file
|
60
|
-
```
|
61
|
-
Multiple instances may be instantiated, in which case `SystemTor` options for :socks_port (default 9150) and :control_port (default 9151) must be set for each so they do not conflict. For example:
|
55
|
+
Running multiple `tor` processes requires that each uses different ports for SocksPort and ControlPort. These and other valid tor options can be passed using the `:tor_opts` key. Recognized options are snake_case equivalents of the camel case options reconized by tor. For a list see `man tor`. An example using the [Parallel gem](https://rubygems.org/gems/parallel):
|
62
56
|
```ruby
|
63
|
-
|
64
|
-
system_tor = Selenium::WebDriver::Tor::SystemTor.new(socks_port: socks_p, control_port: control_p)
|
65
|
-
options = Selenium::WebDriver::Tor::Options.new system_tor: system_tor
|
66
|
-
Selenium::WebDriver.for :tor, options: options
|
67
|
-
end
|
57
|
+
require 'parallel'
|
68
58
|
|
69
59
|
socks_port = 9150
|
70
60
|
control_port = 9151
|
71
61
|
|
72
|
-
|
73
|
-
socks_port
|
74
|
-
|
75
|
-
|
62
|
+
def driver(socks_p, control_p)
|
63
|
+
options = Selenium::WebDriver::Tor::Options.new tor_opts: { socks_port: socks_p, control_port: control_p }
|
64
|
+
Selenium::WebDriver.for :tor, options: options # new tor process started with tor_opts
|
65
|
+
end
|
66
|
+
|
67
|
+
driver1 = driver(socks_port, control_port)
|
68
|
+
driver2 = driver(socks_port += 2, control_port += 2)
|
69
|
+
|
70
|
+
title = 'Congratulations. This browser is configured to use Tor.'
|
71
|
+
|
72
|
+
Parallel.all?([driver1, driver2], in_threads: 2) do |driver|
|
73
|
+
driver.get 'https://check.torproject.org'
|
74
|
+
driver.title == title
|
75
|
+
end
|
76
|
+
# => true
|
77
|
+
|
78
|
+
driver1&.quit
|
79
|
+
driver2&.quit
|
80
|
+
```
|
81
|
+
### Tor Browser specific functionality
|
76
82
|
|
77
|
-
|
78
|
-
|
83
|
+
You can get and set the secuirty level (shield icon in TB) as follows. 4 is 'Standard', 2 is 'Safer', 1 is 'Safest'.
|
84
|
+
```ruby
|
85
|
+
@driver = Selenium::WebDriver.for :tor
|
86
|
+
@driver.security_level
|
87
|
+
# => 4 - the default value
|
88
|
+
@driver.security_level = 1
|
89
|
+
@driver.security_level
|
90
|
+
# => 1
|
91
|
+
@driver.quit
|
92
|
+
```
|
93
|
+
You can get a new circuit for the current page ('New Tor circuit for this site' on TB's application menu):
|
94
|
+
```ruby
|
95
|
+
@driver = Selenium::WebDriver.for :tor
|
96
|
+
@driver.get 'https://example.com'
|
97
|
+
@driver.new_circuit_for_page # new circuit for the current page only
|
98
|
+
@driver.quit
|
79
99
|
```
|
100
|
+
### Miscellaneous
|
80
101
|
|
81
102
|
The `Selenium::WebDriver::Tor` namespace is used for `Driver`, `Options`, `Profile` and all tor-specific classes. Otherwise Selenium's `Selenium::WebDriver::Firefox` namespace is used.
|
82
103
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
DO NOT MODIFY THE CONTENTS OF THIS DIRECTORY
|
2
|
+
|
3
|
+
Any adjustment to bundled fonts will result in an altered fingerprint. Font
|
4
|
+
fingerprinting is more than just detecting what fonts you have, it also includes
|
5
|
+
font fallbacks and characters (unicode code points) and any change in those can
|
6
|
+
be measured.
|
7
|
+
|
data/lib/tor/driver.rb
CHANGED
@@ -1,58 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'open-uri'
|
4
|
-
require_relative '
|
4
|
+
require_relative '../service'
|
5
|
+
require_relative '../options'
|
6
|
+
require_relative 'profile'
|
7
|
+
require_relative 'tor_process'
|
5
8
|
|
6
9
|
module Selenium
|
7
10
|
module WebDriver
|
8
11
|
module Tor
|
12
|
+
# delegate class
|
13
|
+
class DriverDelegate < Firefox::Driver; end
|
14
|
+
|
9
15
|
# tor driver
|
10
|
-
class Driver < DelegateClass(
|
16
|
+
class Driver < DelegateClass(DriverDelegate)
|
11
17
|
class TorNetworkError < StandardError; end
|
12
18
|
|
13
|
-
|
14
|
-
TOR_PREFS_CONNECTION_URL = 'about:preferences#connection'
|
15
|
-
|
16
|
-
attr_reader :system_tor
|
19
|
+
TB_SECURITY_LEVELS = [4, 2, 1].freeze
|
17
20
|
|
18
21
|
def initialize(options: nil, **)
|
19
|
-
|
20
|
-
|
22
|
+
@data_dir = Dir.mktmpdir
|
23
|
+
@options = options || Options.new # fix for issue #11, 'tis a puzzlement
|
24
|
+
add_torrc_path_to_options
|
25
|
+
@instance = DriverDelegate.new(options: @options, **)
|
26
|
+
install_extensions @instance
|
27
|
+
create_tor_process_and_start_tor @options.tor_opts
|
21
28
|
super(@instance)
|
22
|
-
|
23
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
def browser
|
32
|
+
:tor # overides :firefox
|
24
33
|
end
|
25
34
|
|
26
35
|
def quit
|
27
|
-
|
36
|
+
@tor_process&.stop_tor
|
37
|
+
FileUtils.rm_rf @data_dir
|
28
38
|
super
|
29
39
|
end
|
30
40
|
|
31
|
-
|
41
|
+
def security_level
|
42
|
+
old_context = context
|
43
|
+
self.context = 'chrome'
|
44
|
+
execute_script("return Services.prefs.getIntPref('browser.security_level.security_slider')")
|
45
|
+
ensure
|
46
|
+
self.context = old_context
|
47
|
+
end
|
32
48
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
def security_level=(int)
|
50
|
+
old_context = context
|
51
|
+
self.context = 'chrome'
|
52
|
+
raise(ArgumentError, 'Security level can be set to 4, 2 or 1') unless TB_SECURITY_LEVELS.include?(int)
|
53
|
+
|
54
|
+
execute_script("return Services.prefs.setIntPref('browser.security_level.security_slider', #{int})")
|
55
|
+
ensure
|
56
|
+
self.context = old_context if old_context
|
57
|
+
end
|
58
|
+
|
59
|
+
def new_circuit_for_page
|
60
|
+
old_context = context
|
61
|
+
self.context = 'chrome'
|
62
|
+
execute_script "ChromeUtils.importESModule('resource://gre/modules/TorDomainIsolator.sys.mjs');
|
63
|
+
TorDomainIsolator.newCircuitForDomain('#{current_url}')"
|
37
64
|
ensure
|
38
|
-
|
65
|
+
self.context = old_context
|
39
66
|
end
|
40
67
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
@instance.quit # abort initialization
|
46
|
-
raise TorNetworkError, "Cannot connect to Tor network: #{e.message}"
|
68
|
+
private
|
69
|
+
|
70
|
+
def add_torrc_path_to_options
|
71
|
+
@options.prefs['extensions.torlauncher.torrc_path'] = File.join(@data_dir, 'torrc')
|
47
72
|
end
|
48
73
|
|
49
|
-
def install_extensions
|
50
|
-
Pathname.new(TBB_EXTENSIONS_DIR).children.each { |xpi_path|
|
74
|
+
def install_extensions(instance)
|
75
|
+
Pathname.new(TBB_EXTENSIONS_DIR).children.each { |xpi_path| instance.install_addon xpi_path }
|
51
76
|
end
|
52
77
|
|
53
|
-
def
|
54
|
-
@
|
55
|
-
|
78
|
+
def create_tor_process_and_start_tor(opts)
|
79
|
+
@tor_process = TorProcess.new(@data_dir, opts || {})
|
80
|
+
@tor_process.start_tor
|
56
81
|
end
|
57
82
|
end
|
58
83
|
end
|
data/lib/tor/options.rb
CHANGED
@@ -9,44 +9,47 @@ module Selenium
|
|
9
9
|
module Tor
|
10
10
|
# tor options
|
11
11
|
class Options < Firefox::Options
|
12
|
-
CAPABILITIES = CAPABILITIES.merge(
|
12
|
+
CAPABILITIES = CAPABILITIES.merge(tor_opts: 'tor_opts')
|
13
|
+
CAPABILITIES[:system_tor] = 'system_tor' # DEPRECATED 2.0
|
13
14
|
DEFAULT_TBB_DIR = File.join Dir.home, 'tor-browser'
|
14
15
|
|
15
16
|
Tor::TBB_DIR = ENV.fetch('TOR_BROWSER_ROOT_DIR', nil) || DEFAULT_TBB_DIR
|
16
17
|
Tor::TBB_BROWSER_DIR = File.join Tor::TBB_DIR, 'Browser'
|
17
18
|
Tor::TBB_BINARY_PATH = File.join Tor::TBB_BROWSER_DIR, 'firefox'
|
19
|
+
Tor::TBB_TOR_BINARY_PATH = File.join Tor::TBB_BROWSER_DIR, *%w[TorBrowser Tor tor]
|
18
20
|
Tor::TBB_PROFILE_DIR = File.join Tor::TBB_BROWSER_DIR, *%w[TorBrowser Data Browser profile.default]
|
19
21
|
Tor::TBB_EXTENSIONS_DIR = File.join Tor::TBB_PROFILE_DIR, 'extensions'
|
20
22
|
Tor::TBB_VERSION = JSON.parse(File.read(File.join(Tor::TBB_BROWSER_DIR, 'tbb_version.json')))['version']
|
21
23
|
|
22
|
-
attr_reader :
|
24
|
+
attr_reader :tor_opts
|
23
25
|
|
24
26
|
def initialize(log_level: nil, **opts)
|
25
|
-
|
26
|
-
|
27
|
-
super(log_level: log_level, **tor_options(opts.delete(:prefs)).merge(opts))
|
27
|
+
opts[:tor_opts] = opts.delete(:system_tor).opts if opts[:system_tor] # DEPRECATED 2.0
|
28
|
+
@tor_opts = opts[:tor_opts] ? opts.delete(:tor_opts) : {} # must be deleted before call to super
|
28
29
|
do_start_tor_browser_script_stuff # stuff the start-tor-browser script in TBB does
|
30
|
+
copy_fonts # so we don't have to change dir before executing TB binary - issues #2 and #9
|
31
|
+
super(log_level: log_level, **tor_options(opts.delete(:prefs)).merge(opts))
|
29
32
|
end
|
30
33
|
|
31
34
|
private
|
32
35
|
|
33
|
-
def system_tor_prefs(system_tor)
|
34
|
-
@system_tor = system_tor
|
35
|
-
@system_tor_prefs = system_tor.prefs
|
36
|
-
end
|
37
|
-
|
38
36
|
def tor_options(prefs)
|
39
|
-
{ binary: TBB_BINARY_PATH, prefs: (prefs || {}).merge(TOR_PREFS)
|
37
|
+
{ binary: TBB_BINARY_PATH, prefs: (prefs || {}).merge(TOR_PREFS) }
|
40
38
|
end
|
41
39
|
|
42
40
|
def do_start_tor_browser_script_stuff
|
43
41
|
ENV['SESSION_MANAGER'] = nil
|
44
42
|
ENV['XAUTHORITY'] = File.join(Dir.home, '.Xauthority') unless ENV.fetch('XAUTHORITY', nil)
|
45
|
-
ENV['FONTCONFIG_PATH'] = File.join
|
43
|
+
ENV['FONTCONFIG_PATH'] = File.join TBB_BROWSER_DIR, 'fontconfig'
|
46
44
|
ENV['FONTCONFIG_FILE'] = 'fonts.conf'
|
47
45
|
FileUtils.rm_rf File.join(Tor::TBB_BROWSER_DIR, *%w[TorBrowser Data fontconfig])
|
48
46
|
ENV['GSETTINGS_BACKEND'] = 'memory'
|
49
|
-
|
47
|
+
end
|
48
|
+
|
49
|
+
def copy_fonts
|
50
|
+
return unless Dir[File.join('fonts', '*')].size == 1 # README checked into git
|
51
|
+
|
52
|
+
FileUtils.cp(Dir[File.join(Tor::TBB_BROWSER_DIR, 'fonts', '*')], File.expand_path('fonts'))
|
50
53
|
end
|
51
54
|
end
|
52
55
|
end
|
data/lib/tor/system_tor.rb
CHANGED
@@ -1,82 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'timeout'
|
4
|
-
require_relative 'torrc'
|
5
|
-
require_relative '../string_extensions'
|
6
|
-
|
7
3
|
module Selenium
|
8
4
|
module WebDriver
|
9
5
|
module Tor
|
10
|
-
#
|
6
|
+
# DEPRECATED
|
11
7
|
class SystemTor
|
12
|
-
|
13
|
-
|
14
|
-
class SystemTorError < StandardError; end
|
15
|
-
|
16
|
-
BOOTSTRAP_SUCCESS_REGEX = /Bootstrapped 100% \(done\): Done$/
|
17
|
-
BOOTSTRAP_FAIL_REGEX = /^[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
18
|
-
# BOOTSTRAP_TIMEOUT_REGEX = /^[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
19
|
-
|
20
|
-
TORRC_PREFS = { 'extensions.torlauncher.start_tor' => false }.freeze
|
21
|
-
TORRC_PATH_KEY = 'extensions.torlauncher.torrc_path'
|
22
|
-
|
23
|
-
attr_reader :pid, :config
|
8
|
+
attr_reader :opts
|
24
9
|
|
25
10
|
def initialize(opts = {})
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@opts = map_opts_to_torrc_keys opts
|
30
|
-
@data_dir = Dir.mktmpdir # each tor process needs a separate data directory
|
31
|
-
@torrc = Torrc.new(@data_dir)
|
32
|
-
@config ||= setup_config # Hash to store torrc config
|
33
|
-
end
|
34
|
-
|
35
|
-
def prefs
|
36
|
-
TORRC_PREFS.merge({ TORRC_PATH_KEY => @torrc.path })
|
37
|
-
end
|
38
|
-
|
39
|
-
def start_tor
|
40
|
-
r, io = IO.pipe
|
41
|
-
pid = Process.spawn "tor -f #{@torrc.path}", out: io, err: :out
|
42
|
-
io.close
|
43
|
-
errors = parse_tor_bootstrap_errors r
|
44
|
-
errors.empty? ? @pid = pid : raise(SystemTorError, "Tor failed to start with errors:\n\n#{errors}")
|
45
|
-
ensure
|
46
|
-
r.close
|
47
|
-
end
|
48
|
-
|
49
|
-
def stop_tor
|
50
|
-
Process.kill 'KILL', pid if pid
|
51
|
-
FileUtils.rm_rf @data_dir if @data_dir
|
52
|
-
@pid = nil
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def map_opts_to_torrc_keys(opts)
|
58
|
-
raise SystemTorError, 'Options hash keys must be snake case' unless opts.keys.map!(&:to_s).all?(&:snake_case?)
|
59
|
-
|
60
|
-
opts.transform_keys { |k| k.to_s.split('_').map(&:capitalize).join }
|
61
|
-
end
|
62
|
-
|
63
|
-
def setup_config
|
64
|
-
@torrc.write_to_config @opts
|
65
|
-
@torrc.config
|
66
|
-
end
|
67
|
-
|
68
|
-
def tor_executable?
|
69
|
-
`which tor`
|
70
|
-
end
|
71
|
-
|
72
|
-
def parse_tor_bootstrap_errors(io)
|
73
|
-
lines = []
|
74
|
-
io.each_line do |line|
|
75
|
-
lines << line
|
76
|
-
break lines if line.match BOOTSTRAP_FAIL_REGEX
|
77
|
-
break [] if line.match BOOTSTRAP_SUCCESS_REGEX
|
78
|
-
# break false if line.match BOOTSTRAP_TIMEOUT_REGEX
|
79
|
-
end
|
11
|
+
warn '[DEPRECATION] `SystemTor` options are deprecated in `selenium_tor` 2.0. Use `tor_opts` instead.'
|
12
|
+
@opts = opts
|
80
13
|
end
|
81
14
|
end
|
82
15
|
end
|
data/lib/tor/tor_prefs.rb
CHANGED
@@ -17,7 +17,8 @@ module Selenium
|
|
17
17
|
|
18
18
|
OTHER_PREFS = {
|
19
19
|
'intl.language_notification.shown' => true, # affects font fingerprint (viewport size)
|
20
|
-
'remote.active-protocols' => 3 # CDP & BiDi future support
|
20
|
+
'remote.active-protocols' => 3, # CDP & BiDi future support
|
21
|
+
'extensions.torlauncher.start_tor' => false
|
21
22
|
}.freeze
|
22
23
|
|
23
24
|
TOR_PREFS = FIRST_CONNECTION_PREFS.merge OTHER_PREFS
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
require_relative 'torrc'
|
5
|
+
require_relative '../string_extensions'
|
6
|
+
|
7
|
+
module Selenium
|
8
|
+
module WebDriver
|
9
|
+
module Tor
|
10
|
+
# Respresentation of a tor process
|
11
|
+
class TorProcess
|
12
|
+
include StringExtensions
|
13
|
+
|
14
|
+
class TorProcessError < StandardError; end
|
15
|
+
|
16
|
+
BOOTSTRAP_SUCCESS_REGEX = /Bootstrapped 100% \(done\): Done$/
|
17
|
+
BOOTSTRAP_FAIL_REGEX = /^[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
18
|
+
# BOOTSTRAP_TIMEOUT_REGEX = /^[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
19
|
+
|
20
|
+
attr_reader :pid, :config
|
21
|
+
|
22
|
+
def initialize(data_dir, opts = {})
|
23
|
+
valid_data_dir?(data_dir)
|
24
|
+
raise ArgumentError, 'TorProcess.new takes an options hash' unless opts.is_a? Hash
|
25
|
+
|
26
|
+
@data_dir = data_dir # each tor process needs a separate data directory
|
27
|
+
@opts = map_opts_to_torrc_keys opts
|
28
|
+
@torrc = Torrc.new(@data_dir)
|
29
|
+
@config ||= setup_config # Hash to store torrc config
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_tor
|
33
|
+
r, io = IO.pipe
|
34
|
+
pid = Process.spawn "#{TBB_TOR_BINARY_PATH} -f #{@torrc.path}", out: io, err: :out
|
35
|
+
io.close
|
36
|
+
errors = parse_tor_bootstrap_errors r
|
37
|
+
errors.empty? ? @pid = pid : raise(TorProcessError, "Tor failed to start with errors:\n\n#{errors}")
|
38
|
+
ensure
|
39
|
+
r.close
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop_tor
|
43
|
+
Process.kill 'KILL', pid if pid
|
44
|
+
FileUtils.rm_rf @data_dir if @data_dir
|
45
|
+
@pid = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# private
|
49
|
+
|
50
|
+
def valid_data_dir?(dir)
|
51
|
+
msg = 'data_dir must exist and be a dir'
|
52
|
+
raise ArgumentError, msg unless Pathname.new(dir.to_s).directory? && Pathname.new(dir.to_s).empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def map_opts_to_torrc_keys(opts)
|
56
|
+
raise TorProcessError, 'Options keys must be snake case' unless opts.keys.map!(&:to_s).all?(&:snake_case?)
|
57
|
+
|
58
|
+
opts.transform_keys { |k| k.to_s.split('_').map(&:capitalize).join }
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_config
|
62
|
+
@torrc.write_to_config @opts
|
63
|
+
@torrc.config
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_tor_bootstrap_errors(io)
|
67
|
+
lines = []
|
68
|
+
io.each_line do |line|
|
69
|
+
lines << line
|
70
|
+
break lines.join if line.match BOOTSTRAP_FAIL_REGEX
|
71
|
+
break '' if line.match BOOTSTRAP_SUCCESS_REGEX
|
72
|
+
# break false if line.match BOOTSTRAP_TIMEOUT_REGEX
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/tor/torrc.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'options' # for Tor::TBB_TOR_BINARY_PATH
|
4
|
+
|
3
5
|
module Selenium
|
4
6
|
module WebDriver
|
5
7
|
module Tor
|
@@ -41,7 +43,7 @@ module Selenium
|
|
41
43
|
tmp = Tempfile.new
|
42
44
|
tmp.write hash_to_config_string(hash)
|
43
45
|
tmp.rewind
|
44
|
-
invalid_opts = parse_invalid_opts
|
46
|
+
invalid_opts = parse_invalid_opts `#{TBB_TOR_BINARY_PATH} -f #{tmp.path} --verify-config`
|
45
47
|
raise TorrcError, "Invalid torrc opts: #{invalid_opts}" unless invalid_opts.empty?
|
46
48
|
ensure
|
47
49
|
tmp.unlink
|
data/lib/tor/version.rb
CHANGED
data/selenium_tor.gemspec
CHANGED
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
31
|
spec.require_paths = ['lib']
|
32
32
|
|
33
|
-
spec.
|
33
|
+
spec.add_dependency 'selenium-webdriver', '>= 4.23'
|
34
34
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selenium_tor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MatzFan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: selenium-webdriver
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '4.
|
19
|
+
version: '4.23'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '4.
|
26
|
+
version: '4.23'
|
27
27
|
description: An extension for Selenium::WebDriver that automates Tor Browser
|
28
28
|
email:
|
29
29
|
executables: []
|
@@ -39,17 +39,18 @@ files:
|
|
39
39
|
- LICENSE.txt
|
40
40
|
- README.md
|
41
41
|
- Rakefile
|
42
|
+
- fonts/000_README.txt
|
42
43
|
- lib/driver.rb
|
43
44
|
- lib/options.rb
|
44
45
|
- lib/selenium_tor.rb
|
45
46
|
- lib/service.rb
|
46
47
|
- lib/string_extensions.rb
|
47
48
|
- lib/tor/driver.rb
|
48
|
-
- lib/tor/driver_not_yet_connected_to_tor_network.rb
|
49
49
|
- lib/tor/options.rb
|
50
50
|
- lib/tor/profile.rb
|
51
51
|
- lib/tor/system_tor.rb
|
52
52
|
- lib/tor/tor_prefs.rb
|
53
|
+
- lib/tor/tor_process.rb
|
53
54
|
- lib/tor/torrc.rb
|
54
55
|
- lib/tor/version.rb
|
55
56
|
- selenium_tor.gemspec
|
@@ -77,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
78
|
- !ruby/object:Gem::Version
|
78
79
|
version: '0'
|
79
80
|
requirements: []
|
80
|
-
rubygems_version: 3.5.
|
81
|
+
rubygems_version: 3.5.16
|
81
82
|
signing_key:
|
82
83
|
specification_version: 4
|
83
84
|
summary: Selenium extension for Tor Browser
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../service'
|
4
|
-
require_relative '../options'
|
5
|
-
require_relative '../tor/profile'
|
6
|
-
|
7
|
-
module Selenium
|
8
|
-
module WebDriver
|
9
|
-
module Tor
|
10
|
-
# subclass - doesn't wait to be connected to tor network
|
11
|
-
class DriverNotYetConnectedToTorNetwork < Firefox::Driver
|
12
|
-
def browser
|
13
|
-
:tor # overides :firefox
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|