selenium_tor 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99d3e4260b0873513c67db620d3057d61078b4c1cbae7b5c8c7b3e4cfdbbfbec
4
- data.tar.gz: bb02cb9d07024a4798723185f4103246dac9b4c008e3f20abec5f710426a26a6
3
+ metadata.gz: fcf1656f54ce23992e788e76b1d6342157b6ba3996ec95f04c955a2857e9b2be
4
+ data.tar.gz: a9ef924f85fdd1370638ff3c12f9009ec1b2ab1b65b5a872bb8bd3d3e3e6e4f5
5
5
  SHA512:
6
- metadata.gz: 654db110ca016a71d04b6def25d4befb86efc27cd2c0cce1ed7874834921d309262bc537b6b23bf8b77603b4d29960c88a10d83c8508ce6398b1d44a13577df1
7
- data.tar.gz: 7d7369f8055ca42c229079d8732ab49d8d1467af49d35c547528dd792f1208101abcd123a6f05873b9fee4b704863a364708fc9148850f798635b9f3f6b3b8da
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
- Layout/LineLength:
14
- Max: 120
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 # => Congratulations. This browser is configured to use Tor.
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(socks_p, control_p)
59
- options = Selenium::WebDriver::Tor::Options.new tor_opts: { socks_port: socks_p, control_port: control_p }
60
- Selenium::WebDriver.for :tor, options: options # new tor process started with tor_opts
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
- driver1 = driver(socks_port, control_port)
64
- driver2 = driver(socks_port += 2, control_port += 2)
70
+ @drivers = [driver, driver]
65
71
 
66
- title = 'Congratulations. This browser is configured to use Tor.'
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
- driver1&.quit
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 so extensions can be added & connection to tor made
13
- class DriverNotYetConnectedToTorNetwork < Firefox::Driver; end
13
+ # delegate class
14
+ class DriverDelegate < Firefox::Driver; end
14
15
 
15
16
  # tor driver
16
- class Driver < DelegateClass(DriverNotYetConnectedToTorNetwork)
17
- class TorNetworkError < StandardError; end
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
- add_torrc_path_to_options options
22
- @instance = DriverNotYetConnectedToTorNetwork.new(options: options, **)
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
- private
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
- def add_torrc_path_to_options(options)
41
- return unless options
52
+ pref[BROWSER_SECURITY_LEVEL_PREF] = int
53
+ end
42
54
 
43
- options.prefs['extensions.torlauncher.torrc_path'] = File.join(@data_dir, 'torrc')
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
@@ -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{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\] .*/
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
@@ -3,7 +3,7 @@
3
3
  module Selenium
4
4
  module WebDriver
5
5
  module Tor
6
- VERSION = '1.1.0'
6
+ VERSION = '1.3.0'
7
7
  end
8
8
  end
9
9
  end
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.add_runtime_dependency 'selenium-webdriver', '~> 4.22'
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.1.0
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-07-28 00:00:00.000000000 Z
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.22'
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.22'
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.16
83
+ rubygems_version: 3.5.17
82
84
  signing_key:
83
85
  specification_version: 4
84
86
  summary: Selenium extension for Tor Browser