diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py
index c7d74a70..020c2776 100644
--- a/cli/onionshare_cli/settings.py
+++ b/cli/onionshare_cli/settings.py
@@ -113,6 +113,7 @@ class Settings(object):
"persistent_tabs": [],
"locale": None, # this gets defined in fill_in_defaults()
"theme": 0,
+ "censorship_circumvention": False,
}
self._settings = {}
self.fill_in_defaults()
diff --git a/cli/tests/test_cli_settings.py b/cli/tests/test_cli_settings.py
index 9513b013..9486e8d1 100644
--- a/cli/tests/test_cli_settings.py
+++ b/cli/tests/test_cli_settings.py
@@ -36,6 +36,7 @@ class TestSettings:
"bridges_custom": "",
"persistent_tabs": [],
"theme": 0,
+ "censorship_circumvention": False,
}
for key in settings_obj._settings:
# Skip locale, it will not always default to the same thing
diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json
index 868a6fa9..a69a7101 100644
--- a/desktop/src/onionshare/resources/locale/en.json
+++ b/desktop/src/onionshare/resources/locale/en.json
@@ -63,6 +63,7 @@
"gui_settings_tor_bridges": "Connect using a Tor bridge?",
"gui_settings_tor_bridges_label": "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.",
"gui_settings_bridge_use_checkbox": "Use a bridge",
+ "gui_settings_censorship_circumvention_checkbox": "Attempt to automatically find a bridge based on your country if Tor fails to connect",
"gui_settings_bridge_radio_builtin": "Select a built-in bridge",
"gui_settings_bridge_none_radio_option": "Don't use a bridge",
"gui_settings_meek_lite_expensive_warning": "Warning: The meek-azure bridges are very costly for the Tor Project to run.
Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.",
@@ -231,4 +232,4 @@
"moat_captcha_error": "The solution is not correct. Please try again.",
"moat_solution_empty_error": "You must enter the characters from the image",
"mode_tor_not_connected_label": "OnionShare is not connected to the Tor network"
-}
\ No newline at end of file
+}
diff --git a/desktop/src/onionshare/tor_connection.py b/desktop/src/onionshare/tor_connection.py
index 2cc599c4..0f3b7b2b 100644
--- a/desktop/src/onionshare/tor_connection.py
+++ b/desktop/src/onionshare/tor_connection.py
@@ -41,7 +41,7 @@ from onionshare_cli.onion import (
from . import strings
from .gui_common import GuiCommon
from .widgets import Alert
-
+from onionshare_cli.censorship import CensorshipCircumvention
class TorConnectionDialog(QtWidgets.QProgressDialog):
"""
@@ -165,7 +165,7 @@ class TorConnectionWidget(QtWidgets.QWidget):
success = QtCore.Signal()
fail = QtCore.Signal(str)
- def __init__(self, common, status_bar):
+ def __init__(self, common, status_bar, meek):
super(TorConnectionWidget, self).__init__(None)
self.common = common
self.common.log("TorConnectionWidget", "__init__")
@@ -181,6 +181,8 @@ class TorConnectionWidget(QtWidgets.QWidget):
)
self.cancel_button.clicked.connect(self.cancel_clicked)
+ self.meek = meek
+
progress_layout = QtWidgets.QHBoxLayout()
progress_layout.addWidget(self.progress)
progress_layout.addWidget(self.cancel_button)
@@ -263,7 +265,30 @@ class TorConnectionWidget(QtWidgets.QWidget):
def _error_connecting_to_tor(self, msg):
self.common.log("TorConnectionWidget", "_error_connecting_to_tor")
self.active = False
- self.fail.emit(msg)
+ # If we are allowed to try automatically resolving connection issues
+ # (e.g possible censorship) by obtaining bridges for the user, do so
+ if self.settings.get("censorship_circumvention"):
+ # Automatically try to obtain bridges from the Censorship Circumvention API
+ self.common.log("TorConnectionWidget", "_error_connecting_to_tor", "Trying to automatically obtain bridges")
+ self.meek.start()
+ self.censorship_circumvention = CensorshipCircumvention(self.common, self.meek)
+ request_bridges = self.censorship_circumvention.request_settings(country="cn")
+ if request_bridges:
+ # @TODO there might be several bridges
+ bridges = request_bridges["settings"][0]["bridges"]["bridge_strings"][0]
+ self.common.log("TorConnectionWidget", "_error_connecting_to_tor", f"Obtained bridges: {bridges}")
+ self.settings.set("bridges_enabled", True)
+ self.settings.set("bridges_type", "custom")
+ # @TODO there might be several bridges
+ self.settings.set("bridges_custom", bridges)
+ self.common.log("TorConnectionWidget", "_error_connecting_to_tor", "Starting Tor again")
+ self.settings.save()
+ # Now try and connect again
+ self.start()
+ else:
+ self.fail.emit()
+ else:
+ self.fail.emit()
class TorConnectionThread(QtCore.QThread):
diff --git a/desktop/src/onionshare/tor_settings_tab.py b/desktop/src/onionshare/tor_settings_tab.py
index e28e5260..382b34fd 100644
--- a/desktop/src/onionshare/tor_settings_tab.py
+++ b/desktop/src/onionshare/tor_settings_tab.py
@@ -91,6 +91,12 @@ class TorSettingsTab(QtWidgets.QWidget):
self.bridge_use_checkbox.stateChanged.connect(
self.bridge_use_checkbox_state_changed
)
+ self.censorship_circumvention_checkbox = QtWidgets.QCheckBox(
+ strings._("gui_settings_censorship_circumvention_checkbox")
+ )
+ self.censorship_circumvention_checkbox.stateChanged.connect(
+ self.censorship_circumvention_checkbox_state_changed
+ )
# Built-in bridge
self.bridge_builtin_radio = QtWidgets.QRadioButton(
@@ -164,6 +170,7 @@ class TorSettingsTab(QtWidgets.QWidget):
bridges_layout = QtWidgets.QVBoxLayout()
bridges_layout.addWidget(bridges_label)
bridges_layout.addWidget(self.bridge_use_checkbox)
+ bridges_layout.addWidget(self.censorship_circumvention_checkbox)
bridges_layout.addWidget(self.bridge_settings)
self.bridges = QtWidgets.QWidget()
@@ -330,7 +337,7 @@ class TorSettingsTab(QtWidgets.QWidget):
columns_wrapper.setLayout(columns_layout)
# Tor connection widget
- self.tor_con = TorConnectionWidget(self.common, self.status_bar)
+ self.tor_con = TorConnectionWidget(self.common, self.status_bar, self.meek)
self.tor_con.success.connect(self.tor_con_success)
self.tor_con.fail.connect(self.tor_con_fail)
self.tor_con.hide()
@@ -430,6 +437,7 @@ class TorSettingsTab(QtWidgets.QWidget):
if self.old_settings.get("bridges_enabled"):
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
+ self.censorship_circumvention_checkbox.setCheckState(QtCore.Qt.Checked)
self.bridge_settings.show()
bridges_type = self.old_settings.get("bridges_type")
@@ -506,6 +514,16 @@ class TorSettingsTab(QtWidgets.QWidget):
else:
self.bridge_settings.hide()
+ def censorship_circumvention_checkbox_state_changed(self, state):
+ """
+ 'Allow censorship circumvention (automatic bridges)' checkbox changed
+ """
+ # Turning on censorship circumvention through the act of
+ # automatic bridge selection, implicitly means enabling
+ # bridges.
+ if state == QtCore.Qt.Checked:
+ self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
+
def bridge_builtin_radio_toggled(self, checked):
"""
'Select a built-in bridge' radio button toggled
@@ -812,6 +830,9 @@ class TorSettingsTab(QtWidgets.QWidget):
if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
settings.set("bridges_enabled", True)
+ if self.censorship_circumvention_checkbox.checkState() == QtCore.Qt.Checked:
+ settings.set("censorship_circumvention", True)
+
if self.bridge_builtin_radio.isChecked():
settings.set("bridges_type", "built-in")