diff --git a/onionshare/__init__.py b/onionshare/__init__.py index ec008f5d..1e07f11c 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -20,7 +20,8 @@ along with this program. If not, see . import os, sys, time, argparse, threading -from . import strings, common +from . import strings +from .common import Common from .web import Web from .onion import * from .onionshare import OnionShare @@ -31,11 +32,13 @@ def main(cwd=None): The main() function implements all of the logic that the command-line version of onionshare uses. """ + common = Common() + strings.load_strings(common) - print(strings._('version_string').format(common.get_version())) + print(strings._('version_string').format(common.version)) # OnionShare CLI in OSX needs to change current working directory (#132) - if common.get_platform() == 'Darwin': + if common.platform == 'Darwin': if cwd: os.chdir(cwd) @@ -69,8 +72,7 @@ def main(cwd=None): sys.exit() # Debug mode? - if debug: - common.set_debug(debug) + common.debug = debug # Validate filenames if not receive: @@ -86,7 +88,7 @@ def main(cwd=None): sys.exit() # Load settings - settings = Settings(config) + settings = Settings(common, config) settings.load() # In receive mode, validate downloads dir @@ -105,10 +107,10 @@ def main(cwd=None): sys.exit() # Create the Web object - web = Web(debug, stay_open, False, receive) + web = Web(common, stay_open, False, receive) # Start the Onion object - onion = Onion() + onion = Onion(common) try: onion.connect(settings=False, config=config) except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: @@ -119,7 +121,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) app.start_onion_service() except KeyboardInterrupt: diff --git a/onionshare/common.py b/onionshare/common.py index d51fcbaf..36848738 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -29,212 +29,197 @@ import tempfile import threading import time -debug = False - - -def log(module, func, msg=None): +class Common(object): """ - If debug mode is on, log error messages to stdout + The Common object is shared amongst all parts of OnionShare. """ - global debug - if debug: - timestamp = time.strftime("%b %d %Y %X") + def __init__(self, debug=False): + self.debug = debug - final_msg = "[{}] {}.{}".format(timestamp, module, func) - if msg: - final_msg = '{}: {}'.format(final_msg, msg) - print(final_msg) + # The platform OnionShare is running on + self.platform = platform.system() + if self.platform.endswith('BSD'): + self.platform = 'BSD' + # The current version of OnionShare + with open(self.get_resource_path('version.txt')) as f: + self.version = f.read().strip() -def set_debug(new_debug): - global debug - debug = new_debug + def log(self, module, func, msg=None): + """ + If debug mode is on, log error messages to stdout + """ + if self.debug: + timestamp = time.strftime("%b %d %Y %X") + final_msg = "[{}] {}.{}".format(timestamp, module, func) + if msg: + final_msg = '{}: {}'.format(final_msg, msg) + print(final_msg) -def get_platform(): - """ - Returns the platform OnionShare is running on. - """ - plat = platform.system() - if plat.endswith('BSD'): - plat = 'BSD' - return plat + def get_resource_path(self, filename): + """ + Returns the absolute path of a resource, regardless of whether OnionShare is installed + systemwide, and whether regardless of platform + """ + # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes + if self.platform == 'Windows': + filename = filename.replace('/', '\\') + if getattr(sys, 'onionshare_dev_mode', False): + # Look for resources directory relative to python file + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') + if not os.path.exists(prefix): + # While running tests during stdeb bdist_deb, look 3 directories up for the share folder + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') -def get_resource_path(filename): - """ - Returns the absolute path of a resource, regardless of whether OnionShare is installed - systemwide, and whether regardless of platform - """ - p = get_platform() + elif self.platform == 'BSD' or self.platform == 'Linux': + # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode + prefix = os.path.join(sys.prefix, 'share/onionshare') - # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes - if p == 'Windows': - filename = filename.replace('/', '\\') + elif getattr(sys, 'frozen', False): + # Check if app is "frozen" + # https://pythonhosted.org/PyInstaller/#run-time-information + if self.platform == 'Darwin': + prefix = os.path.join(sys._MEIPASS, 'share') + elif self.platform == 'Windows': + prefix = os.path.join(os.path.dirname(sys.executable), 'share') - if getattr(sys, 'onionshare_dev_mode', False): - # Look for resources directory relative to python file - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') - if not os.path.exists(prefix): - # While running tests during stdeb bdist_deb, look 3 directories up for the share folder - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') + return os.path.join(prefix, filename) - elif p == 'BSD' or p == 'Linux': - # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode - prefix = os.path.join(sys.prefix, 'share/onionshare') + def get_tor_paths(self): + if self.platform == 'Linux': + tor_path = '/usr/bin/tor' + tor_geo_ip_file_path = '/usr/share/tor/geoip' + tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' + obfs4proxy_file_path = '/usr/bin/obfs4proxy' + elif self.platform == 'Windows': + base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor') + tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') + obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') + tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') + tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') + elif self.platform == 'Darwin': + base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path('')))) + tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') + tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') + tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') + obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy') + elif self.platform == 'BSD': + tor_path = '/usr/local/bin/tor' + tor_geo_ip_file_path = '/usr/local/share/tor/geoip' + tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' + obfs4proxy_file_path = '/usr/local/bin/obfs4proxy' - elif getattr(sys, 'frozen', False): - # Check if app is "frozen" - # https://pythonhosted.org/PyInstaller/#run-time-information - if p == 'Darwin': - prefix = os.path.join(sys._MEIPASS, 'share') - elif p == 'Windows': - prefix = os.path.join(os.path.dirname(sys.executable), 'share') + return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) - return os.path.join(prefix, filename) + def build_slug(self): + """ + Returns a random string made from two words from the wordlist, such as "deter-trig". + """ + with open(self.get_resource_path('wordlist.txt')) as f: + wordlist = f.read().split() + r = random.SystemRandom() + return '-'.join(r.choice(wordlist) for _ in range(2)) -def get_tor_paths(): - p = get_platform() - if p == 'Linux': - tor_path = '/usr/bin/tor' - tor_geo_ip_file_path = '/usr/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' - obfs4proxy_file_path = '/usr/bin/obfs4proxy' - elif p == 'Windows': - base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor') - tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') - obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') - tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') - tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - elif p == 'Darwin': - base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path('')))) - tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') - tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') - tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') - obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy') - elif p == 'BSD': - tor_path = '/usr/local/bin/tor' - tor_geo_ip_file_path = '/usr/local/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' - obfs4proxy_file_path = '/usr/local/bin/obfs4proxy' + @staticmethod + def random_string(num_bytes, output_len=None): + """ + Returns a random string with a specified number of bytes. + """ + b = os.urandom(num_bytes) + h = hashlib.sha256(b).digest()[:16] + s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') + if not output_len: + return s + return s[:output_len] - return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) - - -def get_version(): - """ - Returns the version of OnionShare that is running. - """ - with open(get_resource_path('version.txt')) as f: - version = f.read().strip() - return version - - -def random_string(num_bytes, output_len=None): - """ - Returns a random string with a specified number of bytes. - """ - b = os.urandom(num_bytes) - h = hashlib.sha256(b).digest()[:16] - s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') - if not output_len: - return s - return s[:output_len] - - -def build_slug(): - """ - Returns a random string made from two words from the wordlist, such as "deter-trig". - """ - with open(get_resource_path('wordlist.txt')) as f: - wordlist = f.read().split() - - r = random.SystemRandom() - return '-'.join(r.choice(wordlist) for _ in range(2)) - - -def human_readable_filesize(b): - """ - Returns filesize in a human readable format. - """ - thresh = 1024.0 - if b < thresh: - return '{:.1f} B'.format(b) - units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') - u = 0 - b /= thresh - while b >= thresh: + @staticmethod + def human_readable_filesize(b): + """ + Returns filesize in a human readable format. + """ + thresh = 1024.0 + if b < thresh: + return '{:.1f} B'.format(b) + units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') + u = 0 b /= thresh - u += 1 - return '{:.1f} {}'.format(b, units[u]) + while b >= thresh: + b /= thresh + u += 1 + return '{:.1f} {}'.format(b, units[u]) + @staticmethod + def format_seconds(seconds): + """Return a human-readable string of the format 1d2h3m4s""" + days, seconds = divmod(seconds, 86400) + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) -def format_seconds(seconds): - """Return a human-readable string of the format 1d2h3m4s""" - days, seconds = divmod(seconds, 86400) - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) + human_readable = [] + if days: + human_readable.append("{:.0f}d".format(days)) + if hours: + human_readable.append("{:.0f}h".format(hours)) + if minutes: + human_readable.append("{:.0f}m".format(minutes)) + if seconds or not human_readable: + human_readable.append("{:.0f}s".format(seconds)) + return ''.join(human_readable) - human_readable = [] - if days: - human_readable.append("{:.0f}d".format(days)) - if hours: - human_readable.append("{:.0f}h".format(hours)) - if minutes: - human_readable.append("{:.0f}m".format(minutes)) - if seconds or not human_readable: - human_readable.append("{:.0f}s".format(seconds)) - return ''.join(human_readable) + @staticmethod + def estimated_time_remaining(bytes_downloaded, total_bytes, started): + now = time.time() + time_elapsed = now - started # in seconds + download_rate = bytes_downloaded / time_elapsed + remaining_bytes = total_bytes - bytes_downloaded + eta = remaining_bytes / download_rate + return format_seconds(eta) + @staticmethod + def get_available_port(min_port, max_port): + """ + Find a random available port within the given range. + """ + with socket.socket() as tmpsock: + while True: + try: + tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port))) + break + except OSError as e: + raise OSError(e) + _, port = tmpsock.getsockname() + return port -def estimated_time_remaining(bytes_downloaded, total_bytes, started): - now = time.time() - time_elapsed = now - started # in seconds - download_rate = bytes_downloaded / time_elapsed - remaining_bytes = total_bytes - bytes_downloaded - eta = remaining_bytes / download_rate - return format_seconds(eta) - - -def get_available_port(min_port, max_port): - """ - Find a random available port within the given range. - """ - with socket.socket() as tmpsock: - while True: - try: - tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port))) - break - except OSError as e: - raise OSError(e) - _, port = tmpsock.getsockname() - return port - - -def dir_size(start_path): - """ - Calculates the total size, in bytes, of all of the files in a directory. - """ - total_size = 0 - for dirpath, dirnames, filenames in os.walk(start_path): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - total_size += os.path.getsize(fp) - return total_size + @staticmethod + def dir_size(start_path): + """ + Calculates the total size, in bytes, of all of the files in a directory. + """ + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + total_size += os.path.getsize(fp) + return total_size class ShutdownTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ - def __init__(self, time): + def __init__(self, common, time): threading.Thread.__init__(self) + + self.common = common + self.setDaemon(True) self.time = time def run(self): - log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) + self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) time.sleep(self.time) return 1 diff --git a/onionshare/onion.py b/onionshare/onion.py index 068648ba..4b3b0971 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -125,22 +125,22 @@ class Onion(object): call this function and pass in a status string while connecting to tor. This is necessary for status updates to reach the GUI. """ - def __init__(self): - common.log('Onion', '__init__') + def __init__(self, common): + self.common = common + + self.common.log('Onion', '__init__') self.stealth = False self.service_id = None - self.system = common.get_platform() - # Is bundled tor supported? - if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): self.bundle_tor_supported = False else: self.bundle_tor_supported = True # Set the path of the tor binary, for bundled tor - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() + (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() # The tor process self.tor_proc = None @@ -149,13 +149,13 @@ class Onion(object): self.connected_to_tor = False def connect(self, settings=False, config=False, tor_status_update_func=None): - common.log('Onion', 'connect') + self.common.log('Onion', 'connect') # Either use settings that are passed in, or load them from disk if settings: self.settings = settings else: - self.settings = Settings(config) + self.settings = Settings(self.common, config) self.settings.load() # The Tor controller @@ -168,29 +168,29 @@ class Onion(object): # Create a torrc for this session self.tor_data_directory = tempfile.TemporaryDirectory() - if self.system == 'Windows': + if self.common.platform == 'Windows': # Windows needs to use network ports, doesn't support unix sockets - torrc_template = open(common.get_resource_path('torrc_template-windows')).read() + torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read() try: - self.tor_control_port = common.get_available_port(1000, 65535) + self.tor_control_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_control_socket = None self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') try: - self.tor_socks_port = common.get_available_port(1000, 65535) + self.tor_socks_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') else: # Linux, Mac and BSD can use unix sockets - with open(common.get_resource_path('torrc_template')) as f: + with open(self.common.get_resource_path('torrc_template')) as f: torrc_template = f.read() self.tor_control_port = None self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') try: - self.tor_socks_port = common.get_available_port(1000, 65535) + self.tor_socks_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') @@ -208,17 +208,17 @@ class Onion(object): # Bridge support if self.settings.get('tor_bridges_use_obfs4'): f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-obfs4')) as o: + with open(self.common.get_resource_path('torrc_template-obfs4')) as o: for line in o: f.write(line) elif self.settings.get('tor_bridges_use_meek_lite_amazon'): f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o: + with open(self.common.get_resource_path('torrc_template-meek_lite_amazon')) as o: for line in o: f.write(line) elif self.settings.get('tor_bridges_use_meek_lite_azure'): f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o: + with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o: for line in o: f.write(line) @@ -232,7 +232,7 @@ class Onion(object): # Execute a tor subprocess start_ts = time.time() - if self.system == 'Windows': + if self.common.platform == 'Windows': # In Windows, hide console window when opening tor.exe subprocess startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW @@ -245,7 +245,7 @@ class Onion(object): # Connect to the controller try: - if self.system == 'Windows': + if self.common.platform == 'Windows': self.c = Controller.from_port(port=self.tor_control_port) self.c.authenticate() else: @@ -270,7 +270,7 @@ class Onion(object): if callable(tor_status_update_func): if not tor_status_update_func(progress, summary): # If the dialog was canceled, stop connecting to Tor - common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor') + self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor') print() return False @@ -322,7 +322,7 @@ class Onion(object): socket_file_path = '' if not found_tor: try: - if self.system == 'Darwin': + if self.common.platform == 'Darwin': socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket') self.c = Controller.from_socket_file(path=socket_file_path) @@ -334,11 +334,11 @@ class Onion(object): # guessing the socket file name next if not found_tor: try: - if self.system == 'Linux' or self.system == 'BSD': + if self.common.platform == 'Linux' or self.common.platform == 'BSD': socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.system == 'Darwin': + elif self.common.platform == 'Darwin': socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.system == 'Windows': + elif self.common.platform == 'Windows': # Windows doesn't support unix sockets raise TorErrorAutomatic(strings._('settings_error_automatic')) @@ -424,7 +424,7 @@ class Onion(object): Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ - common.log('Onion', 'start_onion_service') + self.common.log('Onion', 'start_onion_service') self.auth_string = None if not self.supports_ephemeral: @@ -447,11 +447,11 @@ class Onion(object): if self.settings.get('private_key'): key_type = "RSA1024" key_content = self.settings.get('private_key') - common.log('Onion', 'Starting a hidden service with a saved private key') + self.common.log('Onion', 'Starting a hidden service with a saved private key') else: key_type = "NEW" key_content = "RSA1024" - common.log('Onion', 'Starting a hidden service with a new private key') + self.common.log('Onion', 'Starting a hidden service with a new private key') try: if basic_auth != None: @@ -498,17 +498,17 @@ class Onion(object): """ Stop onion services that were created earlier. If there's a tor subprocess running, kill it. """ - common.log('Onion', 'cleanup') + self.common.log('Onion', 'cleanup') # Cleanup the ephemeral onion services, if we have any try: onions = self.c.list_ephemeral_hidden_services() for onion in onions: try: - common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion)) + self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion)) self.c.remove_ephemeral_hidden_service(onion) except: - common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion)) + self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion)) pass except: pass @@ -545,7 +545,7 @@ class Onion(object): """ Returns a (address, port) tuple for the Tor SOCKS port """ - common.log('Onion', 'get_tor_socks_port') + self.common.log('Onion', 'get_tor_socks_port') if self.settings.get('connection_type') == 'bundled': return ('127.0.0.1', self.tor_socks_port) diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 02da4b62..10d73751 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -28,8 +28,10 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0): - common.log('OnionShare', '__init__') + def __init__(self, common, onion, local_only=False, stay_open=False, shutdown_timeout=0): + self.common = common + + self.common.log('OnionShare', '__init__') # The Onion object self.onion = onion @@ -53,7 +55,7 @@ class OnionShare(object): self.shutdown_timer = None def set_stealth(self, stealth): - common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) + self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) self.stealth = stealth self.onion.stealth = stealth @@ -62,11 +64,11 @@ class OnionShare(object): """ Start the onionshare onion service. """ - common.log('OnionShare', 'start_onion_service') + self.common.log('OnionShare', 'start_onion_service') # Choose a random port try: - self.port = common.get_available_port(17600, 17650) + self.port = self.common.get_available_port(17600, 17650) except: raise OSError(strings._('no_available_port')) @@ -75,7 +77,7 @@ class OnionShare(object): return if self.shutdown_timeout > 0: - self.shutdown_timer = ShutdownTimer(self.shutdown_timeout) + self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout) self.onion_host = self.onion.start_onion_service(self.port) @@ -86,7 +88,7 @@ class OnionShare(object): """ Shut everything down and clean up temporary files, etc. """ - common.log('OnionShare', 'cleanup') + self.common.log('OnionShare', 'cleanup') # cleanup files for filename in self.cleanup_filenames: diff --git a/onionshare/settings.py b/onionshare/settings.py index 9b61beaf..1c7638c0 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -22,7 +22,7 @@ import json import os import platform -from . import strings, common +from . import strings class Settings(object): @@ -32,8 +32,10 @@ class Settings(object): which is to attempt to connect automatically using default Tor Browser settings. """ - def __init__(self, config=False): - common.log('Settings', '__init__') + def __init__(self, common, config=False): + self.common = common + + self.common.log('Settings', '__init__') # Default config self.filename = self.build_filename() @@ -43,11 +45,11 @@ class Settings(object): if os.path.isfile(config): self.filename = config else: - common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') + self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') # These are the default settings. They will get overwritten when loading from disk self.default_settings = { - 'version': common.get_version(), + 'version': self.common.version, 'connection_type': 'bundled', 'control_port_address': '127.0.0.1', 'control_port_port': 9051, @@ -110,12 +112,12 @@ class Settings(object): """ Load the settings from file. """ - common.log('Settings', 'load') + self.common.log('Settings', 'load') # If the settings file exists, load it if os.path.exists(self.filename): try: - common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) + self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) with open(self.filename, 'r') as f: self._settings = json.load(f) self.fill_in_defaults() @@ -126,7 +128,7 @@ class Settings(object): """ Save settings to file. """ - common.log('Settings', 'save') + self.common.log('Settings', 'save') try: os.makedirs(os.path.dirname(self.filename)) diff --git a/onionshare/web.py b/onionshare/web.py index c797a9e4..b6739bcb 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -41,14 +41,16 @@ class Web(object): """ The Web object is the OnionShare web server, powered by flask """ - def __init__(self, debug, stay_open, gui_mode, receive_mode=False): + def __init__(self, common, stay_open, gui_mode, receive_mode=False): + self.common = common + # The flask app self.app = Flask(__name__, static_folder=common.get_resource_path('static'), template_folder=common.get_resource_path('templates')) # Debug mode? - if debug: + if self.common.debug: self.debug_mode() # Stay open after the first download? @@ -107,7 +109,7 @@ class Web(object): self.client_cancel = False # shutting down the server only works within the context of flask, so the easiest way to do it is over http - self.shutdown_slug = common.random_string(16) + self.shutdown_slug = self.common.random_string(16) # Define the ewb app routes self.common_routes() @@ -143,7 +145,7 @@ class Web(object): file_info=self.file_info, filename=os.path.basename(self.zip_filename), filesize=self.zip_filesize, - filesize_human=common.human_readable_filesize(self.zip_filesize))) + filesize_human=self.common.human_readable_filesize(self.zip_filesize))) return self.add_security_headers(r) @self.app.route("//download") @@ -206,10 +208,9 @@ class Web(object): percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - plat = common.get_platform() - if not self.gui_mode or plat == 'Linux' or plat == 'BSD': + if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent)) + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) sys.stdout.flush() self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) @@ -224,7 +225,7 @@ class Web(object): fp.close() - if common.get_platform() != 'Darwin': + if self.common.platform != 'Darwin': sys.stdout.write("\n") # Download is finished @@ -315,17 +316,17 @@ class Web(object): } if os.path.isfile(filename): info['size'] = os.path.getsize(filename) - info['size_human'] = common.human_readable_filesize(info['size']) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['files'].append(info) if os.path.isdir(filename): - info['size'] = common.dir_size(filename) - info['size_human'] = common.human_readable_filesize(info['size']) + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['dirs'].append(info) self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) # zip up the files and folders - z = ZipWriter(processed_size_callback=processed_size_callback) + z = ZipWriter(self.common, processed_size_callback=processed_size_callback) for info in self.file_info['files']: z.add_file(info['filename']) for info in self.file_info['dirs']: @@ -353,7 +354,7 @@ class Web(object): if persistent_slug: self.slug = persistent_slug else: - self.slug = common.build_slug() + self.slug = self.common.build_slug() def debug_mode(self): """ @@ -424,11 +425,13 @@ class ZipWriter(object): with. If a zip_filename is not passed in, it will use the default onionshare filename. """ - def __init__(self, zip_filename=None, processed_size_callback=None): + def __init__(self, common, zip_filename=None, processed_size_callback=None): + self.common = common + if zip_filename: self.zip_filename = zip_filename else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), common.random_string(4, 6)) + self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) self.processed_size_callback = processed_size_callback diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 205f1c82..04fe1e62 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -22,7 +22,8 @@ import os, sys, platform, argparse from .alert import Alert from PyQt5 import QtCore, QtWidgets -from onionshare import strings, common +from onionshare import strings +from onionshare.common import Common from onionshare.web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare @@ -35,9 +36,8 @@ class Application(QtWidgets.QApplication): This is Qt's QApplication class. It has been overridden to support threads and the quick keyboard shortcut. """ - def __init__(self): - system = common.get_platform() - if system == 'Linux' or system == 'BSD': + def __init__(self, common): + if common.platform == 'Linux' or common.platform == 'BSD': self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtWidgets.QApplication.__init__(self, sys.argv) self.installEventFilter(self) @@ -54,12 +54,14 @@ def main(): """ The main() function implements all of the logic that the GUI version of onionshare uses. """ + common = Common() + strings.load_strings(common) - print(strings._('version_string').format(common.get_version())) + print(strings._('version_string').format(common.version)) # Start the Qt app global qtapp - qtapp = Application() + qtapp = Application(common) # Parse arguments parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) @@ -84,34 +86,33 @@ def main(): debug = bool(args.debug) # Debug mode? - if debug: - common.set_debug(debug) + common.debug = debug # Validation if filenames: valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): - Alert(strings._("not_a_file", True).format(filename)) + Alert(self.common, strings._("not_a_file", True).format(filename)) valid = False if not os.access(filename, os.R_OK): - Alert(strings._("not_a_readable_file", True).format(filename)) + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) valid = False if not valid: sys.exit() # Create the Web object - web = Web(debug, stay_open, True) + web = Web(common, stay_open, True) # Start the Onion - onion = Onion() + onion = Onion(common) # Start the OnionShare app - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) # Launch the gui web.stay_open = stay_open - gui = OnionShareGui(web, onion, qtapp, app, filenames, config) + gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/alert.py b/onionshare_gui/alert.py index 814ff786..981225c6 100644 --- a/onionshare_gui/alert.py +++ b/onionshare_gui/alert.py @@ -19,18 +19,19 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import common - class Alert(QtWidgets.QMessageBox): """ An alert box dialog. """ - def __init__(self, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): + def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): super(Alert, self).__init__(None) - common.log('Alert', '__init__') + + self.common = common + + self.common.log('Alert', '__init__') self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setText(message) self.setIcon(icon) self.setStandardButtons(buttons) diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 166f14a4..db82d30a 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -21,11 +21,13 @@ import time from PyQt5 import QtCore, QtWidgets -from onionshare import strings, common +from onionshare import strings class Download(object): - def __init__(self, download_id, total_bytes): + def __init__(self, common, download_id, total_bytes): + self.common = common + self.download_id = download_id self.started = time.time() self.total_bytes = total_bytes @@ -64,7 +66,7 @@ class Download(object): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: pb_fmt = strings._('gui_download_progress_complete').format( - common.format_seconds(time.time() - self.started)) + self.common.format_seconds(time.time() - self.started)) else: elapsed = time.time() - self.started if elapsed < 10: @@ -72,10 +74,10 @@ class Download(object): # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. pb_fmt = strings._('gui_download_progress_starting').format( - common.human_readable_filesize(downloaded_bytes)) + self.common.human_readable_filesize(downloaded_bytes)) else: pb_fmt = strings._('gui_download_progress_eta').format( - common.human_readable_filesize(downloaded_bytes), + self.common.human_readable_filesize(downloaded_bytes), self.estimated_time_remaining) self.progress_bar.setFormat(pb_fmt) @@ -85,7 +87,7 @@ class Download(object): @property def estimated_time_remaining(self): - return common.estimated_time_remaining(self.downloaded_bytes, + return self.common.estimated_time_remaining(self.downloaded_bytes, self.total_bytes, self.started) @@ -95,8 +97,11 @@ class Downloads(QtWidgets.QWidget): The downloads chunk of the GUI. This lists all of the active download progress bars. """ - def __init__(self): + def __init__(self, common): super(Downloads, self).__init__() + + self.common = common + self.downloads = {} self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout) @@ -108,7 +113,7 @@ class Downloads(QtWidgets.QWidget): self.parent().show() # add it to the list - download = Download(download_id, total_bytes) + download = Download(self.common, download_id, total_bytes) self.downloads[download_id] = download self.layout.insertWidget(-1, download.progress_bar) diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py index 29bcc592..fbc4995b 100644 --- a/onionshare_gui/file_selection.py +++ b/onionshare_gui/file_selection.py @@ -21,21 +21,24 @@ import os from PyQt5 import QtCore, QtWidgets, QtGui from .alert import Alert -from onionshare import strings, common +from onionshare import strings class DropHereLabel(QtWidgets.QLabel): """ When there are no files or folders in the FileList yet, display the 'drop files here' message and graphic. """ - def __init__(self, parent, image=False): + def __init__(self, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) + + self.common = common + self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(common.get_resource_path('images/logo_transparent.png')))) + self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) else: self.setText(strings._('gui_drag_and_drop', True)) self.setStyleSheet('color: #999999;') @@ -53,9 +56,12 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ - def __init__(self, parent): + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) + + self.common = common + self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) self.setText(strings._('gui_drag_and_drop', True)) @@ -74,16 +80,19 @@ class FileList(QtWidgets.QListWidget): files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() - def __init__(self, parent=None): + def __init__(self, common, parent=None): super(FileList, self).__init__(parent) + + self.common = common + self.setAcceptDrops(True) self.setIconSize(QtCore.QSize(32, 32)) self.setSortingEnabled(True) self.setMinimumHeight(205) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.drop_here_image = DropHereLabel(self, True) - self.drop_here_text = DropHereLabel(self, False) - self.drop_count = DropCountLabel(self) + self.drop_here_image = DropHereLabel(self.common, self, True) + self.drop_here_text = DropHereLabel(self.common, self, False) + self.drop_count = DropCountLabel(self.common, self) self.resizeEvent(None) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) @@ -206,7 +215,7 @@ class FileList(QtWidgets.QListWidget): if filename not in filenames: if not os.access(filename, os.R_OK): - Alert(strings._("not_a_readable_file", True).format(filename)) + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) return fileinfo = QtCore.QFileInfo(filename) @@ -215,10 +224,10 @@ class FileList(QtWidgets.QListWidget): if os.path.isfile(filename): size_bytes = fileinfo.size() - size_readable = common.human_readable_filesize(size_bytes) + size_readable = self.common.human_readable_filesize(size_bytes) else: - size_bytes = common.dir_size(filename) - size_readable = common.human_readable_filesize(size_bytes) + size_bytes = self.common.dir_size(filename) + size_readable = self.common.human_readable_filesize(size_bytes) # Create a new item item = QtWidgets.QListWidgetItem() @@ -245,7 +254,7 @@ class FileList(QtWidgets.QListWidget): item.item_button = QtWidgets.QPushButton() item.item_button.setDefault(False) item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) ) + item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) item.item_button.clicked.connect(delete_item) item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) @@ -277,12 +286,15 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ - def __init__(self): + def __init__(self, common): super(FileSelection, self).__init__() + + self.common = common + self.server_on = False # File list - self.file_list = FileList() + self.file_list = FileList(self.common) self.file_list.itemSelectionChanged.connect(self.update) self.file_list.files_dropped.connect(self.update) self.file_list.files_updated.connect(self.update) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3e7addce..04b8a066 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -24,7 +24,7 @@ import queue from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common -from onionshare.common import ShutdownTimer +from onionshare.common import Common, ShutdownTimer from onionshare.settings import Settings from onionshare.onion import * @@ -47,12 +47,13 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - def __init__(self, web, onion, qtapp, app, filenames, config=False): + def __init__(self, common, web, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() - self._initSystemTray() + self.common = common + self.common.log('OnionShareGui', '__init__') - common.log('OnionShareGui', '__init__') + self._initSystemTray() self.web = web self.onion = onion @@ -60,22 +61,22 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app = app self.setWindowTitle('OnionShare') - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) # Load settings self.config = config - self.settings = Settings(self.config) + self.settings = Settings(self.common, self.config) self.settings.load() # File selection - self.file_selection = FileSelection() + self.file_selection = FileSelection(self.common) if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status - self.server_status = ServerStatus(self.qtapp, self.app, self.web, self.file_selection, self.settings) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection, self.settings) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) @@ -107,7 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.hide() # Downloads - self.downloads = Downloads() + self.downloads = Downloads(self.common) self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) @@ -147,13 +148,13 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setFixedWidth(40) - self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) ) + self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png')) + self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) + self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) + self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel() @@ -221,7 +222,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() - tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() @@ -244,7 +245,7 @@ class OnionShareGui(QtWidgets.QMainWindow): for index in range(self.file_selection.file_list.count()): item = self.file_selection.file_list.item(index) total_size_bytes += item.size_bytes - total_size_readable = common.human_readable_filesize(total_size_bytes) + total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) @@ -259,7 +260,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.adjustSize() def update_server_status_indicator(self): - common.log('OnionShareGui', 'update_server_status_indicator') + self.common.log('OnionShareGui', 'update_server_status_indicator') # Set the status image if self.server_status.status == self.server_status.STATUS_STOPPED: @@ -273,8 +274,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_label.setText(strings._('gui_status_indicator_started', True)) def _initSystemTray(self): - system = common.get_platform() - menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) @@ -285,10 +284,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale - if system == 'Darwin': - self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png'))) + if self.common.platform == 'Darwin': + self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) else: - self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() @@ -297,10 +296,10 @@ class OnionShareGui(QtWidgets.QMainWindow): If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ - common.log('OnionShareGui', '_tor_connection_canceled') + self.common.log('OnionShareGui', '_tor_connection_canceled') def ask(): - a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) + a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) @@ -310,12 +309,12 @@ class OnionShareGui(QtWidgets.QMainWindow): if a.clickedButton() == settings_button: # Open settings - common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') + self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit - common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') + self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) @@ -327,7 +326,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ The TorConnectionDialog wants to open the Settings dialog """ - common.log('OnionShareGui', '_tor_connection_open_settings') + self.common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) @@ -336,10 +335,10 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Open the SettingsDialog. """ - common.log('OnionShareGui', 'open_settings') + self.common.log('OnionShareGui', 'open_settings') def reload_settings(): - common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') + self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new @@ -356,7 +355,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() - d = SettingsDialog(self.onion, self.qtapp, self.config) + d = SettingsDialog(self.common, self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() @@ -368,7 +367,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - common.log('OnionShareGui', 'start_server') + self.common.log('OnionShareGui', 'start_server') self.set_server_active(True) @@ -405,8 +404,8 @@ class OnionShareGui(QtWidgets.QMainWindow): # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) - common.log('OnionshareGui', 'start_server', 'Starting an onion thread') - self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) + self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() @@ -414,7 +413,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Step 2 in starting the onionshare server. Zipping up files. """ - common.log('OnionShareGui', 'start_server_step2') + self.common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) @@ -451,7 +450,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ - common.log('OnionShareGui', 'start_server_step3') + self.common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: @@ -469,7 +468,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.timeout) + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: @@ -480,11 +479,11 @@ class OnionShareGui(QtWidgets.QMainWindow): """ If there's an error when trying to start the onion service """ - common.log('OnionShareGui', 'start_server_error') + self.common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) - Alert(error, QtWidgets.QMessageBox.Warning) + Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) @@ -503,7 +502,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Stop the onionshare server. """ - common.log('OnionShareGui', 'stop_server') + self.common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: @@ -527,13 +526,12 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Check for updates in a new thread, if enabled. """ - system = common.get_platform() - if system == 'Windows' or system == 'Darwin': + if self.common.platform == 'Windows' or self.common.platform == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): - Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) - self.update_thread = UpdateThread(self.onion, self.config) + self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread.update_available.connect(update_available) self.update_thread.start() @@ -544,7 +542,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): - total_size += common.dir_size(filename) + total_size += Common.dir_size(filename) return total_size def check_for_requests(self): @@ -594,7 +592,7 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == self.web.REQUEST_RATE_LIMIT: self.stop_server() - Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == self.web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) @@ -655,7 +653,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the URL gets copied to the clipboard, display this in the status bar. """ - common.log('OnionShareGui', 'copy_url') + self.common.log('OnionShareGui', 'copy_url') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) @@ -663,7 +661,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ - common.log('OnionShareGui', 'copy_hidservauth') + self.common.log('OnionShareGui', 'copy_hidservauth') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) @@ -697,9 +695,9 @@ class OnionShareGui(QtWidgets.QMainWindow): Update the 'Downloads completed' info widget. """ if count == 0: - self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png') + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') else: - self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png') + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) @@ -708,17 +706,17 @@ class OnionShareGui(QtWidgets.QMainWindow): Update the 'Downloads in progress' info widget. """ if count == 0: - self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') else: - self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) def closeEvent(self, e): - common.log('OnionShareGui', 'closeEvent') + self.common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: - common.log('OnionShareGui', 'closeEvent, opening warning dialog') + self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setText(strings._('gui_quit_warning', True)) @@ -803,9 +801,12 @@ class OnionThread(QtCore.QThread): decided to cancel (in which case do not proceed with obtaining the Onion address and starting the web server). """ - def __init__(self, function, kwargs=None): + def __init__(self, common, function, kwargs=None): super(OnionThread, self).__init__() - common.log('OnionThread', '__init__') + + self.common = common + + self.common.log('OnionThread', '__init__') self.function = function if not kwargs: self.kwargs = {} @@ -813,6 +814,6 @@ class OnionThread(QtCore.QThread): self.kwargs = kwargs def run(self): - common.log('OnionThread', 'run') + self.common.log('OnionThread', 'run') self.function(**self.kwargs) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 03540415..62df81ff 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -21,7 +21,7 @@ import platform from .alert import Alert from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common, settings +from onionshare import strings class ServerStatus(QtWidgets.QWidget): """ @@ -38,8 +38,11 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, qtapp, app, web, file_selection, settings): + def __init__(self, common, qtapp, app, web, file_selection, settings): super(ServerStatus, self).__init__() + + self.common = common + self.status = self.STATUS_STOPPED self.qtapp = qtapp @@ -129,7 +132,7 @@ class ServerStatus(QtWidgets.QWidget): if self.status == self.STATUS_STARTED: self.url_description.show() - info_image = common.get_resource_path('images/info.png') + info_image = self.common.get_resource_path('images/info.png') self.url_description.setText(strings._('gui_url_description', True).format(info_image)) # Show a Tool Tip explaining the lifecycle of this URL if self.settings.get('save_private_key'): @@ -212,7 +215,7 @@ class ServerStatus(QtWidgets.QWidget): self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) else: self.start_server() else: @@ -252,7 +255,7 @@ class ServerStatus(QtWidgets.QWidget): """ Cancel the server. """ - common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') self.status = self.STATUS_WORKING self.shutdown_timeout_reset() self.update() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7c81afc6..2bd20d84 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -34,9 +34,12 @@ class SettingsDialog(QtWidgets.QDialog): """ settings_saved = QtCore.pyqtSignal() - def __init__(self, onion, qtapp, config=False): + def __init__(self, common, onion, qtapp, config=False): super(SettingsDialog, self).__init__() - common.log('SettingsDialog', '__init__') + + self.common = common + + self.common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp @@ -44,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog): self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.system = platform.system() @@ -156,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog): # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() + (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_obfs4_radio.setEnabled(False) @@ -166,7 +169,7 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite-amazon option radio # if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() + (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False) @@ -176,7 +179,7 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths() + (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) @@ -330,7 +333,7 @@ class SettingsDialog(QtWidgets.QDialog): self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version())) + version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) version_label.setStyleSheet('color: #666666') self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) @@ -372,7 +375,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setFocus() # Load settings, and fill them in - self.old_settings = Settings(self.config) + self.old_settings = Settings(self.common, self.config) self.old_settings.load() close_after_first_download = self.old_settings.get('close_after_first_download') @@ -470,7 +473,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type bundled was toggled. If checked, hide authentication fields. """ - common.log('SettingsDialog', 'connection_type_bundled_toggled') + self.common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -515,7 +518,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - common.log('SettingsDialog', 'connection_type_automatic_toggled') + self.common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -526,7 +529,7 @@ class SettingsDialog(QtWidgets.QDialog): Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'connection_type_control_port_toggled') + self.common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() @@ -541,7 +544,7 @@ class SettingsDialog(QtWidgets.QDialog): Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'connection_type_socket_file_toggled') + self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() @@ -554,14 +557,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - common.log('SettingsDialog', 'authenticate_no_auth_toggled') + self.common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'authenticate_password_toggled') + self.common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: @@ -572,7 +575,7 @@ class SettingsDialog(QtWidgets.QDialog): Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ - common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') + self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) @@ -581,7 +584,7 @@ class SettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - common.log('SettingsDialog', 'test_tor_clicked') + self.common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: @@ -600,13 +603,13 @@ class SettingsDialog(QtWidgets.QDialog): onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) # If an exception hasn't been raised yet, the Tor settings work - Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) + Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: - Alert(e.args[0], QtWidgets.QMessageBox.Warning) + Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() @@ -615,14 +618,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - common.log('SettingsDialog', 'check_for_updates') + self.common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() def update_timestamp(): # Update the last checked label - settings = Settings(self.config) + settings = Settings(self.common, self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) @@ -636,22 +639,22 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) close_forced_update_thread() def update_not_available(): - Alert(strings._('update_not_available', True)) + Alert(self.common, strings._('update_not_available', True)) close_forced_update_thread() def update_error(): - Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) close_forced_update_thread() def update_invalid_version(): - Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) close_forced_update_thread() - forced_update_thread = UpdateThread(self.onion, self.config, force=True) + forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -662,7 +665,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - common.log('SettingsDialog', 'save_clicked') + self.common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() if settings: @@ -672,7 +675,7 @@ class SettingsDialog(QtWidgets.QDialog): # the Onion object reboot_onion = False if self.onion.is_authenticated(): - common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values @@ -694,20 +697,20 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = True else: - common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object - common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() - tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, settings, self.onion) tor_con.start() - common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() @@ -721,9 +724,9 @@ class SettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - common.log('SettingsDialog', 'cancel_clicked') + self.common.log('SettingsDialog', 'cancel_clicked') if not self.onion.is_authenticated(): - Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() @@ -732,7 +735,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Help button clicked. """ - common.log('SettingsDialog', 'help_clicked') + self.common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) @@ -740,8 +743,8 @@ class SettingsDialog(QtWidgets.QDialog): """ Return a Settings object that's full of values from the settings dialog. """ - common.log('SettingsDialog', 'settings_from_fields') - settings = Settings(self.config) + self.common.log('SettingsDialog', 'settings_from_fields') + settings = Settings(self.common, self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) @@ -846,24 +849,24 @@ class SettingsDialog(QtWidgets.QDialog): new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: - Alert(strings._('gui_settings_tor_bridges_invalid', True)) + Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True)) settings.set('no_bridges', True) return False return settings def closeEvent(self, e): - common.log('SettingsDialog', 'closeEvent') + self.common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.is_authenticated(): - common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): - common.log('SettingsDialog', '_update_autoupdate_timestamp') + self.common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) @@ -880,7 +883,7 @@ class SettingsDialog(QtWidgets.QDialog): self._enable_buttons() def _disable_buttons(self): - common.log('SettingsDialog', '_disable_buttons') + self.common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) @@ -888,7 +891,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setEnabled(False) def _enable_buttons(self): - common.log('SettingsDialog', '_enable_buttons') + self.common.log('SettingsDialog', '_enable_buttons') # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index dc472725..6d127df9 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -19,7 +19,7 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common +from onionshare import strings from onionshare.onion import * from .alert import Alert @@ -30,16 +30,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): """ open_settings = QtCore.pyqtSignal() - def __init__(self, qtapp, settings, onion): + def __init__(self, common, qtapp, settings, onion): super(TorConnectionDialog, self).__init__(None) - common.log('TorConnectionDialog', '__init__') + + self.common = common + + self.common.log('TorConnectionDialog', '__init__') self.qtapp = qtapp self.settings = settings self.onion = onion self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setModal(True) self.setFixedSize(400, 150) @@ -55,9 +58,9 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self._tor_status_update(0, '') def start(self): - common.log('TorConnectionDialog', 'start') + self.common.log('TorConnectionDialog', 'start') - t = TorConnectionThread(self, self.settings, self.onion) + t = TorConnectionThread(self.common, self, self.settings, self.onion) t.tor_status_update.connect(self._tor_status_update) t.connected_to_tor.connect(self._connected_to_tor) t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor) @@ -77,14 +80,14 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setLabelText("{}
{}".format(strings._('connecting_to_tor', True), summary)) def _connected_to_tor(self): - common.log('TorConnectionDialog', '_connected_to_tor') + self.common.log('TorConnectionDialog', '_connected_to_tor') self.active = False # Close the dialog after connecting self.setValue(self.maximum()) def _canceled_connecting_to_tor(self): - common.log('TorConnectionDialog', '_canceled_connecting_to_tor') + self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor') self.active = False self.onion.cleanup() @@ -92,12 +95,12 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): - common.log('TorConnectionDialog', '_error_connecting_to_tor') + self.common.log('TorConnectionDialog', '_error_connecting_to_tor') self.active = False def alert_and_open_settings(): # Display the exception in an alert box - Alert("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) + Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) # Open settings self.open_settings.emit() @@ -113,16 +116,19 @@ class TorConnectionThread(QtCore.QThread): canceled_connecting_to_tor = QtCore.pyqtSignal() error_connecting_to_tor = QtCore.pyqtSignal(str) - def __init__(self, dialog, settings, onion): + def __init__(self, common, dialog, settings, onion): super(TorConnectionThread, self).__init__() - common.log('TorConnectionThread', '__init__') + + self.common = common + + self.common.log('TorConnectionThread', '__init__') self.dialog = dialog self.settings = settings self.onion = onion def run(self): - common.log('TorConnectionThread', 'run') + self.common.log('TorConnectionThread', 'run') # Connect to the Onion try: @@ -133,11 +139,11 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: - common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') + self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') self.canceled_connecting_to_tor.emit() except Exception as e: - common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) + self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) self.error_connecting_to_tor.emit(str(e.args[0])) def _tor_status_update(self, progress, summary): diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 8b4884a2..5dc72091 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -25,7 +25,7 @@ from onionshare import socks from onionshare.settings import Settings from onionshare.onion import Onion -from . import strings, common +from . import strings class UpdateCheckerCheckError(Exception): """ @@ -55,16 +55,19 @@ class UpdateChecker(QtCore.QObject): update_error = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal() - def __init__(self, onion, config=False): + def __init__(self, common, onion, config=False): super(UpdateChecker, self).__init__() - common.log('UpdateChecker', '__init__') + + self.common = common + + self.common.log('UpdateChecker', '__init__') self.onion = onion self.config = config def check(self, force=False, config=False): - common.log('UpdateChecker', 'check', 'force={}'.format(force)) + self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) # Load the settings - settings = Settings(config) + settings = Settings(self.common, config) settings.load() # If force=True, then definitely check @@ -87,11 +90,11 @@ class UpdateChecker(QtCore.QObject): # Check for updates if check_for_updates: - common.log('UpdateChecker', 'check', 'checking for updates') + self.common.log('UpdateChecker', 'check', 'checking for updates') # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform - user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system()) + user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users @@ -104,7 +107,7 @@ class UpdateChecker(QtCore.QObject): else: onion_domain = 'elx57ue5uyfplgva.onion' - common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) + self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) @@ -122,10 +125,10 @@ class UpdateChecker(QtCore.QObject): http_response = s.recv(1024) latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') - common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) + self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) except Exception as e: - common.log('UpdateChecker', 'check', '{}'.format(e)) + self.common.log('UpdateChecker', 'check', '{}'.format(e)) self.update_error.emit() raise UpdateCheckerCheckError @@ -145,7 +148,7 @@ class UpdateChecker(QtCore.QObject): # Do we need to update? update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) - installed_version = common.get_version() + installed_version = self.common.version if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return @@ -159,17 +162,20 @@ class UpdateThread(QtCore.QThread): update_error = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal() - def __init__(self, onion, config=False, force=False): + def __init__(self, common, onion, config=False, force=False): super(UpdateThread, self).__init__() - common.log('UpdateThread', '__init__') + + self.common = common + + self.common.log('UpdateThread', '__init__') self.onion = onion self.config = config self.force = force def run(self): - common.log('UpdateThread', 'run') + self.common.log('UpdateThread', 'run') - u = UpdateChecker(self.onion, self.config) + u = UpdateChecker(self.common, self.onion, self.config) u.update_available.connect(self._update_available) u.update_not_available.connect(self._update_not_available) u.update_error.connect(self._update_error) @@ -179,25 +185,25 @@ class UpdateThread(QtCore.QThread): u.check(config=self.config,force=self.force) except Exception as e: # If update check fails, silently ignore - common.log('UpdateThread', 'run', '{}'.format(e)) + self.common.log('UpdateThread', 'run', '{}'.format(e)) pass def _update_available(self, update_url, installed_version, latest_version): - common.log('UpdateThread', '_update_available') + self.common.log('UpdateThread', '_update_available') self.active = False self.update_available.emit(update_url, installed_version, latest_version) def _update_not_available(self): - common.log('UpdateThread', '_update_not_available') + self.common.log('UpdateThread', '_update_not_available') self.active = False self.update_not_available.emit() def _update_error(self): - common.log('UpdateThread', '_update_error') + self.common.log('UpdateThread', '_update_error') self.active = False self.update_error.emit() def _update_invalid_version(self): - common.log('UpdateThread', '_update_invalid_version') + self.common.log('UpdateThread', '_update_invalid_version') self.active = False self.update_invalid_version.emit() diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index 66e4a808..ae8e4217 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -157,13 +157,13 @@ class TestGetAvailablePort: class TestGetPlatform: def test_darwin(self, platform_darwin): - assert common.get_platform() == 'Darwin' + assert common.platform == 'Darwin' def test_linux(self, platform_linux): - assert common.get_platform() == 'Linux' + assert common.platform == 'Linux' def test_windows(self, platform_windows): - assert common.get_platform() == 'Windows' + assert common.platform == 'Windows' # TODO: double-check these tests @@ -235,14 +235,6 @@ class TestGetTorPaths: (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) -class TestGetVersion: - def test_get_version(self, sys_onionshare_dev_mode): - with open(common.get_resource_path('version.txt')) as f: - version = f.read().strip() - - assert version == common.get_version() - - class TestHumanReadableFilesize: @pytest.mark.parametrize('test_input,expected', ( (1024 ** 0, '1.0 B'), @@ -284,13 +276,3 @@ class TestLog: line_one, line_two, _ = output.split('\n') assert LOG_MSG_REGEX.match(line_one) assert LOG_MSG_REGEX.match(line_two) - - -class TestSetDebug: - def test_debug_true(self, set_debug_false): - common.set_debug(True) - assert common.debug is True - - def test_debug_false(self, set_debug_true): - common.set_debug(False) - assert common.debug is False diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 88997749..377fba16 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -28,7 +28,7 @@ from onionshare import common, settings, strings @pytest.fixture def custom_version(monkeypatch): - monkeypatch.setattr(common, 'get_version', lambda: 'DUMMY_VERSION_1.2.3') + monkeypatch.setattr(common, 'version', 'DUMMY_VERSION_1.2.3') @pytest.fixture