selenium_tor 1.2.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: 6aeb8724ddcdf0d7260330ca616651d54b94ce33c6a635c71ef5c5f3577de220
4
- data.tar.gz: 12b5d264f6a774dab990148feb15179d0b7873e42eb5b1c4bb99e8383f5e5577
3
+ metadata.gz: fcf1656f54ce23992e788e76b1d6342157b6ba3996ec95f04c955a2857e9b2be
4
+ data.tar.gz: a9ef924f85fdd1370638ff3c12f9009ec1b2ab1b65b5a872bb8bd3d3e3e6e4f5
5
5
  SHA512:
6
- metadata.gz: deafbf4cbbc646e2fef5dc1800bc009edceb72906d65dc071942d0c742a49dd6aa17ac4fe2f88fc8d36f909445b31378996073f7d44b8cff473db2e9e3f5d403
7
- data.tar.gz: 71c615ce16eb80f696aaa4dc26d73459df4ce8c4b7d994998580be1d93405baa5dc9ee94f321e7494f1567f3bed1f48481e53149829c2e1671268a6069d9d91f
6
+ metadata.gz: 68018127837d8e59dd60647d9a0d60da4af363da8fad7104ac6ed52bf15844d7ead4d4cbc81f1b2403e64ad2fdda55f488ce725ca71b23c618686fb3ed5dbde3
7
+ data.tar.gz: 3a6bd3db60f2a9de62e232774edabb11e4ccd7bb6e48158c4b6709339f8c964e56f4817f78d471d8ae57a970fec25b325a76a4c3779a3ee67aee8bdc2ad5250f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
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
+
3
14
  ## [1.2.0] - 2024-07-31
4
15
 
5
16
  ### Bug fixes
data/README.md CHANGED
@@ -45,7 +45,8 @@ options = Selenium::WebDriver::Tor::Options.new
45
45
  @driver = Selenium::WebDriver.for :tor, options: options
46
46
 
47
47
  @driver.get 'https://check.torproject.org'
48
- @driver.title # => Congratulations. This browser is configured to use Tor.
48
+ @driver.title
49
+ # => Congratulations. This browser is configured to use Tor.
49
50
  @driver.quit
50
51
  ```
51
52
  If the network is inaccessible for any reason a `TorNetworkError` will result.
@@ -56,49 +57,49 @@ Running multiple `tor` processes requires that each uses different ports for Soc
56
57
  ```ruby
57
58
  require 'parallel'
58
59
 
59
- socks_port = 9150
60
- control_port = 9151
60
+ @socks_port = 9150
61
+ @control_port = 9151
61
62
 
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
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
65
68
  end
66
69
 
67
- driver1 = driver(socks_port, control_port)
68
- driver2 = driver(socks_port += 2, control_port += 2)
70
+ @drivers = [driver, driver]
69
71
 
70
- title = 'Congratulations. This browser is configured to use Tor.'
71
-
72
- Parallel.all?([driver1, driver2], in_threads: 2) do |driver|
72
+ Parallel.all?(@drivers, in_threads: @drivers.size) do |driver|
73
73
  driver.get 'https://check.torproject.org'
74
- driver.title == title
75
74
  end
76
- # => true
77
75
 
78
- driver1&.quit
79
- driver2&.quit
76
+ @drivers.each(&:quit)
80
77
  ```
81
78
  ### Tor Browser specific functionality
82
79
 
83
80
  You can get and set the secuirty level (shield icon in TB) as follows. 4 is 'Standard', 2 is 'Safer', 1 is 'Safest'.
84
81
  ```ruby
85
- @driver = Selenium::WebDriver.for :tor
86
82
  @driver.security_level
87
83
  # => 4 - the default value
88
- @driver.security_level = 1
84
+ @driver.security_level = 2
89
85
  @driver.security_level
90
- # => 1
91
- @driver.quit
86
+ # => 2
92
87
  ```
93
88
  You can get a new circuit for the current page ('New Tor circuit for this site' on TB's application menu):
94
89
  ```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
90
+ @driver.new_circuit_for_site # new circuit for the current page domain
99
91
  ```
100
92
  ### Miscellaneous
101
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
+
102
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.
103
104
 
104
105
  Remote functionality is not tested, but may be if a suitable Tor Browser Docker container becomes available.
@@ -117,6 +118,8 @@ Tor::TBB_VERSION # the version installed, e.g. "13.0.1", note: driver.capabiliti
117
118
 
118
119
  [Tor Browser](https://www.torproject.org/download).
119
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
+
120
123
  As with Firefox browser, `geckodriver` needs to be installed and in your PATH.
121
124
 
122
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,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
- class TorNetworkError < StandardError; end
18
-
19
- TB_SECURITY_LEVELS = [4, 2, 1].freeze
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
- 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
46
+ pref[BROWSER_SECURITY_LEVEL_PREF]
47
47
  end
48
48
 
49
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)
50
+ raise(ArgumentError, "Valid security levels are: #{TB_SEC_LEVELS.values}") unless TB_SEC_LEVELS.value?(int)
53
51
 
54
- execute_script("return Services.prefs.setIntPref('browser.security_level.security_slider', #{int})")
55
- ensure
56
- self.context = old_context if old_context
52
+ pref[BROWSER_SECURITY_LEVEL_PREF] = int
57
53
  end
58
54
 
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}')"
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['extensions.torlauncher.torrc_path'] = File.join(@data_dir, 'torrc')
64
+ @options.prefs[TORRC_PATH_PREF] = File.join(@data_dir, TORRC)
72
65
  end
73
66
 
74
67
  def install_extensions(instance)
@@ -78,6 +71,12 @@ module Selenium
78
71
  def create_tor_process_and_start_tor(opts)
79
72
  @tor_process = TorProcess.new(@data_dir, opts || {})
80
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+$/)
81
80
  end
82
81
  end
83
82
  end
@@ -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.2.0'
6
+ VERSION = '1.3.0'
7
7
  end
8
8
  end
9
9
  end
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.2.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-31 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
@@ -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