selenium_tor 1.1.0 → 1.3.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 +6 -2
- data/CHANGELOG.md +22 -0
- data/README.md +43 -15
- data/lib/firefox_js_helpers.rb +42 -0
- data/lib/firefox_prefs.rb +75 -0
- data/lib/tor/driver.rb +39 -12
- data/lib/tor/options.rb +2 -2
- data/lib/tor/tor_process.rb +1 -3
- data/lib/tor/version.rb +1 -1
- data/selenium_tor.gemspec +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcf1656f54ce23992e788e76b1d6342157b6ba3996ec95f04c955a2857e9b2be
|
4
|
+
data.tar.gz: a9ef924f85fdd1370638ff3c12f9009ec1b2ab1b65b5a872bb8bd3d3e3e6e4f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68018127837d8e59dd60647d9a0d60da4af363da8fad7104ac6ed52bf15844d7ead4d4cbc81f1b2403e64ad2fdda55f488ce725ca71b23c618686fb3ed5dbde3
|
7
|
+
data.tar.gz: 3a6bd3db60f2a9de62e232774edabb11e4ccd7bb6e48158c4b6709339f8c964e56f4817f78d471d8ae57a970fec25b325a76a4c3779a3ee67aee8bdc2ad5250f
|
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:
|
@@ -28,6 +29,9 @@ Style/RegexpLiteral:
|
|
28
29
|
Minitest/TestMethodName:
|
29
30
|
Enabled: true
|
30
31
|
|
32
|
+
Minitest/AssertTruthy:
|
33
|
+
Enabled: false
|
34
|
+
|
31
35
|
Minitest/TestFileName:
|
32
36
|
Exclude:
|
33
37
|
- test/features/fingerprinting/vglrun_test_fingerprintjs.rb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## [1.3.0] - 2024-08-05
|
4
|
+
|
5
|
+
### Bug fixes
|
6
|
+
|
7
|
+
* [#14](https://gitlab.com/matzfan/selenium-tor/-/issues/14)
|
8
|
+
|
9
|
+
### New features
|
10
|
+
|
11
|
+
* add ability to get and set driver preferences dynamically
|
12
|
+
* add Tor Browser alpha release to CI tests ([#12](https://gitlab.com/matzfan/selenium-tor/-/issues/12))
|
13
|
+
|
14
|
+
## [1.2.0] - 2024-07-31
|
15
|
+
|
16
|
+
### Bug fixes
|
17
|
+
|
18
|
+
* [#11](https://gitlab.com/matzfan/selenium-tor/-/issues/11)
|
19
|
+
|
20
|
+
### New features
|
21
|
+
|
22
|
+
* add ability to change security level
|
23
|
+
* add ability to get new circuit for page ([#10](https://gitlab.com/matzfan/selenium-tor/-/issues/10))
|
24
|
+
|
3
25
|
## [1.1.0] - 2024-07-28
|
4
26
|
|
5
27
|
### Bug fixes
|
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
|
@@ -41,7 +45,8 @@ options = Selenium::WebDriver::Tor::Options.new
|
|
41
45
|
@driver = Selenium::WebDriver.for :tor, options: options
|
42
46
|
|
43
47
|
@driver.get 'https://check.torproject.org'
|
44
|
-
@driver.title
|
48
|
+
@driver.title
|
49
|
+
# => Congratulations. This browser is configured to use Tor.
|
45
50
|
@driver.quit
|
46
51
|
```
|
47
52
|
If the network is inaccessible for any reason a `TorNetworkError` will result.
|
@@ -52,28 +57,49 @@ Running multiple `tor` processes requires that each uses different ports for Soc
|
|
52
57
|
```ruby
|
53
58
|
require 'parallel'
|
54
59
|
|
55
|
-
socks_port = 9150
|
56
|
-
control_port = 9151
|
60
|
+
@socks_port = 9150
|
61
|
+
@control_port = 9151
|
57
62
|
|
58
|
-
def driver
|
59
|
-
|
60
|
-
|
63
|
+
def driver
|
64
|
+
@socks_port += 2
|
65
|
+
@control_port += 2
|
66
|
+
options = Selenium::WebDriver::Tor::Options.new tor_opts: { socks_port: @socks_port, control_port: @control_port }
|
67
|
+
Selenium::WebDriver.for :tor, options: options
|
61
68
|
end
|
62
69
|
|
63
|
-
|
64
|
-
driver2 = driver(socks_port += 2, control_port += 2)
|
70
|
+
@drivers = [driver, driver]
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
Parallel.all?([driver1, driver2], in_threads: 2) do |driver|
|
72
|
+
Parallel.all?(@drivers, in_threads: @drivers.size) do |driver|
|
69
73
|
driver.get 'https://check.torproject.org'
|
70
|
-
driver.title == title
|
71
74
|
end
|
72
|
-
# => true
|
73
75
|
|
74
|
-
|
75
|
-
driver2&.quit
|
76
|
+
@drivers.each(&:quit)
|
76
77
|
```
|
78
|
+
### Tor Browser specific functionality
|
79
|
+
|
80
|
+
You can get and set the secuirty level (shield icon in TB) as follows. 4 is 'Standard', 2 is 'Safer', 1 is 'Safest'.
|
81
|
+
```ruby
|
82
|
+
@driver.security_level
|
83
|
+
# => 4 - the default value
|
84
|
+
@driver.security_level = 2
|
85
|
+
@driver.security_level
|
86
|
+
# => 2
|
87
|
+
```
|
88
|
+
You can get a new circuit for the current page ('New Tor circuit for this site' on TB's application menu):
|
89
|
+
```ruby
|
90
|
+
@driver.new_circuit_for_site # new circuit for the current page domain
|
91
|
+
```
|
92
|
+
### Miscellaneous
|
93
|
+
|
94
|
+
You can get and set Driver preferences dynamically like this. Caveat emptor.
|
95
|
+
```ruby
|
96
|
+
@driver.pref['browser.download.dir']
|
97
|
+
# => ""
|
98
|
+
@driver.pref['browser.download.dir'] = 'home/user/Downloads'
|
99
|
+
@driver.pref['browser.download.dir']
|
100
|
+
# => "home/user/Downloads"
|
101
|
+
```
|
102
|
+
|
77
103
|
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.
|
78
104
|
|
79
105
|
Remote functionality is not tested, but may be if a suitable Tor Browser Docker container becomes available.
|
@@ -92,6 +118,8 @@ Tor::TBB_VERSION # the version installed, e.g. "13.0.1", note: driver.capabiliti
|
|
92
118
|
|
93
119
|
[Tor Browser](https://www.torproject.org/download).
|
94
120
|
|
121
|
+
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 similar.
|
122
|
+
|
95
123
|
As with Firefox browser, `geckodriver` needs to be installed and in your PATH.
|
96
124
|
|
97
125
|
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:
|
@@ -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,26 +3,33 @@
|
|
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
|
|
9
10
|
module Selenium
|
10
11
|
module WebDriver
|
11
12
|
module Tor
|
12
|
-
# delegate class
|
13
|
-
class
|
13
|
+
# delegate class
|
14
|
+
class DriverDelegate < Firefox::Driver; end
|
14
15
|
|
15
16
|
# tor driver
|
16
|
-
class Driver < DelegateClass(
|
17
|
-
|
17
|
+
class Driver < DelegateClass(DriverDelegate)
|
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
|
18
24
|
|
19
25
|
def initialize(options: nil, **)
|
20
26
|
@data_dir = Dir.mktmpdir
|
21
|
-
|
22
|
-
|
27
|
+
@options = options || Options.new # fix for issue #11, 'tis a puzzlement
|
28
|
+
add_torrc_path_to_options
|
29
|
+
@instance = DriverDelegate.new(options: @options, **)
|
30
|
+
install_extensions @instance
|
31
|
+
create_tor_process_and_start_tor @options.tor_opts
|
23
32
|
super(@instance)
|
24
|
-
install_extensions(@instance)
|
25
|
-
create_tor_process_and_start_tor options&.tor_opts
|
26
33
|
end
|
27
34
|
|
28
35
|
def browser
|
@@ -35,12 +42,26 @@ module Selenium
|
|
35
42
|
super
|
36
43
|
end
|
37
44
|
|
38
|
-
|
45
|
+
def security_level
|
46
|
+
pref[BROWSER_SECURITY_LEVEL_PREF]
|
47
|
+
end
|
48
|
+
|
49
|
+
def security_level=(int)
|
50
|
+
raise(ArgumentError, "Valid security levels are: #{TB_SEC_LEVELS.values}") unless TB_SEC_LEVELS.value?(int)
|
39
51
|
|
40
|
-
|
41
|
-
|
52
|
+
pref[BROWSER_SECURITY_LEVEL_PREF] = int
|
53
|
+
end
|
42
54
|
|
43
|
-
|
55
|
+
def new_circuit_for_site
|
56
|
+
execute_script_in_chrome_context es6_function_call(es6: DOMAIN_ISOLATOR, func: NEW_CIRCUIT, args: domain)
|
57
|
+
end
|
58
|
+
|
59
|
+
alias new_circuit_for_page new_circuit_for_site
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def add_torrc_path_to_options
|
64
|
+
@options.prefs[TORRC_PATH_PREF] = File.join(@data_dir, TORRC)
|
44
65
|
end
|
45
66
|
|
46
67
|
def install_extensions(instance)
|
@@ -50,6 +71,12 @@ module Selenium
|
|
50
71
|
def create_tor_process_and_start_tor(opts)
|
51
72
|
@tor_process = TorProcess.new(@data_dir, opts || {})
|
52
73
|
@tor_process.start_tor
|
74
|
+
rescue Tor::TorProcess::TorProcessError => e
|
75
|
+
raise Error::WebDriverError, e
|
76
|
+
end
|
77
|
+
|
78
|
+
def domain
|
79
|
+
URI(current_url).host&.match(/[^\.]+\.\w+$/)
|
53
80
|
end
|
54
81
|
end
|
55
82
|
end
|
data/lib/tor/options.rb
CHANGED
@@ -26,9 +26,9 @@ module Selenium
|
|
26
26
|
def initialize(log_level: nil, **opts)
|
27
27
|
opts[:tor_opts] = opts.delete(:system_tor).opts if opts[:system_tor] # DEPRECATED 2.0
|
28
28
|
@tor_opts = opts[:tor_opts] ? opts.delete(:tor_opts) : {} # must be deleted before call to super
|
29
|
-
super(log_level: log_level, **tor_options(opts.delete(:prefs)).merge(opts))
|
30
29
|
do_start_tor_browser_script_stuff # stuff the start-tor-browser script in TBB does
|
31
|
-
copy_fonts # so we don't have to change dir before executing TB binary #2 and #9
|
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))
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
data/lib/tor/tor_process.rb
CHANGED
@@ -14,8 +14,7 @@ module Selenium
|
|
14
14
|
class TorProcessError < StandardError; end
|
15
15
|
|
16
16
|
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\] .*/
|
17
|
+
BOOTSTRAP_FAIL_REGEX = /^[A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2}\.\d{3} \[err\] .*/
|
19
18
|
|
20
19
|
attr_reader :pid, :config
|
21
20
|
|
@@ -69,7 +68,6 @@ module Selenium
|
|
69
68
|
lines << line
|
70
69
|
break lines.join if line.match BOOTSTRAP_FAIL_REGEX
|
71
70
|
break '' if line.match BOOTSTRAP_SUCCESS_REGEX
|
72
|
-
# break false if line.match BOOTSTRAP_TIMEOUT_REGEX
|
73
71
|
end
|
74
72
|
end
|
75
73
|
end
|
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.3.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-
|
11
|
+
date: 2024-08-05 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: []
|
@@ -41,6 +41,8 @@ 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
|
@@ -78,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
80
|
- !ruby/object:Gem::Version
|
79
81
|
version: '0'
|
80
82
|
requirements: []
|
81
|
-
rubygems_version: 3.5.
|
83
|
+
rubygems_version: 3.5.17
|
82
84
|
signing_key:
|
83
85
|
specification_version: 4
|
84
86
|
summary: Selenium extension for Tor Browser
|