selenium_tor 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +62 -57
- data/lib/firefox_js_helpers.rb +42 -0
- data/lib/firefox_prefs.rb +75 -0
- data/lib/tor/driver.rb +28 -24
- data/lib/tor/tor_process.rb +20 -18
- data/lib/tor/version.rb +1 -1
- metadata +5 -4
- data/lib/string_extensions.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89802b75ed87505f2f9ba8671aeb649b66db4a7fd63a9f44f695acc3b7bcce18
|
4
|
+
data.tar.gz: fcaa6f71fbac4a9ba4a165ab6919c1c7160fb01b0d3d79a8b2c4db55bd65dec0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01fb59e8597bf62a649785f65ea8c616479384e8a18c376954981d7dedc978cb063dca1f8ce280a23dfbe1c05a0ca538e8115b3c6b4c5fd4a1c8cb56ebf173fd
|
7
|
+
data.tar.gz: 91cb32514aef461e29695b1e5e5470e439c8e0c407ec19229adb84b9207e7febc94cde429c235a809ad4db8cca37fb57c6f857d909b5589e408970bbbdadfb0e
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## [1.4.0] - 2024-08-07
|
4
|
+
|
5
|
+
### Breaking changes
|
6
|
+
|
7
|
+
* only :socks_port and :control_port symbols are valid in :tor_opts
|
8
|
+
|
9
|
+
### New features
|
10
|
+
|
11
|
+
* add ability to set tor bootstrap timeout with tor_opts
|
12
|
+
|
13
|
+
### Bug fixes
|
14
|
+
|
15
|
+
* [#8](https://gitlab.com/matzfan/selenium-tor/-/issues/8)
|
16
|
+
|
17
|
+
## [1.3.0] - 2024-08-05
|
18
|
+
|
19
|
+
### New features
|
20
|
+
|
21
|
+
* add ability to get and set driver preferences dynamically
|
22
|
+
* add Tor Browser alpha release to CI tests ([#12](https://gitlab.com/matzfan/selenium-tor/-/issues/12))
|
23
|
+
|
3
24
|
## [1.2.0] - 2024-07-31
|
4
25
|
|
5
26
|
### Bug fixes
|
data/README.md
CHANGED
@@ -5,8 +5,14 @@
|
|
5
5
|
|
6
6
|
A Selenium extension for Tor Browser.
|
7
7
|
```ruby
|
8
|
-
|
9
|
-
@driver.
|
8
|
+
options = Selenium::WebDriver::Tor::Options.new
|
9
|
+
@driver = Selenium::WebDriver.for :tor, options: options
|
10
|
+
```
|
11
|
+
Once you have a driver instance:
|
12
|
+
```ruby
|
13
|
+
@driver.get 'https://check.torproject.org'
|
14
|
+
@driver.title
|
15
|
+
# => Congratulations. This browser is configured to use Tor.
|
10
16
|
```
|
11
17
|
|
12
18
|
## \_why?
|
@@ -15,14 +21,6 @@ I can use Firefox with Selenium and set a SOCKS proxy to use the Tor network, so
|
|
15
21
|
|
16
22
|
The above approach will hide your IP, but there is a good chance your browser's unique or near-unique fingerprint may be logged by site owners. Subsequent visits could identify you. A primary aim of this project is to enable Selenium to leverage Tor Browser's unique anonymity characteristics - in particular its resistance to browser fingerprinting. The aim is to ensure Selenium Tor site visits leave an identical fingerprint to the thousands of regular Tor Browser users.
|
17
23
|
|
18
|
-
## Known issues
|
19
|
-
|
20
|
-
Known issues are recorded [here](https://gitlab.com/matzfan/selenium-tor/-/issues).
|
21
|
-
|
22
|
-
### Fingerprinting
|
23
|
-
|
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).
|
25
|
-
|
26
24
|
## Installation
|
27
25
|
|
28
26
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -33,72 +31,87 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
33
31
|
|
34
32
|
$ gem install selenium_tor
|
35
33
|
|
34
|
+
## Dependencies and configuration
|
35
|
+
|
36
|
+
[Tor Browser](https://www.torproject.org/download).
|
37
|
+
|
38
|
+
The shared libraries Tor Bowser requires will be available if you have Firefox or Firefox ESR installed on your system. If not, do `sudo apt install firefox-esr` or the equivalent for your package manager.
|
39
|
+
|
40
|
+
As with Firefox browser, `geckodriver` needs to be installed and in your PATH.
|
41
|
+
|
42
|
+
The gem needs to know the location of the Tor Browser Bundle (TBB). The Tor Browser download package archive must be extracted and the root TBB directory (named "tor-browser") placed somewhere on your system. By default it is assumed to be in the current user's HOME directory. An alternative location can be set via the env var `TOR_BROWSER_ROOT_DIR` - e.g. `export TOR_BROWSER_ROOT_DIR=/home/<user>/Downloads`. The Tor Browser binary location is *automatically set* by reference to this directory, so there is no need to do this:
|
43
|
+
```ruby
|
44
|
+
options = Selenium::WebDriver::Tor::Options.new
|
45
|
+
options.binary = '/some/path/to/tor_firefox_binary' # UNNECESSARY
|
46
|
+
```
|
47
|
+
|
48
|
+
The location of the TBB is not expected to change during code execution.
|
49
|
+
|
50
|
+
Tor Selenium is tested on **Linux only** right now.
|
51
|
+
|
36
52
|
## Usage
|
37
53
|
```ruby
|
38
54
|
require 'selenium_tor'
|
39
55
|
```
|
40
56
|
Tor Browser is based on Firefox, so for usage please read the Selenium [docs](https://www.selenium.dev/documentation/webdriver/browsers/firefox) for Firefox browser.
|
41
57
|
|
42
|
-
|
43
|
-
```ruby
|
44
|
-
options = Selenium::WebDriver::Tor::Options.new
|
45
|
-
@driver = Selenium::WebDriver.for :tor, options: options
|
58
|
+
If the tor network is inaccessible for any reason a `Selenium::WebDriver::Error::TimeoutError` will result.
|
46
59
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
```
|
51
|
-
If the network is inaccessible for any reason a `TorNetworkError` will result.
|
60
|
+
A separate tor process is used for each driver. Failure to call `Driver#quit` after code execution may leave orphaned tor processes.
|
61
|
+
|
62
|
+
### Tor options
|
52
63
|
|
53
|
-
|
64
|
+
In addition to the regular Firefox options, a :tor_opts key may be passed to an instance of `Tor::Options` with a hash of tor options. All valid tor options are recognized - see `man tor`. For convenience, "SocksPort" and "ControlPort" options may be set using snake_case symbols - i.e. :socks_port and :control_port. Additionally, a :tor_opts timeout value may be set with the :timeout key. This overrides the default time allowed for the tor process to bootstrap (10 seconds).
|
54
65
|
|
55
|
-
|
66
|
+
### Multiple driver instances (headless drivers only)
|
67
|
+
|
68
|
+
Running multiple tor processes requires that each uses different ports for SocksPort (and ControlPort, if used). These must be passed using the `:tor_opts` key. An example using the [Parallel gem](https://rubygems.org/gems/parallel):
|
56
69
|
```ruby
|
57
70
|
require 'parallel'
|
58
71
|
|
59
|
-
socks_port = 9150
|
60
|
-
control_port = 9151
|
72
|
+
@socks_port = 9150
|
73
|
+
@control_port = 9151
|
61
74
|
|
62
|
-
def driver
|
63
|
-
|
64
|
-
|
75
|
+
def driver
|
76
|
+
@socks_port += 2
|
77
|
+
@control_port += 2
|
78
|
+
options = Selenium::WebDriver::Tor::Options.new tor_opts: { socks_port: @socks_port, control_port: @control_port }
|
79
|
+
Selenium::WebDriver.for :tor, options: options
|
65
80
|
end
|
66
81
|
|
67
|
-
|
68
|
-
driver2 = driver(socks_port += 2, control_port += 2)
|
69
|
-
|
70
|
-
title = 'Congratulations. This browser is configured to use Tor.'
|
82
|
+
@drivers = [driver, driver]
|
71
83
|
|
72
|
-
Parallel.all?(
|
84
|
+
Parallel.all?(@drivers, in_threads: @drivers.size) do |driver|
|
73
85
|
driver.get 'https://check.torproject.org'
|
74
|
-
driver.title == title
|
75
86
|
end
|
76
|
-
# => true
|
77
87
|
|
78
|
-
|
79
|
-
driver2&.quit
|
88
|
+
@drivers.each(&:quit)
|
80
89
|
```
|
81
90
|
### Tor Browser specific functionality
|
82
91
|
|
83
|
-
You can get and set the
|
92
|
+
You can get and set the security level (shield icon in TB) as follows. 4 is 'Standard', 2 is 'Safer', 1 is 'Safest'.
|
84
93
|
```ruby
|
85
|
-
@driver = Selenium::WebDriver.for :tor
|
86
94
|
@driver.security_level
|
87
95
|
# => 4 - the default value
|
88
|
-
@driver.security_level =
|
96
|
+
@driver.security_level = 2
|
89
97
|
@driver.security_level
|
90
|
-
# =>
|
91
|
-
@driver.quit
|
98
|
+
# => 2
|
92
99
|
```
|
93
100
|
You can get a new circuit for the current page ('New Tor circuit for this site' on TB's application menu):
|
94
101
|
```ruby
|
95
|
-
@driver
|
96
|
-
@driver.get 'https://example.com'
|
97
|
-
@driver.new_circuit_for_page # new circuit for the current page only
|
98
|
-
@driver.quit
|
102
|
+
@driver.new_circuit_for_site # new circuit for the current page domain
|
99
103
|
```
|
100
104
|
### Miscellaneous
|
101
105
|
|
106
|
+
You can get and set Driver preferences dynamically like this. Caveat emptor.
|
107
|
+
```ruby
|
108
|
+
@driver.pref['browser.download.dir']
|
109
|
+
# => ""
|
110
|
+
@driver.pref['browser.download.dir'] = 'home/user/Downloads'
|
111
|
+
@driver.pref['browser.download.dir']
|
112
|
+
# => "home/user/Downloads"
|
113
|
+
```
|
114
|
+
|
102
115
|
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.
|
103
116
|
|
104
117
|
Remote functionality is not tested, but may be if a suitable Tor Browser Docker container becomes available.
|
@@ -113,21 +126,13 @@ Tor::TBB_EXTENSIONS_DIR # path to the 'extensions' directory in the above
|
|
113
126
|
Tor::TBB_VERSION # the version installed, e.g. "13.0.1", note: driver.capabilities.browser_version returns the Firefox version Tor Browser is based on
|
114
127
|
```
|
115
128
|
|
116
|
-
##
|
117
|
-
|
118
|
-
[Tor Browser](https://www.torproject.org/download).
|
119
|
-
|
120
|
-
As with Firefox browser, `geckodriver` needs to be installed and in your PATH.
|
129
|
+
## Known issues
|
121
130
|
|
122
|
-
|
123
|
-
```ruby
|
124
|
-
options = Selenium::WebDriver::Tor::Options.new
|
125
|
-
options.binary = '/some/path/to/tor_firefox_binary' # UNNECESSARY
|
126
|
-
```
|
131
|
+
Known issues are recorded [here](https://gitlab.com/matzfan/selenium-tor/-/issues).
|
127
132
|
|
128
|
-
|
133
|
+
### Fingerprinting
|
129
134
|
|
130
|
-
Tor
|
135
|
+
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).
|
131
136
|
|
132
137
|
## Testing
|
133
138
|
|
@@ -137,7 +142,7 @@ Tests are run in the display set by the `DISPLAY` env var, usually :0 by default
|
|
137
142
|
|
138
143
|
$ DISPLAY=:99 bundle exec rake
|
139
144
|
|
140
|
-
If you find
|
145
|
+
If you find driver instantiation failing with port bind failure error messages ( these include "Address already in use. Is Tor already running?") check you have no other tor processes running with conflicting ports. With timeout errors, you may also want to check the Tor network is actually up, it isn't always..
|
141
146
|
|
142
147
|
If you wish run the `vglrun` WebGL fingerprint test install VirtualGL (see above, assumes you have the relevent drivers installed) and run the following command:
|
143
148
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'selenium-webdriver'
|
4
|
+
|
5
|
+
module Selenium
|
6
|
+
module WebDriver
|
7
|
+
# namespace
|
8
|
+
module Firefox
|
9
|
+
# js helpers
|
10
|
+
module FirefoxJsHelpers
|
11
|
+
def execute_script_in_chrome_context(...)
|
12
|
+
in_chrome_context { execute_script(...) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def es6_const_call(es6:, const:)
|
16
|
+
es6_import(es6) << "return m.#{const}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def es6_function_call(es6:, func:, args:)
|
20
|
+
es6_import(es6) << "return m.#{es6}.#{func}('#{args}')"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def es6_import(es6)
|
26
|
+
"let m = ChromeUtils.importESModule('resource://gre/modules/#{es6}.sys.mjs');"
|
27
|
+
end
|
28
|
+
|
29
|
+
def in_chrome_context
|
30
|
+
raise(ArgumentError, "##{__method__} takes a block arg") unless block_given?
|
31
|
+
|
32
|
+
old_context = context
|
33
|
+
self.context = 'chrome'
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
self.context = old_context if old_context
|
37
|
+
end
|
38
|
+
end
|
39
|
+
Firefox::Driver.prepend FirefoxJsHelpers
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/firefox_js_helpers'
|
4
|
+
|
5
|
+
module Selenium
|
6
|
+
module WebDriver
|
7
|
+
module Firefox
|
8
|
+
# representation of a firefox pref
|
9
|
+
class Preference
|
10
|
+
PREF_TYPES = { 'PREF_BOOL' => 128, 'PREF_INT' => 64, 'PREF_INVALID' => 0, 'PREF_STRING' => 32 }.freeze
|
11
|
+
GET_METHODS = { 0 => 'getStringPref', 32 => 'getStringPref', 64 => 'getIntPref', 128 => 'getBoolPref' }.freeze
|
12
|
+
SET_METHODS = { 32 => 'setStringPref', 64 => 'setIntPref', 128 => 'setBoolPref' }.freeze
|
13
|
+
|
14
|
+
def initialize(driver)
|
15
|
+
@driver = driver
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](str)
|
19
|
+
@driver.execute_script_in_chrome_context script_string(str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(str, val)
|
23
|
+
@driver.execute_script_in_chrome_context script_string(str, val)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def script_string(str, val = nil)
|
29
|
+
val ? setter_script(str, val) : getter_script(str)
|
30
|
+
end
|
31
|
+
|
32
|
+
def getter_script(str)
|
33
|
+
type = pref_type(str)
|
34
|
+
"return Services.prefs.#{GET_METHODS[type]}('#{str}'#{default(type)})"
|
35
|
+
end
|
36
|
+
|
37
|
+
def default(type)
|
38
|
+
type.zero? ? ", ''" : '' # empty string default arg if pref not found, otherwise no default arg
|
39
|
+
end
|
40
|
+
|
41
|
+
def setter_script(str, val)
|
42
|
+
"Services.prefs.#{set_method(str, val)}('#{str}', #{quote_str(val)})"
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_method(str, val)
|
46
|
+
type = pref_type(str)
|
47
|
+
return SET_METHODS[type] unless type.zero?
|
48
|
+
return 'setBoolPref' if val.instance_of?(TrueClass) || val.instance_of?(FalseClass)
|
49
|
+
return 'setIntPref' if val.is_a? Integer
|
50
|
+
|
51
|
+
'setStringPref' # everthing else gets set as a string
|
52
|
+
end
|
53
|
+
|
54
|
+
def pref_type(string)
|
55
|
+
@driver.execute_script_in_chrome_context("return Services.prefs.getPrefType('#{string}')")
|
56
|
+
end
|
57
|
+
|
58
|
+
def quote_str(value)
|
59
|
+
value.is_a?(String) ? "'#{value}'" : value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# adds Driver#pref method and [] and []= methods on that object
|
64
|
+
module FirefoxPrefs
|
65
|
+
def pref
|
66
|
+
Preference.new self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Driver
|
71
|
+
prepend FirefoxPrefs
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/tor/driver.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'open-uri'
|
4
4
|
require_relative '../service'
|
5
5
|
require_relative '../options'
|
6
|
+
require_relative '../firefox_prefs'
|
6
7
|
require_relative 'profile'
|
7
8
|
require_relative 'tor_process'
|
8
9
|
|
@@ -14,9 +15,12 @@ module Selenium
|
|
14
15
|
|
15
16
|
# tor driver
|
16
17
|
class Driver < DelegateClass(DriverDelegate)
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
TORRC_PATH_PREF = 'extensions.torlauncher.torrc_path'
|
19
|
+
TORRC = 'torrc'
|
20
|
+
BROWSER_SECURITY_LEVEL_PREF = 'browser.security_level.security_slider'
|
21
|
+
DOMAIN_ISOLATOR = 'TorDomainIsolator' # ES6 module
|
22
|
+
NEW_CIRCUIT = 'newCircuitForDomain' # js function
|
23
|
+
TB_SEC_LEVELS = { 'standard' => 4, 'safer' => 2, 'safest' => 1 }.freeze
|
20
24
|
|
21
25
|
def initialize(options: nil, **)
|
22
26
|
@data_dir = Dir.mktmpdir
|
@@ -39,36 +43,25 @@ module Selenium
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def security_level
|
42
|
-
|
43
|
-
self.context = 'chrome'
|
44
|
-
execute_script("return Services.prefs.getIntPref('browser.security_level.security_slider')")
|
45
|
-
ensure
|
46
|
-
self.context = old_context
|
46
|
+
pref[BROWSER_SECURITY_LEVEL_PREF]
|
47
47
|
end
|
48
48
|
|
49
49
|
def security_level=(int)
|
50
|
-
|
51
|
-
self.context = 'chrome'
|
52
|
-
raise(ArgumentError, 'Security level can be set to 4, 2 or 1') unless TB_SECURITY_LEVELS.include?(int)
|
50
|
+
raise(ArgumentError, "Valid security levels are: #{TB_SEC_LEVELS.values}") unless TB_SEC_LEVELS.value?(int)
|
53
51
|
|
54
|
-
|
55
|
-
ensure
|
56
|
-
self.context = old_context if old_context
|
52
|
+
pref[BROWSER_SECURITY_LEVEL_PREF] = int
|
57
53
|
end
|
58
54
|
|
59
|
-
def
|
60
|
-
|
61
|
-
self.context = 'chrome'
|
62
|
-
execute_script "ChromeUtils.importESModule('resource://gre/modules/TorDomainIsolator.sys.mjs');
|
63
|
-
TorDomainIsolator.newCircuitForDomain('#{current_url}')"
|
64
|
-
ensure
|
65
|
-
self.context = old_context
|
55
|
+
def new_circuit_for_site
|
56
|
+
execute_script_in_chrome_context es6_function_call(es6: DOMAIN_ISOLATOR, func: NEW_CIRCUIT, args: domain)
|
66
57
|
end
|
67
58
|
|
59
|
+
alias new_circuit_for_page new_circuit_for_site
|
60
|
+
|
68
61
|
private
|
69
62
|
|
70
63
|
def add_torrc_path_to_options
|
71
|
-
@options.prefs[
|
64
|
+
@options.prefs[TORRC_PATH_PREF] = File.join(@data_dir, TORRC)
|
72
65
|
end
|
73
66
|
|
74
67
|
def install_extensions(instance)
|
@@ -76,8 +69,19 @@ module Selenium
|
|
76
69
|
end
|
77
70
|
|
78
71
|
def create_tor_process_and_start_tor(opts)
|
79
|
-
|
80
|
-
@tor_process
|
72
|
+
timeout = opts.delete :timeout
|
73
|
+
@tor_process = tor_process(opts)
|
74
|
+
@tor_process.start_tor(timeout: timeout)
|
75
|
+
rescue Tor::TorProcess::TorProcessError => e
|
76
|
+
raise Error::WebDriverError, e
|
77
|
+
end
|
78
|
+
|
79
|
+
def tor_process(opts)
|
80
|
+
TorProcess.new(@data_dir, opts || {})
|
81
|
+
end
|
82
|
+
|
83
|
+
def domain
|
84
|
+
URI(current_url).host&.match(/[^\.]+\.\w+$/)
|
81
85
|
end
|
82
86
|
end
|
83
87
|
end
|
data/lib/tor/tor_process.rb
CHANGED
@@ -2,20 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'timeout'
|
4
4
|
require_relative 'torrc'
|
5
|
-
require_relative '../string_extensions'
|
6
5
|
|
7
6
|
module Selenium
|
8
7
|
module WebDriver
|
9
8
|
module Tor
|
10
9
|
# Respresentation of a tor process
|
11
10
|
class TorProcess
|
12
|
-
include StringExtensions
|
13
|
-
|
14
11
|
class TorProcessError < StandardError; end
|
15
12
|
|
16
13
|
BOOTSTRAP_SUCCESS_REGEX = /Bootstrapped 100% \(done\): Done$/
|
17
|
-
BOOTSTRAP_FAIL_REGEX = /^[A-Z][a-z]{2} \d{
|
18
|
-
# BOOTSTRAP_TIMEOUT_REGEX = /^[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
14
|
+
BOOTSTRAP_FAIL_REGEX = /^[A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
19
15
|
|
20
16
|
attr_reader :pid, :config
|
21
17
|
|
@@ -29,12 +25,15 @@ module Selenium
|
|
29
25
|
@config ||= setup_config # Hash to store torrc config
|
30
26
|
end
|
31
27
|
|
32
|
-
def start_tor
|
28
|
+
def start_tor(timeout: 10)
|
33
29
|
r, io = IO.pipe
|
34
|
-
pid = Process.spawn
|
30
|
+
pid = Process.spawn tor_command, out: io, err: :out
|
35
31
|
io.close
|
36
|
-
errors =
|
32
|
+
errors = parse_tor_bootstrap_errors_with_timeout(r, timeout: timeout)
|
37
33
|
errors.empty? ? @pid = pid : raise(TorProcessError, "Tor failed to start with errors:\n\n#{errors}")
|
34
|
+
rescue Timeout::Error
|
35
|
+
Process.kill 'KILL', pid if pid
|
36
|
+
raise Error::TimeoutError, "Tor not bootstrapped after #{timeout} seconds"
|
38
37
|
ensure
|
39
38
|
r.close
|
40
39
|
end
|
@@ -45,7 +44,7 @@ module Selenium
|
|
45
44
|
@pid = nil
|
46
45
|
end
|
47
46
|
|
48
|
-
|
47
|
+
private
|
49
48
|
|
50
49
|
def valid_data_dir?(dir)
|
51
50
|
msg = 'data_dir must exist and be a dir'
|
@@ -53,9 +52,7 @@ module Selenium
|
|
53
52
|
end
|
54
53
|
|
55
54
|
def map_opts_to_torrc_keys(opts)
|
56
|
-
|
57
|
-
|
58
|
-
opts.transform_keys { |k| k.to_s.split('_').map(&:capitalize).join }
|
55
|
+
opts.transform_keys { |k| k.is_a?(String) ? k : k.to_s.split('_').map(&:capitalize).join }
|
59
56
|
end
|
60
57
|
|
61
58
|
def setup_config
|
@@ -63,13 +60,18 @@ module Selenium
|
|
63
60
|
@torrc.config
|
64
61
|
end
|
65
62
|
|
66
|
-
def
|
63
|
+
def tor_command
|
64
|
+
"#{TBB_TOR_BINARY_PATH} -f #{@torrc.path}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_tor_bootstrap_errors_with_timeout(io, timeout:)
|
67
68
|
lines = []
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
Timeout.timeout timeout do
|
70
|
+
io.each_line do |line|
|
71
|
+
lines << line
|
72
|
+
break lines.join if line.match BOOTSTRAP_FAIL_REGEX
|
73
|
+
break '' if line.match BOOTSTRAP_SUCCESS_REGEX
|
74
|
+
end
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|
data/lib/tor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selenium_tor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.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-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: selenium-webdriver
|
@@ -41,10 +41,11 @@ files:
|
|
41
41
|
- Rakefile
|
42
42
|
- fonts/000_README.txt
|
43
43
|
- lib/driver.rb
|
44
|
+
- lib/firefox_js_helpers.rb
|
45
|
+
- lib/firefox_prefs.rb
|
44
46
|
- lib/options.rb
|
45
47
|
- lib/selenium_tor.rb
|
46
48
|
- lib/service.rb
|
47
|
-
- lib/string_extensions.rb
|
48
49
|
- lib/tor/driver.rb
|
49
50
|
- lib/tor/options.rb
|
50
51
|
- lib/tor/profile.rb
|
@@ -78,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
79
|
- !ruby/object:Gem::Version
|
79
80
|
version: '0'
|
80
81
|
requirements: []
|
81
|
-
rubygems_version: 3.5.
|
82
|
+
rubygems_version: 3.5.17
|
82
83
|
signing_key:
|
83
84
|
specification_version: 4
|
84
85
|
summary: Selenium extension for Tor Browser
|