From 180da51978631be125058170c95705828538ee55 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 10:42:04 -0800 Subject: [PATCH 1/9] Add rebuild-cli.sh script --- desktop/README.md | 16 ++++++---------- desktop/scripts/rebuild-cli.sh | 9 +++++++++ 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100755 desktop/scripts/rebuild-cli.sh diff --git a/desktop/README.md b/desktop/README.md index a1406b47..053bf4ea 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -53,16 +53,6 @@ python scripts\get-tor-windows.py ### Prepare the code -In order to work with the desktop app, you'll need to build a wheel of the CLI package first, and copy it into the `desktop` folder: - -```sh -cd ../cli -poetry install -poetry build -cp dist/onionshare_cli-*.whl ../desktop -cd ../desktop -``` - OnionShare uses [Briefcase](https://briefcase.readthedocs.io/en/latest/). Install Briefcase dependencies by following [these instructions](https://docs.beeware.org/en/latest/tutorial/tutorial-0.html#install-dependencies). @@ -86,6 +76,12 @@ While your virtual environment is active, install briefcase from pip. pip install briefcase ``` +In order to work with the desktop app, you'll need to build a wheel of the CLI package first, and copy it into the `desktop` folder. You'll need to re-run this script each time you change the CLI code. + +```sh +./scripts/rebuild-cli.sh +``` + Run OnionShare from the source tree like this: ``` diff --git a/desktop/scripts/rebuild-cli.sh b/desktop/scripts/rebuild-cli.sh new file mode 100755 index 00000000..f8191955 --- /dev/null +++ b/desktop/scripts/rebuild-cli.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Build the CLI python wheel and copy it to the desktop folder + +SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +cd $SCRIPTS_DIR +cd ../../cli +poetry build +cp dist/*.whl ../desktop \ No newline at end of file From ee5b950f7c07c22f3acbcdd81b3d776f1535a70f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 11:08:46 -0800 Subject: [PATCH 2/9] Make CLI throw an error if it cannot find tor binary --- cli/onionshare_cli/__init__.py | 12 ++++++++++-- cli/onionshare_cli/common.py | 10 ++++++++++ cli/onionshare_cli/onion.py | 19 ------------------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py index 7361ac9e..34627deb 100644 --- a/cli/onionshare_cli/__init__.py +++ b/cli/onionshare_cli/__init__.py @@ -22,7 +22,7 @@ import os, sys, time, argparse, threading from datetime import datetime from datetime import timedelta -from .common import Common +from .common import Common, CannotFindTor from .web import Web from .onion import * from .onionshare import OnionShare @@ -320,7 +320,15 @@ def main(cwd=None): web = Web(common, False, mode_settings, mode) # Start the Onion object - onion = Onion(common, use_tmp_dir=True) + try: + onion = Onion(common, use_tmp_dir=True) + except CannotFindTor: + print("You must install tor to use OnionShare from the command line") + if common.platform == "Darwin": + print("In macOS, you can do this with Homebrew (https://brew.sh):") + print(" brew install tor") + sys.exit() + try: onion.connect( custom_settings=False, diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py index a1213387..e8b174b6 100644 --- a/cli/onionshare_cli/common.py +++ b/cli/onionshare_cli/common.py @@ -34,6 +34,12 @@ from pkg_resources import resource_filename from .settings import Settings +class CannotFindTor(Exception): + """ + OnionShare can't find a tor binary + """ + + class Common: """ The Common object is shared amongst all parts of OnionShare. @@ -82,6 +88,8 @@ class Common: def get_tor_paths(self): if self.platform == "Linux": tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() obfs4proxy_file_path = shutil.which("obfs4proxy") prefix = os.path.dirname(os.path.dirname(tor_path)) tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") @@ -94,6 +102,8 @@ class Common: tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6") elif self.platform == "Darwin": tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() obfs4proxy_file_path = shutil.which("obfs4proxy") prefix = os.path.dirname(os.path.dirname(tor_path)) tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 1b025bf8..d6f59a59 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -32,7 +32,6 @@ import getpass import psutil from distutils.version import LooseVersion as Version -from . import common from .settings import Settings # TODO: Figure out how to localize this for the GUI @@ -44,40 +43,30 @@ class TorErrorAutomatic(Exception): using automatic settings that should work with Tor Browser. """ - pass - class TorErrorInvalidSetting(Exception): """ This exception is raised if the settings just don't make sense. """ - pass - class TorErrorSocketPort(Exception): """ OnionShare can't connect to the Tor controller using the supplied address and port. """ - pass - class TorErrorSocketFile(Exception): """ OnionShare can't connect to the Tor controller using the supplied socket file. """ - pass - class TorErrorMissingPassword(Exception): """ OnionShare connected to the Tor controller, but it requires a password. """ - pass - class TorErrorUnreadableCookieFile(Exception): """ @@ -85,8 +74,6 @@ class TorErrorUnreadableCookieFile(Exception): to access the cookie file. """ - pass - class TorErrorAuthError(Exception): """ @@ -94,8 +81,6 @@ class TorErrorAuthError(Exception): that a Tor controller isn't listening on this port. """ - pass - class TorErrorProtocolError(Exception): """ @@ -103,8 +88,6 @@ class TorErrorProtocolError(Exception): isn't acting like a Tor controller (such as in Whonix). """ - pass - class TorTooOld(Exception): """ @@ -113,8 +96,6 @@ class TorTooOld(Exception): is too old. """ - pass - class BundledTorTimeout(Exception): """ From d416abdf9531586816e7d7a7e39e73e5c7f7b89f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 11:18:03 -0800 Subject: [PATCH 3/9] Make settings dialog use the correct get_tor_paths method --- desktop/src/onionshare/settings_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 5f37bda1..40de8648 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -142,7 +142,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, - ) = self.common.get_tor_paths() + ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): @@ -165,7 +165,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, - ) = self.common.get_tor_paths() + ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): From bbd5d6147bd24207d189a9f0bc485b06b6af8df7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 11:23:23 -0800 Subject: [PATCH 4/9] Update desktop readme, and add a helper script to use CLI args --- desktop/README.md | 14 ++++++++++++-- desktop/scripts/dev.sh | 9 +++++++++ desktop/scripts/rebuild-cli.sh | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100755 desktop/scripts/dev.sh diff --git a/desktop/README.md b/desktop/README.md index 053bf4ea..5e1c11ea 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -51,7 +51,7 @@ Download Tor Browser and extract the binaries: python scripts\get-tor-windows.py ``` -### Prepare the code +### Prepare the virtual environment OnionShare uses [Briefcase](https://briefcase.readthedocs.io/en/latest/). @@ -82,12 +82,22 @@ In order to work with the desktop app, you'll need to build a wheel of the CLI p ./scripts/rebuild-cli.sh ``` -Run OnionShare from the source tree like this: +### Running OnionShare from the source code tree + +Inside the virtual environment, run OnionShare like this to install all of the dependencies: ``` briefcase dev -d ``` +Once you have the dependencies installed, you can run it using the `dev.sh` script, which lets you use command line arguments, such as to `--verbose` or `--local-only`: + +``` +./scripts/dev.sh --help +./scripts/dev.sh -v +./scripts/dev.sh -v --local-only +``` + ## Running tests Install these packages inside your virtual environment: diff --git a/desktop/scripts/dev.sh b/desktop/scripts/dev.sh new file mode 100755 index 00000000..6ce5e796 --- /dev/null +++ b/desktop/scripts/dev.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Run OnionShare desktop, allowing you to use command-line arguments + +SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +cd $SCRIPTS_DIR + +cd ../src +python -c "import onionshare; onionshare.main()" $@ \ No newline at end of file diff --git a/desktop/scripts/rebuild-cli.sh b/desktop/scripts/rebuild-cli.sh index f8191955..9d6a1338 100755 --- a/desktop/scripts/rebuild-cli.sh +++ b/desktop/scripts/rebuild-cli.sh @@ -5,5 +5,6 @@ SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" cd $SCRIPTS_DIR cd ../../cli +poetry install poetry build cp dist/*.whl ../desktop \ No newline at end of file From 6ffa9b62f8e7ae45b49a53f3d3f6163f64f756ed Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 12:25:09 -0800 Subject: [PATCH 5/9] All Tor-related that get thrown from the onionshare_cli.Onion object now get translated in the GUI --- cli/onionshare_cli/onion.py | 112 +++++++++--------- cli/onionshare_cli/onionshare.py | 1 - desktop/src/onionshare/gui_common.py | 51 +++++++- .../src/onionshare/resources/locale/en.json | 3 +- desktop/src/onionshare/settings_dialog.py | 36 +++++- desktop/src/onionshare/threads.py | 16 ++- .../src/onionshare/tor_connection_dialog.py | 41 ++++++- 7 files changed, 188 insertions(+), 72 deletions(-) diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 1b025bf8..4cf360b5 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -35,8 +35,6 @@ from distutils.version import LooseVersion as Version from . import common from .settings import Settings -# TODO: Figure out how to localize this for the GUI - class TorErrorAutomatic(Exception): """ @@ -106,16 +104,20 @@ class TorErrorProtocolError(Exception): pass -class TorTooOld(Exception): +class TorTooOldEphemeral(Exception): """ - This exception is raised if onionshare needs to use a feature of Tor or stem - (like stealth ephemeral onion services) but the version you have installed - is too old. + This exception is raised if the version of tor doesn't support ephemeral onion services """ pass +class TorTooOldStealth(Exception): + """ + This exception is raised if the version of tor doesn't support stealth onion services + """ + + class BundledTorTimeout(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, @@ -137,6 +139,12 @@ class BundledTorBroken(Exception): """ +class PortNotAvailable(Exception): + """ + There are no available ports for OnionShare to use, which really shouldn't ever happen + """ + + class Onion(object): """ Onion is an abstraction layer for connecting to the Tor control port and @@ -233,7 +241,8 @@ class Onion(object): try: self.tor_socks_port = self.common.get_available_port(1000, 65535) except: - raise OSError("OnionShare port not available") + print("OnionShare port not available") + raise PortNotAvailable() self.tor_torrc = os.path.join(self.tor_data_directory_name, "torrc") # If there is an existing OnionShare tor process, kill it @@ -265,7 +274,8 @@ class Onion(object): try: self.tor_control_port = self.common.get_available_port(1000, 65535) except: - raise OSError("OnionShare port not available") + print("OnionShare port not available") + raise PortNotAvailable() self.tor_control_socket = None else: # Linux and BSD can use unix sockets @@ -334,10 +344,6 @@ class Onion(object): f.write(self.settings.get("tor_bridges_use_custom_bridges")) f.write("\nUseBridges 1") - # Make sure the tor path is accurate - if not os.path.exists(self.tor_path): - raise BundledTorNotSupported(f"Cannot find tor binary: {self.tor_path}") - # Execute a tor subprocess start_ts = time.time() if self.common.platform == "Windows": @@ -372,10 +378,8 @@ class Onion(object): self.c = Controller.from_socket_file(path=self.tor_control_socket) self.c.authenticate() except Exception as e: - raise BundledTorBroken( - # strings._("settings_error_bundled_tor_broken").format(e.args[0]) - "OnionShare could not connect to Tor:\n{}".format(e.args[0]) - ) + print("OnionShare could not connect to Tor:\n{}".format(e.args[0])) + raise BundledTorBroken(e.args[0]) while True: try: @@ -422,15 +426,16 @@ class Onion(object): print("") try: self.tor_proc.terminate() - raise BundledTorTimeout( - # strings._("settings_error_bundled_tor_timeout") + print( "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?" ) + raise BundledTorTimeout() except FileNotFoundError: pass elif self.settings.get("connection_type") == "automatic": # Automatically try to guess the right way to connect to Tor Browser + automatic_error = "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" # Try connecting to control port found_tor = False @@ -482,30 +487,25 @@ class Onion(object): ) elif self.common.platform == "Windows": # Windows doesn't support unix sockets - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() self.c = Controller.from_socket_file(path=socket_file_path) except: - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() # Try authenticating try: self.c.authenticate() except: - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() else: # Use specific settings to connect to tor + invalid_settings_error = "Can't connect to Tor controller because your settings don't make sense." # Try connecting try: @@ -519,27 +519,28 @@ class Onion(object): path=self.settings.get("socket_file_path") ) else: - raise TorErrorInvalidSetting( - # strings._("settings_error_unknown") - "Can't connect to Tor controller because your settings don't make sense." - ) + print(invalid_settings_error) + raise TorErrorInvalidSetting() except: if self.settings.get("connection_type") == "control_port": - raise TorErrorSocketPort( - # strings._("settings_error_socket_port") + print( "Can't connect to the Tor controller at {}:{}.".format( self.settings.get("control_port_address"), self.settings.get("control_port_port"), ) ) + raise TorErrorSocketPort( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) else: - raise TorErrorSocketFile( - # strings._("settings_error_socket_file") + print( "Can't connect to the Tor controller using socket file {}.".format( self.settings.get("socket_file_path") ) ) + raise TorErrorSocketFile(self.settings.get("socket_file_path")) # Try authenticating try: @@ -548,29 +549,30 @@ class Onion(object): elif self.settings.get("auth_type") == "password": self.c.authenticate(self.settings.get("auth_password")) else: - raise TorErrorInvalidSetting( - # strings._("settings_error_unknown") - "Can't connect to Tor controller because your settings don't make sense." - ) + print(invalid_settings_error) + raise TorErrorInvalidSetting() except MissingPassword: - raise TorErrorMissingPassword( - # strings._("settings_error_missing_password") + print( "Connected to Tor controller, but it requires a password to authenticate." ) + raise TorErrorMissingPassword() except UnreadableCookieFile: - raise TorErrorUnreadableCookieFile( - # strings._("settings_error_unreadable_cookie_file") + print( "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file." ) + raise TorErrorUnreadableCookieFile() except AuthenticationFailure: - raise TorErrorAuthError( - # strings._("settings_error_auth") + print( "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?".format( self.settings.get("control_port_address"), self.settings.get("control_port_port"), ) ) + raise TorErrorAuthError( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) # If we made it this far, we should be connected to Tor self.connected_to_tor = True @@ -625,15 +627,15 @@ class Onion(object): self.common.log("Onion", "start_onion_service", f"port={port}") if not self.supports_ephemeral: - raise TorTooOld( - # strings._("error_ephemeral_not_supported") + print( "Your version of Tor is too old, ephemeral onion services are not supported" ) + raise TorTooOldEphemeral() if mode_settings.get("general", "client_auth") and not self.supports_stealth: - raise TorTooOld( - # strings._("error_stealth_not_supported") + print( "Your version of Tor is too old, stealth onion services are not supported" ) + raise TorTooOldStealth() auth_cookie = None if mode_settings.get("general", "client_auth"): @@ -690,10 +692,8 @@ class Onion(object): ) except ProtocolError as e: - raise TorErrorProtocolError( - # strings._("error_tor_protocol_error") - "Tor error: {}".format(e.args[0]) - ) + print("Tor error: {}".format(e.args[0])) + raise TorErrorProtocolError(e.args[0]) onion_host = res.service_id + ".onion" diff --git a/cli/onionshare_cli/onionshare.py b/cli/onionshare_cli/onionshare.py index f74672ce..513dbc34 100644 --- a/cli/onionshare_cli/onionshare.py +++ b/cli/onionshare_cli/onionshare.py @@ -21,7 +21,6 @@ along with this program. If not, see . import os, shutil from . import common -from .onion import TorTooOld, TorErrorProtocolError from .common import AutoStopTimer diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index f488a740..017fd6b7 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -24,7 +24,22 @@ import shutil from pkg_resources import resource_filename from . import strings -from onionshare_cli.onion import Onion +from onionshare_cli.onion import ( + Onion, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) class GuiCommon: @@ -377,3 +392,37 @@ class GuiCommon: Returns the absolute path of a resource """ return resource_filename("onionshare", os.path.join("resources", filename)) + + @staticmethod + def get_translated_tor_error(e): + """ + Takes an exception defined in onion.py and returns a translated error message + """ + if type(e) is TorErrorInvalidSetting: + return strings._("settings_error_unknown") + elif type(e) is TorErrorAutomatic: + return strings._("settings_error_automatic") + elif type(e) is TorErrorSocketPort: + return strings._("settings_error_socket_port").format(e.args[0], e.args[1]) + elif type(e) is TorErrorSocketFile: + return strings._("settings_error_socket_file").format(e.args[0]) + elif type(e) is TorErrorMissingPassword: + return strings._("settings_error_missing_password") + elif type(e) is TorErrorUnreadableCookieFile: + return strings._("settings_error_unreadable_cookie_file") + elif type(e) is TorErrorAuthError: + return strings._("settings_error_auth").format(e.args[0], e.args[1]) + elif type(e) is TorErrorProtocolError: + return strings._("error_tor_protocol_error").format(e.args[0]) + elif type(e) is BundledTorTimeout: + return strings._("settings_error_bundled_tor_timeout") + elif type(e) is BundledTorBroken: + return strings._("settings_error_bundled_tor_broken").format(e.args[0]) + elif type(e) is TorTooOldEphemeral: + return strings._("error_ephemeral_not_supported") + elif type(e) is TorTooOldStealth: + return strings._("error_stealth_not_supported") + elif type(e) is PortNotAvailable: + return strings._("error_port_not_available") + + return None diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 1f98d7a7..9617e9c4 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -187,5 +187,6 @@ "settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.", "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", - "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}" + "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}", + "error_port_not_available": "OnionShare port not available" } \ No newline at end of file diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 5f37bda1..1803b170 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -27,11 +27,31 @@ import os from onionshare_cli import common from onionshare_cli.settings import Settings -from onionshare_cli.onion import * +from onionshare_cli.onion import ( + Onion, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) from . import strings from .widgets import Alert -from .update_checker import * +from .update_checker import ( + UpdateCheckerCheckError, + UpdateCheckerInvalidLatestVersion, + UpdateChecker, + UpdateThread, +) from .tor_connection_dialog import TorConnectionDialog from .gui_common import GuiCommon @@ -698,10 +718,18 @@ class SettingsDialog(QtWidgets.QDialog): TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, - BundledTorNotSupported, BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) as e: - Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) + message = self.common.gui.get_translated_tor_error(e) + Alert( + self.common, + message, + QtWidgets.QMessageBox.Warning, + ) if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() diff --git a/desktop/src/onionshare/threads.py b/desktop/src/onionshare/threads.py index 338bbf27..285b51b9 100644 --- a/desktop/src/onionshare/threads.py +++ b/desktop/src/onionshare/threads.py @@ -24,7 +24,6 @@ import os from PySide2 import QtCore from onionshare_cli.onion import ( - TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, @@ -34,6 +33,10 @@ from onionshare_cli.onion import ( TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) from . import strings @@ -93,7 +96,6 @@ class OnionThread(QtCore.QThread): self.success.emit() except ( - TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, @@ -103,9 +105,13 @@ class OnionThread(QtCore.QThread): TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, - OSError, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) as e: - self.error.emit(e.args[0]) + message = self.mode.common.gui.get_translated_tor_error(e) + self.error.emit(message) return @@ -174,7 +180,7 @@ class AutoStartTimer(QtCore.QThread): class EventHandlerThread(QtCore.QThread): """ - To trigger an event, write a JSON line to the events file. When that file changes, + To trigger an event, write a JSON line to the events file. When that file changes, each line will be handled as an event. Valid events are: {"type": "new_tab"} {"type": "new_share_tab", "filenames": ["file1", "file2"]} diff --git a/desktop/src/onionshare/tor_connection_dialog.py b/desktop/src/onionshare/tor_connection_dialog.py index d5fa72a0..a9201aed 100644 --- a/desktop/src/onionshare/tor_connection_dialog.py +++ b/desktop/src/onionshare/tor_connection_dialog.py @@ -18,9 +18,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import time from PySide2 import QtCore, QtWidgets, QtGui -from onionshare_cli.onion import * +from onionshare_cli.onion import ( + BundledTorCanceled, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) from . import strings from .gui_common import GuiCommon @@ -156,9 +172,26 @@ class TorConnectionThread(QtCore.QThread): ) self.canceled_connecting_to_tor.emit() - except Exception as e: - self.common.log("TorConnectionThread", "run", f"caught exception: {e}") - self.error_connecting_to_tor.emit(str(e)) + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, + ) as e: + message = self.common.gui.get_translated_tor_error(e) + self.common.log( + "TorConnectionThread", "run", f"caught exception: {message}" + ) + self.error_connecting_to_tor.emit(message) def _tor_status_update(self, progress, summary): self.tor_status_update.emit(progress, summary) From 30b86efd450ac031c2a3f9d5622ca7ef7413f924 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 12:30:19 -0800 Subject: [PATCH 6/9] The CLI error message gets printed from the Onion object, so don't try to print it the exception --- cli/onionshare_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py index 7361ac9e..bcc22f15 100644 --- a/cli/onionshare_cli/__init__.py +++ b/cli/onionshare_cli/__init__.py @@ -332,7 +332,7 @@ def main(cwd=None): print("") sys.exit() except Exception as e: - sys.exit(e.args[0]) + sys.exit() # Start the onionshare app try: From c2a8c7d75a996920c23545fbc4e7fe3fbecdb370 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 13:57:53 -0800 Subject: [PATCH 7/9] Write rebuild-cli in python instead of bash to work in Windows, and make the dev script available in Windows --- desktop/README.md | 4 ++- desktop/scripts/dev.bat | 3 +++ desktop/scripts/rebuild-cli.py | 45 ++++++++++++++++++++++++++++++++++ desktop/scripts/rebuild-cli.sh | 10 -------- 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 desktop/scripts/dev.bat create mode 100755 desktop/scripts/rebuild-cli.py delete mode 100755 desktop/scripts/rebuild-cli.sh diff --git a/desktop/README.md b/desktop/README.md index 5e1c11ea..97d0fd30 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -79,7 +79,7 @@ pip install briefcase In order to work with the desktop app, you'll need to build a wheel of the CLI package first, and copy it into the `desktop` folder. You'll need to re-run this script each time you change the CLI code. ```sh -./scripts/rebuild-cli.sh +python scripts/rebuild-cli.py ``` ### Running OnionShare from the source code tree @@ -98,6 +98,8 @@ Once you have the dependencies installed, you can run it using the `dev.sh` scri ./scripts/dev.sh -v --local-only ``` +Windows uses `scripts\dev.bat` instead. + ## Running tests Install these packages inside your virtual environment: diff --git a/desktop/scripts/dev.bat b/desktop/scripts/dev.bat new file mode 100644 index 00000000..9b537a90 --- /dev/null +++ b/desktop/scripts/dev.bat @@ -0,0 +1,3 @@ +cd src +python -c "import onionshare; onionshare.main()" %* +cd .. \ No newline at end of file diff --git a/desktop/scripts/rebuild-cli.py b/desktop/scripts/rebuild-cli.py new file mode 100755 index 00000000..c13461bc --- /dev/null +++ b/desktop/scripts/rebuild-cli.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +""" +This script builds the CLI python wheel, copies it to the desktop folder, +and installs it in the virtual environment. +""" + +import inspect +import os +import sys +import glob +import subprocess +import shutil + + +def main(): + # Build paths + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + ) + cli_path = os.path.join(root_path, "cli") + desktop_path = os.path.join(root_path, "desktop") + + # Delete old wheels + for filename in glob.glob(os.path.join(cli_path, "dist", "*.whl")): + os.remove(filename) + + # Build new wheel + subprocess.call(["poetry", "install"], cwd=cli_path) + subprocess.call(["poetry", "build"], cwd=cli_path) + wheel_filename = glob.glob(os.path.join(cli_path, "dist", "*.whl"))[0] + wheel_basename = os.path.basename(wheel_filename) + shutil.copyfile( + wheel_filename, + os.path.join(desktop_path, wheel_basename), + ) + + # Reinstall the new wheel + subprocess.call(["pip", "uninstall", "onionshare-cli", "-y"]) + subprocess.call(["pip", "install", os.path.join(desktop_path, wheel_basename)]) + + +if __name__ == "__main__": + main() diff --git a/desktop/scripts/rebuild-cli.sh b/desktop/scripts/rebuild-cli.sh deleted file mode 100755 index 9d6a1338..00000000 --- a/desktop/scripts/rebuild-cli.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Build the CLI python wheel and copy it to the desktop folder - -SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" -cd $SCRIPTS_DIR -cd ../../cli -poetry install -poetry build -cp dist/*.whl ../desktop \ No newline at end of file From 88736a8255903fd4c44c8093dc15cc41e712fda2 Mon Sep 17 00:00:00 2001 From: Saptak S Date: Sun, 13 Dec 2020 21:05:51 +0530 Subject: [PATCH 8/9] Adjusts height of title based on length of text --- desktop/src/onionshare/gui_common.py | 2 +- desktop/src/onionshare/tab/tab.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 017fd6b7..7d367b99 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -260,7 +260,7 @@ class GuiCommon: QLabel { text-align: center; color: #333333; - font-size: 28px; + font-size: 25px; } """, # Share mode and child widget styles diff --git a/desktop/src/onionshare/tab/tab.py b/desktop/src/onionshare/tab/tab.py index 5e819405..8f5ffd08 100644 --- a/desktop/src/onionshare/tab/tab.py +++ b/desktop/src/onionshare/tab/tab.py @@ -53,16 +53,22 @@ class NewTabButton(QtWidgets.QPushButton): ) self.image_label.setAlignment(QtCore.Qt.AlignCenter) self.image_label.setStyleSheet(self.common.gui.css["new_tab_button_image"]) - self.image_label.setGeometry(0, 0, self.width(), 200) + self.image_label.setGeometry(0, 0, self.width(), 190) self.image_label.show() # Title self.title_label = QtWidgets.QLabel(title, parent=self) + self.title_label.setWordWrap(True) self.title_label.setAlignment(QtCore.Qt.AlignCenter) self.title_label.setStyleSheet(self.common.gui.css["new_tab_title_text"]) - self.title_label.setGeometry( - (self.width() - 250) / 2, self.height() - 100, 250, 30 - ) + if self.title_label.sizeHint().width() >= 250: + self.title_label.setGeometry( + (self.width() - 250) / 2, self.height() - 120, 250, 60 + ) + else: + self.title_label.setGeometry( + (self.width() - 250) / 2, self.height() - 100, 250, 30 + ) self.title_label.show() # Text From 92990ed3a91a478a4a7cbebb15289e759ca5c7e2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 13 Dec 2020 11:10:09 -0800 Subject: [PATCH 9/9] Don't call server_status.stop_server() twice, which prevents overwriting the status message --- desktop/src/onionshare/tab/mode/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/desktop/src/onionshare/tab/mode/__init__.py b/desktop/src/onionshare/tab/mode/__init__.py index f92632f0..7b325704 100644 --- a/desktop/src/onionshare/tab/mode/__init__.py +++ b/desktop/src/onionshare/tab/mode/__init__.py @@ -177,8 +177,7 @@ class Mode(QtWidgets.QWidget): self.status_bar.clearMessage() if not self.app.autostop_timer_thread.is_alive(): - if self.autostop_timer_finished_should_stop_server(): - self.server_status.stop_server() + self.autostop_timer_finished_should_stop_server() def timer_callback_custom(self): """