selenium_tor 0.1.3 → 1.0.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: 387e1f6800b7d9b1f7eb269a0e6d53f699123b59f3daa1b4ae66536037fdc3ff
4
- data.tar.gz: a59fa065eef2328abb0832ead2ada221172b5d4d55c34fe0285c02f6075e8d93
3
+ metadata.gz: cd34aa798531de8ada12d53163c343733610aa6115141d58aeaa3b4875c40cd6
4
+ data.tar.gz: f989c3ca02651ba825beefde17a4f2ac007ae25bb7c09e29ca080eee24cd3d03
5
5
  SHA512:
6
- metadata.gz: 6a5d2aa8ecf3527edadcec9aab17480dfdd0055525322b7c97f0c8f4e6588e392d0e5b10d4eca05c5a1766e22f87b6793109da8965a45ac6bc26216888be50de
7
- data.tar.gz: b145dab8c18821750a8be414af2cf44a8046a0393e69907c52dc14cc8dd4f376e626d10d77fe473655604d27810d93ea5539f5a316f78659a6a8abaaf6780f8c
6
+ metadata.gz: 1a71cffb60fc1e7f58d4a8cb4ffc9b0b42abfca1f5c541332b0febf50b78440fb5adfacb76f18e7289184bd7fb185582c4b338f6c2cde8177847b0db4e9a8ba7
7
+ data.tar.gz: 3c9291018aede2ef805bf96110ff3742b4e2791634801be4ac607ad27e4d318ddafe931950946499a8547883c48ca750d6af9a409674b91e40cf9cce41e418b5
data/.jshintrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "esversion": 6
3
+ }
data/.rubocop.yml CHANGED
@@ -6,10 +6,24 @@ require:
6
6
 
7
7
  AllCops:
8
8
  NewCops: enable
9
- TargetRubyVersion: 2.7
10
9
 
11
10
  Style/HashSyntax:
12
11
  Enabled: false # yuk Ruby 3.1
13
12
 
14
13
  Layout/LineLength:
15
14
  Max: 120
15
+
16
+ Security/Eval:
17
+ Exclude:
18
+ - 'test/test_readme.rb'
19
+
20
+ Lint/RescueException:
21
+ Exclude:
22
+ - 'test/test_readme.rb'
23
+
24
+ Style/RegexpLiteral:
25
+ Exclude:
26
+ - 'Guardfile'
27
+
28
+ Minitest/TestMethodName:
29
+ Enabled: true
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.0-preview3
1
+ 3.3.4
data/CHANGELOG.md CHANGED
@@ -1,11 +1,36 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## [1.0.0] - 2024-07-15
4
+
5
+ ### New features
6
+
7
+ * add ability to use system tor
8
+ * add ability to run multiple driver instances simultaneously (fixes [#6](https://gitlab.com/matzfan/selenium-tor/-/issues/6))
9
+
10
+ ## [0.2.0] - 2024-07-10
11
+
12
+ ### Breaking changes
13
+
14
+ * RUN_HEADED_TESTS env var now ignored
15
+
16
+ ### New features
17
+
18
+ * add README code block tests
19
+ * fingerprintjs tests cover WebGL llvmpipe driver and WebGL being disabled
20
+ * special test added to test WebGL fingerprint when code run with vglrun
21
+
22
+ ### Bug fixes
23
+
24
+ * [#4](https://gitlab.com/matzfan/selenium-tor/-/issues/4)
25
+ * [#5](https://gitlab.com/matzfan/selenium-tor/-/issues/5)
26
+ * [#7](https://gitlab.com/matzfan/selenium-tor/-/issues/7)
27
+
3
28
  ## [0.1.3] - 2023-12-08
4
29
 
5
30
  ### Bug fixes
6
31
 
7
32
  * added selenium-webdriver as runtime dependency
8
- * [#2](https://gitlab.com/matzfan/selenium_tor/-/issues/2) fixed, fingerprintjs VisitorId now same as TB's
33
+ * [#2](https://gitlab.com/matzfan/selenium-tor/-/issues/2) fixed, fingerprintjs VisitorId now same as TB's
9
34
 
10
35
  ### New features
11
36
 
@@ -30,7 +55,7 @@
30
55
  ### Bug fixes
31
56
 
32
57
  * Fix inability to add options to Tor::Options
33
- * [#1](https://gitlab.com/matzfan/selenium_tor/-/issues/1): Fix fonts leak at browserleaks
58
+ * [#1](https://gitlab.com/matzfan/selenium-tor/-/issues/1): Fix fonts leak at browserleaks
34
59
 
35
60
  ### New features
36
61
 
data/Guardfile CHANGED
@@ -1,25 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A sample Guardfile
4
- # More info at https://github.com/guard/guard#readme
5
-
6
- ## Uncomment and set this to only include directories you want to watch
7
- # directories %w(app lib config test spec features) \
8
- # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
-
10
- ## Note: if you are using the `directories` clause above and you are not
11
- ## watching the project directory ('.'), then you will want to move
12
- ## the Guardfile to a watched dir and symlink it back, e.g.
13
- #
14
- # $ mkdir config
15
- # $ mv Guardfile config/
16
- # $ ln -s config/Guardfile .
17
- #
18
- # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
-
20
3
  guard :minitest do
21
4
  watch(%r{^test/(.*)/?test_(.*)\.rb$})
22
5
  watch(%r{^features/(.*)/?test_(.*)\.rb$})
23
- watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
24
- watch(%r{^test/test_helper\.rb$}) { 'test' }
6
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
7
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
8
+ watch(%r{^README.md$}) { 'test/test_readme.rb' }
25
9
  end
data/README.md CHANGED
@@ -5,14 +5,19 @@
5
5
 
6
6
  A Selenium extension for Tor Browser.
7
7
  ```ruby
8
- driver = Selenium::WebDriver.for :tor
8
+ @driver = Selenium::WebDriver.for :tor
9
+ @driver.quit
9
10
  ```
10
11
 
11
- # \_why?
12
+ ## \_why?
12
13
 
13
14
  I can use Firefox with Selenium and set a SOCKS proxy to use the Tor network, so why the need?
14
15
 
15
- 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 resitance to browser fingerprinting. The aim is to ensure Selenium Tor site visits leave an identical fingerprint to the thousands of regular Tor Browser users.
16
+ 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
+
18
+ ## Known issues
19
+
20
+ 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).
16
21
 
17
22
  ## Installation
18
23
 
@@ -25,30 +30,63 @@ If bundler is not being used to manage dependencies, install the gem by executin
25
30
  $ gem install selenium_tor
26
31
 
27
32
  ## Usage
28
-
29
- 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.
33
+ ```ruby
34
+ require 'selenium_tor'
35
+ ```
36
+ 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.
30
37
 
31
38
  A driver is instantiated like this:
32
39
  ```ruby
33
40
  options = Selenium::WebDriver::Tor::Options.new
34
- options.add_argument '--headless' # whatever you need
35
- driver = Selenium::WebDriver.for :tor, options: options
41
+ @driver = Selenium::WebDriver.for :tor, options: options
36
42
 
37
- driver.get 'https://check.torproject.org'
38
- puts driver.title # => Congratulations. This browser is configured to use Tor.
39
- driver.quit
43
+ @driver.get 'https://check.torproject.org'
44
+ @driver.title # => Congratulations. This browser is configured to use Tor.
45
+ @driver.quit
40
46
  ```
41
- The driver will not be instantiated until a connection to the Tor network is made. If the network is inaccessible for any reason a `TorNetworkError` will result.
47
+ By default Tor Broswer will use the bundled `tor` process, in which case the driver will not be instantiated until a connection to the Tor network is made. If the network is inaccessible for any reason a `TorNetworkError` will result.
42
48
 
43
- The `Selenium::WebDriver::Tor` namespace is used for `Driver`, `Options` and `Profile`. Otherwise Selenium's `Selenium::WebDriver::Firefox` namespace is used.
49
+ ### Multiple instances using the system tor
44
50
 
45
- Remote functionality is not tested, but will be if Selenium provide a Tor Browser Docker container for Selenium Grid.
51
+ The following options require `tor` to be installed. Tor is available via various package managers e.g. `sudo apt install tor`.
52
+
53
+ If a `SystemTor` object is passed to options, the system `tor` will be used instead of the `tor` included in the Tor Browser Bundle (TBB).
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:
62
+ ```ruby
63
+ def new_driver(socks_p, control_p)
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
68
+
69
+ socks_port = 9150
70
+ control_port = 9151
71
+
72
+ driver1 = new_driver(socks_port, control_port)
73
+ socks_port += 2
74
+ control_port += 2
75
+ driver2 = new_driver(socks_port, control_port)
76
+
77
+ driver1.quit
78
+ driver2.quit
79
+ ```
80
+
81
+ 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
+
83
+ Remote functionality is not tested, but may be if a suitable Tor Browser Docker container becomes available.
46
84
 
47
85
  A number of constants are set during driver initialization based upon values found in the Tor Browser Bundle (TBB) root directory - see below. These include:
48
86
  ```ruby
49
87
  Tor::TBB_DIR # path to the TBB root directory
50
88
  Tor::TBB_BROWSER_DIR # path to the 'Browser' directory in the above
51
- Tor::TBB_BINARY_PATH # path to the binary
89
+ Tor::TBB_BINARY_PATH # path to the firefox binary
52
90
  Tor::TBB_PROFILE_DIR # path to the default profile directory
53
91
  Tor::TBB_EXTENSIONS_DIR # path to the 'extensions' directory in the above
54
92
  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
@@ -56,26 +94,33 @@ Tor::TBB_VERSION # the version installed, e.g. "13.0.1", note: driver.capabiliti
56
94
 
57
95
  ## Dependencies and configuration
58
96
 
97
+ [Tor Browser](https://www.torproject.org/download).
98
+
59
99
  As with Firefox browser, `geckodriver` needs to be installed and in your PATH.
60
100
 
61
- The gem needs to know the location of the Tor Browser Bundle (TBB). This can be installed from the Tor Project [downloads page](https://www.torproject.org/download/). The 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:
101
+ 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:
62
102
  ```ruby
103
+ options = Selenium::WebDriver::Tor::Options.new
63
104
  options.binary = '/some/path/to/tor_firefox_binary' # UNNECESSARY
64
105
  ```
65
106
 
66
- The location of the TBB is not expected to change during execution.
107
+ The location of the TBB is not expected to change during code execution.
67
108
 
68
109
  Tor Selenium is tested on **Linux only** right now.
69
110
 
70
111
  ## Testing
71
112
 
72
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests.
113
+ After checking out the repo, run `bin/setup` to install dependencies.
114
+
115
+ Tests are run in the display set by the `DISPLAY` env var, usually :0 by default. To run headless tests, set it to another value:
73
116
 
74
- Tests are run in headless mode by default. If you want to see what the browser is doing you can do this:
117
+ $ DISPLAY=:99 bundle exec rake
75
118
 
76
- $ export RUN_HEADED_TESTS=1 && bundle exec rake
119
+ If you find tests are failing with `TorNetworkError` and a timeout message, check you have no other Tor Browser processes running. The processes to look for are either "firefox.real" or "firefox-esr". The latter may be legitimate if you are also running Firefox browser. You may also want to check the Tor network is actually up, it isn't always..
77
120
 
78
- If you find tests are failing with `TorNetworkError` and a timeout message, check you have no other Tor Browser processes running. The process to look for is called "firefox.real". You may also want to check the Tor network is actually up, it isn't always..
121
+ 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:
122
+
123
+ $ DISPLAY=:99 vglrun bundle exec ruby test/features/fingerprinting/vglrun_test_fingerprintjs.rb
79
124
 
80
125
  ## Development
81
126
 
@@ -85,14 +130,8 @@ To install this gem onto your local machine, run `bundle exec rake install`.
85
130
 
86
131
  ## Contributing
87
132
 
88
- Bug reports and pull requests are welcome on GitLab at https://gitlab.com/matzfan/selenium_tor. For a pull request please checkout a suitably named feature branch before making and submitting changes.
89
-
90
- This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://gitlab.com/matzfan/selenium_tor/CODE_OF_CONDUCT.md).
133
+ Bug reports and pull requests are welcome on GitLab at https://gitlab.com/matzfan/selenium-tor. For a pull request please checkout a suitably named feature branch before making and submitting changes.
91
134
 
92
135
  ## License
93
136
 
94
137
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
95
-
96
- ## Code of Conduct
97
-
98
- Everyone interacting in the Selenium Tor project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/matzfan/selenium_tor/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -7,6 +7,7 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << 'test'
8
8
  t.libs << 'lib'
9
9
  t.test_files = FileList['test/**/test_*.rb']
10
+ # t.warning = false
10
11
  end
11
12
 
12
13
  require 'rubocop/rake_task'
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # patch String
4
+ module StringExtensions
5
+ def snake_case?
6
+ match(/\A[a-z]+(_[a-z]+)?\z/)
7
+ end
8
+ end
9
+
10
+ class String
11
+ prepend StringExtensions
12
+ end
data/lib/tor/driver.rb CHANGED
@@ -10,29 +10,49 @@ module Selenium
10
10
  class Driver < DelegateClass(DriverNotYetConnectedToTorNetwork)
11
11
  class TorNetworkError < StandardError; end
12
12
 
13
- CONNECTION_STATUS_ID = 'torPreferences-status-tor-connect'
14
- CONNECTED = "Tor Network:\nConnected"
15
- NO_SCRIPT_XPI_URL = 'https://addons.mozilla.org/firefox/downloads/file/4178438/noscript-11.4.28.xpi'
13
+ CONNECT_BUTTON_ID = 'network-status-tor-connect-button' # visible only until connected
14
+ TOR_PREFS_CONNECTION_URL = 'about:preferences#connection'
16
15
 
17
- def initialize(...)
18
- @instance = DriverNotYetConnectedToTorNetwork.new(...)
16
+ attr_reader :system_tor
17
+
18
+ def initialize(options: nil, **)
19
+ instantiate_driver_from_tbb_dir(options: options, **)
20
+ setup_system_tor options.system_tor if options&.system_tor
19
21
  super(@instance)
20
- wait_for_tor_connection
21
- install_addons
22
+ install_extensions
23
+ wait_for_tor_connection unless system_tor # no need if system tor used
24
+ end
25
+
26
+ def quit
27
+ system_tor&.stop_tor
28
+ super
22
29
  end
23
30
 
24
31
  private
25
32
 
33
+ def instantiate_driver_from_tbb_dir(...)
34
+ cwd = FileUtils.pwd
35
+ FileUtils.cd TBB_BROWSER_DIR # crucial - fixes [#2](https://gitlab.com/matzfan/selenium-tor/-/issues/2)
36
+ @instance = DriverNotYetConnectedToTorNetwork.new(...)
37
+ ensure
38
+ FileUtils.cd cwd
39
+ end
40
+
26
41
  def wait_for_tor_connection
27
- @instance.get 'about:preferences#connection'
28
- Wait.new(timeout: 10).until { @instance.find_element(:id, CONNECTION_STATUS_ID).text == CONNECTED }
42
+ @instance.get TOR_PREFS_CONNECTION_URL
43
+ Wait.new(timeout: 10).until { !@instance.find_element(:id, CONNECT_BUTTON_ID).displayed? }
29
44
  rescue Selenium::WebDriver::Error::TimeoutError => e
30
45
  @instance.quit # abort initialization
31
46
  raise TorNetworkError, "Cannot connect to Tor network: #{e.message}"
32
47
  end
33
48
 
34
- def install_addons
35
- @instance.install_addon URI.parse(NO_SCRIPT_XPI_URL).open
49
+ def install_extensions
50
+ Pathname.new(TBB_EXTENSIONS_DIR).children.each { |xpi_path| @instance.install_addon xpi_path }
51
+ end
52
+
53
+ def setup_system_tor(system_tor)
54
+ @system_tor = system_tor
55
+ system_tor.start_tor
36
56
  end
37
57
  end
38
58
  end
data/lib/tor/options.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'tor_prefs'
4
+ require_relative 'system_tor'
4
5
  require 'selenium-webdriver'
5
6
 
6
7
  module Selenium
@@ -8,7 +9,9 @@ module Selenium
8
9
  module Tor
9
10
  # tor options
10
11
  class Options < Firefox::Options
12
+ CAPABILITIES = CAPABILITIES.merge(system_tor: 'system_tor')
11
13
  DEFAULT_TBB_DIR = File.join Dir.home, 'tor-browser'
14
+
12
15
  Tor::TBB_DIR = ENV.fetch('TOR_BROWSER_ROOT_DIR', nil) || DEFAULT_TBB_DIR
13
16
  Tor::TBB_BROWSER_DIR = File.join Tor::TBB_DIR, 'Browser'
14
17
  Tor::TBB_BINARY_PATH = File.join Tor::TBB_BROWSER_DIR, 'firefox'
@@ -16,15 +19,24 @@ module Selenium
16
19
  Tor::TBB_EXTENSIONS_DIR = File.join Tor::TBB_PROFILE_DIR, 'extensions'
17
20
  Tor::TBB_VERSION = JSON.parse(File.read(File.join(Tor::TBB_BROWSER_DIR, 'tbb_version.json')))['version']
18
21
 
22
+ attr_reader :system_tor # read by Driver
23
+
19
24
  def initialize(log_level: nil, **opts)
25
+ @system_tor_prefs = {}
26
+ system_tor_prefs(opts.delete(:system_tor)) if opts[:system_tor] # must be deleted before call to super
20
27
  super(log_level: log_level, **tor_options(opts.delete(:prefs)).merge(opts))
21
28
  do_start_tor_browser_script_stuff # stuff the start-tor-browser script in TBB does
22
29
  end
23
30
 
24
31
  private
25
32
 
33
+ def system_tor_prefs(system_tor)
34
+ @system_tor = system_tor
35
+ @system_tor_prefs = system_tor.prefs
36
+ end
37
+
26
38
  def tor_options(prefs)
27
- { binary: TBB_BINARY_PATH, prefs: (prefs || {}).merge(TOR_PREFS) }
39
+ { binary: TBB_BINARY_PATH, prefs: (prefs || {}).merge(TOR_PREFS).merge(@system_tor_prefs) }
28
40
  end
29
41
 
30
42
  def do_start_tor_browser_script_stuff
@@ -34,7 +46,7 @@ module Selenium
34
46
  ENV['FONTCONFIG_FILE'] = 'fonts.conf'
35
47
  FileUtils.rm_rf File.join(Tor::TBB_BROWSER_DIR, *%w[TorBrowser Data fontconfig])
36
48
  ENV['GSETTINGS_BACKEND'] = 'memory'
37
- FileUtils.cd Tor::TBB_BROWSER_DIR # crucial - fixes [#2](https://gitlab.com/matzfan/selenium-tor/-/issues/2)
49
+ # cd to TBB_BROWSER_DIR now handled in Tor::Driver
38
50
  end
39
51
  end
40
52
  end
data/lib/tor/profile.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'selenium-webdriver'
4
4
  require_relative 'options'
5
- require_relative '../firefox_driver_lock_file_patch' unless
6
- Gem::Version.new(Selenium::WebDriver::VERSION) > Gem::Version.new('4.15')
7
5
 
8
6
  module Selenium
9
7
  module WebDriver
@@ -0,0 +1,84 @@
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 system tor process
11
+ class SystemTor
12
+ include StringExtensions
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
24
+
25
+ def initialize(opts = {})
26
+ raise SystemTorError, 'tor executable not found in PATH' unless tor_executable?
27
+ raise ArgumentError, 'SystemTor.new takes an options hash' unless opts.is_a? Hash
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
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
data/lib/tor/tor_prefs.rb CHANGED
@@ -16,7 +16,8 @@ module Selenium
16
16
  }.freeze
17
17
 
18
18
  OTHER_PREFS = {
19
- 'intl.language_notification.shown' => true # affects font fingerprint (viewport size)
19
+ 'intl.language_notification.shown' => true, # affects font fingerprint (viewport size)
20
+ 'remote.active-protocols' => 3 # CDP & BiDi future support
20
21
  }.freeze
21
22
 
22
23
  TOR_PREFS = FIRST_CONNECTION_PREFS.merge OTHER_PREFS
data/lib/tor/torrc.rb ADDED
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Selenium
4
+ module WebDriver
5
+ module Tor
6
+ # Respresentation of a torrc file
7
+ class Torrc
8
+ class TorrcError < StandardError; end
9
+
10
+ DEFAULT_PORTS = { 'SocksPort' => 9150, 'ControlPort' => 9151 }.freeze
11
+ INVALID_OPTION_REGEX = %r{\[warn\] Failed to parse/validate config: (.*).$}
12
+
13
+ attr_reader :path
14
+
15
+ def initialize(data_dir)
16
+ @data_dir = data_dir
17
+ @torrc_file = File.new File.join(@data_dir, 'torrc'), 'w'
18
+ @path = @torrc_file.path
19
+ write_default_config
20
+ end
21
+
22
+ def config
23
+ parse_config
24
+ end
25
+
26
+ def write_to_config(hash)
27
+ raise ArgumentError, 'Torrc#write_to_config takes a hash as argument' unless hash.is_a? Hash
28
+
29
+ validate_torrc_options hash
30
+ @torrc_file.write hash_to_config_string(config.merge(hash))
31
+ @torrc_file.rewind
32
+ end
33
+
34
+ private
35
+
36
+ def write_default_config
37
+ write_to_config DEFAULT_PORTS.merge('DataDirectory' => @data_dir)
38
+ end
39
+
40
+ def validate_torrc_options(hash)
41
+ tmp = Tempfile.new
42
+ tmp.write hash_to_config_string(hash)
43
+ tmp.rewind
44
+ invalid_opts = parse_invalid_opts `tor -f #{tmp.path} --verify-config`
45
+ raise TorrcError, "Invalid torrc opts: #{invalid_opts}" unless invalid_opts.empty?
46
+ ensure
47
+ tmp.unlink
48
+ end
49
+
50
+ def parse_invalid_opts(tor_output_string)
51
+ tor_output_string.split("\n").filter_map { |line| line[INVALID_OPTION_REGEX, 1] }
52
+ end
53
+
54
+ def hash_to_config_string(hash)
55
+ hash.map { |k, v| "#{k} #{v}" }.join("\n")
56
+ end
57
+
58
+ def parse_config
59
+ File.readlines(@torrc_file).inject({}) do |memo, line|
60
+ arr = line.chomp.split
61
+ arr << '' if arr.size == 1 # deal with keys with no value
62
+ memo.merge coerce_hash_int_values_to_int(Hash[*arr])
63
+ end
64
+ end
65
+
66
+ def coerce_hash_int_values_to_int(hash)
67
+ hash.transform_values { |v| v.to_i.to_s == v ? v.to_i : v }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ 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 = '0.1.3'
6
+ VERSION = '1.0.0'
7
7
  end
8
8
  end
9
9
  end
data/selenium_tor.gemspec CHANGED
@@ -6,19 +6,16 @@ Gem::Specification.new do |spec|
6
6
  spec.name = 'selenium_tor'
7
7
  spec.version = Selenium::WebDriver::Tor::VERSION
8
8
  spec.authors = ['MatzFan']
9
- spec.email = ['matzfan@mailinator.com']
10
9
 
11
10
  spec.summary = 'Selenium extension for Tor Browser'
12
11
  spec.description = 'An extension for Selenium::WebDriver that automates Tor Browser'
13
- spec.homepage = 'https://gitlab.com/matzfan/selenium_tor'
12
+ spec.homepage = 'https://gitlab.com/matzfan/selenium-tor'
14
13
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 2.7.0'
16
-
17
- # spec.metadata['allowed_push_host'] =
14
+ spec.required_ruby_version = '>= 3.3.3'
18
15
 
19
16
  spec.metadata['homepage_uri'] = spec.homepage
20
- spec.metadata['source_code_uri'] = 'https://gitlab.com/matzfan/selenium_tor'
21
- spec.metadata['changelog_uri'] = 'https://gitlab.com/matzfan/selenium_tor/CHANGELOG.md'
17
+ spec.metadata['source_code_uri'] = 'https://gitlab.com/matzfan/selenium-tor'
18
+ spec.metadata['changelog_uri'] = 'https://gitlab.com/matzfan/selenium-tor/CHANGELOG.md'
22
19
  spec.metadata['rubygems_mfa_required'] = 'true'
23
20
 
24
21
  # Specify which files should be added to the gem when it is released.
@@ -33,5 +30,5 @@ Gem::Specification.new do |spec|
33
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
31
  spec.require_paths = ['lib']
35
32
 
36
- spec.add_runtime_dependency 'selenium-webdriver', '4.16.0'
33
+ spec.add_runtime_dependency 'selenium-webdriver', '~> 4.22'
37
34
  end
metadata CHANGED
@@ -1,65 +1,66 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selenium_tor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MatzFan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-08 00:00:00.000000000 Z
11
+ date: 2024-07-15 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.16.0
19
+ version: '4.22'
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.16.0
26
+ version: '4.22'
27
27
  description: An extension for Selenium::WebDriver that automates Tor Browser
28
28
  email:
29
- - matzfan@mailinator.com
30
29
  executables: []
31
30
  extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
33
+ - ".jshintrc"
34
34
  - ".rubocop.yml"
35
35
  - ".ruby-version"
36
36
  - ".yamllint"
37
37
  - CHANGELOG.md
38
- - CODE_OF_CONDUCT.md
39
38
  - Guardfile
40
39
  - LICENSE.txt
41
40
  - README.md
42
41
  - Rakefile
43
42
  - lib/driver.rb
44
- - lib/firefox_driver_lock_file_patch.rb
45
43
  - lib/options.rb
46
44
  - lib/selenium_tor.rb
47
45
  - lib/service.rb
46
+ - lib/string_extensions.rb
48
47
  - lib/tor/driver.rb
49
48
  - lib/tor/driver_not_yet_connected_to_tor_network.rb
50
49
  - lib/tor/options.rb
51
50
  - lib/tor/profile.rb
51
+ - lib/tor/system_tor.rb
52
52
  - lib/tor/tor_prefs.rb
53
+ - lib/tor/torrc.rb
53
54
  - lib/tor/version.rb
54
55
  - selenium_tor.gemspec
55
56
  - sig/tor.rbs
56
- homepage: https://gitlab.com/matzfan/selenium_tor
57
+ homepage: https://gitlab.com/matzfan/selenium-tor
57
58
  licenses:
58
59
  - MIT
59
60
  metadata:
60
- homepage_uri: https://gitlab.com/matzfan/selenium_tor
61
- source_code_uri: https://gitlab.com/matzfan/selenium_tor
62
- changelog_uri: https://gitlab.com/matzfan/selenium_tor/CHANGELOG.md
61
+ homepage_uri: https://gitlab.com/matzfan/selenium-tor
62
+ source_code_uri: https://gitlab.com/matzfan/selenium-tor
63
+ changelog_uri: https://gitlab.com/matzfan/selenium-tor/CHANGELOG.md
63
64
  rubygems_mfa_required: 'true'
64
65
  post_install_message:
65
66
  rdoc_options: []
@@ -69,14 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
70
  requirements:
70
71
  - - ">="
71
72
  - !ruby/object:Gem::Version
72
- version: 2.7.0
73
+ version: 3.3.3
73
74
  required_rubygems_version: !ruby/object:Gem::Requirement
74
75
  requirements:
75
76
  - - ">="
76
77
  - !ruby/object:Gem::Version
77
78
  version: '0'
78
79
  requirements: []
79
- rubygems_version: 3.5.0.dev
80
+ rubygems_version: 3.5.15
80
81
  signing_key:
81
82
  specification_version: 4
82
83
  summary: Selenium extension for Tor Browser
data/CODE_OF_CONDUCT.md DELETED
@@ -1,84 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
-
7
- We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
-
9
- ## Our Standards
10
-
11
- Examples of behavior that contributes to a positive environment for our community include:
12
-
13
- * Demonstrating empathy and kindness toward other people
14
- * Being respectful of differing opinions, viewpoints, and experiences
15
- * Giving and gracefully accepting constructive feedback
16
- * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
- * Focusing on what is best not just for us as individuals, but for the overall community
18
-
19
- Examples of unacceptable behavior include:
20
-
21
- * The use of sexualized language or imagery, and sexual attention or
22
- advances of any kind
23
- * Trolling, insulting or derogatory comments, and personal or political attacks
24
- * Public or private harassment
25
- * Publishing others' private information, such as a physical or email
26
- address, without their explicit permission
27
- * Other conduct which could reasonably be considered inappropriate in a
28
- professional setting
29
-
30
- ## Enforcement Responsibilities
31
-
32
- Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
-
34
- Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
-
36
- ## Scope
37
-
38
- This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
-
40
- ## Enforcement
41
-
42
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at matzfan@mailinator.com. All complaints will be reviewed and investigated promptly and fairly.
43
-
44
- All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
-
46
- ## Enforcement Guidelines
47
-
48
- Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
-
50
- ### 1. Correction
51
-
52
- **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
-
54
- **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
-
56
- ### 2. Warning
57
-
58
- **Community Impact**: A violation through a single incident or series of actions.
59
-
60
- **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
-
62
- ### 3. Temporary Ban
63
-
64
- **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
-
66
- **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
-
68
- ### 4. Permanent Ban
69
-
70
- **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
-
72
- **Consequence**: A permanent ban from any sort of public interaction within the community.
73
-
74
- ## Attribution
75
-
76
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
- available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
-
79
- Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
-
81
- [homepage]: https://www.contributor-covenant.org
82
-
83
- For answers to common questions about this code of conduct, see the FAQ at
84
- https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Selenium
4
- module WebDriver
5
- module Firefox
6
- # patched class
7
- class Profile
8
- alias old_delete_lock_files delete_lock_files
9
-
10
- def delete_lock_files(directory)
11
- %w[.parentlock parent.lock lock].each do |name| # 'lock' file missing from Selenium < 4.16
12
- FileUtils.rm_f File.join(directory, name)
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end