From 303772789048bcf24d4f937ae47014af4b20781c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 12 Oct 2019 21:01:25 -0700 Subject: [PATCH] Format all code using black --- install/check_lacked_trans.py | 69 +- install/get-tor-osx.py | 83 +- install/get-tor-windows.py | 50 +- install/scripts/onionshare-nautilus.py | 34 +- onionshare/__init__.py | 206 +++-- onionshare/common.py | 245 ++--- onionshare/onion.py | 434 ++++++--- onionshare/onionshare.py | 20 +- onionshare/settings.py | 157 ++-- onionshare/strings.py | 9 +- onionshare/web/receive_mode.py | 278 ++++-- onionshare/web/send_base_mode.py | 137 +-- onionshare/web/share_mode.py | 185 ++-- onionshare/web/web.py | 184 ++-- onionshare/web/website_mode.py | 40 +- onionshare_gui/__init__.py | 51 +- onionshare_gui/mode/__init__.py | 124 ++- onionshare_gui/mode/file_selection.py | 91 +- onionshare_gui/mode/history.py | 283 +++--- onionshare_gui/mode/receive_mode/__init__.py | 101 ++- onionshare_gui/mode/share_mode/__init__.py | 76 +- onionshare_gui/mode/share_mode/threads.py | 15 +- onionshare_gui/mode/website_mode/__init__.py | 51 +- onionshare_gui/onionshare_gui.py | 406 ++++++--- onionshare_gui/server_status.py | 288 ++++-- onionshare_gui/settings_dialog.py | 844 ++++++++++++------ onionshare_gui/threads.py | 65 +- onionshare_gui/tor_connection_dialog.py | 43 +- onionshare_gui/update_checker.py | 96 +- onionshare_gui/widgets.py | 22 +- setup.py | 142 +-- tests/GuiBaseTest.py | 225 +++-- tests/GuiReceiveTest.py | 85 +- tests/GuiShareTest.py | 169 ++-- tests/GuiWebsiteTest.py | 59 +- tests/SettingsGuiBaseTest.py | 158 +++- tests/TorGuiBaseTest.py | 112 ++- tests/TorGuiReceiveTest.py | 31 +- tests/TorGuiShareTest.py | 29 +- tests/conftest.py | 53 +- ...re_401_public_mode_skips_ratelimit_test.py | 9 +- ..._onionshare_401_triggers_ratelimit_test.py | 8 +- ...tting_during_share_prompts_warning_test.py | 8 +- ...hare_receive_mode_clear_all_button_test.py | 7 +- ...ocal_onionshare_receive_mode_timer_test.py | 9 +- ...ceive_mode_upload_non_writable_dir_test.py | 8 +- ...pload_public_mode_non_writable_dir_test.py | 9 +- ...re_receive_mode_upload_public_mode_test.py | 9 +- ...cal_onionshare_receive_mode_upload_test.py | 8 +- ...onshare_settings_dialog_legacy_tor_test.py | 2 +- ..._onionshare_settings_dialog_no_tor_test.py | 2 +- ..._onionshare_settings_dialog_v3_tor_test.py | 2 +- ...ostart_and_autostop_timer_mismatch_test.py | 4 +- ...onshare_share_mode_autostart_timer_test.py | 9 +- ...are_mode_autostart_timer_too_short_test.py | 13 +- ...onionshare_share_mode_cancel_share_test.py | 8 +- ...nshare_share_mode_clear_all_button_test.py | 8 +- ...re_share_mode_download_public_mode_test.py | 8 +- ...hare_share_mode_download_stay_open_test.py | 8 +- ...cal_onionshare_share_mode_download_test.py | 7 +- ...ode_individual_file_view_stay_open_test.py | 8 +- ...re_share_mode_individual_file_view_test.py | 8 +- ...ionshare_share_mode_large_download_test.py | 7 +- ...are_share_mode_password_persistent_test.py | 4 +- .../local_onionshare_share_mode_timer_test.py | 9 +- ...onshare_share_mode_timer_too_short_test.py | 13 +- ...onshare_share_mode_unreadable_file_test.py | 7 +- ...nionshare_website_mode_csp_enabled_test.py | 10 +- tests/local_onionshare_website_mode_test.py | 10 +- ...onshare_790_cancel_on_second_share_test.py | 7 +- ...re_receive_mode_upload_public_mode_test.py | 9 +- tests/onionshare_receive_mode_upload_test.py | 8 +- ...onionshare_share_mode_cancel_share_test.py | 8 +- ...re_share_mode_download_public_mode_test.py | 8 +- ...hare_share_mode_download_stay_open_test.py | 8 +- tests/onionshare_share_mode_download_test.py | 7 +- .../onionshare_share_mode_persistent_test.py | 4 +- tests/onionshare_share_mode_stealth_test.py | 9 +- tests/onionshare_share_mode_timer_test.py | 9 +- ...e_share_mode_tor_connection_killed_test.py | 6 +- tests/onionshare_share_mode_v2_onion_test.py | 8 +- tests/test_helpers.py | 2 +- tests/test_onionshare.py | 13 +- tests/test_onionshare_common.py | 285 +++--- tests/test_onionshare_settings.py | 179 ++-- tests/test_onionshare_strings.py | 19 +- tests/test_onionshare_web.py | 106 ++- 87 files changed, 4293 insertions(+), 2374 deletions(-) diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 5ccce923..62986707 100755 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -33,12 +33,25 @@ import fileinput, argparse, re, os, codecs, json, sys def arg_parser(): desc = __doc__.strip().splitlines()[0] p = argparse.ArgumentParser(description=desc) - p.add_argument('-d', default='.', help='onionshare directory', - metavar='ONIONSHARE_DIR', dest='onionshare_dir') - p.add_argument('--show-all-keys', action='store_true', - help='show translation key in source and exit'), - p.add_argument('-l', default='all', help='language code (default: all)', - metavar='LANG_CODE', dest='lang_code') + p.add_argument( + "-d", + default=".", + help="onionshare directory", + metavar="ONIONSHARE_DIR", + dest="onionshare_dir", + ) + p.add_argument( + "--show-all-keys", + action="store_true", + help="show translation key in source and exit", + ), + p.add_argument( + "-l", + default="all", + help="language code (default: all)", + metavar="LANG_CODE", + dest="lang_code", + ) return p @@ -54,27 +67,29 @@ def main(): dir = args.onionshare_dir - src = files_in(dir, 'onionshare') + \ - files_in(dir, 'onionshare_gui') + \ - files_in(dir, 'onionshare_gui/mode') + \ - files_in(dir, 'onionshare_gui/mode/share_mode') + \ - files_in(dir, 'onionshare_gui/mode/receive_mode') + \ - files_in(dir, 'onionshare_gui/mode/website_mode') + \ - files_in(dir, 'install/scripts') + \ - files_in(dir, 'tests') - pysrc = [p for p in src if p.endswith('.py')] + src = ( + files_in(dir, "onionshare") + + files_in(dir, "onionshare_gui") + + files_in(dir, "onionshare_gui/mode") + + files_in(dir, "onionshare_gui/mode/share_mode") + + files_in(dir, "onionshare_gui/mode/receive_mode") + + files_in(dir, "onionshare_gui/mode/website_mode") + + files_in(dir, "install/scripts") + + files_in(dir, "tests") + ) + pysrc = [p for p in src if p.endswith(".py")] lang_code = args.lang_code translate_keys = set() # load translate key from python source - for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded('utf-8')): + for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded("utf-8")): # search `strings._('translate_key')` # `strings._('translate_key', True)` - m = re.findall(r'strings\._\((.*?)\)', line) + m = re.findall(r"strings\._\((.*?)\)", line) if m: for match in m: - key = match.split(',')[0].strip('''"' ''') + key = match.split(",")[0].strip(""""' """) translate_keys.add(key) if args.show_all_keys: @@ -82,12 +97,16 @@ def main(): print(k) sys.exit() - if lang_code == 'all': - locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('.json')] + if lang_code == "all": + locale_files = [f for f in files_in(dir, "share/locale") if f.endswith(".json")] else: - locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('%s.json' % lang_code)] + locale_files = [ + f + for f in files_in(dir, "share/locale") + if f.endswith("%s.json" % lang_code) + ] for locale_file in locale_files: - with codecs.open(locale_file, 'r', encoding='utf-8') as f: + with codecs.open(locale_file, "r", encoding="utf-8") as f: trans = json.load(f) # trans -> {"key1": "translate-text1", "key2": "translate-text2", ...} locale_keys = set(trans.keys()) @@ -97,11 +116,11 @@ def main(): locale, ext = os.path.splitext(os.path.basename(locale_file)) for k in sorted(disused): - print(locale, 'disused', k) + print(locale, "disused", k) for k in sorted(lacked): - print(locale, 'lacked', k) + print(locale, "lacked", k) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index a13756b7..c68a81a6 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -34,17 +34,24 @@ import shutil import subprocess import requests + def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.5.5-osx64_en-US.dmg' - expected_dmg_sha256 = '9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511' + dmg_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg" + dmg_filename = "TorBrowser-8.5.5-osx64_en-US.dmg" + expected_dmg_sha256 = ( + "9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511" + ) # Build paths - root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) - working_path = os.path.join(root_path, 'build', 'tor') - dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'Tor Browser.app', 'Contents') + root_path = os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + working_path = os.path.join(root_path, "build", "tor") + dmg_tor_path = os.path.join( + "/Volumes", "Tor Browser", "Tor Browser.app", "Contents" + ) dmg_path = os.path.join(working_path, dmg_filename) - dist_path = os.path.join(root_path, 'dist', 'OnionShare.app', 'Contents') + dist_path = os.path.join(root_path, "dist", "OnionShare.app", "Contents") # Make sure the working folder exists if not os.path.exists(working_path): @@ -54,10 +61,10 @@ def main(): if not os.path.exists(dmg_path): print("Downloading {}".format(dmg_url)) r = requests.get(dmg_url) - open(dmg_path, 'wb').write(r.content) + open(dmg_path, "wb").write(r.content) dmg_sha256 = hashlib.sha256(r.content).hexdigest() else: - dmg_data = open(dmg_path, 'rb').read() + dmg_data = open(dmg_path, "rb").read() dmg_sha256 = hashlib.sha256(dmg_data).hexdigest() # Compare the hash @@ -68,34 +75,52 @@ def main(): sys.exit(-1) # Mount the dmg, copy data to the working path - subprocess.call(['hdiutil', 'attach', dmg_path]) + subprocess.call(["hdiutil", "attach", dmg_path]) # Make sure Resources/tor exists before copying files - if os.path.exists(os.path.join(dist_path, 'Resources', 'Tor')): - shutil.rmtree(os.path.join(dist_path, 'Resources', 'Tor')) - os.makedirs(os.path.join(dist_path, 'Resources', 'Tor')) - if os.path.exists(os.path.join(dist_path, 'MacOS', 'Tor')): - shutil.rmtree(os.path.join(dist_path, 'MacOS', 'Tor')) - os.makedirs(os.path.join(dist_path, 'MacOS', 'Tor')) + if os.path.exists(os.path.join(dist_path, "Resources", "Tor")): + shutil.rmtree(os.path.join(dist_path, "Resources", "Tor")) + os.makedirs(os.path.join(dist_path, "Resources", "Tor")) + if os.path.exists(os.path.join(dist_path, "MacOS", "Tor")): + shutil.rmtree(os.path.join(dist_path, "MacOS", "Tor")) + os.makedirs(os.path.join(dist_path, "MacOS", "Tor")) # Modify the tor script to adjust the path - tor_script = open(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'tor'), 'r').read() - tor_script = tor_script.replace('../../../MacOS/Tor', '../../MacOS/Tor') - open(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 'w').write(tor_script) + tor_script = open( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "tor"), "r" + ).read() + tor_script = tor_script.replace("../../../MacOS/Tor", "../../MacOS/Tor") + open(os.path.join(dist_path, "Resources", "Tor", "tor"), "w").write(tor_script) # Copy into dist - shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip')) - shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip6'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip6')) - os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 0o755) - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real')) - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib')) - os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip"), + os.path.join(dist_path, "Resources", "Tor", "geoip"), + ) + shutil.copyfile( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip6"), + os.path.join(dist_path, "Resources", "Tor", "geoip6"), + ) + os.chmod(os.path.join(dist_path, "Resources", "Tor", "tor"), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "tor.real"), + os.path.join(dist_path, "MacOS", "Tor", "tor.real"), + ) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "libevent-2.1.6.dylib"), + os.path.join(dist_path, "MacOS", "Tor", "libevent-2.1.6.dylib"), + ) + os.chmod(os.path.join(dist_path, "MacOS", "Tor", "tor.real"), 0o755) # obfs4proxy binary - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy')) - os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "obfs4proxy"), + os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"), + ) + os.chmod(os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"), 0o755) # Eject dmg - subprocess.call(['diskutil', 'eject', '/Volumes/Tor Browser']) + subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"]) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 6fc15c74..99608706 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -32,15 +32,22 @@ import shutil import subprocess import requests + def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe' - exe_filename = 'torbrowser-install-8.5.5_en-US.exe' - expected_exe_sha256 = 'a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0' + exe_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe" + exe_filename = "torbrowser-install-8.5.5_en-US.exe" + expected_exe_sha256 = ( + "a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0" + ) # Build paths - root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) - working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') + root_path = os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + working_path = os.path.join(os.path.join(root_path, "build"), "tor") exe_path = os.path.join(working_path, exe_filename) - dist_path = os.path.join(os.path.join(os.path.join(root_path, 'dist'), 'onionshare'), 'tor') + dist_path = os.path.join( + os.path.join(os.path.join(root_path, "dist"), "onionshare"), "tor" + ) # Make sure the working folder exists if not os.path.exists(working_path): @@ -50,10 +57,10 @@ def main(): if not os.path.exists(exe_path): print("Downloading {}".format(exe_url)) r = requests.get(exe_url) - open(exe_path, 'wb').write(r.content) + open(exe_path, "wb").write(r.content) exe_sha256 = hashlib.sha256(r.content).hexdigest() else: - exe_data = open(exe_path, 'rb').read() + exe_data = open(exe_path, "rb").read() exe_sha256 = hashlib.sha256(exe_data).hexdigest() # Compare the hash @@ -64,8 +71,22 @@ def main(): sys.exit(-1) # Extract the bits we need from the exe - cmd = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Tor', '-o%s' % os.path.join(working_path, 'Tor')] - cmd2 = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Data\Tor\geoip*', '-o%s' % os.path.join(working_path, 'Data')] + cmd = [ + "7z", + "e", + "-y", + exe_path, + "Browser\TorBrowser\Tor", + "-o%s" % os.path.join(working_path, "Tor"), + ] + cmd2 = [ + "7z", + "e", + "-y", + exe_path, + "Browser\TorBrowser\Data\Tor\geoip*", + "-o%s" % os.path.join(working_path, "Data"), + ] subprocess.Popen(cmd).wait() subprocess.Popen(cmd2).wait() @@ -73,8 +94,11 @@ def main(): if os.path.exists(dist_path): shutil.rmtree(dist_path) os.makedirs(dist_path) - shutil.copytree( os.path.join(working_path, 'Tor'), os.path.join(dist_path, 'Tor') ) - shutil.copytree( os.path.join(working_path, 'Data'), os.path.join(dist_path, 'Data', 'Tor') ) + shutil.copytree(os.path.join(working_path, "Tor"), os.path.join(dist_path, "Tor")) + shutil.copytree( + os.path.join(working_path, "Data"), os.path.join(dist_path, "Data", "Tor") + ) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/install/scripts/onionshare-nautilus.py b/install/scripts/onionshare-nautilus.py index ed50fb23..dad2330c 100644 --- a/install/scripts/onionshare-nautilus.py +++ b/install/scripts/onionshare-nautilus.py @@ -5,7 +5,8 @@ import locale import subprocess import urllib import gi -gi.require_version('Nautilus', '3.0') + +gi.require_version("Nautilus", "3.0") from gi.repository import Nautilus from gi.repository import GObject @@ -15,12 +16,12 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): def __init__(self): # Get the localized string for "Share via OnionShare" label self.label = None - default_label = 'Share via OnionShare' + default_label = "Share via OnionShare" try: # Re-implement localization in python2 - default_locale = 'en' - locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale') + default_locale = "en" + locale_dir = os.path.join(sys.prefix, "share/onionshare/locale") if os.path.exists(locale_dir): # Load all translations strings = {} @@ -28,7 +29,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): for filename in os.listdir(locale_dir): abs_filename = os.path.join(locale_dir, filename) lang, ext = os.path.splitext(filename) - if ext == '.json': + if ext == ".json": with open(abs_filename) as f: translations[lang] = json.load(f) @@ -42,7 +43,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): if key in translations[lang]: strings[key] = translations[lang][key] - self.label = strings['share_via_onionshare'] + self.label = strings["share_via_onionshare"] except: self.label = default_label @@ -63,30 +64,29 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): self.label = 'Share via OnionShare' """ - def url2path(self,url): + def url2path(self, url): file_uri = url.get_activation_uri() arg_uri = file_uri[7:] path = urllib.url2pathname(arg_uri) return path def exec_onionshare(self, filenames): -# Would prefer this method but there is a conflict between GTK 2.0 vs GTK 3.0 components being loaded at once -# (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported -# sys.argv = ["", "--filenames"] + filenames -# sys.exit(onionshare_gui.main()) - path = os.path.join(os.sep, 'usr', 'bin', 'onionshare-gui') + # Would prefer this method but there is a conflict between GTK 2.0 vs GTK 3.0 components being loaded at once + # (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported + # sys.argv = ["", "--filenames"] + filenames + # sys.exit(onionshare_gui.main()) + path = os.path.join(os.sep, "usr", "bin", "onionshare-gui") cmd = [path, "--filenames"] + filenames subprocess.Popen(cmd) def get_file_items(self, window, files): - menuitem = Nautilus.MenuItem(name='OnionShare::Nautilus', - label=self.label, - tip='', - icon='') + menuitem = Nautilus.MenuItem( + name="OnionShare::Nautilus", label=self.label, tip="", icon="" + ) menu = Nautilus.Menu() menu.append_item(menuitem) menuitem.connect("activate", self.menu_activate_cb, files) - return menuitem, + return (menuitem,) def menu_activate_cb(self, menu, files): file_list = [] diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 65e605f6..108ffd0b 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -30,10 +30,10 @@ from .onionshare import OnionShare def build_url(common, app, web): # Build the URL - if common.settings.get('public_mode'): - return 'http://{0:s}'.format(app.onion_host) + if common.settings.get("public_mode"): + return "http://{0:s}".format(app.onion_host) else: - return 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host) + return "http://onionshare:{0:s}@{1:s}".format(web.password, app.onion_host) def main(cwd=None): @@ -47,23 +47,84 @@ def main(cwd=None): print("OnionShare {0:s} | https://onionshare.org/".format(common.version)) # OnionShare CLI in OSX needs to change current working directory (#132) - if common.platform == 'Darwin': + if common.platform == "Darwin": if cwd: os.chdir(cwd) # Parse arguments - parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28)) - parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)") - parser.add_argument('--stay-open', action='store_true', dest='stay_open', help="Continue sharing after files have been sent") - parser.add_argument('--auto-start-timer', metavar='', dest='autostart_timer', default=0, help="Schedule this share to start N seconds from now") - parser.add_argument('--auto-stop-timer', metavar='', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds") - parser.add_argument('--connect-timeout', metavar='', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)") - parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)") - parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them") - parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website") - parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") - parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share") + parser = argparse.ArgumentParser( + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=28) + ) + parser.add_argument( + "--local-only", + action="store_true", + dest="local_only", + help="Don't use Tor (only for development)", + ) + parser.add_argument( + "--stay-open", + action="store_true", + dest="stay_open", + help="Continue sharing after files have been sent", + ) + parser.add_argument( + "--auto-start-timer", + metavar="", + dest="autostart_timer", + default=0, + help="Schedule this share to start N seconds from now", + ) + parser.add_argument( + "--auto-stop-timer", + metavar="", + dest="autostop_timer", + default=0, + help="Stop sharing after a given amount of seconds", + ) + parser.add_argument( + "--connect-timeout", + metavar="", + dest="connect_timeout", + default=120, + help="Give up connecting to Tor after a given amount of seconds (default: 120)", + ) + parser.add_argument( + "--stealth", + action="store_true", + dest="stealth", + help="Use client authorization (advanced)", + ) + parser.add_argument( + "--receive", + action="store_true", + dest="receive", + help="Receive shares instead of sending them", + ) + parser.add_argument( + "--website", + action="store_true", + dest="website", + help="Publish a static website", + ) + parser.add_argument( + "--config", + metavar="config", + default=False, + help="Custom JSON config file location (optional)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Log OnionShare errors to stdout, and web errors to disk", + ) + parser.add_argument( + "filename", + metavar="filename", + nargs="*", + help="List of files or folders to share", + ) args = parser.parse_args() filenames = args.filename @@ -82,14 +143,14 @@ def main(cwd=None): config = args.config if receive: - mode = 'receive' + mode = "receive" elif website: - mode = 'website' + mode = "website" else: - mode = 'share' + mode = "share" # In share an website mode, you must supply a list of filenames - if mode == 'share' or mode == 'website': + if mode == "share" or mode == "website": # Make sure filenames given if not using receiver mode if len(filenames) == 0: parser.print_help() @@ -122,7 +183,9 @@ def main(cwd=None): # Start the Onion object onion = Onion(common) try: - onion.connect(custom_settings=False, config=config, connect_timeout=connect_timeout) + onion.connect( + custom_settings=False, config=config, connect_timeout=connect_timeout + ) except KeyboardInterrupt: print("") sys.exit() @@ -132,8 +195,8 @@ def main(cwd=None): # Start the onionshare app try: common.settings.load() - if not common.settings.get('public_mode'): - web.generate_password(common.settings.get('password')) + if not common.settings.get("public_mode"): + web.generate_password(common.settings.get("password")) else: web.password = None app = OnionShare(common, onion, local_only, autostop_timer) @@ -144,30 +207,54 @@ def main(cwd=None): if autostart_timer > 0: # Can't set a schedule that is later than the auto-stop timer if app.autostop_timer > 0 and app.autostop_timer < autostart_timer: - print("The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing.") + print( + "The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing." + ) sys.exit() app.start_onion_service(False, True) url = build_url(common, app, web) schedule = datetime.now() + timedelta(seconds=autostart_timer) - if mode == 'receive': - print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) - print('') - print("Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.") - print('') + if mode == "receive": + print( + "Files sent to you appear in this folder: {}".format( + common.settings.get("data_dir") + ) + ) + print("") + print( + "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing." + ) + print("") if stealth: - print("Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(app.auth_string) else: - print("Give this address to your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address to your sender, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) else: if stealth: - print("Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(app.auth_string) else: - print("Give this address to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address to your recipient, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(url) - print('') + print("") print("Waiting for the scheduled time before starting...") app.onion.cleanup(False) time.sleep(autostart_timer) @@ -182,7 +269,7 @@ def main(cwd=None): print(e.args[0]) sys.exit() - if mode == 'website': + if mode == "website": # Prepare files to share try: web.website_mode.set_file_info(filenames) @@ -190,7 +277,7 @@ def main(cwd=None): print(e.strerror) sys.exit(1) - if mode == 'share': + if mode == "share": # Prepare files to share print("Compressing files.") try: @@ -202,12 +289,15 @@ def main(cwd=None): # Warn about sending large files over Tor if web.share_mode.download_filesize >= 157286400: # 150mb - print('') + print("") print("Warning: Sending a large share could take hours") - print('') + print("") # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.password)) + t = threading.Thread( + target=web.start, + args=(app.port, stay_open, common.settings.get("public_mode"), web.password), + ) t.daemon = True t.start() @@ -220,23 +310,29 @@ def main(cwd=None): app.autostop_timer_thread.start() # Save the web password if we are using a persistent private key - if common.settings.get('save_private_key'): - if not common.settings.get('password'): - common.settings.set('password', web.password) + if common.settings.get("save_private_key"): + if not common.settings.get("password"): + common.settings.set("password", web.password) common.settings.save() # Build the URL url = build_url(common, app, web) - print('') + print("") if autostart_timer > 0: print("Server started") else: - if mode == 'receive': - print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) - print('') - print("Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.") - print('') + if mode == "receive": + print( + "Files sent to you appear in this folder: {}".format( + common.settings.get("data_dir") + ) + ) + print("") + print( + "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing." + ) + print("") if stealth: print("Give this address and HidServAuth to the sender:") @@ -253,7 +349,7 @@ def main(cwd=None): else: print("Give this address to the recipient:") print(url) - print('') + print("") print("Press Ctrl+C to stop the server") # Wait for app to close @@ -261,14 +357,17 @@ def main(cwd=None): if app.autostop_timer > 0: # if the auto-stop timer was set and has run out, stop the server if not app.autostop_timer_thread.is_alive(): - if mode == 'share' or (mode == 'website'): + if mode == "share" or (mode == "website"): # If there were no attempts to download the share, or all downloads are done, we can stop if web.share_mode.cur_history_id == 0 or web.done: print("Stopped because auto-stop timer ran out") web.stop(app.port) break - if mode == 'receive': - if web.receive_mode.cur_history_id == 0 or not web.receive_mode.uploads_in_progress: + if mode == "receive": + if ( + web.receive_mode.cur_history_id == 0 + or not web.receive_mode.uploads_in_progress + ): print("Stopped because auto-stop timer ran out") web.stop(app.port) break @@ -284,5 +383,6 @@ def main(cwd=None): app.cleanup() onion.cleanup() -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/onionshare/common.py b/onionshare/common.py index ab503fdc..3373462b 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -36,16 +36,17 @@ class Common(object): """ The Common object is shared amongst all parts of OnionShare. """ + def __init__(self, verbose=False): self.verbose = verbose # The platform OnionShare is running on self.platform = platform.system() - if self.platform.endswith('BSD') or self.platform == 'DragonFly': - self.platform = 'BSD' + if self.platform.endswith("BSD") or self.platform == "DragonFly": + self.platform = "BSD" # The current version of OnionShare - with open(self.get_resource_path('version.txt')) as f: + with open(self.get_resource_path("version.txt")) as f: self.version = f.read().strip() def load_settings(self, config=None): @@ -64,7 +65,7 @@ class Common(object): final_msg = "[{}] {}.{}".format(timestamp, module, func) if msg: - final_msg = '{}: {}'.format(final_msg, msg) + final_msg = "{}: {}".format(final_msg, msg) print(final_msg) def get_resource_path(self, filename): @@ -73,72 +74,105 @@ class Common(object): 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 self.platform == "Windows": + filename = filename.replace("/", "\\") - if getattr(sys, 'onionshare_dev_mode', False): + 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') + 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') + prefix = os.path.join( + os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(prefix))) + ), + "share", + ) - elif self.platform == 'BSD' or self.platform == 'Linux': + 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') + prefix = os.path.join(sys.prefix, "share/onionshare") - elif getattr(sys, 'frozen', False): + 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 self.platform == "Darwin": + prefix = os.path.join(sys._MEIPASS, "share") + elif self.platform == "Windows": + prefix = os.path.join(os.path.dirname(sys.executable), "share") return os.path.join(prefix, filename) 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' + 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" - return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) + return ( + tor_path, + tor_geo_ip_file_path, + tor_geo_ipv6_file_path, + obfs4proxy_file_path, + ) def build_data_dir(self): """ Returns the path of the OnionShare data directory. """ - if self.platform == 'Windows': + if self.platform == "Windows": try: - appdata = os.environ['APPDATA'] - onionshare_data_dir = '{}\\OnionShare'.format(appdata) + appdata = os.environ["APPDATA"] + onionshare_data_dir = "{}\\OnionShare".format(appdata) except: # If for some reason we don't have the 'APPDATA' environment variable # (like running tests in Linux while pretending to be in Windows) - onionshare_data_dir = os.path.expanduser('~/.config/onionshare') - elif self.platform == 'Darwin': - onionshare_data_dir = os.path.expanduser('~/Library/Application Support/OnionShare') + onionshare_data_dir = os.path.expanduser("~/.config/onionshare") + elif self.platform == "Darwin": + onionshare_data_dir = os.path.expanduser( + "~/Library/Application Support/OnionShare" + ) else: - onionshare_data_dir = os.path.expanduser('~/.config/onionshare') + onionshare_data_dir = os.path.expanduser("~/.config/onionshare") os.makedirs(onionshare_data_dir, 0o700, True) return onionshare_data_dir @@ -147,11 +181,11 @@ class Common(object): """ 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: + 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)) + return "-".join(r.choice(wordlist) for _ in range(2)) def define_css(self): """ @@ -160,7 +194,7 @@ class Common(object): """ self.css = { # OnionShareGui styles - 'mode_switcher_selected_style': """ + "mode_switcher_selected_style": """ QPushButton { color: #ffffff; background-color: #4e064f; @@ -169,8 +203,7 @@ class Common(object): font-weight: bold; border-radius: 0; }""", - - 'mode_switcher_unselected_style': """ + "mode_switcher_unselected_style": """ QPushButton { color: #ffffff; background-color: #601f61; @@ -178,23 +211,20 @@ class Common(object): font-weight: normal; border-radius: 0; }""", - - 'settings_button': """ + "settings_button": """ QPushButton { background-color: #601f61; border: 0; border-left: 1px solid #69266b; border-radius: 0; }""", - - 'server_status_indicator_label': """ + "server_status_indicator_label": """ QLabel { font-style: italic; color: #666666; padding: 2px; }""", - - 'status_bar': """ + "status_bar": """ QStatusBar { font-style: italic; color: #666666; @@ -202,16 +232,14 @@ class Common(object): QStatusBar::item { border: 0px; }""", - # Common styles between modes and their child widgets - 'mode_info_label': """ + "mode_info_label": """ QLabel { font-size: 12px; color: #666666; } """, - - 'server_status_url': """ + "server_status_url": """ QLabel { background-color: #ffffff; color: #000000; @@ -220,14 +248,12 @@ class Common(object): font-size: 12px; } """, - - 'server_status_url_buttons': """ + "server_status_url_buttons": """ QPushButton { color: #3f7fcf; } """, - - 'server_status_button_stopped': """ + "server_status_button_stopped": """ QPushButton { background-color: #5fa416; color: #ffffff; @@ -235,8 +261,7 @@ class Common(object): border: 0; border-radius: 5px; }""", - - 'server_status_button_working': """ + "server_status_button_working": """ QPushButton { background-color: #4c8211; color: #ffffff; @@ -245,8 +270,7 @@ class Common(object): border-radius: 5px; font-style: italic; }""", - - 'server_status_button_started': """ + "server_status_button_started": """ QPushButton { background-color: #d0011b; color: #ffffff; @@ -254,8 +278,7 @@ class Common(object): border: 0; border-radius: 5px; }""", - - 'downloads_uploads_empty': """ + "downloads_uploads_empty": """ QWidget { background-color: #ffffff; border: 1px solid #999999; @@ -265,13 +288,11 @@ class Common(object): border: 0px; } """, - - 'downloads_uploads_empty_text': """ + "downloads_uploads_empty_text": """ QLabel { color: #999999; }""", - - 'downloads_uploads_label': """ + "downloads_uploads_label": """ QLabel { font-weight: bold; font-size 14px; @@ -279,14 +300,12 @@ class Common(object): background-color: none; border: none; }""", - - 'downloads_uploads_clear': """ + "downloads_uploads_clear": """ QPushButton { color: #3f7fcf; } """, - - 'download_uploads_indicator': """ + "download_uploads_indicator": """ QLabel { color: #ffffff; background-color: #f44449; @@ -296,8 +315,7 @@ class Common(object): border-radius: 7px; text-align: center; }""", - - 'downloads_uploads_progress_bar': """ + "downloads_uploads_progress_bar": """ QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; @@ -309,24 +327,20 @@ class Common(object): background-color: #4e064f; width: 10px; }""", - - 'history_individual_file_timestamp_label': """ + "history_individual_file_timestamp_label": """ QLabel { color: #666666; }""", - - 'history_individual_file_status_code_label_2xx': """ + "history_individual_file_status_code_label_2xx": """ QLabel { color: #008800; }""", - - 'history_individual_file_status_code_label_4xx': """ + "history_individual_file_status_code_label_4xx": """ QLabel { color: #cc0000; }""", - # Share mode and child widget styles - 'share_zip_progess_bar': """ + "share_zip_progess_bar": """ QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; @@ -338,21 +352,18 @@ class Common(object): background-color: #4e064f; width: 10px; }""", - - 'share_filesize_warning': """ + "share_filesize_warning": """ QLabel { padding: 10px 0; font-weight: bold; color: #333333; } """, - - 'share_file_selection_drop_here_label': """ + "share_file_selection_drop_here_label": """ QLabel { color: #999999; }""", - - 'share_file_selection_drop_count_label': """ + "share_file_selection_drop_count_label": """ QLabel { color: #ffffff; background-color: #f44449; @@ -360,60 +371,51 @@ class Common(object): padding: 5px 10px; border-radius: 10px; }""", - - 'share_file_list_drag_enter': """ + "share_file_list_drag_enter": """ FileList { border: 3px solid #538ad0; } """, - - 'share_file_list_drag_leave': """ + "share_file_list_drag_leave": """ FileList { border: none; } """, - - 'share_file_list_item_size': """ + "share_file_list_item_size": """ QLabel { color: #666666; font-size: 11px; }""", - # Receive mode and child widget styles - 'receive_file': """ + "receive_file": """ QWidget { background-color: #ffffff; } """, - - 'receive_file_size': """ + "receive_file_size": """ QLabel { color: #666666; font-size: 11px; }""", - # Settings dialog - 'settings_version': """ + "settings_version": """ QLabel { color: #666666; }""", - - 'settings_tor_status': """ + "settings_tor_status": """ QLabel { background-color: #ffffff; color: #000000; padding: 10px; }""", - - 'settings_whats_this': """ + "settings_whats_this": """ QLabel { font-size: 12px; }""", - - 'settings_connect_to_tor': """ + "settings_connect_to_tor": """ QLabel { font-style: italic; - }""" + }""", } @staticmethod @@ -423,7 +425,7 @@ class Common(object): """ b = os.urandom(num_bytes) h = hashlib.sha256(b).digest()[:16] - s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') + s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8") if not output_len: return s return s[:output_len] @@ -435,14 +437,14 @@ class Common(object): """ thresh = 1024.0 if b < thresh: - return '{:.1f} B'.format(b) - units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') + return "{:.1f} B".format(b) + units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB") u = 0 b /= thresh while b >= thresh: b /= thresh u += 1 - return '{:.1f} {}'.format(b, units[u]) + return "{:.1f} {}".format(b, units[u]) @staticmethod def format_seconds(seconds): @@ -460,7 +462,7 @@ class Common(object): human_readable.append("{:.0f}m".format(minutes)) if seconds or not human_readable: human_readable.append("{:.0f}s".format(seconds)) - return ''.join(human_readable) + return "".join(human_readable) @staticmethod def estimated_time_remaining(bytes_downloaded, total_bytes, started): @@ -504,6 +506,7 @@ class AutoStopTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ + def __init__(self, common, time): threading.Thread.__init__(self) @@ -513,6 +516,8 @@ class AutoStopTimer(threading.Thread): self.time = time def run(self): - self.common.log('AutoStopTimer', 'Server will shut down after {} seconds'.format(self.time)) + self.common.log( + "AutoStopTimer", "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 b0499449..727cf5f1 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -28,90 +28,113 @@ from distutils.version import LooseVersion as Version from . import common, strings from .settings import Settings + class TorErrorAutomatic(Exception): """ OnionShare is failing to connect and authenticate to the Tor controller, 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): """ OnionShare connected to the Tor controller, but your user does not have permission to access the cookie file. """ + pass + class TorErrorAuthError(Exception): """ OnionShare connected to the address and port, but can't authenticate. It's possible that a Tor controller isn't listening on this port. """ + pass + class TorErrorProtocolError(Exception): """ This exception is raised if onionshare connects to the Tor controller, but it isn't acting like a Tor controller (such as in Whonix). """ + pass + class TorTooOld(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. """ + pass + class BundledTorNotSupported(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but it's not supported on that platform, or in dev mode. """ + class BundledTorTimeout(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but Tor doesn't finish connecting promptly. """ + class BundledTorCanceled(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, and the user cancels connecting to Tor """ + class BundledTorBroken(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but the process seems to fail to run. """ + class Onion(object): """ Onion is an abstraction layer for connecting to the Tor control port and @@ -126,10 +149,11 @@ 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): self.common = common - self.common.log('Onion', '__init__') + self.common.log("Onion", "__init__") self.stealth = False self.service_id = None @@ -137,13 +161,20 @@ class Onion(object): self.scheduled_auth_cookie = None # Is bundled tor supported? - if (self.common.platform == 'Windows' or self.common.platform == '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) = self.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 @@ -154,8 +185,14 @@ class Onion(object): # Start out not connected to Tor self.connected_to_tor = False - def connect(self, custom_settings=False, config=False, tor_status_update_func=None, connect_timeout=120): - self.common.log('Onion', 'connect') + def connect( + self, + custom_settings=False, + config=False, + tor_status_update_func=None, + connect_timeout=120, + ): + self.common.log("Onion", "connect") # Either use settings that are passed in, or use them from common if custom_settings: @@ -171,95 +208,157 @@ class Onion(object): # The Tor controller self.c = None - if self.settings.get('connection_type') == 'bundled': + if self.settings.get("connection_type") == "bundled": if not self.bundle_tor_supported: - raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported')) + raise BundledTorNotSupported( + strings._("settings_error_bundled_tor_not_supported") + ) # Create a torrc for this session - self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir()) - self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name)) + self.tor_data_directory = tempfile.TemporaryDirectory( + dir=self.common.build_data_dir() + ) + self.common.log( + "Onion", + "connect", + "tor_data_directory={}".format(self.tor_data_directory.name), + ) # Create the torrc - with open(self.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_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') + self.tor_cookie_auth_file = os.path.join( + self.tor_data_directory.name, "cookie" + ) try: 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') + raise OSError(strings._("no_available_port")) + self.tor_torrc = os.path.join(self.tor_data_directory.name, "torrc") - if self.common.platform == 'Windows' or self.common.platform == "Darwin": + if self.common.platform == "Windows" or self.common.platform == "Darwin": # Windows doesn't support unix sockets, so it must use a network port. # macOS can't use unix sockets either because socket filenames are limited to # 100 chars, and the macOS sandbox forces us to put the socket file in a place # with a really long path. - torrc_template += 'ControlPort {{control_port}}\n' + torrc_template += "ControlPort {{control_port}}\n" try: self.tor_control_port = self.common.get_available_port(1000, 65535) except: - raise OSError(strings._('no_available_port')) + raise OSError(strings._("no_available_port")) self.tor_control_socket = None else: # Linux and BSD can use unix sockets - torrc_template += 'ControlSocket {{control_socket}}\n' + torrc_template += "ControlSocket {{control_socket}}\n" self.tor_control_port = None - self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') + self.tor_control_socket = os.path.join( + self.tor_data_directory.name, "control_socket" + ) - torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name) - torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port)) - torrc_template = torrc_template.replace('{{control_socket}}', str(self.tor_control_socket)) - torrc_template = torrc_template.replace('{{cookie_auth_file}}', self.tor_cookie_auth_file) - torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path) - torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path) - torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port)) + torrc_template = torrc_template.replace( + "{{data_directory}}", self.tor_data_directory.name + ) + torrc_template = torrc_template.replace( + "{{control_port}}", str(self.tor_control_port) + ) + torrc_template = torrc_template.replace( + "{{control_socket}}", str(self.tor_control_socket) + ) + torrc_template = torrc_template.replace( + "{{cookie_auth_file}}", self.tor_cookie_auth_file + ) + torrc_template = torrc_template.replace( + "{{geo_ip_file}}", self.tor_geo_ip_file_path + ) + torrc_template = torrc_template.replace( + "{{geo_ipv6_file}}", self.tor_geo_ipv6_file_path + ) + torrc_template = torrc_template.replace( + "{{socks_port}}", str(self.tor_socks_port) + ) - with open(self.tor_torrc, 'w') as f: + with open(self.tor_torrc, "w") as f: f.write(torrc_template) # Bridge support - if self.settings.get('tor_bridges_use_obfs4'): - f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - with open(self.common.get_resource_path('torrc_template-obfs4')) as o: + if self.settings.get("tor_bridges_use_obfs4"): + f.write( + "ClientTransportPlugin obfs4 exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + 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_azure'): - f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o: + elif self.settings.get("tor_bridges_use_meek_lite_azure"): + f.write( + "ClientTransportPlugin meek_lite exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + with open( + self.common.get_resource_path("torrc_template-meek_lite_azure") + ) as o: for line in o: f.write(line) - if self.settings.get('tor_bridges_use_custom_bridges'): - if 'obfs4' in self.settings.get('tor_bridges_use_custom_bridges'): - f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - elif 'meek_lite' in self.settings.get('tor_bridges_use_custom_bridges'): - f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - f.write(self.settings.get('tor_bridges_use_custom_bridges')) - f.write('\nUseBridges 1') + if self.settings.get("tor_bridges_use_custom_bridges"): + if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): + f.write( + "ClientTransportPlugin obfs4 exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + elif "meek_lite" in self.settings.get( + "tor_bridges_use_custom_bridges" + ): + f.write( + "ClientTransportPlugin meek_lite exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + f.write(self.settings.get("tor_bridges_use_custom_bridges")) + f.write("\nUseBridges 1") # Execute a tor subprocess start_ts = time.time() - if self.common.platform == 'Windows': + if self.common.platform == "Windows": # In Windows, hide console window when opening tor.exe subprocess startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) + self.tor_proc = subprocess.Popen( + [self.tor_path, "-f", self.tor_torrc], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + ) else: - self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.tor_proc = subprocess.Popen( + [self.tor_path, "-f", self.tor_torrc], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) # Wait for the tor controller to start time.sleep(2) # Connect to the controller try: - if self.common.platform == 'Windows' or self.common.platform == "Darwin": + if ( + self.common.platform == "Windows" + or self.common.platform == "Darwin" + ): self.c = Controller.from_port(port=self.tor_control_port) self.c.authenticate() else: 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])) + raise BundledTorBroken( + strings._("settings_error_bundled_tor_broken").format(e.args[0]) + ) while True: try: @@ -268,47 +367,60 @@ class Onion(object): raise BundledTorCanceled() res_parts = shlex.split(res) - progress = res_parts[2].split('=')[1] - summary = res_parts[4].split('=')[1] + progress = res_parts[2].split("=")[1] + summary = res_parts[4].split("=")[1] # "\033[K" clears the rest of the line - print("Connecting to the Tor network: {}% - {}{}".format(progress, summary, "\033[K"), end="\r") + print( + "Connecting to the Tor network: {}% - {}{}".format( + progress, summary, "\033[K" + ), + end="\r", + ) if callable(tor_status_update_func): if not tor_status_update_func(progress, summary): # If the dialog was canceled, stop connecting to Tor - self.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 - if summary == 'Done': + if summary == "Done": print("") break time.sleep(0.2) # If using bridges, it might take a bit longer to connect to Tor - if self.settings.get('tor_bridges_use_custom_bridges') or \ - self.settings.get('tor_bridges_use_obfs4') or \ - self.settings.get('tor_bridges_use_meek_lite_azure'): - # Only override timeout if a custom timeout has not been passed in - if connect_timeout == 120: - connect_timeout = 150 + if ( + self.settings.get("tor_bridges_use_custom_bridges") + or self.settings.get("tor_bridges_use_obfs4") + or self.settings.get("tor_bridges_use_meek_lite_azure") + ): + # Only override timeout if a custom timeout has not been passed in + if connect_timeout == 120: + connect_timeout = 150 if time.time() - start_ts > connect_timeout: print("") try: self.tor_proc.terminate() - raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) + raise BundledTorTimeout( + strings._("settings_error_bundled_tor_timeout") + ) except FileNotFoundError: pass - elif self.settings.get('connection_type') == 'automatic': + elif self.settings.get("connection_type") == "automatic": # Automatically try to guess the right way to connect to Tor Browser # Try connecting to control port found_tor = False # If the TOR_CONTROL_PORT environment variable is set, use that - env_port = os.environ.get('TOR_CONTROL_PORT') + env_port = os.environ.get("TOR_CONTROL_PORT") if env_port: try: self.c = Controller.from_port(port=int(env_port)) @@ -327,11 +439,13 @@ class Onion(object): pass # If this still didn't work, try guessing the default socket file path - socket_file_path = '' + socket_file_path = "" if not found_tor: try: - if self.common.platform == 'Darwin': - socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket') + 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) found_tor = True @@ -342,74 +456,108 @@ class Onion(object): # guessing the socket file name next if not found_tor: try: - if self.common.platform == 'Linux' or self.common.platform == 'BSD': - socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.common.platform == 'Darwin': - socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.common.platform == 'Windows': + if self.common.platform == "Linux" or self.common.platform == "BSD": + socket_file_path = "/run/user/{}/Tor/control.socket".format( + os.geteuid() + ) + elif self.common.platform == "Darwin": + socket_file_path = "/run/user/{}/Tor/control.socket".format( + os.geteuid() + ) + elif self.common.platform == "Windows": # Windows doesn't support unix sockets - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) self.c = Controller.from_socket_file(path=socket_file_path) except: - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) # Try authenticating try: self.c.authenticate() except: - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) else: # Use specific settings to connect to tor # Try connecting try: - if self.settings.get('connection_type') == 'control_port': - self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port')) - elif self.settings.get('connection_type') == 'socket_file': - self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path')) + if self.settings.get("connection_type") == "control_port": + self.c = Controller.from_port( + address=self.settings.get("control_port_address"), + port=self.settings.get("control_port_port"), + ) + elif self.settings.get("connection_type") == "socket_file": + self.c = Controller.from_socket_file( + path=self.settings.get("socket_file_path") + ) else: raise TorErrorInvalidSetting(strings._("settings_error_unknown")) except: - if self.settings.get('connection_type') == 'control_port': - raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port'))) + if self.settings.get("connection_type") == "control_port": + raise TorErrorSocketPort( + strings._("settings_error_socket_port").format( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) + ) else: - raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path'))) - + raise TorErrorSocketFile( + strings._("settings_error_socket_file").format( + self.settings.get("socket_file_path") + ) + ) # Try authenticating try: - if self.settings.get('auth_type') == 'no_auth': + if self.settings.get("auth_type") == "no_auth": self.c.authenticate() - elif self.settings.get('auth_type') == 'password': - self.c.authenticate(self.settings.get('auth_password')) + elif self.settings.get("auth_type") == "password": + self.c.authenticate(self.settings.get("auth_password")) else: raise TorErrorInvalidSetting(strings._("settings_error_unknown")) except MissingPassword: - raise TorErrorMissingPassword(strings._('settings_error_missing_password')) + raise TorErrorMissingPassword( + strings._("settings_error_missing_password") + ) except UnreadableCookieFile: - raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file')) + raise TorErrorUnreadableCookieFile( + strings._("settings_error_unreadable_cookie_file") + ) except AuthenticationFailure: - raise TorErrorAuthError(strings._('settings_error_auth').format(self.settings.get('control_port_address'), self.settings.get('control_port_port'))) + raise TorErrorAuthError( + strings._("settings_error_auth").format( + 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 # Get the tor version self.tor_version = self.c.get_version().version_str - self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version)) + self.common.log( + "Onion", "connect", "Connected to tor {}".format(self.tor_version) + ) # Do the versions of stem and tor that I'm using support ephemeral onion services? - list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None) - self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1' + list_ephemeral_hidden_services = getattr( + self.c, "list_ephemeral_hidden_services", None + ) + self.supports_ephemeral = ( + callable(list_ephemeral_hidden_services) and self.tor_version >= "0.2.7.1" + ) # Do the versions of stem and tor that I'm using support stealth onion services? try: - res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False) + res = self.c.create_ephemeral_hidden_service( + {1: 1}, basic_auth={"onionshare": None}, await_publication=False + ) tmp_service_id = res.service_id self.c.remove_ephemeral_hidden_service(tmp_service_id) self.supports_stealth = True @@ -420,7 +568,7 @@ class Onion(object): # Does this version of Tor support next-gen ('v3') onions? # Note, this is the version of Tor where this bug was fixed: # https://trac.torproject.org/projects/tor/ticket/28619 - self.supports_v3_onions = self.tor_version >= Version('0.3.5.7') + self.supports_v3_onions = self.tor_version >= Version("0.3.5.7") def is_authenticated(self): """ @@ -431,13 +579,12 @@ class Onion(object): else: return False - def start_onion_service(self, port, await_publication, save_scheduled_key=False): """ Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ - self.common.log('Onion', 'start_onion_service') + self.common.log("Onion", "start_onion_service") # Settings may have changed in the frontend but not updated in our settings object, # such as persistence. Reload the settings now just to be sure. self.settings.load() @@ -445,27 +592,27 @@ class Onion(object): self.auth_string = None if not self.supports_ephemeral: - raise TorTooOld(strings._('error_ephemeral_not_supported')) + raise TorTooOld(strings._("error_ephemeral_not_supported")) if self.stealth and not self.supports_stealth: - raise TorTooOld(strings._('error_stealth_not_supported')) + raise TorTooOld(strings._("error_stealth_not_supported")) if not save_scheduled_key: print("Setting up onion service on port {0:d}.".format(int(port))) if self.stealth: - if self.settings.get('hidservauth_string'): - hidservauth_string = self.settings.get('hidservauth_string').split()[2] - basic_auth = {'onionshare':hidservauth_string} + if self.settings.get("hidservauth_string"): + hidservauth_string = self.settings.get("hidservauth_string").split()[2] + basic_auth = {"onionshare": hidservauth_string} else: if self.scheduled_auth_cookie: - basic_auth = {'onionshare':self.scheduled_auth_cookie} + basic_auth = {"onionshare": self.scheduled_auth_cookie} else: - basic_auth = {'onionshare':None} + basic_auth = {"onionshare": None} else: basic_auth = None - if self.settings.get('private_key'): - key_content = self.settings.get('private_key') + if self.settings.get("private_key"): + key_content = self.settings.get("private_key") if self.is_v2_key(key_content): key_type = "RSA1024" else: @@ -483,7 +630,9 @@ class Onion(object): else: key_type = "NEW" # Work out if we can support v3 onion services, which are preferred - if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'): + if self.supports_v3_onions and not self.settings.get( + "use_legacy_v2_onions" + ): key_content = "ED25519-V3" else: # fall back to v2 onion services @@ -491,31 +640,48 @@ class Onion(object): # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 - if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): + if ( + key_type == "NEW" + and key_content == "ED25519-V3" + and not self.settings.get("use_legacy_v2_onions") + ): basic_auth = None self.stealth = False - debug_message = 'key_type={}'.format(key_type) + debug_message = "key_type={}".format(key_type) if key_type == "NEW": - debug_message += ', key_content={}'.format(key_content) - self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message)) + debug_message += ", key_content={}".format(key_content) + self.common.log("Onion", "start_onion_service", "{}".format(debug_message)) try: if basic_auth != None: - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service( + {80: port}, + await_publication=await_publication, + basic_auth=basic_auth, + key_type=key_type, + key_content=key_content, + ) else: # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service( + {80: port}, + await_publication=await_publication, + key_type=key_type, + key_content=key_content, + ) except ProtocolError as e: - raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0])) + raise TorErrorProtocolError( + strings._("error_tor_protocol_error").format(e.args[0]) + ) self.service_id = res.service_id - onion_host = self.service_id + '.onion' + onion_host = self.service_id + ".onion" # A new private key was generated and is in the Control port response. - if self.settings.get('save_private_key'): - if not self.settings.get('private_key'): - self.settings.set('private_key', res.private_key) + if self.settings.get("save_private_key"): + if not self.settings.get("private_key"): + self.settings.set("private_key", res.private_key) # If we were scheduling a future share, register the private key for later re-use if save_scheduled_key: @@ -529,24 +695,30 @@ class Onion(object): # in the first place. # If we sent the basic_auth (due to a saved hidservauth_string in the settings), # there is no response here, so use the saved value from settings. - if self.settings.get('save_private_key'): - if self.settings.get('hidservauth_string'): - self.auth_string = self.settings.get('hidservauth_string') + if self.settings.get("save_private_key"): + if self.settings.get("hidservauth_string"): + self.auth_string = self.settings.get("hidservauth_string") else: auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) - self.settings.set('hidservauth_string', self.auth_string) + self.auth_string = "HidServAuth {} {}".format( + onion_host, auth_cookie + ) + self.settings.set("hidservauth_string", self.auth_string) else: if not self.scheduled_auth_cookie: auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + self.auth_string = "HidServAuth {} {}".format( + onion_host, auth_cookie + ) if save_scheduled_key: # Register the HidServAuth for the scheduled share self.scheduled_auth_cookie = auth_cookie else: self.scheduled_auth_cookie = None else: - self.auth_string = 'HidServAuth {} {}'.format(onion_host, self.scheduled_auth_cookie) + self.auth_string = "HidServAuth {} {}".format( + onion_host, self.scheduled_auth_cookie + ) if not save_scheduled_key: # We've used the scheduled share's HidServAuth. Reset it to None for future shares self.scheduled_auth_cookie = None @@ -555,23 +727,29 @@ class Onion(object): self.settings.save() return onion_host else: - raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown')) + raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown")) def cleanup(self, stop_tor=True): """ Stop onion services that were created earlier. If there's a tor subprocess running, kill it. """ - self.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: - self.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: - self.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 @@ -608,14 +786,14 @@ class Onion(object): """ Returns a (address, port) tuple for the Tor SOCKS port """ - self.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) - elif self.settings.get('connection_type') == 'automatic': - return ('127.0.0.1', 9150) + if self.settings.get("connection_type") == "bundled": + return ("127.0.0.1", self.tor_socks_port) + elif self.settings.get("connection_type") == "automatic": + return ("127.0.0.1", 9150) else: - return (self.settings.get('socks_address'), self.settings.get('socks_port')) + return (self.settings.get("socks_address"), self.settings.get("socks_port")) def is_v2_key(self, key): """ diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index e746bae1..41a4e5a8 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -22,17 +22,19 @@ import os, shutil from . import common, strings from .onion import TorTooOld, TorErrorProtocolError -from .common import AutoStopTimer +from .common import AutoStopTimer + 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, common, onion, local_only=False, autostop_timer=0): self.common = common - self.common.log('OnionShare', '__init__') + self.common.log("OnionShare", "__init__") # The Onion object self.onion = onion @@ -54,7 +56,7 @@ class OnionShare(object): self.autostop_timer_thread = None def set_stealth(self, stealth): - self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) + self.common.log("OnionShare", "set_stealth", "stealth={}".format(stealth)) self.stealth = stealth self.onion.stealth = stealth @@ -66,13 +68,13 @@ class OnionShare(object): try: self.port = self.common.get_available_port(17600, 17650) except: - raise OSError(strings._('no_available_port')) + raise OSError(strings._("no_available_port")) def start_onion_service(self, await_publication=True, save_scheduled_key=False): """ Start the onionshare onion service. """ - self.common.log('OnionShare', 'start_onion_service') + self.common.log("OnionShare", "start_onion_service") if not self.port: self.choose_port() @@ -81,10 +83,12 @@ class OnionShare(object): self.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer) if self.local_only: - self.onion_host = '127.0.0.1:{0:d}'.format(self.port) + self.onion_host = "127.0.0.1:{0:d}".format(self.port) return - self.onion_host = self.onion.start_onion_service(self.port, await_publication, save_scheduled_key) + self.onion_host = self.onion.start_onion_service( + self.port, await_publication, save_scheduled_key + ) if self.stealth: self.auth_string = self.onion.auth_string @@ -93,7 +97,7 @@ class OnionShare(object): """ Shut everything down and clean up temporary files, etc. """ - self.common.log('OnionShare', 'cleanup') + self.common.log("OnionShare", "cleanup") # Cleanup files try: diff --git a/onionshare/settings.py b/onionshare/settings.py index 0cccb8e9..a44e0c37 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -39,17 +39,22 @@ class Settings(object): which is to attempt to connect automatically using default Tor Browser settings. """ + def __init__(self, common, config=False): self.common = common - self.common.log('Settings', '__init__') + self.common.log("Settings", "__init__") # If a readable config file was provided, use that instead if config: if os.path.isfile(config): self.filename = config else: - self.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", + ) self.filename = self.build_filename() else: @@ -60,62 +65,62 @@ class Settings(object): # mapped to the language name, in that language self.available_locales = { #'bn': 'বাংলা', # Bengali (commented out because not at 90% translation) - 'ca': 'Català', # Catalan - 'zh_Hant': '正體中文 (繁體)', # Traditional Chinese - 'zh_Hans': '中文 (简体)', # Simplified Chinese - 'da': 'Dansk', # Danish - 'en': 'English', # English - 'fi': 'Suomi', # Finnish - 'fr': 'Français', # French - 'de': 'Deutsch', # German - 'el': 'Ελληνικά', # Greek - 'is': 'Íslenska', # Icelandic - 'ga': 'Gaeilge', # Irish - 'it': 'Italiano', # Italian - 'ja': '日本語', # Japanese - 'nb': 'Norsk Bokmål', # Norwegian Bokmål + "ca": "Català", # Catalan + "zh_Hant": "正體中文 (繁體)", # Traditional Chinese + "zh_Hans": "中文 (简体)", # Simplified Chinese + "da": "Dansk", # Danish + "en": "English", # English + "fi": "Suomi", # Finnish + "fr": "Français", # French + "de": "Deutsch", # German + "el": "Ελληνικά", # Greek + "is": "Íslenska", # Icelandic + "ga": "Gaeilge", # Irish + "it": "Italiano", # Italian + "ja": "日本語", # Japanese + "nb": "Norsk Bokmål", # Norwegian Bokmål #'fa': 'فارسی', # Persian (commented out because not at 90% translation) - 'pl': 'Polski', # Polish - 'pt_BR': 'Português (Brasil)', # Portuguese Brazil - 'pt_PT': 'Português (Portugal)', # Portuguese Portugal - 'ru': 'Русский', # Russian - 'es': 'Español', # Spanish - 'sv': 'Svenska', # Swedish - 'te': 'తెలుగు', # Telugu - 'tr': 'Türkçe', # Turkish - 'uk': 'Українська', # Ukrainian + "pl": "Polski", # Polish + "pt_BR": "Português (Brasil)", # Portuguese Brazil + "pt_PT": "Português (Portugal)", # Portuguese Portugal + "ru": "Русский", # Russian + "es": "Español", # Spanish + "sv": "Svenska", # Swedish + "te": "తెలుగు", # Telugu + "tr": "Türkçe", # Turkish + "uk": "Українська", # Ukrainian } # These are the default settings. They will get overwritten when loading from disk self.default_settings = { - 'version': self.common.version, - 'connection_type': 'bundled', - 'control_port_address': '127.0.0.1', - 'control_port_port': 9051, - 'socks_address': '127.0.0.1', - 'socks_port': 9050, - 'socket_file_path': '/var/run/tor/control', - 'auth_type': 'no_auth', - 'auth_password': '', - 'close_after_first_download': True, - 'autostop_timer': False, - 'autostart_timer': False, - 'use_stealth': False, - 'use_autoupdate': True, - 'autoupdate_timestamp': None, - 'no_bridges': True, - 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_azure': False, - 'tor_bridges_use_custom_bridges': '', - 'use_legacy_v2_onions': False, - 'save_private_key': False, - 'private_key': '', - 'public_mode': False, - 'password': '', - 'hidservauth_string': '', - 'data_dir': self.build_default_data_dir(), - 'csp_header_disabled': False, - 'locale': None # this gets defined in fill_in_defaults() + "version": self.common.version, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "auth_type": "no_auth", + "auth_password": "", + "close_after_first_download": True, + "autostop_timer": False, + "autostart_timer": False, + "use_stealth": False, + "use_autoupdate": True, + "autoupdate_timestamp": None, + "no_bridges": True, + "tor_bridges_use_obfs4": False, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_custom_bridges": "", + "use_legacy_v2_onions": False, + "save_private_key": False, + "private_key": "", + "public_mode": False, + "password": "", + "hidservauth_string": "", + "data_dir": self.build_default_data_dir(), + "csp_header_disabled": False, + "locale": None, # this gets defined in fill_in_defaults() } self._settings = {} self.fill_in_defaults() @@ -130,14 +135,14 @@ class Settings(object): self._settings[key] = self.default_settings[key] # Choose the default locale based on the OS preference, and fall-back to English - if self._settings['locale'] is None: + if self._settings["locale"] is None: language_code, encoding = locale.getdefaultlocale() # Default to English if not language_code: - language_code = 'en_US' + language_code = "en_US" - if language_code == 'pt_PT' and language_code == 'pt_BR': + if language_code == "pt_PT" and language_code == "pt_BR": # Portuguese locales include country code default_locale = language_code else: @@ -145,14 +150,14 @@ class Settings(object): default_locale = language_code[:2] if default_locale not in self.available_locales: - default_locale = 'en' - self._settings['locale'] = default_locale + default_locale = "en" + self._settings["locale"] = default_locale def build_filename(self): """ Returns the path of the settings file. """ - return os.path.join(self.common.build_data_dir(), 'onionshare.json') + return os.path.join(self.common.build_data_dir(), "onionshare.json") def build_default_data_dir(self): """ @@ -163,26 +168,28 @@ class Settings(object): # We can't use os.path.expanduser() in macOS because in the sandbox it # returns the path to the sandboxed homedir real_homedir = pwd.getpwuid(os.getuid()).pw_dir - return os.path.join(real_homedir, 'OnionShare') + return os.path.join(real_homedir, "OnionShare") elif self.common.platform == "Windows": # On Windows, os.path.expanduser() needs to use backslash, or else it # retains the forward slash, which breaks opening the folder in explorer. - return os.path.expanduser('~\OnionShare') + return os.path.expanduser("~\OnionShare") else: # All other OSes - return os.path.expanduser('~/OnionShare') + return os.path.expanduser("~/OnionShare") def load(self): """ Load the settings from file. """ - self.common.log('Settings', 'load') + self.common.log("Settings", "load") # If the settings file exists, load it if os.path.exists(self.filename): try: - self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) - with open(self.filename, 'r') as f: + 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() except: @@ -190,7 +197,7 @@ class Settings(object): # Make sure data_dir exists try: - os.makedirs(self.get('data_dir'), exist_ok=True) + os.makedirs(self.get("data_dir"), exist_ok=True) except: pass @@ -198,22 +205,24 @@ class Settings(object): """ Save settings to file. """ - self.common.log('Settings', 'save') - open(self.filename, 'w').write(json.dumps(self._settings, indent=2)) - self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename)) + self.common.log("Settings", "save") + open(self.filename, "w").write(json.dumps(self._settings, indent=2)) + self.common.log( + "Settings", "save", "Settings saved in {}".format(self.filename) + ) def get(self, key): return self._settings[key] def set(self, key, val): # If typecasting int values fails, fallback to default values - if key == 'control_port_port' or key == 'socks_port': + if key == "control_port_port" or key == "socks_port": try: val = int(val) except: - if key == 'control_port_port': - val = self.default_settings['control_port_port'] - elif key == 'socks_port': - val = self.default_settings['socks_port'] + if key == "control_port_port": + val = self.default_settings["control_port_port"] + elif key == "socks_port": + val = self.default_settings["socks_port"] self._settings[key] = val diff --git a/onionshare/strings.py b/onionshare/strings.py index 643186dd..76360a42 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -35,14 +35,14 @@ def load_strings(common): # Load all translations translations = {} for locale in common.settings.available_locales: - locale_dir = common.get_resource_path('locale') + locale_dir = common.get_resource_path("locale") filename = os.path.join(locale_dir, "{}.json".format(locale)) - with open(filename, encoding='utf-8') as f: + with open(filename, encoding="utf-8") as f: translations[locale] = json.load(f) # Build strings - default_locale = 'en' - current_locale = common.settings.get('locale') + default_locale = "en" + current_locale = common.settings.get("locale") strings = {} for s in translations[default_locale]: if s in translations[current_locale] and translations[current_locale][s] != "": @@ -57,4 +57,5 @@ def translated(k): """ return strings[k] + _ = translated diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 83040683..90f000b9 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -12,9 +12,10 @@ class ReceiveModeWeb: """ All of the web logic for receive mode """ + def __init__(self, common, web): self.common = common - self.common.log('ReceiveModeWeb', '__init__') + self.common.log("ReceiveModeWeb", "__init__") self.web = web @@ -30,59 +31,84 @@ class ReceiveModeWeb: """ The web app routes for receiving files """ + @self.web.app.route("/") def index(): history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'status_code': 200 - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + "{}".format(request.path), + {"id": history_id, "status_code": 200}, + ) self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response(render_template('receive.html', - static_url_path=self.web.static_url_path)) + r = make_response( + render_template( + "receive.html", static_url_path=self.web.static_url_path + ) + ) return self.web.add_security_headers(r) - @self.web.app.route("/upload", methods=['POST']) + @self.web.app.route("/upload", methods=["POST"]) def upload(ajax=False): """ Handle the upload files POST request, though at this point, the files have already been uploaded and saved to their correct locations. """ - files = request.files.getlist('file[]') + files = request.files.getlist("file[]") filenames = [] for f in files: - if f.filename != '': + if f.filename != "": filename = secure_filename(f.filename) filenames.append(filename) local_path = os.path.join(request.receive_mode_dir, filename) basename = os.path.basename(local_path) # Tell the GUI the receive mode directory for this file - self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, { - 'id': request.history_id, - 'filename': basename, - 'dir': request.receive_mode_dir - }) + self.web.add_request( + self.web.REQUEST_UPLOAD_SET_DIR, + request.path, + { + "id": request.history_id, + "filename": basename, + "dir": request.receive_mode_dir, + }, + ) - self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print('\n' + "Received: {}".format(local_path)) + self.common.log( + "ReceiveModeWeb", + "define_routes", + "/upload, uploaded {}, saving to {}".format( + f.filename, local_path + ), + ) + print("\n" + "Received: {}".format(local_path)) if request.upload_error: - self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error') + self.common.log( + "ReceiveModeWeb", + "define_routes", + "/upload, there was an upload error", + ) - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": request.receive_mode_dir - }) - print("Could not create OnionShare data folder: {}".format(request.receive_mode_dir)) + self.web.add_request( + self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, + request.path, + {"receive_mode_dir": request.receive_mode_dir}, + ) + print( + "Could not create OnionShare data folder: {}".format( + request.receive_mode_dir + ) + ) - msg = 'Error uploading, please inform the OnionShare user' + msg = "Error uploading, please inform the OnionShare user" if ajax: return json.dumps({"error_flashes": [msg]}) else: - flash(msg, 'error') - return redirect('/') + flash(msg, "error") + return redirect("/") # Note that flash strings are in English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user @@ -90,37 +116,45 @@ class ReceiveModeWeb: info_flashes = [] if len(filenames) == 0: - msg = 'No files uploaded' + msg = "No files uploaded" if ajax: info_flashes.append(msg) else: - flash(msg, 'info') + flash(msg, "info") else: - msg = 'Sent ' + msg = "Sent " for filename in filenames: - msg += '{}, '.format(filename) - msg = msg.rstrip(', ') + msg += "{}, ".format(filename) + msg = msg.rstrip(", ") if ajax: info_flashes.append(msg) else: - flash(msg, 'info') + flash(msg, "info") if self.can_upload: if ajax: return json.dumps({"info_flashes": info_flashes}) else: - return redirect('/') + return redirect("/") else: if ajax: - return json.dumps({ - "new_body": render_template('thankyou.html', static_url_path=self.web.static_url_path) - }) + return json.dumps( + { + "new_body": render_template( + "thankyou.html", + static_url_path=self.web.static_url_path, + ) + } + ) else: # It was the last upload and the timer ran out - r = make_response(render_template('thankyou.html'), static_url_path=self.web.static_url_path) + r = make_response( + render_template("thankyou.html"), + static_url_path=self.web.static_url_path, + ) return self.web.add_security_headers(r) - @self.web.app.route("/upload-ajax", methods=['POST']) + @self.web.app.route("/upload-ajax", methods=["POST"]) def upload_ajax_public(): if not self.can_upload: return self.web.error403() @@ -132,13 +166,14 @@ class ReceiveModeWSGIMiddleware(object): Custom WSGI middleware in order to attach the Web object to environ, so ReceiveModeRequest can access it. """ + def __init__(self, app, web): self.app = app self.web = web def __call__(self, environ, start_response): - environ['web'] = self.web - environ['stop_q'] = self.web.stop_q + environ["web"] = self.web + environ["stop_q"] = self.web.stop_q return self.app(environ, start_response) @@ -148,6 +183,7 @@ class ReceiveModeFile(object): written to it, in order to track the progress of uploads. It starts out with a .part file extension, and when it's complete it removes that extension. """ + def __init__(self, request, filename, write_func, close_func): self.onionshare_request = request self.onionshare_filename = filename @@ -155,24 +191,44 @@ class ReceiveModeFile(object): self.onionshare_close_func = close_func self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename) - self.filename_in_progress = '{}.part'.format(self.filename) + self.filename_in_progress = "{}.part".format(self.filename) # Open the file self.upload_error = False try: - self.f = open(self.filename_in_progress, 'wb+') + self.f = open(self.filename_in_progress, "wb+") except: # This will only happen if someone is messing with the data dir while # OnionShare is running, but if it does make sure to throw an error self.upload_error = True - self.f = tempfile.TemporaryFile('wb+') + self.f = tempfile.TemporaryFile("wb+") # Make all the file-like methods and attributes actually access the # TemporaryFile, except for write - attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', - 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', - 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', - 'truncate', 'writable', 'writelines'] + attrs = [ + "closed", + "detach", + "fileno", + "flush", + "isatty", + "mode", + "name", + "peek", + "raw", + "read", + "read1", + "readable", + "readinto", + "readinto1", + "readline", + "readlines", + "seek", + "seekable", + "tell", + "truncate", + "writable", + "writelines", + ] for attr in attrs: setattr(self, attr, getattr(self.f, attr)) @@ -214,20 +270,21 @@ class ReceiveModeRequest(Request): A custom flask Request object that keeps track of how much data has been uploaded for each file, for receive mode. """ + def __init__(self, environ, populate_request=True, shallow=False): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) - self.web = environ['web'] - self.stop_q = environ['stop_q'] + self.web = environ["web"] + self.stop_q = environ["stop_q"] - self.web.common.log('ReceiveModeRequest', '__init__') + self.web.common.log("ReceiveModeRequest", "__init__") # Prevent running the close() method more than once self.closed = False # Is this a valid upload request? self.upload_request = False - if self.method == 'POST': - if self.path == '/upload' or self.path == '/upload-ajax': + if self.method == "POST": + if self.path == "/upload" or self.path == "/upload-ajax": self.upload_request = True if self.upload_request: @@ -238,7 +295,9 @@ class ReceiveModeRequest(Request): now = datetime.now() date_dir = now.strftime("%Y-%m-%d") time_dir = now.strftime("%H.%M.%S") - self.receive_mode_dir = os.path.join(self.web.common.settings.get('data_dir'), date_dir, time_dir) + self.receive_mode_dir = os.path.join( + self.web.common.settings.get("data_dir"), date_dir, time_dir + ) # Create that directory, which shouldn't exist yet try: @@ -250,7 +309,7 @@ class ReceiveModeRequest(Request): # Keep going until we find a directory name that's available i = 1 while True: - new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i) + new_receive_mode_dir = "{}-{}".format(self.receive_mode_dir, i) try: os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False) self.receive_mode_dir = new_receive_mode_dir @@ -260,15 +319,29 @@ class ReceiveModeRequest(Request): i += 1 # Failsafe if i == 100: - self.web.common.log('ReceiveModeRequest', '__init__', 'Error finding available receive mode directory') + self.web.common.log( + "ReceiveModeRequest", + "__init__", + "Error finding available receive mode directory", + ) self.upload_error = True break except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": self.receive_mode_dir - }) - print("Could not create OnionShare data folder: {}".format(self.receive_mode_dir)) - self.web.common.log('ReceiveModeRequest', '__init__', 'Permission denied creating receive mode directory') + self.web.add_request( + self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, + request.path, + {"receive_mode_dir": self.receive_mode_dir}, + ) + print( + "Could not create OnionShare data folder: {}".format( + self.receive_mode_dir + ) + ) + self.web.common.log( + "ReceiveModeRequest", + "__init__", + "Permission denied creating receive mode directory", + ) self.upload_error = True # If there's an error so far, finish early @@ -285,23 +358,29 @@ class ReceiveModeRequest(Request): self.history_id = self.web.receive_mode.cur_history_id self.web.receive_mode.cur_history_id += 1 - # Figure out the content length + # Figure out the content length try: - self.content_length = int(self.headers['Content-Length']) + self.content_length = int(self.headers["Content-Length"]) except: self.content_length = 0 - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) + print( + "{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format( + self.web.common.human_readable_filesize(self.content_length) + ), + ) + ) # Don't tell the GUI that a request has started until we start receiving files self.told_gui_about_request = False self.previous_file = None - def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): + def _get_file_stream( + self, total_content_length, content_type, filename=None, content_length=None + ): """ This gets called for each file that gets uploaded, and returns an file-like writable stream. @@ -309,24 +388,26 @@ class ReceiveModeRequest(Request): if self.upload_request: if not self.told_gui_about_request: # Tell the GUI about the request - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.history_id, - 'content_length': self.content_length - }) + self.web.add_request( + self.web.REQUEST_STARTED, + self.path, + {"id": self.history_id, "content_length": self.content_length}, + ) self.web.receive_mode.uploads_in_progress.append(self.history_id) self.told_gui_about_request = True self.filename = secure_filename(filename) - self.progress[self.filename] = { - 'uploaded_bytes': 0, - 'complete': False - } + self.progress[self.filename] = {"uploaded_bytes": 0, "complete": False} - f = ReceiveModeFile(self, self.filename, self.file_write_func, self.file_close_func) + f = ReceiveModeFile( + self, self.filename, self.file_write_func, self.file_close_func + ) if f.upload_error: - self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file') + self.web.common.log( + "ReceiveModeRequest", "_get_file_stream", "Error creating file" + ) self.upload_error = True return f @@ -341,22 +422,25 @@ class ReceiveModeRequest(Request): return self.closed = True - self.web.common.log('ReceiveModeRequest', 'close') + self.web.common.log("ReceiveModeRequest", "close") try: if self.told_gui_about_request: history_id = self.history_id - if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']: + if ( + not self.web.stop_q.empty() + or not self.progress[self.filename]["complete"] + ): # Inform the GUI that the upload has canceled - self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_UPLOAD_CANCELED, self.path, {"id": history_id} + ) else: # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_UPLOAD_FINISHED, self.path, {"id": history_id} + ) self.web.receive_mode.uploads_in_progress.remove(history_id) except AttributeError: @@ -370,28 +454,34 @@ class ReceiveModeRequest(Request): return if self.upload_request: - self.progress[filename]['uploaded_bytes'] += length + self.progress[filename]["uploaded_bytes"] += length if self.previous_file != filename: self.previous_file = filename - print('\r=> {:15s} {}'.format( - self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), - filename - ), end='') + print( + "\r=> {:15s} {}".format( + self.web.common.human_readable_filesize( + self.progress[filename]["uploaded_bytes"] + ), + filename, + ), + end="", + ) # Update the GUI on the upload progress if self.told_gui_about_request: - self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { - 'id': self.history_id, - 'progress': self.progress - }) + self.web.add_request( + self.web.REQUEST_PROGRESS, + self.path, + {"id": self.history_id, "progress": self.progress}, + ) def file_close_func(self, filename, upload_error=False): """ This function gets called when a specific file is closed. """ - self.progress[filename]['complete'] = True + self.progress[filename]["complete"] = True # If the file tells us there was an upload error, let the request know as well if upload_error: diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 24ad55d7..86d34016 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -12,6 +12,7 @@ class SendBaseModeWeb: """ All of the web logic shared between share and website mode (modes where the user sends files) """ + def __init__(self, common, web): super(SendBaseModeWeb, self).__init__() self.common = common @@ -41,20 +42,24 @@ class SendBaseModeWeb: """ # If there's just one folder, replace filenames with a list of files inside that folder if len(filenames) == 1 and os.path.isdir(filenames[0]): - filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + filenames = [ + os.path.join(filenames[0], x) for x in os.listdir(filenames[0]) + ] # Re-initialize - self.files = {} # Dictionary mapping file paths to filenames on disk - self.root_files = {} # This is only the root files and dirs, as opposed to all of them + self.files = {} # Dictionary mapping file paths to filenames on disk + self.root_files = ( + {} + ) # This is only the root files and dirs, as opposed to all of them self.cleanup_filenames = [] self.cur_history_id = 0 - self.file_info = {'files': [], 'dirs': []} + self.file_info = {"files": [], "dirs": []} self.gzip_individual_files = {} self.init() # Build the file list for filename in filenames: - basename = os.path.basename(filename.rstrip('/')) + basename = os.path.basename(filename.rstrip("/")) # If it's a filename, add it if os.path.isfile(filename): @@ -63,42 +68,50 @@ class SendBaseModeWeb: # If it's a directory, add it recursively elif os.path.isdir(filename): - self.root_files[basename + '/'] = filename + self.root_files[basename + "/"] = filename for root, _, nested_filenames in os.walk(filename): # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". # The normalized_root should be "some_folder/foobar" - normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') + normalized_root = os.path.join( + basename, root[len(filename) :].lstrip("/") + ).rstrip("/") # Add the dir itself - self.files[normalized_root + '/'] = root + self.files[normalized_root + "/"] = root # Add the files in this dir for nested_filename in nested_filenames: - self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) + self.files[ + os.path.join(normalized_root, nested_filename) + ] = os.path.join(root, nested_filename) self.set_file_info_custom(filenames, processed_size_callback) - def directory_listing(self, filenames, path='', filesystem_path=None): + def directory_listing(self, filenames, path="", filesystem_path=None): # Tell the GUI about the directory listing history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '/{}'.format(path), { - 'id': history_id, - 'method': request.method, - 'status_code': 200 - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + "/{}".format(path), + {"id": history_id, "method": request.method, "status_code": 200}, + ) - breadcrumbs = [('☗', '/')] - parts = path.split('/')[:-1] + breadcrumbs = [("☗", "/")] + parts = path.split("/")[:-1] for i in range(len(parts)): - breadcrumbs.append(('{}'.format(parts[i]), '/{}/'.format('/'.join(parts[0:i+1])))) + breadcrumbs.append( + ("{}".format(parts[i]), "/{}/".format("/".join(parts[0 : i + 1]))) + ) breadcrumbs_leaf = breadcrumbs.pop()[0] # If filesystem_path is None, this is the root directory listing files, dirs = self.build_directory_listing(filenames, filesystem_path) - r = self.directory_listing_template(path, files, dirs, breadcrumbs, breadcrumbs_leaf) + r = self.directory_listing_template( + path, files, dirs, breadcrumbs, breadcrumbs_leaf + ) return self.web.add_security_headers(r) def build_directory_listing(self, filenames, filesystem_path): @@ -114,16 +127,11 @@ class SendBaseModeWeb: is_dir = os.path.isdir(this_filesystem_path) if is_dir: - dirs.append({ - 'basename': filename - }) + dirs.append({"basename": filename}) else: size = os.path.getsize(this_filesystem_path) size_human = self.common.human_readable_filesize(size) - files.append({ - 'basename': filename, - 'size_human': size_human - }) + files.append({"basename": filename, "size_human": size_human}) return files, dirs def stream_individual_file(self, filesystem_path): @@ -136,7 +144,7 @@ class SendBaseModeWeb: # gzip compress the individual file, if it hasn't already been compressed if use_gzip: if filesystem_path not in self.gzip_individual_files: - gzip_filename = tempfile.mkstemp('wb+')[1] + gzip_filename = tempfile.mkstemp("wb+")[1] self._gzip_compress(filesystem_path, gzip_filename, 6, None) self.gzip_individual_files[filesystem_path] = gzip_filename @@ -154,10 +162,11 @@ class SendBaseModeWeb: # Tell GUI the individual file started history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { - 'id': history_id, - 'filesize': filesize - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + path, + {"id": history_id, "filesize": filesize}, + ) # Only GET requests are allowed, any other method should fail if request.method != "GET": @@ -166,11 +175,11 @@ class SendBaseModeWeb: def generate(): chunk_size = 102400 # 100kb - fp = open(file_to_download, 'rb') + fp = open(file_to_download, "rb") done = False while not done: chunk = fp.read(chunk_size) - if chunk == b'': + if chunk == b"": done = True else: try: @@ -179,59 +188,79 @@ class SendBaseModeWeb: # Tell GUI the progress downloaded_bytes = fp.tell() percent = (1.0 * downloaded_bytes / filesize) * 100 - if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': + if ( + not self.web.is_gui + or self.common.platform == "Linux" + or self.common.platform == "BSD" + ): sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.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.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, { - 'id': history_id, - 'bytes': downloaded_bytes, - 'filesize': filesize - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, + path, + { + "id": history_id, + "bytes": downloaded_bytes, + "filesize": filesize, + }, + ) done = False except: # Looks like the download was canceled done = True # Tell the GUI the individual file was canceled - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, + path, + {"id": history_id}, + ) fp.close() - if self.common.platform != 'Darwin': + if self.common.platform != "Darwin": sys.stdout.write("\n") basename = os.path.basename(filesystem_path) r = Response(generate()) if use_gzip: - r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', filesize) - r.headers.set('Content-Disposition', 'inline', filename=basename) + r.headers.set("Content-Encoding", "gzip") + r.headers.set("Content-Length", filesize) + r.headers.set("Content-Disposition", "inline", filename=basename) r = self.web.add_security_headers(r) (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: - r.headers.set('Content-Type', content_type) + r.headers.set("Content-Type", content_type) return r def should_use_gzip(self): """ Should we use gzip for this browser? """ - return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + return (not self.is_zipped) and ( + "gzip" in request.headers.get("Accept-Encoding", "").lower() + ) - def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): + def _gzip_compress( + self, input_filename, output_filename, level, processed_size_callback=None + ): """ Compress a file with gzip, without loading the whole thing into memory Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror """ bytes_processed = 0 - blocksize = 1 << 16 # 64kB - with open(input_filename, 'rb') as input_file: - output_file = gzip.open(output_filename, 'wb', level) + blocksize = 1 << 16 # 64kB + with open(input_filename, "rb") as input_file: + output_file = gzip.open(output_filename, "wb", level) while True: if processed_size_callback is not None: processed_size_callback(bytes_processed) @@ -269,7 +298,7 @@ class SendBaseModeWeb: """ pass - def render_logic(self, path=''): + def render_logic(self, path=""): """ Inherited class will implement this. """ diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 8a3f5969..21dea639 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -13,18 +13,22 @@ class ShareModeWeb(SendBaseModeWeb): """ All of the web logic for share mode """ + def init(self): - self.common.log('ShareModeWeb', 'init') + self.common.log("ShareModeWeb", "init") # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked - self.download_individual_files = not self.common.settings.get('close_after_first_download') + self.download_individual_files = not self.common.settings.get( + "close_after_first_download" + ) def define_routes(self): """ The web app routes for sharing files """ - @self.web.app.route('/', defaults={'path': ''}) - @self.web.app.route('/') + + @self.web.app.route("/", defaults={"path": ""}) + @self.web.app.route("/") def index(path): """ Render the template for the onionshare landing page. @@ -35,8 +39,10 @@ class ShareModeWeb(SendBaseModeWeb): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html'), - static_url_path=self.web.static_url_path) + r = make_response( + render_template("denied.html"), + static_url_path=self.web.static_url_path, + ) return self.web.add_security_headers(r) # If download is allowed to continue, serve download page @@ -56,13 +62,16 @@ class ShareModeWeb(SendBaseModeWeb): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html', - static_url_path=self.web.static_url_path)) + r = make_response( + render_template( + "denied.html", static_url_path=self.web.static_url_path + ) + ) return self.web.add_security_headers(r) # Prepare some variables to use inside generate() function below # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') + shutdown_func = request.environ.get("werkzeug.server.shutdown") path = request.path # If this is a zipped file, then serve as-is. If it's not zipped, then, @@ -79,10 +88,9 @@ class ShareModeWeb(SendBaseModeWeb): # Tell GUI the download started history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': history_id, - 'use_gzip': use_gzip - }) + self.web.add_request( + self.web.REQUEST_STARTED, path, {"id": history_id, "use_gzip": use_gzip} + ) basename = os.path.basename(self.download_filename) @@ -93,19 +101,19 @@ class ShareModeWeb(SendBaseModeWeb): chunk_size = 102400 # 100kb - fp = open(file_to_download, 'rb') + fp = open(file_to_download, "rb") self.web.done = False canceled = False while not self.web.done: # The user has canceled the download, so stop serving the file if not self.web.stop_q.empty(): - self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_CANCELED, path, {"id": history_id} + ) break chunk = fp.read(chunk_size) - if chunk == b'': + if chunk == b"": self.web.done = True else: try: @@ -116,15 +124,26 @@ class ShareModeWeb(SendBaseModeWeb): percent = (1.0 * downloaded_bytes / self.filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': + if ( + not self.web.is_gui + or self.common.platform == "Linux" + or self.common.platform == "BSD" + ): sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.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.web.add_request(self.web.REQUEST_PROGRESS, path, { - 'id': history_id, - 'bytes': downloaded_bytes - }) + self.web.add_request( + self.web.REQUEST_PROGRESS, + path, + {"id": history_id, "bytes": downloaded_bytes}, + ) self.web.done = False except: # looks like the download was canceled @@ -132,13 +151,13 @@ class ShareModeWeb(SendBaseModeWeb): canceled = True # tell the GUI the download has canceled - self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_CANCELED, path, {"id": history_id} + ) fp.close() - if self.common.platform != 'Darwin': + if self.common.platform != "Darwin": sys.stdout.write("\n") # Download is finished @@ -151,44 +170,51 @@ class ShareModeWeb(SendBaseModeWeb): self.web.running = False try: if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') + raise RuntimeError("Not running with the Werkzeug Server") shutdown_func() except: pass r = Response(generate()) if use_gzip: - r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', self.filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) + r.headers.set("Content-Encoding", "gzip") + r.headers.set("Content-Length", self.filesize) + r.headers.set("Content-Disposition", "attachment", filename=basename) r = self.web.add_security_headers(r) # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: - r.headers.set('Content-Type', content_type) + r.headers.set("Content-Type", content_type) return r - def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): - return make_response(render_template( - 'send.html', - file_info=self.file_info, - files=files, - dirs=dirs, - breadcrumbs=breadcrumbs, - breadcrumbs_leaf=breadcrumbs_leaf, - filename=os.path.basename(self.download_filename), - filesize=self.filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped, - static_url_path=self.web.static_url_path, - download_individual_files=self.download_individual_files)) + def directory_listing_template( + self, path, files, dirs, breadcrumbs, breadcrumbs_leaf + ): + return make_response( + render_template( + "send.html", + file_info=self.file_info, + files=files, + dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, + filename=os.path.basename(self.download_filename), + filesize=self.filesize, + filesize_human=self.common.human_readable_filesize( + self.download_filesize + ), + is_zipped=self.is_zipped, + static_url_path=self.web.static_url_path, + download_individual_files=self.download_individual_files, + ) + ) def set_file_info_custom(self, filenames, processed_size_callback): self.common.log("ShareModeWeb", "set_file_info_custom") self.web.cancel_compression = False self.build_zipfile_list(filenames, processed_size_callback) - def render_logic(self, path=''): + def render_logic(self, path=""): if path in self.files: filesystem_path = self.files[path] @@ -198,7 +224,7 @@ class ShareModeWeb(SendBaseModeWeb): filenames = [] for filename in os.listdir(filesystem_path): if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') + filenames.append(filename + "/") else: filenames.append(filename) filenames.sort() @@ -221,7 +247,7 @@ class ShareModeWeb(SendBaseModeWeb): else: # Special case loading / - if path == '': + if path == "": # Root directory listing filenames = list(self.root_files) filenames.sort() @@ -237,28 +263,34 @@ class ShareModeWeb(SendBaseModeWeb): self.common.log("ShareModeWeb", "build_zipfile_list") for filename in filenames: info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) + "filename": filename, + "basename": os.path.basename(filename.rstrip("/")), } if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) + info["size"] = os.path.getsize(filename) + info["size_human"] = self.common.human_readable_filesize(info["size"]) + self.file_info["files"].append(info) if os.path.isdir(filename): - 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']) + 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"] + ) # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] + if len(self.file_info["files"]) == 1 and len(self.file_info["dirs"]) == 0: + self.download_filename = self.file_info["files"][0]["filename"] + self.download_filesize = self.file_info["files"][0]["size"] # Compress the file with gzip now, so we don't have to do it on each request - self.gzip_filename = tempfile.mkstemp('wb+')[1] - self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback) + self.gzip_filename = tempfile.mkstemp("wb+")[1] + self._gzip_compress( + self.download_filename, self.gzip_filename, 6, processed_size_callback + ) self.gzip_filesize = os.path.getsize(self.gzip_filename) # Make sure the gzip file gets cleaned up when onionshare stops @@ -268,17 +300,19 @@ class ShareModeWeb(SendBaseModeWeb): else: # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.zip_writer = ZipWriter( + self.common, processed_size_callback=processed_size_callback + ) self.download_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) + for info in self.file_info["files"]: + self.zip_writer.add_file(info["filename"]) # Canceling early? if self.web.cancel_compression: self.zip_writer.close() return False - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): + for info in self.file_info["dirs"]: + if not self.zip_writer.add_dir(info["filename"]): return False self.zip_writer.close() @@ -298,6 +332,7 @@ class ZipWriter(object): with. If a zip_filename is not passed in, it will use the default onionshare filename. """ + def __init__(self, common, zip_filename=None, processed_size_callback=None): self.common = common self.cancel_compression = False @@ -305,9 +340,11 @@ class ZipWriter(object): if zip_filename: self.zip_filename = zip_filename else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.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.z = zipfile.ZipFile(self.zip_filename, "w", allowZip64=True) self.processed_size_callback = processed_size_callback if self.processed_size_callback is None: self.processed_size_callback = lambda _: None @@ -326,7 +363,7 @@ class ZipWriter(object): """ Add a directory, and all of its children, to the zip archive. """ - dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' + dir_to_strip = os.path.dirname(filename.rstrip("/")) + "/" for dirpath, dirnames, filenames in os.walk(filename): for f in filenames: # Canceling early? @@ -335,7 +372,7 @@ class ZipWriter(object): full_filename = os.path.join(dirpath, f) if not os.path.islink(full_filename): - arc_filename = full_filename[len(dir_to_strip):] + arc_filename = full_filename[len(dir_to_strip) :] self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) self._size += os.path.getsize(full_filename) self.processed_size_callback(self._size) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index f3e1e07a..4c4207e6 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -10,7 +10,15 @@ from distutils.version import LooseVersion as Version from urllib.request import urlopen import flask -from flask import Flask, request, render_template, abort, make_response, send_file, __version__ as flask_version +from flask import ( + Flask, + request, + render_template, + abort, + make_response, + send_file, + __version__ as flask_version, +) from flask_httpauth import HTTPBasicAuth from .. import strings @@ -24,6 +32,7 @@ from .website_mode import WebsiteModeWeb def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): pass + try: flask.cli.show_server_banner = stubbed_show_server_banner except: @@ -34,6 +43,7 @@ class Web: """ The Web object is the OnionShare web server, powered by flask """ + REQUEST_LOAD = 0 REQUEST_STARTED = 1 REQUEST_PROGRESS = 2 @@ -50,14 +60,16 @@ class Web: REQUEST_OTHER = 13 REQUEST_INVALID_PASSWORD = 14 - def __init__(self, common, is_gui, mode='share'): + def __init__(self, common, is_gui, mode="share"): self.common = common - self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode)) + self.common.log("Web", "__init__", "is_gui={}, mode={}".format(is_gui, mode)) # The flask app - self.app = Flask(__name__, - static_folder=self.common.get_resource_path('static'), - template_folder=self.common.get_resource_path('templates')) + self.app = Flask( + __name__, + static_folder=self.common.get_resource_path("static"), + template_folder=self.common.get_resource_path("templates"), + ) self.app.secret_key = self.common.random_string(8) self.generate_static_url_path() self.auth = HTTPBasicAuth() @@ -77,7 +89,7 @@ class Web: # Are we using receive mode? self.mode = mode - if self.mode == 'receive': + if self.mode == "receive": # Use custom WSGI middleware, to modify environ self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) # Use a custom Request class to track upload progess @@ -87,16 +99,16 @@ class Web: # by default. To prevent content injection through template variables in # earlier versions of Flask, we force autoescaping in the Jinja2 template # engine if we detect a Flask version with insecure default behavior. - if Version(flask_version) < Version('0.11'): + if Version(flask_version) < Version("0.11"): # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape self.security_headers = [ - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') + ("X-Frame-Options", "DENY"), + ("X-Xss-Protection", "1; mode=block"), + ("X-Content-Type-Options", "nosniff"), + ("Referrer-Policy", "no-referrer"), + ("Server", "OnionShare"), ] self.q = queue.Queue() @@ -119,19 +131,19 @@ class Web: self.share_mode = None self.receive_mode = None self.website_mode = None - if self.mode == 'share': + if self.mode == "share": self.share_mode = ShareModeWeb(self.common, self) - elif self.mode == 'receive': + elif self.mode == "receive": self.receive_mode = ReceiveModeWeb(self.common, self) - elif self.mode == 'website': + elif self.mode == "website": self.website_mode = WebsiteModeWeb(self.common, self) def get_mode(self): - if self.mode == 'share': + if self.mode == "share": return self.share_mode - elif self.mode == 'receive': + elif self.mode == "receive": return self.receive_mode - elif self.mode == 'website': + elif self.mode == "website": return self.website_mode else: return None @@ -139,14 +151,20 @@ class Web: def generate_static_url_path(self): # The static URL path has a 128-bit random number in it to avoid having name # collisions with files that might be getting shared - self.static_url_path = '/static_{}'.format(self.common.random_string(16)) - self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path)) + self.static_url_path = "/static_{}".format(self.common.random_string(16)) + self.common.log( + "Web", + "generate_static_url_path", + "new static_url_path is {}".format(self.static_url_path), + ) # Update the flask route to handle the new static URL path self.app.static_url_path = self.static_url_path self.app.add_url_rule( - self.static_url_path + '/', - endpoint='static', view_func=self.app.send_static_file) + self.static_url_path + "/", + endpoint="static", + view_func=self.app.send_static_file, + ) def define_common_routes(self): """ @@ -155,7 +173,7 @@ class Web: @self.auth.get_password def get_pw(username): - if username == 'onionshare': + if username == "onionshare": return self.password else: return None @@ -163,11 +181,12 @@ class Web: @self.app.before_request def conditional_auth_check(): # Allow static files without basic authentication - if(request.path.startswith(self.static_url_path + '/')): + if request.path.startswith(self.static_url_path + "/"): return None # If public mode is disabled, require authentication - if not self.common.settings.get('public_mode'): + if not self.common.settings.get("public_mode"): + @self.auth.login_required def _check_login(): return None @@ -191,46 +210,63 @@ class Web: return "" abort(404) - if self.mode != 'website': + if self.mode != "website": + @self.app.route("/favicon.ico") def favicon(): - return send_file('{}/img/favicon.ico'.format(self.common.get_resource_path('static'))) + return send_file( + "{}/img/favicon.ico".format(self.common.get_resource_path("static")) + ) def error401(self): auth = request.authorization if auth: - if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_passwords: - print('Invalid password guess: {}'.format(auth['password'])) - self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth['password']) + if ( + auth["username"] == "onionshare" + and auth["password"] not in self.invalid_passwords + ): + print("Invalid password guess: {}".format(auth["password"])) + self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth["password"]) - self.invalid_passwords.append(auth['password']) + self.invalid_passwords.append(auth["password"]) self.invalid_passwords_count += 1 if self.invalid_passwords_count == 20: self.add_request(Web.REQUEST_RATE_LIMIT) self.force_shutdown() - print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") + print( + "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share." + ) - r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) + r = make_response( + render_template("401.html", static_url_path=self.static_url_path), 401 + ) return self.add_security_headers(r) def error403(self): self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) + r = make_response( + render_template("403.html", static_url_path=self.static_url_path), 403 + ) return self.add_security_headers(r) def error404(self, history_id): - self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'status_code': 404 - }) + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + "{}".format(request.path), + {"id": history_id, "status_code": 404}, + ) self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) + r = make_response( + render_template("404.html", static_url_path=self.static_url_path), 404 + ) return self.add_security_headers(r) def error405(self): - r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405) + r = make_response( + render_template("405.html", static_url_path=self.static_url_path), 405 + ) return self.add_security_headers(r) def add_security_headers(self, r): @@ -240,39 +276,53 @@ class Web: for header, value in self.security_headers: r.headers.set(header, value) # Set a CSP header unless in website mode and the user has disabled it - if not self.common.settings.get('csp_header_disabled') or self.mode != 'website': - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;') + if ( + not self.common.settings.get("csp_header_disabled") + or self.mode != "website" + ): + r.headers.set( + "Content-Security-Policy", + "default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:;", + ) return r def _safe_select_jinja_autoescape(self, filename): if filename is None: return True - return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + return filename.endswith((".html", ".htm", ".xml", ".xhtml")) def add_request(self, request_type, path=None, data=None): """ Add a request to the queue, to communicate with the GUI. """ - self.q.put({ - 'type': request_type, - 'path': path, - 'data': data - }) + self.q.put({"type": request_type, "path": path, "data": data}) def generate_password(self, persistent_password=None): - self.common.log('Web', 'generate_password', 'persistent_password={}'.format(persistent_password)) - if persistent_password != None and persistent_password != '': + self.common.log( + "Web", + "generate_password", + "persistent_password={}".format(persistent_password), + ) + if persistent_password != None and persistent_password != "": self.password = persistent_password - self.common.log('Web', 'generate_password', 'persistent_password sent, so password is: "{}"'.format(self.password)) + self.common.log( + "Web", + "generate_password", + 'persistent_password sent, so password is: "{}"'.format(self.password), + ) else: self.password = self.common.build_password() - self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password)) + self.common.log( + "Web", + "generate_password", + 'built random password: "{}"'.format(self.password), + ) def verbose_mode(self): """ Turn on verbose mode, which will log flask errors to a file. """ - flask_log_filename = os.path.join(self.common.build_data_dir(), 'flask.log') + flask_log_filename = os.path.join(self.common.build_data_dir(), "flask.log") log_handler = logging.FileHandler(flask_log_filename) log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) @@ -287,9 +337,9 @@ class Web: """ # Shutdown the flask service try: - func = request.environ.get('werkzeug.server.shutdown') + func = request.environ.get("werkzeug.server.shutdown") if func is None: - raise RuntimeError('Not running with the Werkzeug Server') + raise RuntimeError("Not running with the Werkzeug Server") func() except: pass @@ -299,7 +349,13 @@ class Web: """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, password={}'.format(port, stay_open, public_mode, password)) + self.common.log( + "Web", + "start", + "port={}, stay_open={}, public_mode={}, password={}".format( + port, stay_open, public_mode, password + ), + ) self.stay_open = stay_open @@ -311,10 +367,10 @@ class Web: pass # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) - if os.path.exists('/usr/share/anon-ws-base-files/workstation'): - host = '0.0.0.0' + if os.path.exists("/usr/share/anon-ws-base-files/workstation"): + host = "0.0.0.0" else: - host = '127.0.0.1' + host = "127.0.0.1" self.running = True self.app.run(host=host, port=port, threaded=True) @@ -323,7 +379,7 @@ class Web: """ Stop the flask web server by loading /shutdown. """ - self.common.log('Web', 'stop', 'stopping server') + self.common.log("Web", "stop", "stopping server") # Let the mode know that the user stopped the server self.stop_q.put(True) @@ -331,8 +387,10 @@ class Web: # To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown # (We're putting the shutdown_password in the path as well to make routing simpler) if self.running: - requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_password), - auth=requests.auth.HTTPBasicAuth('onionshare', self.password)) + requests.get( + "http://127.0.0.1:{}/{}/shutdown".format(port, self.shutdown_password), + auth=requests.auth.HTTPBasicAuth("onionshare", self.password), + ) # Reset any password that was in use self.password = None diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 9dac8627..61b6d2c6 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -12,6 +12,7 @@ class WebsiteModeWeb(SendBaseModeWeb): """ All of the web logic for website mode """ + def init(self): pass @@ -19,38 +20,45 @@ class WebsiteModeWeb(SendBaseModeWeb): """ The web app routes for sharing a website """ - @self.web.app.route('/', defaults={'path': ''}) - @self.web.app.route('/') + + @self.web.app.route("/", defaults={"path": ""}) + @self.web.app.route("/") def path_public(path): return path_logic(path) - def path_logic(path=''): + def path_logic(path=""): """ Render the onionshare website. """ return self.render_logic(path) - def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): - return make_response(render_template('listing.html', - path=path, - files=files, - dirs=dirs, - breadcrumbs=breadcrumbs, - breadcrumbs_leaf=breadcrumbs_leaf, - static_url_path=self.web.static_url_path)) + def directory_listing_template( + self, path, files, dirs, breadcrumbs, breadcrumbs_leaf + ): + return make_response( + render_template( + "listing.html", + path=path, + files=files, + dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, + static_url_path=self.web.static_url_path, + ) + ) def set_file_info_custom(self, filenames, processed_size_callback): self.common.log("WebsiteModeWeb", "set_file_info_custom") self.web.cancel_compression = True - def render_logic(self, path=''): + def render_logic(self, path=""): if path in self.files: filesystem_path = self.files[path] # If it's a directory if os.path.isdir(filesystem_path): # Is there an index.html? - index_path = os.path.join(path, 'index.html') + index_path = os.path.join(path, "index.html") if index_path in self.files: # Render it return self.stream_individual_file(self.files[index_path]) @@ -60,7 +68,7 @@ class WebsiteModeWeb(SendBaseModeWeb): filenames = [] for filename in os.listdir(filesystem_path): if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') + filenames.append(filename + "/") else: filenames.append(filename) filenames.sort() @@ -78,8 +86,8 @@ class WebsiteModeWeb(SendBaseModeWeb): else: # Special case loading / - if path == '': - index_path = 'index.html' + if path == "": + index_path = "index.html" if index_path in self.files: # Render it return self.stream_individual_file(self.files[index_path]) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 99c52937..23d8dd3d 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -32,22 +32,26 @@ from onionshare.onionshare import OnionShare from .onionshare_gui import OnionShareGui + 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, common): - if common.platform == 'Linux' or common.platform == 'BSD': + if common.platform == "Linux" or common.platform == "BSD": self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtWidgets.QApplication.__init__(self, sys.argv) self.installEventFilter(self) def eventFilter(self, obj, event): - if (event.type() == QtCore.QEvent.KeyPress and - event.key() == QtCore.Qt.Key_Q and - event.modifiers() == QtCore.Qt.ControlModifier): - self.quit() + if ( + event.type() == QtCore.QEvent.KeyPress + and event.key() == QtCore.Qt.Key_Q + and event.modifiers() == QtCore.Qt.ControlModifier + ): + self.quit() return False @@ -70,11 +74,34 @@ def main(): qtapp = Application(common) # Parse arguments - parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) - parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") - parser.add_argument('--filenames', metavar='filenames', nargs='+', help="List of files or folders to share") - parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") + parser = argparse.ArgumentParser( + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=48) + ) + parser.add_argument( + "--local-only", + action="store_true", + dest="local_only", + help="Don't use Tor (only for development)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Log OnionShare errors to stdout, and web errors to disk", + ) + parser.add_argument( + "--filenames", + metavar="filenames", + nargs="+", + help="List of files or folders to share", + ) + parser.add_argument( + "--config", + metavar="config", + default=False, + help="Custom JSON config file location (optional)", + ) args = parser.parse_args() filenames = args.filenames @@ -118,10 +145,12 @@ def main(): def shutdown(): onion.cleanup() app.cleanup() + qtapp.aboutToQuit.connect(shutdown) # All done sys.exit(qtapp.exec_()) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 3ef285c4..04709dc2 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -29,10 +29,12 @@ from ..threads import OnionThread from ..threads import AutoStartTimer from ..widgets import Alert + class Mode(QtWidgets.QWidget): """ The class that all modes inherit from """ + start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() @@ -41,7 +43,17 @@ class Mode(QtWidgets.QWidget): starting_server_early = QtCore.pyqtSignal() set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): + def __init__( + self, + common, + qtapp, + app, + status_bar, + server_status_label, + system_tray, + filenames=None, + local_only=False, + ): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -65,7 +77,9 @@ class Mode(QtWidgets.QWidget): self.startup_thread = None # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) + self.server_status = ServerStatus( + self.common, self.qtapp, self.app, None, self.local_only + ) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -98,16 +112,26 @@ class Mode(QtWidgets.QWidget): """ Returns a human-friendly time delta from given seconds. """ - days = secs//86400 - hours = (secs - days*86400)//3600 - minutes = (secs - days*86400 - hours*3600)//60 - seconds = secs - days*86400 - hours*3600 - minutes*60 + days = secs // 86400 + hours = (secs - days * 86400) // 3600 + minutes = (secs - days * 86400 - hours * 3600) // 60 + seconds = secs - days * 86400 - hours * 3600 - minutes * 60 if not seconds: - seconds = '0' - result = ("{0}{1}, ".format(days, strings._('days_first_letter')) if days else "") + \ - ("{0}{1}, ".format(hours, strings._('hours_first_letter')) if hours else "") + \ - ("{0}{1}, ".format(minutes, strings._('minutes_first_letter')) if minutes else "") + \ - "{0}{1}".format(seconds, strings._('seconds_first_letter')) + seconds = "0" + result = ( + ("{0}{1}, ".format(days, strings._("days_first_letter")) if days else "") + + ( + "{0}{1}, ".format(hours, strings._("hours_first_letter")) + if hours + else "" + ) + + ( + "{0}{1}, ".format(minutes, strings._("minutes_first_letter")) + if minutes + else "" + ) + + "{0}{1}".format(seconds, strings._("seconds_first_letter")) + ) return result @@ -120,25 +144,45 @@ class Mode(QtWidgets.QWidget): if self.server_status.autostart_timer_datetime: now = QtCore.QDateTime.currentDateTime() if self.server_status.local_only: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_widget.dateTime()) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_widget.dateTime() + ) else: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_datetime.replace(second=0, microsecond=0)) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_datetime.replace( + second=0, microsecond=0 + ) + ) # Update the server button if seconds_remaining > 0: - self.server_status.server_button.setText(strings._('gui_waiting_to_start').format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + strings._("gui_waiting_to_start").format( + self.human_friendly_time(seconds_remaining) + ) + ) else: - self.server_status.server_button.setText(strings._('gui_please_wait')) + self.server_status.server_button.setText( + strings._("gui_please_wait") + ) # If the auto-stop timer has stopped, stop the server if self.server_status.status == ServerStatus.STATUS_STARTED: - if self.app.autostop_timer_thread and self.common.settings.get('autostop_timer'): + if self.app.autostop_timer_thread and self.common.settings.get( + "autostop_timer" + ): if self.autostop_timer_datetime_delta > 0: now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime) + seconds_remaining = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Update the server button server_button_text = self.get_stop_server_autostop_timer_text() - self.server_status.server_button.setText(server_button_text.format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + server_button_text.format( + self.human_friendly_time(seconds_remaining) + ) + ) self.status_bar.clearMessage() if not self.app.autostop_timer_thread.is_alive(): @@ -168,16 +212,16 @@ class Mode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('Mode', 'start_server') + self.common.log("Mode", "start_server") self.start_server_custom() self.set_server_active.emit(True) - self.app.set_stealth(self.common.settings.get('use_stealth')) + self.app.set_stealth(self.common.settings.get("use_stealth")) # Clear the status bar self.status_bar.clearMessage() - self.server_status_label.setText('') + self.server_status_label.setText("") # Ensure we always get a new random port each time we might launch an OnionThread self.app.port = None @@ -192,7 +236,7 @@ class Mode(QtWidgets.QWidget): # If scheduling a share, delay starting the real share if self.server_status.autostart_timer_datetime: - self.common.log('Mode', 'start_server', 'Starting auto-start timer') + self.common.log("Mode", "start_server", "Starting auto-start timer") self.startup_thread = AutoStartTimer(self) # Once the timer has finished, start the real share, with a WebThread self.startup_thread.success.connect(self.start_scheduled_service) @@ -201,7 +245,7 @@ class Mode(QtWidgets.QWidget): self.startup_thread.start() def start_onion_thread(self, obtain_onion_early=False): - self.common.log('Mode', 'start_server', 'Starting an onion thread') + self.common.log("Mode", "start_server", "Starting an onion thread") self.obtain_onion_early = obtain_onion_early self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) @@ -213,7 +257,7 @@ class Mode(QtWidgets.QWidget): # We start a new OnionThread with the saved scheduled key from settings self.common.settings.load() self.obtain_onion_early = obtain_onion_early - self.common.log('Mode', 'start_server', 'Starting a scheduled onion thread') + self.common.log("Mode", "start_server", "Starting a scheduled onion thread") self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) self.onion_thread.error.connect(self.starting_server_error.emit) @@ -237,7 +281,7 @@ class Mode(QtWidgets.QWidget): """ Step 2 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step2') + self.common.log("Mode", "start_server_step2") self.start_server_step2_custom() @@ -257,22 +301,28 @@ class Mode(QtWidgets.QWidget): """ Step 3 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step3') + self.common.log("Mode", "start_server_step3") self.start_server_step3_custom() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() - self.autostop_timer_datetime_delta = now.secsTo(self.server_status.autostop_timer_datetime) + self.autostop_timer_datetime_delta = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Start the auto-stop timer if self.autostop_timer_datetime_delta > 0: - self.app.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer_datetime_delta) + self.app.autostop_timer_thread = AutoStopTimer( + self.common, self.autostop_timer_datetime_delta + ) self.app.autostop_timer_thread.start() # The auto-stop timer has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() - self.start_server_error(strings._('gui_server_started_after_autostop_timer')) + self.start_server_error( + strings._("gui_server_started_after_autostop_timer") + ) def start_server_step3_custom(self): """ @@ -284,7 +334,7 @@ class Mode(QtWidgets.QWidget): """ If there's an error when trying to start the onion service """ - self.common.log('Mode', 'start_server_error') + self.common.log("Mode", "start_server_error") Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.set_server_active.emit(False) @@ -305,16 +355,16 @@ class Mode(QtWidgets.QWidget): """ self.cancel_server_custom() if self.startup_thread: - self.common.log('Mode', 'cancel_server: quitting startup thread') + self.common.log("Mode", "cancel_server: quitting startup thread") self.startup_thread.canceled = True self.app.onion.scheduled_key = None self.app.onion.scheduled_auth_cookie = None self.startup_thread.quit() if self.onion_thread: - self.common.log('Mode', 'cancel_server: quitting onion thread') + self.common.log("Mode", "cancel_server: quitting onion thread") self.onion_thread.quit() if self.web_thread: - self.common.log('Mode', 'cancel_server: quitting web thread') + self.common.log("Mode", "cancel_server: quitting web thread") self.web_thread.quit() self.stop_server() @@ -328,7 +378,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('Mode', 'stop_server') + self.common.log("Mode", "stop_server") if self.server_status.status != ServerStatus.STATUS_STOPPED: try: @@ -382,7 +432,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_RATE_LIMIT event. """ self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert( + self.common, strings._("error_rate_limit"), QtWidgets.QMessageBox.Critical + ) def handle_request_progress(self, event): """ diff --git a/onionshare_gui/mode/file_selection.py b/onionshare_gui/mode/file_selection.py index a7af61f8..c505dc03 100644 --- a/onionshare_gui/mode/file_selection.py +++ b/onionshare_gui/mode/file_selection.py @@ -24,11 +24,13 @@ from onionshare import strings from ..widgets import Alert, AddFileDialog + 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, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) @@ -39,10 +41,16 @@ class DropHereLabel(QtWidgets.QLabel): self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.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')) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"]) self.hide() @@ -57,6 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) @@ -65,8 +74,8 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_count_label"]) self.hide() def dragEnterEvent(self, event): @@ -78,6 +87,7 @@ class FileList(QtWidgets.QListWidget): """ The list of files and folders in the GUI. """ + files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() @@ -139,7 +149,7 @@ class FileList(QtWidgets.QListWidget): if self.count() > 0: # Add and delete an empty item, to force all items to get redrawn # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') + item = QtWidgets.QListWidgetItem("fake item") self.addItem(item) self.takeItem(self.row(item)) self.update() @@ -149,21 +159,27 @@ class FileList(QtWidgets.QListWidget): # and extend based on the overall width minus that amount. for index in range(self.count()): metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + elided = metrics.elidedText( + self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200 + ) self.item(index).setText(elided) - def dragEnterEvent(self, event): """ dragEnterEvent for dragging files and directories into the widget. """ if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + self.setStyleSheet(self.common.css["share_file_list_drag_enter"]) count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) + self.drop_count.setText("+{}".format(count)) size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.setGeometry( + self.width() - size_hint.width() - 10, + self.height() - size_hint.height() - 10, + size_hint.width(), + size_hint.height(), + ) self.drop_count.show() event.accept() else: @@ -173,7 +189,7 @@ class FileList(QtWidgets.QListWidget): """ dragLeaveEvent for dragging files and directories into the widget. """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() event.accept() self.update() @@ -201,7 +217,7 @@ class FileList(QtWidgets.QListWidget): else: event.ignore() - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() self.files_dropped.emit() @@ -238,12 +254,14 @@ class FileList(QtWidgets.QListWidget): # Item's filename attribute and size labels item.filename = filename item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + item_size.setStyleSheet(self.common.css["share_file_list_item_size"]) - item.basename = os.path.basename(filename.rstrip('/')) + item.basename = os.path.basename(filename.rstrip("/")) # Use the basename as the method with which to sort the list metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + elided = metrics.elidedText( + item.basename, QtCore.Qt.ElideRight, self.sizeHint().width() + ) item.setData(QtCore.Qt.DisplayRole, elided) # Item's delete button @@ -255,9 +273,13 @@ 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(self.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) + item.item_button.setSizePolicy( + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed + ) # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() @@ -265,7 +287,7 @@ class FileList(QtWidgets.QListWidget): item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') + item_info.setObjectName("item-info") item_info.setLayout(item_info_layout) # Create the item's widget and layouts @@ -288,6 +310,7 @@ 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, common, parent): super(FileSelection, self).__init__() @@ -303,21 +326,21 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": # The macOS sandbox makes it so the Mac version needs separate add files # and folders buttons, in order to use native file selection dialogs - self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button = QtWidgets.QPushButton(strings._("gui_add_files")) self.add_files_button.clicked.connect(self.add_files) - self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button = QtWidgets.QPushButton(strings._("gui_add_folder")) self.add_folder_button.clicked.connect(self.add_folder) else: - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button = QtWidgets.QPushButton(strings._("gui_add")) self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) + self.delete_button = QtWidgets.QPushButton(strings._("gui_delete")) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": button_layout.addWidget(self.add_files_button) button_layout.addWidget(self.add_folder_button) else: @@ -336,14 +359,14 @@ class FileSelection(QtWidgets.QVBoxLayout): """ # All buttons should be hidden if the server is on if self.server_on: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.hide() self.add_folder_button.hide() else: self.add_button.hide() self.delete_button.hide() else: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.show() self.add_folder_button.show() else: @@ -362,7 +385,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) + file_dialog = AddFileDialog(self.common, caption=strings._("gui_choose_items")) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) @@ -374,7 +397,9 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add files button clicked. """ - files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + files = QtWidgets.QFileDialog.getOpenFileNames( + self.parent, caption=strings._("gui_choose_items") + ) filenames = files[0] for filename in filenames: self.file_list.add_file(filename) @@ -383,9 +408,11 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add folder button clicked. """ - filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, - caption=strings._('gui_choose_items'), - options=QtWidgets.QFileDialog.ShowDirsOnly) + filename = QtWidgets.QFileDialog.getExistingDirectory( + self.parent, + caption=strings._("gui_choose_items"), + options=QtWidgets.QFileDialog.ShowDirsOnly, + ) self.file_list.add_file(filename) def delete(self): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index b8baebd1..85eec7e4 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -31,6 +31,7 @@ class HistoryItem(QtWidgets.QWidget): """ The base history item """ + STATUS_STARTED = 0 STATUS_FINISHED = 1 STATUS_CANCELED = 2 @@ -49,34 +50,42 @@ class HistoryItem(QtWidgets.QWidget): When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started) + return self._get_label_text( + "gui_all_modes_transfer_finished", + "gui_all_modes_transfer_finished_range", + started, + ) def get_canceled_label_text(self, started): """ When an item is canceled, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started) + return self._get_label_text( + "gui_all_modes_transfer_canceled", + "gui_all_modes_transfer_canceled_range", + started, + ) def _get_label_text(self, string_name, string_range_name, started): """ Return a string that contains a date, or date range. """ ended = datetime.now() - if started.year == ended.year and started.month == ended.month and started.day == ended.day: + if ( + started.year == ended.year + and started.month == ended.month + and started.day == ended.day + ): if started.hour == ended.hour and started.minute == ended.minute: - text = strings._(string_name).format( - started.strftime("%b %d, %I:%M%p") - ) + text = strings._(string_name).format(started.strftime("%b %d, %I:%M%p")) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p") ) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%b %d, %I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p") ) return text @@ -85,6 +94,7 @@ class ShareHistoryItem(HistoryItem): """ Download history item, for share mode """ + def __init__(self, common, id, total_bytes): super(ShareHistoryItem, self).__init__() self.common = common @@ -97,7 +107,11 @@ class ShareHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -107,7 +121,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) self.progress_bar.total_bytes = total_bytes # Layout @@ -124,8 +140,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_all_modes_progress_complete').format( - self.common.format_seconds(time.time() - self.started)) + pb_fmt = strings._("gui_all_modes_progress_complete").format( + self.common.format_seconds(time.time() - self.started) + ) # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) @@ -137,24 +154,26 @@ class ShareHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class ReceiveHistoryItemFile(QtWidgets.QWidget): @@ -162,7 +181,9 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): super(ReceiveHistoryItemFile, self).__init__() self.common = common - self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + self.common.log( + "ReceiveHistoryItemFile", "__init__", "filename: {}".format(filename) + ) self.filename = filename self.dir = None @@ -174,11 +195,13 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): # File size label self.filesize_label = QtWidgets.QLabel() - self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) + self.filesize_label.setStyleSheet(self.common.css["receive_file_size"]) self.filesize_label.hide() # Folder button - folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) + folder_pixmap = QtGui.QPixmap.fromImage( + QtGui.QImage(self.common.get_resource_path("images/open_folder.png")) + ) folder_icon = QtGui.QIcon(folder_pixmap) self.folder_button = QtWidgets.QPushButton() self.folder_button.clicked.connect(self.open_folder) @@ -213,29 +236,36 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): """ Open the downloads folder, with the file selected, in a cross-platform manner """ - self.common.log('ReceiveHistoryItemFile', 'open_folder') + self.common.log("ReceiveHistoryItemFile", "open_folder") if not self.dir: - self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + self.common.log( + "ReceiveHistoryItemFile", + "open_folder", + "dir has not been set yet, can't open folder", + ) return abs_filename = os.path.join(self.dir, self.filename) # Linux - if self.common.platform == 'Linux' or self.common.platform == 'BSD': + if self.common.platform == "Linux" or self.common.platform == "BSD": try: # If nautilus is available, open it - subprocess.Popen(['nautilus', abs_filename]) + subprocess.Popen(["nautilus", abs_filename]) except: - Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) + Alert( + self.common, + strings._("gui_open_folder_error_nautilus").format(abs_filename), + ) # macOS - elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + elif self.common.platform == "Darwin": + subprocess.call(["open", "-R", abs_filename]) # Windows - elif self.common.platform == 'Windows': - subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) + elif self.common.platform == "Windows": + subprocess.Popen(["explorer", "/select,{}".format(abs_filename)]) class ReceiveHistoryItem(HistoryItem): @@ -248,7 +278,11 @@ class ReceiveHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -257,13 +291,15 @@ class ReceiveHistoryItem(HistoryItem): self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setMinimum(0) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # This layout contains file widgets self.files_layout = QtWidgets.QVBoxLayout() self.files_layout.setContentsMargins(0, 0, 0, 0) files_widget = QtWidgets.QWidget() - files_widget.setStyleSheet(self.common.css['receive_file']) + files_widget.setStyleSheet(self.common.css["receive_file"]) files_widget.setLayout(self.files_layout) # Layout @@ -282,10 +318,10 @@ class ReceiveHistoryItem(HistoryItem): Using the progress from Web, update the progress bar and file size labels for each file """ - if data['action'] == 'progress': + if data["action"] == "progress": total_uploaded_bytes = 0 - for filename in data['progress']: - total_uploaded_bytes += data['progress'][filename]['uploaded_bytes'] + for filename in data["progress"]: + total_uploaded_bytes += data["progress"][filename]["uploaded_bytes"] # Update the progress bar self.progress_bar.setMaximum(self.content_length) @@ -293,35 +329,39 @@ class ReceiveHistoryItem(HistoryItem): elapsed = datetime.now() - self.started if elapsed.seconds < 10: - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(total_uploaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(total_uploaded_bytes) + ) else: estimated_time_remaining = self.common.estimated_time_remaining( - total_uploaded_bytes, - self.content_length, - self.started.timestamp()) - pb_fmt = strings._('gui_all_modes_progress_eta').format( + total_uploaded_bytes, self.content_length, self.started.timestamp() + ) + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) + estimated_time_remaining, + ) # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" - for filename in list(data['progress']): + for filename in list(data["progress"]): # Add a new file if needed if filename not in self.files: self.files[filename] = ReceiveHistoryItemFile(self.common, filename) self.files_layout.addWidget(self.files[filename]) # Update the file - self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + self.files[filename].update( + data["progress"][filename]["uploaded_bytes"], + data["progress"][filename]["complete"], + ) - elif data['action'] == 'rename': - self.files[data['old_filename']].rename(data['new_filename']) - self.files[data['new_filename']] = self.files.pop(data['old_filename']) + elif data["action"] == "rename": + self.files[data["old_filename"]].rename(data["new_filename"]) + self.files[data["new_filename"]] = self.files.pop(data["old_filename"]) - elif data['action'] == 'set_dir': - self.files[data['filename']].set_dir(data['dir']) + elif data["action"] == "set_dir": + self.files[data["filename"]].set_dir(data["dir"]) - elif data['action'] == 'finished': + elif data["action"] == "finished": # Change the status self.status = HistoryItem.STATUS_FINISHED @@ -331,7 +371,7 @@ class ReceiveHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started)) - elif data['action'] == 'canceled': + elif data["action"] == "canceled": # Change the status self.status = HistoryItem.STATUS_CANCELED @@ -346,6 +386,7 @@ class IndividualFileHistoryItem(HistoryItem): """ Individual file history item, for share mode viewing of individual files """ + def __init__(self, common, data, path): super(IndividualFileHistoryItem, self).__init__() self.status = HistoryItem.STATUS_STARTED @@ -359,11 +400,15 @@ class IndividualFileHistoryItem(HistoryItem): self.started_dt = datetime.fromtimestamp(self.started) self.status = HistoryItem.STATUS_STARTED - self.directory_listing = 'directory_listing' in data + self.directory_listing = "directory_listing" in data # Labels - self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) - self.timestamp_label.setStyleSheet(self.common.css['history_individual_file_timestamp_label']) + self.timestamp_label = QtWidgets.QLabel( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + self.timestamp_label.setStyleSheet( + self.common.css["history_individual_file_timestamp_label"] + ) self.path_label = QtWidgets.QLabel("{}".format(self.path)) self.status_code_label = QtWidgets.QLabel() @@ -373,7 +418,9 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # Text layout labels_layout = QtWidgets.QHBoxLayout() @@ -389,21 +436,25 @@ class IndividualFileHistoryItem(HistoryItem): self.setLayout(layout) # Is a status code already sent? - if 'status_code' in data: - self.status_code_label.setText("{}".format(data['status_code'])) - if data['status_code'] >= 200 and data['status_code'] < 300: - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) - if data['status_code'] >= 400 and data['status_code'] < 500: - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_4xx']) + if "status_code" in data: + self.status_code_label.setText("{}".format(data["status_code"])) + if data["status_code"] >= 200 and data["status_code"] < 300: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + if data["status_code"] >= 400 and data["status_code"] < 500: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_4xx"] + ) self.status = HistoryItem.STATUS_FINISHED self.progress_bar.hide() return else: - self.total_bytes = data['filesize'] + self.total_bytes = data["filesize"] self.progress_bar.setMinimum(0) - self.progress_bar.setMaximum(data['filesize']) - self.progress_bar.total_bytes = data['filesize'] + self.progress_bar.setMaximum(data["filesize"]) + self.progress_bar.total_bytes = data["filesize"] # Start at 0 self.update(0) @@ -414,7 +465,9 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: self.status_code_label.setText("200") - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) self.progress_bar.hide() self.status = HistoryItem.STATUS_FINISHED @@ -424,30 +477,33 @@ class IndividualFileHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class HistoryItemList(QtWidgets.QScrollArea): """ List of items """ + def __init__(self, common): super(HistoryItemList, self).__init__() self.common = common @@ -511,12 +567,14 @@ class HistoryItemList(QtWidgets.QScrollArea): item.close() del self.items[key] + class History(QtWidgets.QWidget): """ A history of what's happened so far in this mode. This contains an internal object full of a scrollable list of items. """ - def __init__(self, common, empty_image, empty_text, header_text, mode=''): + + def __init__(self, common, empty_image, empty_text, header_text, mode=""): super(History, self).__init__() self.common = common self.mode = mode @@ -530,17 +588,19 @@ class History(QtWidgets.QWidget): # In progress, completed, and requests labels self.in_progress_label = QtWidgets.QLabel() - self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"]) self.completed_label = QtWidgets.QLabel() - self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label.setStyleSheet(self.common.css["mode_info_label"]) self.requests_label = QtWidgets.QLabel() - self.requests_label.setStyleSheet(self.common.css['mode_info_label']) + self.requests_label.setStyleSheet(self.common.css["mode_info_label"]) # Header self.header_label = QtWidgets.QLabel(header_text) - self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) - self.clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"]) + self.clear_button = QtWidgets.QPushButton( + strings._("gui_all_modes_clear_history") + ) + self.clear_button.setStyleSheet(self.common.css["downloads_uploads_clear"]) self.clear_button.setFlat(True) self.clear_button.clicked.connect(self.reset) header_layout = QtWidgets.QHBoxLayout() @@ -557,14 +617,14 @@ class History(QtWidgets.QWidget): self.empty_image.setPixmap(empty_image) self.empty_text = QtWidgets.QLabel(empty_text) self.empty_text.setAlignment(QtCore.Qt.AlignCenter) - self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + self.empty_text.setStyleSheet(self.common.css["downloads_uploads_empty_text"]) empty_layout = QtWidgets.QVBoxLayout() empty_layout.addStretch() empty_layout.addWidget(self.empty_image) empty_layout.addWidget(self.empty_text) empty_layout.addStretch() self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setStyleSheet(self.common.css["downloads_uploads_empty"]) self.empty.setLayout(empty_layout) # When there are items @@ -589,7 +649,7 @@ class History(QtWidgets.QWidget): """ Add a new item. """ - self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + self.common.log("History", "add", "id: {}, item: {}".format(id, item)) # Hide empty, show not empty self.empty.hide() @@ -636,35 +696,47 @@ class History(QtWidgets.QWidget): Update the 'completed' widget. """ if self.completed_count == 0: - image = self.common.get_resource_path('images/history_completed_none.png') + image = self.common.get_resource_path("images/history_completed_none.png") else: - image = self.common.get_resource_path('images/history_completed.png') - self.completed_label.setText(' {1:d}'.format(image, self.completed_count)) - self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + image = self.common.get_resource_path("images/history_completed.png") + self.completed_label.setText( + ' {1:d}'.format(image, self.completed_count) + ) + self.completed_label.setToolTip( + strings._("history_completed_tooltip").format(self.completed_count) + ) def update_in_progress(self): """ Update the 'in progress' widget. """ if self.in_progress_count == 0: - image = self.common.get_resource_path('images/history_in_progress_none.png') + image = self.common.get_resource_path("images/history_in_progress_none.png") else: - image = self.common.get_resource_path('images/history_in_progress.png') + image = self.common.get_resource_path("images/history_in_progress.png") - self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) - self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + self.in_progress_label.setText( + ' {1:d}'.format(image, self.in_progress_count) + ) + self.in_progress_label.setToolTip( + strings._("history_in_progress_tooltip").format(self.in_progress_count) + ) def update_requests(self): """ Update the 'web requests' widget. """ if self.requests_count == 0: - image = self.common.get_resource_path('images/history_requests_none.png') + image = self.common.get_resource_path("images/history_requests_none.png") else: - image = self.common.get_resource_path('images/history_requests.png') + image = self.common.get_resource_path("images/history_requests.png") - self.requests_label.setText(' {1:d}'.format(image, self.requests_count)) - self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.requests_count)) + self.requests_label.setText( + ' {1:d}'.format(image, self.requests_count) + ) + self.requests_label.setToolTip( + strings._("history_requests_tooltip").format(self.requests_count) + ) class ToggleHistory(QtWidgets.QPushButton): @@ -672,6 +744,7 @@ class ToggleHistory(QtWidgets.QPushButton): Widget for toggling showing or hiding the history, as well as keeping track of the indicator counter if it's hidden """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): super(ToggleHistory, self).__init__() self.common = common @@ -691,7 +764,9 @@ class ToggleHistory(QtWidgets.QPushButton): # Keep track of indicator self.indicator_count = 0 self.indicator_label = QtWidgets.QLabel(parent=self) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.indicator_label.setStyleSheet( + self.common.css["download_uploads_indicator"] + ) self.update_indicator() def update_indicator(self, increment=False): @@ -708,14 +783,16 @@ class ToggleHistory(QtWidgets.QPushButton): self.indicator_label.hide() else: size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.setGeometry( + 35 - size.width(), 0, size.width(), size.height() + ) self.indicator_label.show() def toggle_clicked(self): """ Toggle showing and hiding the history widget """ - self.common.log('ToggleHistory', 'toggle_clicked') + self.common.log("ToggleHistory", "toggle_clicked") if self.history_widget.isVisible(): self.history_widget.hide() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index ecbfa54a..a0507949 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -25,19 +25,21 @@ from onionshare.web import Web from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode + class ReceiveMode(Mode): """ Parts of the main window UI for receiving files. """ + def init(self): """ Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'receive') + self.web = Web(self.common, True, "receive") # Server status - self.server_status.set_mode('receive') + self.server_status.set_mode("receive") self.server_status.server_started_finished.connect(self.update_primary_action) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.update_primary_action) @@ -49,21 +51,31 @@ class ReceiveMode(Mode): # Upload history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))), - strings._('gui_receive_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/receive_icon_transparent.png") + ) + ), + strings._("gui_receive_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle.png") + ), + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle_selected.png") + ), ) # Receive mode warning - receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) + receive_warning = QtWidgets.QLabel(strings._("gui_receive_mode_warning")) receive_warning.setMinimumHeight(80) receive_warning.setWordWrap(True) @@ -90,20 +102,25 @@ class ReceiveMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_receive_stop_server_autostop_timer') + return strings._("gui_receive_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.cur_history_id == 0 or not self.web.receive_mode.uploads_in_progress: + if ( + self.web.receive_mode.cur_history_id == 0 + or not self.web.receive_mode.uploads_in_progress + ): self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # An upload is probably still running - hold off on stopping the share, but block new shares. else: - self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_receive_mode_autostop_timer_waiting") + ) self.web.receive_mode.can_upload = False return False @@ -136,56 +153,68 @@ class ReceiveMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) + self.system_tray.showMessage( + strings._("systray_page_loaded_title"), + strings._("systray_page_loaded_message"), + ) def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ - item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + item = ReceiveHistoryItem( + self.common, event["data"]["id"], event["data"]["content_length"] + ) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message')) + self.system_tray.showMessage( + strings._("systray_receive_started_title"), + strings._("systray_receive_started_message"), + ) def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. """ - self.history.update(event["data"]["id"], { - 'action': 'progress', - 'progress': event["data"]["progress"] - }) + self.history.update( + event["data"]["id"], + {"action": "progress", "progress": event["data"]["progress"]}, + ) def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - self.history.update(event["data"]["id"], { - 'action': 'rename', - 'old_filename': event["data"]["old_filename"], - 'new_filename': event["data"]["new_filename"] - }) + self.history.update( + event["data"]["id"], + { + "action": "rename", + "old_filename": event["data"]["old_filename"], + "new_filename": event["data"]["new_filename"], + }, + ) def handle_request_upload_set_dir(self, event): """ Handle REQUEST_UPLOAD_SET_DIR event. """ - self.history.update(event["data"]["id"], { - 'action': 'set_dir', - 'filename': event["data"]["filename"], - 'dir': event["data"]["dir"] - }) + self.history.update( + event["data"]["id"], + { + "action": "set_dir", + "filename": event["data"]["filename"], + "dir": event["data"]["dir"], + }, + ) def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. """ - self.history.update(event["data"]["id"], { - 'action': 'finished' - }) + self.history.update(event["data"]["id"], {"action": "finished"}) self.history.completed_count += 1 self.history.in_progress_count -= 1 self.history.update_completed() @@ -195,9 +224,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_CANCELED event. """ - self.history.update(event["data"]["id"], { - 'action': 'canceled' - }) + self.history.update(event["data"]["id"], {"action": "canceled"}) self.history.in_progress_count -= 1 self.history.update_in_progress() @@ -216,4 +243,4 @@ class ReceiveMode(Mode): self.toggle_history.update_indicator() def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') + self.common.log("ReceiveMode", "update_primary_action") diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 28b439af..d0cc6a04 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -36,6 +36,7 @@ class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ + def init(self): """ Custom initialization for ReceiveMode. @@ -44,7 +45,7 @@ class ShareMode(Mode): self.compress_thread = None # Create the Web object - self.web = Web(self.common, True, 'share') + self.web = Web(self.common, True, "share") # File selection self.file_selection = FileSelection(self.common, self) @@ -53,7 +54,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('share', self.file_selection) + self.server_status.set_mode("share", self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -68,15 +69,19 @@ class ShareMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_share_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_share_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() @@ -86,9 +91,13 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -125,7 +134,7 @@ class ShareMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ @@ -134,11 +143,13 @@ class ShareMode(Mode): # If there were no attempts to download the share, or all downloads are done, we can stop if self.web.share_mode.cur_history_id == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_share_mode_autostop_timer_waiting") + ) return False def start_server_custom(self): @@ -162,7 +173,9 @@ class ShareMode(Mode): for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size( + self.filenames + ) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread @@ -216,7 +229,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('ShareMode', 'cancel_server: quitting compress thread') + self.common.log("ShareMode", "cancel_server: quitting compress thread") self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -240,7 +253,10 @@ class ShareMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message')) + self.system_tray.showMessage( + strings._("systray_share_started_title"), + strings._("systray_share_started_message"), + ) def handle_request_progress(self, event): """ @@ -250,7 +266,10 @@ class ShareMode(Mode): # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message')) + self.system_tray.showMessage( + strings._("systray_share_completed_title"), + strings._("systray_share_completed_message"), + ) # Update completed and in progress labels self.history.completed_count += 1 @@ -259,10 +278,10 @@ class ShareMode(Mode): self.history.update_in_progress() # Close on finish? - if self.common.settings.get('close_after_first_download'): + if self.common.settings.get("close_after_first_download"): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically')) + self.server_status_label.setText(strings._("closing_automatically")) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.history.cancel(event["data"]["id"]) @@ -278,7 +297,10 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message')) + self.system_tray.showMessage( + strings._("systray_share_canceled_title"), + strings._("systray_share_canceled_message"), + ) def on_reload_settings(self): """ @@ -290,7 +312,7 @@ class ShareMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('ShareMode', 'update_primary_action') + self.common.log("ShareMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -306,9 +328,15 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() @@ -343,8 +371,8 @@ class ZipProgressBar(QtWidgets.QProgressBar): self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - self.setStyleSheet(self.common.css['share_zip_progess_bar']) + self.setFormat(strings._("zip_progress_bar_format")) + self.setStyleSheet(self.common.css["share_zip_progess_bar"]) self._total_files_size = total_files_size self._processed_size = 0 diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index fed362eb..414c7be1 100644 --- a/onionshare_gui/mode/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -24,13 +24,14 @@ class CompressThread(QtCore.QThread): """ Compresses files to be shared """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(CompressThread, self).__init__() self.mode = mode - self.mode.common.log('CompressThread', '__init__') + self.mode.common.log("CompressThread", "__init__") # prepare files to share def set_processed_size(self, x): @@ -38,17 +39,21 @@ class CompressThread(QtCore.QThread): self.mode._zip_progress_bar.update_processed_size_signal.emit(x) def run(self): - self.mode.common.log('CompressThread', 'run') + self.mode.common.log("CompressThread", "run") try: - self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size) + self.mode.web.share_mode.set_file_info( + self.mode.filenames, processed_size_callback=self.set_processed_size + ) self.success.emit() - self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames + self.mode.app.cleanup_filenames += ( + self.mode.web.share_mode.cleanup_filenames + ) except OSError as e: self.error.emit(e.strerror) def cancel(self): - self.mode.common.log('CompressThread', 'cancel') + self.mode.common.log("CompressThread", "cancel") # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 7382f5a7..8cd2eca6 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -33,10 +33,12 @@ from .. import Mode from ..history import History, ToggleHistory from ...widgets import Alert + class WebsiteMode(Mode): """ Parts of the main window UI for sharing files. """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) @@ -45,7 +47,7 @@ class WebsiteMode(Mode): Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'website') + self.web = Web(self.common, True, "website") # File selection self.file_selection = FileSelection(self.common, self) @@ -54,7 +56,7 @@ class WebsiteMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('website', self.file_selection) + self.server_status.set_mode("website", self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -69,16 +71,20 @@ class WebsiteMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_website_mode_no_files'), - strings._('gui_all_modes_history'), - 'website' + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_website_mode_no_files"), + strings._("gui_all_modes_history"), + "website", ) self.history.in_progress_label.hide() self.history.completed_label.hide() @@ -90,9 +96,13 @@ class WebsiteMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -126,7 +136,7 @@ class WebsiteMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ @@ -134,10 +144,9 @@ class WebsiteMode(Mode): """ self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True - def start_server_custom(self): """ Starting the server. @@ -161,7 +170,6 @@ class WebsiteMode(Mode): self.starting_server_step3.emit() self.start_server_finished.emit() - def start_server_step3_custom(self): """ Step 3 in starting the server. Display large filesize @@ -191,8 +199,7 @@ class WebsiteMode(Mode): """ Log that the server has been cancelled """ - self.common.log('WebsiteMode', 'cancel_server') - + self.common.log("WebsiteMode", "cancel_server") def handle_tor_broke_custom(self): """ @@ -210,7 +217,7 @@ class WebsiteMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('WebsiteMode', 'update_primary_action') + self.common.log("WebsiteMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -226,9 +233,15 @@ class WebsiteMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 6406023f..4639ea13 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -33,20 +33,24 @@ from .widgets import Alert from .update_checker import UpdateThread from .server_status import ServerStatus + class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' - MODE_WEBSITE = 'website' - def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False): + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" + + def __init__( + self, common, onion, qtapp, app, filenames, config=False, local_only=False + ): super(OnionShareGui, self).__init__() self.common = common - self.common.log('OnionShareGui', '__init__') + self.common.log("OnionShareGui", "__init__") self.setMinimumWidth(820) self.setMinimumHeight(660) @@ -57,8 +61,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_SHARE - self.setWindowTitle('OnionShare') - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle("OnionShare") + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) # Load settings, if a custom config was passed in self.config = config @@ -71,40 +77,52 @@ class OnionShareGui(QtWidgets.QMainWindow): # System tray menu = QtWidgets.QMenu() - self.settings_action = menu.addAction(strings._('gui_settings_window_title')) + self.settings_action = menu.addAction(strings._("gui_settings_window_title")) self.settings_action.triggered.connect(self.open_settings) - self.help_action = menu.addAction(strings._('gui_settings_button_help')) + self.help_action = menu.addAction(strings._("gui_settings_button_help")) self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self)) - exit_action = menu.addAction(strings._('systray_menu_exit')) + exit_action = menu.addAction(strings._("systray_menu_exit")) exit_action.triggered.connect(self.close) self.system_tray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale - if self.common.platform == 'Darwin': - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) + if self.common.platform == "Darwin": + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo_grayscale.png")) + ) else: - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system_tray.setContextMenu(menu) self.system_tray.show() # Mode switcher, to switch between share files and receive files - self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button')); + self.share_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_share_button") + ) self.share_mode_button.setFixedHeight(50) self.share_mode_button.clicked.connect(self.share_mode_clicked) - self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button')); + self.receive_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_receive_button") + ) self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.clicked.connect(self.receive_mode_clicked) - self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button')); + self.website_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_website_button") + ) self.website_mode_button.setFixedHeight(50) self.website_mode_button.clicked.connect(self.website_mode_clicked) self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFixedWidth(40) self.settings_button.setFixedHeight(50) - self.settings_button.setIcon( QtGui.QIcon(self.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) - self.settings_button.setStyleSheet(self.common.css['settings_button']) - mode_switcher_layout = QtWidgets.QHBoxLayout(); + self.settings_button.setStyleSheet(self.common.css["settings_button"]) + mode_switcher_layout = QtWidgets.QHBoxLayout() mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) mode_switcher_layout.addWidget(self.receive_mode_button) @@ -112,13 +130,21 @@ class OnionShareGui(QtWidgets.QMainWindow): mode_switcher_layout.addWidget(self.settings_button) # Server status indicator on the status bar - 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_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('') - self.server_status_label.setStyleSheet(self.common.css['server_status_indicator_label']) + self.server_status_label = QtWidgets.QLabel("") + self.server_status_label.setStyleSheet( + self.common.css["server_status_indicator_label"] + ) server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) @@ -128,17 +154,34 @@ class OnionShareGui(QtWidgets.QMainWindow): # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) - self.status_bar.setStyleSheet(self.common.css['status_bar']) + self.status_bar.setStyleSheet(self.common.css["status_bar"]) self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) # Share mode - self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only) + self.share_mode = ShareMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + self.local_only, + ) self.share_mode.init() - self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.share_mode.start_server_finished.connect(self.update_server_status_indicator) - self.share_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.share_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.share_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.share_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.share_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.share_mode.stop_server_finished.connect(self.stop_server_finished) self.share_mode.start_server_finished.connect(self.clear_message) self.share_mode.server_status.button_clicked.connect(self.clear_message) @@ -147,31 +190,68 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.set_server_active.connect(self.set_server_active) # Receive mode - self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) + self.receive_mode = ReceiveMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + None, + self.local_only, + ) self.receive_mode.init() - self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.receive_mode.start_server_finished.connect(self.update_server_status_indicator) - self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.receive_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.receive_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.receive_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.receive_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.receive_mode.stop_server_finished.connect(self.stop_server_finished) self.receive_mode.start_server_finished.connect(self.clear_message) self.receive_mode.server_status.button_clicked.connect(self.clear_message) self.receive_mode.server_status.url_copied.connect(self.copy_url) - self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.receive_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) self.receive_mode.set_server_active.connect(self.set_server_active) # Website mode - self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) + self.website_mode = WebsiteMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + ) self.website_mode.init() - self.website_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.website_mode.start_server_finished.connect(self.update_server_status_indicator) - self.website_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.website_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.website_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.website_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.website_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.website_mode.stop_server_finished.connect(self.stop_server_finished) self.website_mode.start_server_finished.connect(self.clear_message) self.website_mode.server_status.button_clicked.connect(self.clear_message) self.website_mode.server_status.url_copied.connect(self.copy_url) - self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.website_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) self.website_mode.set_server_active.connect(self.set_server_active) self.update_mode_switcher() @@ -218,25 +298,43 @@ class OnionShareGui(QtWidgets.QMainWindow): # Based on the current mode, switch the mode switcher button styles, # and show and hide widgets to switch modes if self.mode == self.MODE_SHARE: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.receive_mode.hide() self.share_mode.show() self.website_mode.hide() elif self.mode == self.MODE_WEBSITE: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) self.receive_mode.hide() self.share_mode.hide() self.website_mode.show() else: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.share_mode.hide() self.receive_mode.show() @@ -246,19 +344,19 @@ class OnionShareGui(QtWidgets.QMainWindow): def share_mode_clicked(self): if self.mode != self.MODE_SHARE: - self.common.log('OnionShareGui', 'share_mode_clicked') + self.common.log("OnionShareGui", "share_mode_clicked") self.mode = self.MODE_SHARE self.update_mode_switcher() def receive_mode_clicked(self): if self.mode != self.MODE_RECEIVE: - self.common.log('OnionShareGui', 'receive_mode_clicked') + self.common.log("OnionShareGui", "receive_mode_clicked") self.mode = self.MODE_RECEIVE self.update_mode_switcher() def website_mode_clicked(self): if self.mode != self.MODE_WEBSITE: - self.common.log('OnionShareGui', 'website_mode_clicked') + self.common.log("OnionShareGui", "website_mode_clicked") self.mode = self.MODE_WEBSITE self.update_mode_switcher() @@ -267,42 +365,82 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: # Share mode if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_stopped") + ) elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.share_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_share_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_share_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_started") + ) elif self.mode == self.MODE_WEBSITE: # Website mode if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_stopped") + ) elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_share_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_started") + ) else: # Receive mode if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_stopped") + ) elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.receive_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_receive_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_receive_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_working") + ) elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_started") + ) def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service @@ -313,12 +451,22 @@ class OnionShareGui(QtWidgets.QMainWindow): If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ - self.common.log('OnionShareGui', '_tor_connection_canceled') + self.common.log("OnionShareGui", "_tor_connection_canceled") def ask(): - a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings')) - quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit')) + a = Alert( + self.common, + strings._("gui_tor_connection_ask"), + QtWidgets.QMessageBox.Question, + buttons=QtWidgets.QMessageBox.NoButton, + autostart=False, + ) + settings_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_open_settings") + ) + quit_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_quit") + ) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) @@ -326,12 +474,18 @@ class OnionShareGui(QtWidgets.QMainWindow): if a.clickedButton() == settings_button: # Open settings - self.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 - self.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) @@ -343,7 +497,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ The TorConnectionDialog wants to open the Settings dialog """ - self.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) @@ -352,10 +506,12 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Open the SettingsDialog. """ - self.common.log('OnionShareGui', 'open_settings') + self.common.log("OnionShareGui", "open_settings") def reload_settings(): - self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') + self.common.log( + "OnionShareGui", "open_settings", "settings have changed, reloading" + ) self.common.settings.load() # We might've stopped the main requests timer if a Tor connection failed. @@ -371,12 +527,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.clearMessage() # If we switched off the auto-stop timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostop_timer'): + if not self.common.settings.get("autostop_timer"): self.share_mode.server_status.autostop_timer_container.hide() self.receive_mode.server_status.autostop_timer_container.hide() self.website_mode.server_status.autostop_timer_container.hide() # If we switched off the auto-start timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostart_timer'): + if not self.common.settings.get("autostart_timer"): self.share_mode.server_status.autostart_timer_datetime = None self.receive_mode.server_status.autostart_timer_datetime = None self.website_mode.server_status.autostart_timer_datetime = None @@ -384,7 +540,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.autostart_timer_container.hide() self.website_mode.server_status.autostart_timer_container.hide() - d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) + d = SettingsDialog( + self.common, self.onion, self.qtapp, self.config, self.local_only + ) d.settings_saved.connect(reload_settings) d.exec_() @@ -397,10 +555,16 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Check for updates in a new thread, if enabled. """ - if self.common.platform == 'Windows' or self.common.platform == 'Darwin': - if self.common.settings.get('use_autoupdate'): + if self.common.platform == "Windows" or self.common.platform == "Darwin": + if self.common.settings.get("use_autoupdate"): + def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread.update_available.connect(update_available) @@ -417,8 +581,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - self.status_bar.showMessage(strings._('gui_tor_connection_lost')) - self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings')) + self.status_bar.showMessage(strings._("gui_tor_connection_lost")) + self.system_tray.showMessage( + strings._("gui_tor_connection_lost"), + strings._("gui_tor_connection_error_settings"), + ) self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() @@ -480,14 +647,31 @@ class OnionShareGui(QtWidgets.QMainWindow): mode.handle_request_individual_file_canceled(event) if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) + Alert( + self.common, + strings._("error_cannot_create_data_dir").format( + event["data"]["receive_mode_dir"] + ), + ) if event["type"] == Web.REQUEST_OTHER: - if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_password): - self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"])) + if event["path"] != "/favicon.ico" and event[ + "path" + ] != "/{}/shutdown".format(mode.web.shutdown_password): + self.status_bar.showMessage( + "{0:s}: {1:s}".format( + strings._("other_page_loaded"), event["path"] + ) + ) if event["type"] == Web.REQUEST_INVALID_PASSWORD: - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('incorrect_password'), event["data"])) + self.status_bar.showMessage( + "[#{0:d}] {1:s}: {2:s}".format( + mode.web.invalid_passwords_count, + strings._("incorrect_password"), + event["data"], + ) + ) mode.timer_callback() @@ -495,15 +679,20 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the URL gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_url') - self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url')) + self.common.log("OnionShareGui", "copy_url") + self.system_tray.showMessage( + strings._("gui_copied_url_title"), strings._("gui_copied_url") + ) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_hidservauth') - self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth')) + self.common.log("OnionShareGui", "copy_hidservauth") + self.system_tray.showMessage( + strings._("gui_copied_hidservauth_title"), + strings._("gui_copied_hidservauth"), + ) def clear_message(self): """ @@ -539,7 +728,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_action.setEnabled(not active) def closeEvent(self, e): - self.common.log('OnionShareGui', 'closeEvent') + self.common.log("OnionShareGui", "closeEvent") self.system_tray.hide() try: if self.mode == OnionShareGui.MODE_SHARE: @@ -549,16 +738,21 @@ class OnionShareGui(QtWidgets.QMainWindow): else: server_status = self.receive_mode.server_status if server_status.status != server_status.STATUS_STOPPED: - self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') + self.common.log("OnionShareGui", "closeEvent, opening warning dialog") dialog = QtWidgets.QMessageBox() - dialog.setWindowTitle(strings._('gui_quit_title')) + dialog.setWindowTitle(strings._("gui_quit_title")) if self.mode == OnionShareGui.MODE_SHARE: - dialog.setText(strings._('gui_share_quit_warning')) + dialog.setText(strings._("gui_share_quit_warning")) else: - dialog.setText(strings._('gui_receive_quit_warning')) + dialog.setText(strings._("gui_receive_quit_warning")) dialog.setIcon(QtWidgets.QMessageBox.Critical) - quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole) - dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole) + quit_button = dialog.addButton( + strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.YesRole + ) + dont_quit_button = dialog.addButton( + strings._("gui_quit_warning_dont_quit"), + QtWidgets.QMessageBox.NoRole, + ) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 85b1f230..4ce1f5d2 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -25,10 +25,12 @@ from onionshare import strings from .widgets import Alert + class ServerStatus(QtWidgets.QWidget): """ The server status chunk of the GUI. """ + server_started = QtCore.pyqtSignal() server_started_finished = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal() @@ -37,9 +39,9 @@ class ServerStatus(QtWidgets.QWidget): url_copied = QtCore.pyqtSignal() hidservauth_copied = QtCore.pyqtSignal() - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' - MODE_WEBSITE = 'website' + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" STATUS_STOPPED = 0 STATUS_WORKING = 1 @@ -51,7 +53,7 @@ class ServerStatus(QtWidgets.QWidget): self.common = common self.status = self.STATUS_STOPPED - self.mode = None # Gets set in self.set_mode + self.mode = None # Gets set in self.set_mode self.qtapp = qtapp self.app = app @@ -63,19 +65,31 @@ class ServerStatus(QtWidgets.QWidget): self.resizeEvent(None) # Auto-start timer layout - self.autostart_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostart_timer')) + self.autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostart_timer") + ) self.autostart_timer_widget = QtWidgets.QDateTimeEdit() self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostart_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostart_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostart_timer_layout = QtWidgets.QHBoxLayout() autostart_timer_layout.addWidget(self.autostart_timer_label) autostart_timer_layout.addWidget(self.autostart_timer_widget) @@ -88,19 +102,31 @@ class ServerStatus(QtWidgets.QWidget): self.autostart_timer_container.hide() # Auto-stop timer layout - self.autostop_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostop_timer')) + self.autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostop_timer") + ) self.autostop_timer_widget = QtWidgets.QDateTimeEdit() self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostop_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostop_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostop_timer_layout = QtWidgets.QHBoxLayout() autostop_timer_layout.addWidget(self.autostop_timer_label) autostop_timer_layout.addWidget(self.autostop_timer_widget) @@ -125,16 +151,20 @@ class ServerStatus(QtWidgets.QWidget): self.url.setFont(url_font) self.url.setWordWrap(True) self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet(self.common.css['server_status_url']) + self.url.setStyleSheet(self.common.css["server_status_url"]) - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url')) + self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url")) self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_url_button.setStyleSheet(self.common.css["server_status_url_buttons"]) self.copy_url_button.setMinimumHeight(65) self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) + self.copy_hidservauth_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_hidservauth_button.setStyleSheet( + self.common.css["server_status_url_buttons"] + ) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) url_buttons_layout = QtWidgets.QHBoxLayout() url_buttons_layout.addWidget(self.copy_url_button) @@ -160,7 +190,9 @@ class ServerStatus(QtWidgets.QWidget): """ self.mode = share_mode - if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE): + if (self.mode == ServerStatus.MODE_SHARE) or ( + self.mode == ServerStatus.MODE_WEBSITE + ): self.file_selection = file_selection self.update() @@ -171,7 +203,7 @@ class ServerStatus(QtWidgets.QWidget): """ try: # Wrap the URL label - url_length=len(self.get_url()) + url_length = len(self.get_url()) if url_length > 60: width = self.frameGeometry().width() if width < 530: @@ -186,17 +218,25 @@ class ServerStatus(QtWidgets.QWidget): """ Reset the auto-start timer in the UI after stopping a share """ - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def autostop_timer_reset(self): """ Reset the auto-stop timer in the UI after stopping a share """ - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def show_url(self): """ @@ -204,26 +244,38 @@ class ServerStatus(QtWidgets.QWidget): """ self.url_description.show() - info_image = self.common.get_resource_path('images/info.png') + info_image = self.common.get_resource_path("images/info.png") if self.mode == ServerStatus.MODE_SHARE: - self.url_description.setText(strings._('gui_share_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_share_url_description").format(info_image) + ) elif self.mode == ServerStatus.MODE_WEBSITE: - self.url_description.setText(strings._('gui_website_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_website_url_description").format(info_image) + ) else: - self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_receive_url_description").format(info_image) + ) # Show a Tool Tip explaining the lifecycle of this URL - if self.common.settings.get('save_private_key'): - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent')) + if self.common.settings.get("save_private_key"): + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip( + strings._("gui_url_label_onetime_and_persistent") + ) else: - self.url_description.setToolTip(strings._('gui_url_label_persistent')) + self.url_description.setToolTip(strings._("gui_url_label_persistent")) else: - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime')) + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip(strings._("gui_url_label_onetime")) else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open')) + self.url_description.setToolTip(strings._("gui_url_label_stay_open")) self.url.setText(self.get_url()) self.url.show() @@ -245,15 +297,15 @@ class ServerStatus(QtWidgets.QWidget): self.common.settings.load() self.show_url() - if self.common.settings.get('save_private_key'): - if not self.common.settings.get('password'): - self.common.settings.set('password', self.web.password) + if self.common.settings.get("save_private_key"): + if not self.common.settings.get("password"): + self.common.settings.set("password", self.web.password) self.common.settings.save() - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: self.url_description.hide() @@ -262,59 +314,91 @@ class ServerStatus(QtWidgets.QWidget): self.copy_hidservauth_button.hide() # Button - if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: + if ( + self.mode == ServerStatus.MODE_SHARE + and self.file_selection.get_num_files() == 0 + ): self.server_button.hide() - elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0: + elif ( + self.mode == ServerStatus.MODE_WEBSITE + and self.file_selection.get_num_files() == 0 + ): self.server_button.hide() else: self.server_button.show() if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_stopped"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_start_server')) + self.server_button.setText(strings._("gui_share_start_server")) elif self.mode == ServerStatus.MODE_WEBSITE: - self.server_button.setText(strings._('gui_share_start_server')) + self.server_button.setText(strings._("gui_share_start_server")) else: - self.server_button.setText(strings._('gui_receive_start_server')) - self.server_button.setToolTip('') - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_start_server")) + self.server_button.setToolTip("") + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.show() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.show() elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(self.common.css['server_status_button_started']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_started"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_stop_server')) + self.server_button.setText(strings._("gui_share_stop_server")) elif self.mode == ServerStatus.MODE_WEBSITE: - self.server_button.setText(strings._('gui_share_stop_server')) + self.server_button.setText(strings._("gui_share_stop_server")) else: - self.server_button.setText(strings._('gui_receive_stop_server')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_stop_server")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_stop_server_autostop_timer_tooltip").format( + self.autostop_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(True) if self.autostart_timer_datetime: self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) else: - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostop_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(False) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) - if self.common.settings.get('autostop_timer'): + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() def server_button_clicked(self): @@ -323,28 +407,60 @@ class ServerStatus(QtWidgets.QWidget): """ if self.status == self.STATUS_STOPPED: can_start = True - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): if self.local_only: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime() + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime().toPyDateTime() + ) else: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostart_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostart_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostart_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostop_timer'): + Alert( + self.common, + strings._("gui_server_autostart_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostop_timer"): if self.local_only: - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime() + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime().toPyDateTime() + ) else: # Get the timer chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostop_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostop_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostop_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostart_timer'): + Alert( + self.common, + strings._("gui_server_autostop_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostart_timer"): if self.autostop_timer_datetime <= self.autostart_timer_datetime: - Alert(self.common, strings._('gui_autostop_timer_cant_be_earlier_than_autostart_timer'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._( + "gui_autostop_timer_cant_be_earlier_than_autostart_timer" + ), + QtWidgets.QMessageBox.Warning, + ) can_start = False if can_start: self.start_server() @@ -385,7 +501,9 @@ class ServerStatus(QtWidgets.QWidget): """ Cancel the server. """ - self.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.autostart_timer_reset() self.autostop_timer_reset() @@ -421,8 +539,10 @@ class ServerStatus(QtWidgets.QWidget): """ Returns the OnionShare URL. """ - if self.common.settings.get('public_mode'): - url = 'http://{0:s}'.format(self.app.onion_host) + if self.common.settings.get("public_mode"): + url = "http://{0:s}".format(self.app.onion_host) else: - url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.password, self.app.onion_host) + url = "http://onionshare:{0:s}@{1:s}".format( + self.web.password, self.app.onion_host + ) return url diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index ec91a491..503e53a0 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -37,6 +37,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ + settings_saved = QtCore.pyqtSignal() def __init__(self, common, onion, qtapp, config=False, local_only=False): @@ -44,7 +45,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common = common - self.common.log('SettingsDialog', '__init__') + self.common.log("SettingsDialog", "__init__") self.onion = onion self.qtapp = qtapp @@ -52,22 +53,30 @@ class SettingsDialog(QtWidgets.QDialog): self.local_only = local_only self.setModal(True) - self.setWindowTitle(strings._('gui_settings_window_title')) - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle(strings._("gui_settings_window_title")) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system = platform.system() # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog - self.hide_tor_settings = os.environ.get('ONIONSHARE_HIDE_TOR_SETTINGS') == "1" + self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" # General settings # Use a password or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) - public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) + self.public_mode_checkbox.setText( + strings._("gui_settings_public_mode_checkbox") + ) + public_mode_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Public-Mode" + ) + ) + public_mode_label.setStyleSheet(self.common.css["settings_whats_this"]) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) public_mode_label.setMinimumSize(public_mode_label.sizeHint()) @@ -75,16 +84,22 @@ class SettingsDialog(QtWidgets.QDialog): public_mode_layout.addWidget(self.public_mode_checkbox) public_mode_layout.addWidget(public_mode_label) public_mode_layout.addStretch() - public_mode_layout.setContentsMargins(0,0,0,0) + public_mode_layout.setContentsMargins(0, 0, 0, 0) self.public_mode_widget = QtWidgets.QWidget() self.public_mode_widget.setLayout(public_mode_layout) # Whether or not to use an auto-start timer self.autostart_timer_checkbox = QtWidgets.QCheckBox() self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostart_timer_checkbox.setText(strings._("gui_settings_autostart_timer_checkbox")) - autostart_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer")) - autostart_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostart_timer_checkbox.setText( + strings._("gui_settings_autostart_timer_checkbox") + ) + autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer" + ) + ) + autostart_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostart_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostart_timer_label.setOpenExternalLinks(True) autostart_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -92,16 +107,22 @@ class SettingsDialog(QtWidgets.QDialog): autostart_timer_layout.addWidget(self.autostart_timer_checkbox) autostart_timer_layout.addWidget(autostart_timer_label) autostart_timer_layout.addStretch() - autostart_timer_layout.setContentsMargins(0,0,0,0) + autostart_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostart_timer_widget = QtWidgets.QWidget() self.autostart_timer_widget.setLayout(autostart_timer_layout) # Whether or not to use an auto-stop timer self.autostop_timer_checkbox = QtWidgets.QCheckBox() self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostop_timer_checkbox.setText(strings._("gui_settings_autostop_timer_checkbox")) - autostop_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) - autostop_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostop_timer_checkbox.setText( + strings._("gui_settings_autostop_timer_checkbox") + ) + autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer" + ) + ) + autostop_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostop_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostop_timer_label.setOpenExternalLinks(True) autostop_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -109,7 +130,7 @@ class SettingsDialog(QtWidgets.QDialog): autostop_timer_layout.addWidget(self.autostop_timer_checkbox) autostop_timer_layout.addWidget(autostop_timer_label) autostop_timer_layout.addStretch() - autostop_timer_layout.setContentsMargins(0,0,0,0) + autostop_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostop_timer_widget = QtWidgets.QWidget() self.autostop_timer_widget.setLayout(autostop_timer_layout) @@ -124,39 +145,59 @@ class SettingsDialog(QtWidgets.QDialog): # Onion settings # Label telling user to connect to Tor for onion service settings - self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) - self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + self.connect_to_tor_label = QtWidgets.QLabel( + strings._("gui_connect_to_tor_for_onion_settings") + ) + self.connect_to_tor_label.setStyleSheet( + self.common.css["settings_connect_to_tor"] + ) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) - save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) - save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) + self.save_private_key_checkbox.setText( + strings._("gui_save_private_key_checkbox") + ) + save_private_key_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL" + ) + ) + save_private_key_label.setStyleSheet(self.common.css["settings_whats_this"]) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) save_private_key_layout = QtWidgets.QHBoxLayout() save_private_key_layout.addWidget(self.save_private_key_checkbox) save_private_key_layout.addWidget(save_private_key_label) save_private_key_layout.addStretch() - save_private_key_layout.setContentsMargins(0,0,0,0) + save_private_key_layout.setContentsMargins(0, 0, 0, 0) self.save_private_key_widget = QtWidgets.QWidget() self.save_private_key_widget.setLayout(save_private_key_layout) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox")) - self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) - use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) - use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.use_legacy_v2_onions_checkbox.setText( + strings._("gui_use_legacy_v2_onions_checkbox") + ) + self.use_legacy_v2_onions_checkbox.clicked.connect( + self.use_legacy_v2_onions_checkbox_clicked + ) + use_legacy_v2_onions_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Legacy-Addresses" + ) + ) + use_legacy_v2_onions_label.setStyleSheet(self.common.css["settings_whats_this"]) + use_legacy_v2_onions_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() - use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) + use_legacy_v2_onions_layout.setContentsMargins(0, 0, 0, 0) self.use_legacy_v2_onions_widget = QtWidgets.QWidget() self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) @@ -165,8 +206,12 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option")) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) - use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) + use_stealth_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services" + ) + ) + use_stealth_label.setStyleSheet(self.common.css["settings_whats_this"]) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) @@ -174,17 +219,23 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() - use_stealth_layout.setContentsMargins(0,0,0,0) + use_stealth_layout.setContentsMargins(0, 0, 0, 0) self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget.setLayout(use_stealth_layout) - self.hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) + self.hidservauth_details = QtWidgets.QLabel( + strings._("gui_settings_stealth_hidservauth_string") + ) self.hidservauth_details.setWordWrap(True) self.hidservauth_details.setMinimumSize(self.hidservauth_details.sizeHint()) self.hidservauth_details.hide() - self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) - self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) + self.hidservauth_copy_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) + self.hidservauth_copy_button.clicked.connect( + self.hidservauth_copy_button_clicked + ) self.hidservauth_copy_button.hide() # Onion settings widget @@ -205,14 +256,17 @@ class SettingsDialog(QtWidgets.QDialog): onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label")) onion_group.setLayout(onion_group_layout) - # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) - self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) - individual_downloads_label = QtWidgets.QLabel(strings._("gui_settings_individual_downloads_label")) + self.close_after_first_download_checkbox.setText( + strings._("gui_settings_close_after_first_download_option") + ) + individual_downloads_label = QtWidgets.QLabel( + strings._("gui_settings_individual_downloads_label") + ) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() @@ -222,10 +276,12 @@ class SettingsDialog(QtWidgets.QDialog): sharing_group.setLayout(sharing_group_layout) # OnionShare data dir - data_dir_label = QtWidgets.QLabel(strings._('gui_settings_data_dir_label')); + data_dir_label = QtWidgets.QLabel(strings._("gui_settings_data_dir_label")) self.data_dir_lineedit = QtWidgets.QLineEdit() self.data_dir_lineedit.setReadOnly(True) - data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button')) + data_dir_button = QtWidgets.QPushButton( + strings._("gui_settings_data_dir_browse_button") + ) data_dir_button.clicked.connect(self.data_dir_button_clicked) data_dir_layout = QtWidgets.QHBoxLayout() data_dir_layout.addWidget(data_dir_label) @@ -241,9 +297,15 @@ class SettingsDialog(QtWidgets.QDialog): # Option to disable Content Security Policy (for website sharing) self.csp_header_disabled_checkbox = QtWidgets.QCheckBox() self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.csp_header_disabled_checkbox.setText(strings._("gui_settings_csp_header_disabled_option")) - csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy")) - csp_header_label.setStyleSheet(self.common.css['settings_whats_this']) + self.csp_header_disabled_checkbox.setText( + strings._("gui_settings_csp_header_disabled_option") + ) + csp_header_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Content-Security-Policy" + ) + ) + csp_header_label.setStyleSheet(self.common.css["settings_whats_this"]) csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) csp_header_label.setOpenExternalLinks(True) csp_header_label.setMinimumSize(csp_header_label.sizeHint()) @@ -251,7 +313,7 @@ class SettingsDialog(QtWidgets.QDialog): csp_header_layout.addWidget(self.csp_header_disabled_checkbox) csp_header_layout.addWidget(csp_header_label) csp_header_layout.addStretch() - csp_header_layout.setContentsMargins(0,0,0,0) + csp_header_layout.setContentsMargins(0, 0, 0, 0) self.csp_header_widget = QtWidgets.QWidget() self.csp_header_widget.setLayout(csp_header_layout) @@ -279,7 +341,9 @@ class SettingsDialog(QtWidgets.QDialog): self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button - self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button')) + self.check_for_updates_button = QtWidgets.QPushButton( + strings._("gui_settings_autoupdate_check_button") + ) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: @@ -290,18 +354,22 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) - autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label")) + autoupdate_group = QtWidgets.QGroupBox( + strings._("gui_settings_autoupdate_label") + ) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) - if self.system != 'Windows' and self.system != 'Darwin': + if self.system != "Windows" and self.system != "Darwin": autoupdate_group.hide() # Language settings language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages - language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()} + language_names_to_locales = { + v: k for k, v in self.common.settings.available_locales.items() + } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: @@ -315,56 +383,106 @@ class SettingsDialog(QtWidgets.QDialog): # Connection type: either automatic, control port, or socket file # Bundled Tor - self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option')) - self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) + self.connection_type_bundled_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_bundled_option") + ) + self.connection_type_bundled_radio.toggled.connect( + self.connection_type_bundled_toggled + ) # Bundled Tor doesn't work on dev mode in Windows or Mac - if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if (self.system == "Windows" or self.system == "Darwin") and getattr( + sys, "onionshare_dev_mode", False + ): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option')) - self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) + self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_no_bridges_radio_option") + ) + self.tor_bridges_no_bridges_radio.toggled.connect( + self.tor_bridges_no_bridges_radio_toggled + ) # 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) = self.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')) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") + ) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option')) - self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) + self.tor_bridges_use_obfs4_radio.toggled.connect( + self.tor_bridges_use_obfs4_radio_toggled + ) # 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) = self.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')) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._( + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" + ) + ) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option')) - self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) + self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( + self.tor_bridges_use_meek_lite_azure_radio_toggled + ) # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option')) - self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) + self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_custom_radio_option") + ) + self.tor_bridges_use_custom_radio.toggled.connect( + self.tor_bridges_use_custom_radio_toggled + ) - self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label')) - self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.tor_bridges_use_custom_label = QtWidgets.QLabel( + strings._("gui_settings_tor_bridges_custom_label") + ) + self.tor_bridges_use_custom_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') + self.tor_bridges_use_custom_textbox.setPlaceholderText( + "[address:port] [identifier]" + ) tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_label) - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_textbox) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_label + ) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_textbox + ) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - self.tor_bridges_use_custom_textbox_options.setLayout(tor_bridges_use_custom_textbox_options_layout) + self.tor_bridges_use_custom_textbox_options.setLayout( + tor_bridges_use_custom_textbox_options_layout + ) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget @@ -379,41 +497,73 @@ class SettingsDialog(QtWidgets.QDialog): self.bridges.setLayout(bridges_layout) # Automatic - self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option')) - self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) + self.connection_type_automatic_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_automatic_option") + ) + self.connection_type_automatic_radio.toggled.connect( + self.connection_type_automatic_toggled + ) # Control port - self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option')) - self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) + self.connection_type_control_port_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_control_port_option") + ) + self.connection_type_control_port_radio.toggled.connect( + self.connection_type_control_port_toggled + ) - connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label')) + connection_type_control_port_extras_label = QtWidgets.QLabel( + strings._("gui_settings_control_port_label") + ) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() - connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) + connection_type_control_port_extras_layout.addWidget( + connection_type_control_port_extras_label + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_address + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_port + ) self.connection_type_control_port_extras = QtWidgets.QWidget() - self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) + self.connection_type_control_port_extras.setLayout( + connection_type_control_port_extras_layout + ) self.connection_type_control_port_extras.hide() # Socket file - self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option')) - self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) + self.connection_type_socket_file_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_socket_file_option") + ) + self.connection_type_socket_file_radio.toggled.connect( + self.connection_type_socket_file_toggled + ) - connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label')) + connection_type_socket_file_extras_label = QtWidgets.QLabel( + strings._("gui_settings_socket_file_label") + ) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() - connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) - connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) + connection_type_socket_file_extras_layout.addWidget( + connection_type_socket_file_extras_label + ) + connection_type_socket_file_extras_layout.addWidget( + self.connection_type_socket_file_extras_path + ) self.connection_type_socket_file_extras = QtWidgets.QWidget() - self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) + self.connection_type_socket_file_extras.setLayout( + connection_type_socket_file_extras_layout + ) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port - gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label')) + gui_settings_socks_label = QtWidgets.QLabel( + strings._("gui_settings_socks_label") + ) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() @@ -428,18 +578,32 @@ class SettingsDialog(QtWidgets.QDialog): # Authentication options # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option')) - self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) + self.authenticate_no_auth_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_no_auth_option") + ) + self.authenticate_no_auth_radio.toggled.connect( + self.authenticate_no_auth_toggled + ) # Password - self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option')) - self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) + self.authenticate_password_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_password_option") + ) + self.authenticate_password_radio.toggled.connect( + self.authenticate_password_toggled + ) - authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label')) - self.authenticate_password_extras_password = QtWidgets.QLineEdit('') + authenticate_password_extras_label = QtWidgets.QLabel( + strings._("gui_settings_password_label") + ) + self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() - authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) - authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) + authenticate_password_extras_layout.addWidget( + authenticate_password_extras_label + ) + authenticate_password_extras_layout.addWidget( + self.authenticate_password_extras_password + ) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) @@ -450,27 +614,43 @@ class SettingsDialog(QtWidgets.QDialog): authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label")) + self.authenticate_group = QtWidgets.QGroupBox( + strings._("gui_settings_authenticate_label") + ) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) - connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label")) + connection_type_radio_group_layout.addWidget( + self.connection_type_automatic_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_control_port_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_socket_file_radio + ) + connection_type_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_connection_type_label") + ) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) - self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges")) - self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) + self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_tor_bridges") + ) + self.connection_type_bridges_radio_group.setLayout( + connection_type_bridges_radio_group_layout + ) self.connection_type_bridges_radio_group.hide() # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button')) + self.connection_type_test_button = QtWidgets.QPushButton( + strings._("gui_settings_connection_type_test_button") + ) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget(self.connection_type_test_button) @@ -486,13 +666,15 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons - self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save')) + self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) - self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel')) + self.cancel_button = QtWidgets.QPushButton( + strings._("gui_settings_button_cancel") + ) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) - version_label.setStyleSheet(self.common.css['settings_version']) - self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help')) + version_label = QtWidgets.QLabel("OnionShare {0:s}".format(self.common.version)) + version_label.setStyleSheet(self.common.css["settings_version"]) + self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) @@ -503,7 +685,7 @@ class SettingsDialog(QtWidgets.QDialog): # Tor network connection status self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet(self.common.css['settings_tor_status']) + self.tor_status.setStyleSheet(self.common.css["settings_tor_status"]) self.tor_status.hide() # Layout @@ -542,37 +724,37 @@ class SettingsDialog(QtWidgets.QDialog): self.old_settings = Settings(self.common, self.config) self.old_settings.load() - close_after_first_download = self.old_settings.get('close_after_first_download') + close_after_first_download = self.old_settings.get("close_after_first_download") if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - csp_header_disabled = self.old_settings.get('csp_header_disabled') + csp_header_disabled = self.old_settings.get("csp_header_disabled") if csp_header_disabled: self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Checked) else: self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostart_timer = self.old_settings.get('autostart_timer') + autostart_timer = self.old_settings.get("autostart_timer") if autostart_timer: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostop_timer = self.old_settings.get('autostop_timer') + autostop_timer = self.old_settings.get("autostop_timer") if autostop_timer: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - save_private_key = self.old_settings.get('save_private_key') + save_private_key = self.old_settings.get("save_private_key") if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + use_legacy_v2_onions = self.old_settings.get("use_legacy_v2_onions") if use_legacy_v2_onions: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) @@ -580,84 +762,102 @@ class SettingsDialog(QtWidgets.QDialog): else: self.use_stealth_widget.hide() - data_dir = self.old_settings.get('data_dir') + data_dir = self.old_settings.get("data_dir") self.data_dir_lineedit.setText(data_dir) - public_mode = self.old_settings.get('public_mode') + public_mode = self.old_settings.get("public_mode") if public_mode: self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked) else: self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_stealth = self.old_settings.get('use_stealth') + use_stealth = self.old_settings.get("use_stealth") if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) - if save_private_key and self.old_settings.get('hidservauth_string') != "": + if save_private_key and self.old_settings.get("hidservauth_string") != "": self.hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_autoupdate = self.old_settings.get('use_autoupdate') + use_autoupdate = self.old_settings.get("use_autoupdate") if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') + autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) - locale = self.old_settings.get('locale') + locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(QtCore.QVariant(locale)) self.language_combobox.setCurrentIndex(locale_index) - connection_type = self.old_settings.get('connection_type') - if connection_type == 'bundled': + connection_type = self.old_settings.get("connection_type") + if connection_type == "bundled": if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'automatic': + elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'control_port': + elif connection_type == "control_port": self.connection_type_control_port_radio.setChecked(True) - elif connection_type == 'socket_file': + elif connection_type == "socket_file": self.connection_type_socket_file_radio.setChecked(True) - self.connection_type_control_port_extras_address.setText(self.old_settings.get('control_port_address')) - self.connection_type_control_port_extras_port.setText(str(self.old_settings.get('control_port_port'))) - self.connection_type_socket_file_extras_path.setText(self.old_settings.get('socket_file_path')) - self.connection_type_socks_address.setText(self.old_settings.get('socks_address')) - self.connection_type_socks_port.setText(str(self.old_settings.get('socks_port'))) - auth_type = self.old_settings.get('auth_type') - if auth_type == 'no_auth': + self.connection_type_control_port_extras_address.setText( + self.old_settings.get("control_port_address") + ) + self.connection_type_control_port_extras_port.setText( + str(self.old_settings.get("control_port_port")) + ) + self.connection_type_socket_file_extras_path.setText( + self.old_settings.get("socket_file_path") + ) + self.connection_type_socks_address.setText( + self.old_settings.get("socks_address") + ) + self.connection_type_socks_port.setText( + str(self.old_settings.get("socks_port")) + ) + auth_type = self.old_settings.get("auth_type") + if auth_type == "no_auth": self.authenticate_no_auth_radio.setChecked(True) - elif auth_type == 'password': + elif auth_type == "password": self.authenticate_password_radio.setChecked(True) - self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) + self.authenticate_password_extras_password.setText( + self.old_settings.get("auth_password") + ) - if self.old_settings.get('no_bridges'): + if self.old_settings.get("no_bridges"): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) - self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4')) - self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure')) + self.tor_bridges_use_obfs4_radio.setChecked( + self.old_settings.get("tor_bridges_use_obfs4") + ) + self.tor_bridges_use_meek_lite_azure_radio.setChecked( + self.old_settings.get("tor_bridges_use_meek_lite_azure") + ) - if self.old_settings.get('tor_bridges_use_custom_bridges'): + if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] - bridges = self.old_settings.get('tor_bridges_use_custom_bridges').split('Bridge ') + bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( + "Bridge " + ) for bridge in bridges: new_bridges.append(bridge) - new_bridges = ''.join(new_bridges) + new_bridges = "".join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) # If we're connected to Tor, show onion service settings, show label if not @@ -667,10 +867,12 @@ class SettingsDialog(QtWidgets.QDialog): # If v3 onion services are supported, allow using legacy mode if self.onion.supports_v3_onions: - self.common.log('SettingsDialog', '__init__', 'v3 onions are supported') + self.common.log("SettingsDialog", "__init__", "v3 onions are supported") self.use_legacy_v2_onions_checkbox.show() else: - self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported') + self.common.log( + "SettingsDialog", "__init__", "v3 onions are not supported" + ) self.use_legacy_v2_onions_widget.hide() self.use_legacy_v2_onions_checkbox_clicked(True) else: @@ -681,7 +883,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type bundled was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_bundled_toggled') + self.common.log("SettingsDialog", "connection_type_bundled_toggled") if self.hide_tor_settings: return if checked: @@ -716,8 +918,12 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on - if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): - Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning) + if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): + Alert( + self.common, + strings._("gui_settings_meek_lite_expensive_warning"), + QtWidgets.QMessageBox.Warning, + ) def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -732,7 +938,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_automatic_toggled') + self.common.log("SettingsDialog", "connection_type_automatic_toggled") if self.hide_tor_settings: return if checked: @@ -745,7 +951,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. """ - self.common.log('SettingsDialog', 'connection_type_control_port_toggled') + self.common.log("SettingsDialog", "connection_type_control_port_toggled") if self.hide_tor_settings: return if checked: @@ -756,13 +962,12 @@ class SettingsDialog(QtWidgets.QDialog): else: self.connection_type_control_port_extras.hide() - def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') + self.common.log("SettingsDialog", "connection_type_socket_file_toggled") if self.hide_tor_settings: return if checked: @@ -777,14 +982,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - self.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. """ - self.common.log('SettingsDialog', 'authenticate_password_toggled') + self.common.log("SettingsDialog", "authenticate_password_toggled") if checked: self.authenticate_password_extras.show() else: @@ -795,9 +1000,13 @@ class SettingsDialog(QtWidgets.QDialog): Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ - self.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')) + clipboard.setText(self.old_settings.get("hidservauth_string")) def use_legacy_v2_onions_checkbox_clicked(self, checked): """ @@ -823,11 +1032,16 @@ class SettingsDialog(QtWidgets.QDialog): Browse for a new OnionShare data directory """ data_dir = self.data_dir_lineedit.text() - selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_data_dir_label'), data_dir) + selected_dir = QtWidgets.QFileDialog.getExistingDirectory( + self, strings._("gui_settings_data_dir_label"), data_dir + ) if selected_dir: - self.common.log('SettingsDialog', 'data_dir_button_clicked', 'selected dir: {}'.format(selected_dir)) + self.common.log( + "SettingsDialog", + "data_dir_button_clicked", + "selected dir: {}".format(selected_dir), + ) self.data_dir_lineedit.setText(selected_dir) def test_tor_clicked(self): @@ -835,33 +1049,57 @@ class SettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - self.common.log('SettingsDialog', 'test_tor_clicked') + self.common.log("SettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True + else: tor_status_update_func = None onion = Onion(self.common) - onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) + onion.connect( + custom_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(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions)) + Alert( + self.common, + strings._("settings_test_success").format( + onion.tor_version, + onion.supports_ephemeral, + onion.supports_stealth, + onion.supports_v3_onions, + ), + ) # Clean up onion.cleanup() - except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorNotSupported, + BundledTorTimeout, + ) as e: Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() @@ -869,7 +1107,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - self.common.log('SettingsDialog', 'check_for_updates') + self.common.log("SettingsDialog", "check_for_updates") # Disable buttons self._disable_buttons() self.qtapp.processEvents() @@ -878,7 +1116,7 @@ class SettingsDialog(QtWidgets.QDialog): # Update the last checked label settings = Settings(self.common, self.config) settings.load() - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): @@ -890,22 +1128,37 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) close_forced_update_thread() def update_not_available(): - Alert(self.common, strings._('update_not_available')) + Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): - Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_check_error"), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() def update_invalid_version(latest_version): - Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_invalid_latest_version").format(latest_version), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() - forced_update_thread = UpdateThread(self.common, 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) @@ -916,7 +1169,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - self.common.log('SettingsDialog', 'save_clicked') + self.common.log("SettingsDialog", "save_clicked") def changed(s1, s2, keys): """ @@ -931,13 +1184,19 @@ class SettingsDialog(QtWidgets.QDialog): settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare - if changed(settings, self.old_settings, ['locale']): + if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale - new_locale = settings.get('locale') - if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]: - notice = strings.translations[new_locale]['gui_settings_language_changed_notice'] + new_locale = settings.get("locale") + if ( + new_locale in strings.translations + and "gui_settings_language_changed_notice" + in strings.translations[new_locale] + ): + notice = strings.translations[new_locale][ + "gui_settings_language_changed_notice" + ] else: - notice = strings._('gui_settings_language_changed_notice') + notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings @@ -948,33 +1207,58 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = False if not self.local_only: if self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + self.common.log( + "SettingsDialog", "save_clicked", "Connected to Tor" + ) - if changed(settings, self.old_settings, [ - 'connection_type', 'control_port_address', - 'control_port_port', 'socks_address', 'socks_port', - 'socket_file_path', 'auth_type', 'auth_password', - 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_azure', - 'tor_bridges_use_custom_bridges']): + if changed( + settings, + self.old_settings, + [ + "connection_type", + "control_port_address", + "control_port_port", + "socks_address", + "socks_port", + "socket_file_path", + "auth_type", + "auth_password", + "no_bridges", + "tor_bridges_use_obfs4", + "tor_bridges_use_meek_lite_azure", + "tor_bridges_use_custom_bridges", + ], + ): reboot_onion = True else: - self.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 - self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log( + "SettingsDialog", "save_clicked", "rebooting the Onion" + ) self.onion.cleanup() - tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) + tor_con = TorConnectionDialog( + self.common, self.qtapp, self.onion, settings + ) tor_con.start() - self.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() @@ -991,9 +1275,13 @@ class SettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - self.common.log('SettingsDialog', 'cancel_clicked') + self.common.log("SettingsDialog", "cancel_clicked") if not self.local_only and not self.onion.is_authenticated(): - Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("gui_tor_connection_canceled"), + QtWidgets.QMessageBox.Warning, + ) sys.exit() else: self.close() @@ -1002,26 +1290,31 @@ class SettingsDialog(QtWidgets.QDialog): """ Help button clicked. """ - self.common.log('SettingsDialog', 'help_clicked') + self.common.log("SettingsDialog", "help_clicked") SettingsDialog.open_help() @staticmethod def open_help(): - help_url = 'https://github.com/micahflee/onionshare/wiki' + help_url = "https://github.com/micahflee/onionshare/wiki" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ - self.common.log('SettingsDialog', 'settings_from_fields') + self.common.log("SettingsDialog", "settings_from_fields") settings = Settings(self.common, self.config) - settings.load() # To get the last update timestamp + settings.load() # To get the last update timestamp - settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) - settings.set('csp_header_disabled', self.csp_header_disabled_checkbox.isChecked()) - settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) - settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) + settings.set( + "close_after_first_download", + self.close_after_first_download_checkbox.isChecked(), + ) + settings.set( + "csp_header_disabled", self.csp_header_disabled_checkbox.isChecked() + ) + settings.set("autostart_timer", self.autostart_timer_checkbox.isChecked()) + settings.set("autostop_timer", self.autostop_timer_checkbox.isChecked()) # Complicated logic here to force v2 onion mode on or off depending on other settings if self.use_legacy_v2_onions_checkbox.isChecked(): @@ -1030,142 +1323,167 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = False if self.save_private_key_checkbox.isChecked(): - settings.set('save_private_key', True) - settings.set('private_key', self.old_settings.get('private_key')) - settings.set('password', self.old_settings.get('password')) - settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) + settings.set("save_private_key", True) + settings.set("private_key", self.old_settings.get("private_key")) + settings.set("password", self.old_settings.get("password")) + settings.set( + "hidservauth_string", self.old_settings.get("hidservauth_string") + ) else: - settings.set('save_private_key', False) - settings.set('private_key', '') - settings.set('password', '') + settings.set("save_private_key", False) + settings.set("private_key", "") + settings.set("password", "") # Also unset the HidServAuth if we are removing our reusable private key - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") if use_legacy_v2_onions: - settings.set('use_legacy_v2_onions', True) + settings.set("use_legacy_v2_onions", True) else: - settings.set('use_legacy_v2_onions', False) + settings.set("use_legacy_v2_onions", False) - settings.set('data_dir', self.data_dir_lineedit.text()) - settings.set('public_mode', self.public_mode_checkbox.isChecked()) - settings.set('use_stealth', self.stealth_checkbox.isChecked()) + settings.set("data_dir", self.data_dir_lineedit.text()) + settings.set("public_mode", self.public_mode_checkbox.isChecked()) + settings.set("use_stealth", self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) - settings.set('locale', locale) + settings.set("locale", locale) # Tor connection if self.connection_type_bundled_radio.isChecked(): - settings.set('connection_type', 'bundled') + settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): - settings.set('connection_type', 'automatic') + settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): - settings.set('connection_type', 'control_port') + settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): - settings.set('connection_type', 'socket_file') + settings.set("connection_type", "socket_file") if self.autoupdate_checkbox.isChecked(): - settings.set('use_autoupdate', True) + settings.set("use_autoupdate", True) else: - settings.set('use_autoupdate', False) + settings.set("use_autoupdate", False) - settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) - settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) - settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) + settings.set( + "control_port_address", + self.connection_type_control_port_extras_address.text(), + ) + settings.set( + "control_port_port", self.connection_type_control_port_extras_port.text() + ) + settings.set( + "socket_file_path", self.connection_type_socket_file_extras_path.text() + ) - settings.set('socks_address', self.connection_type_socks_address.text()) - settings.set('socks_port', self.connection_type_socks_port.text()) + settings.set("socks_address", self.connection_type_socks_address.text()) + settings.set("socks_port", self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): - settings.set('auth_type', 'no_auth') + settings.set("auth_type", "no_auth") if self.authenticate_password_radio.isChecked(): - settings.set('auth_type', 'password') + settings.set("auth_type", "password") - settings.set('auth_password', self.authenticate_password_extras_password.text()) + settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set('no_bridges', True) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", True) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', True) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", True) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', True) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] - bridges = self.tor_bridges_use_custom_textbox.toPlainText().split('\n') + bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") bridges_valid = False for bridge in bridges: - if bridge != '': + if bridge != "": # Check the syntax of the custom bridge to make sure it looks legitimate - ipv4_pattern = re.compile("(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$") - ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$") - meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)") - if ipv4_pattern.match(bridge) or \ - ipv6_pattern.match(bridge) or \ - meek_lite_pattern.match(bridge): - new_bridges.append(''.join(['Bridge ', bridge, '\n'])) + ipv4_pattern = re.compile( + "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" + ) + ipv6_pattern = re.compile( + "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" + ) + meek_lite_pattern = re.compile( + "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" + ) + if ( + ipv4_pattern.match(bridge) + or ipv6_pattern.match(bridge) + or meek_lite_pattern.match(bridge) + ): + new_bridges.append("".join(["Bridge ", bridge, "\n"])) bridges_valid = True if bridges_valid: - new_bridges = ''.join(new_bridges) - settings.set('tor_bridges_use_custom_bridges', new_bridges) + new_bridges = "".join(new_bridges) + settings.set("tor_bridges_use_custom_bridges", new_bridges) else: - Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) - settings.set('no_bridges', True) + Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) + settings.set("no_bridges", True) return False return settings def closeEvent(self, e): - self.common.log('SettingsDialog', 'closeEvent') + self.common.log("SettingsDialog", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.local_only: if not self.onion.is_authenticated(): - self.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): - self.common.log('SettingsDialog', '_update_autoupdate_timestamp') + self.common.log("SettingsDialog", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) - last_checked = dt.strftime('%B %d, %Y %H:%M') + last_checked = dt.strftime("%B %d, %Y %H:%M") else: - last_checked = strings._('gui_settings_autoupdate_timestamp_never') - self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked)) + last_checked = strings._("gui_settings_autoupdate_timestamp_never") + self.autoupdate_timestamp.setText( + strings._("gui_settings_autoupdate_timestamp").format(last_checked) + ) def _tor_status_update(self, progress, summary): - self.tor_status.setText('{}
{}% {}'.format(strings._('connecting_to_tor'), progress, summary)) + self.tor_status.setText( + "{}
{}% {}".format( + strings._("connecting_to_tor"), progress, summary + ) + ) self.qtapp.processEvents() - if 'Done' in summary: + if "Done" in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): - 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) @@ -1173,7 +1491,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setEnabled(False) def _enable_buttons(self): - 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/threads.py b/onionshare_gui/threads.py index 57e0f0af..090574c1 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -27,6 +27,7 @@ class OnionThread(QtCore.QThread): """ Starts the onion service, and waits for it to finish """ + success = QtCore.pyqtSignal() success_early = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) @@ -34,28 +35,34 @@ class OnionThread(QtCore.QThread): def __init__(self, mode): super(OnionThread, self).__init__() self.mode = mode - self.mode.common.log('OnionThread', '__init__') + self.mode.common.log("OnionThread", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): - self.mode.common.log('OnionThread', 'run') + self.mode.common.log("OnionThread", "run") # Make a new static URL path for each new share self.mode.web.generate_static_url_path() # Choose port and password early, because we need them to exist in advance for scheduled shares - self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') + self.mode.app.stay_open = not self.mode.common.settings.get( + "close_after_first_download" + ) if not self.mode.app.port: self.mode.app.choose_port() - if not self.mode.common.settings.get('public_mode'): + if not self.mode.common.settings.get("public_mode"): if not self.mode.web.password: - self.mode.web.generate_password(self.mode.common.settings.get('password')) + self.mode.web.generate_password( + self.mode.common.settings.get("password") + ) try: if self.mode.obtain_onion_early: - self.mode.app.start_onion_service(await_publication=False, save_scheduled_key=True) + self.mode.app.start_onion_service( + await_publication=False, save_scheduled_key=True + ) # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) self.success_early.emit() @@ -70,7 +77,19 @@ class OnionThread(QtCore.QThread): self.mode.web_thread.start() self.success.emit() - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + except ( + TorTooOld, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + OSError, + ) as e: self.error.emit(e.args[0]) return @@ -79,17 +98,23 @@ class WebThread(QtCore.QThread): """ Starts the web service """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(WebThread, self).__init__() self.mode = mode - self.mode.common.log('WebThread', '__init__') + self.mode.common.log("WebThread", "__init__") def run(self): - self.mode.common.log('WebThread', 'run') - self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.password) + self.mode.common.log("WebThread", "run") + self.mode.web.start( + self.mode.app.port, + self.mode.app.stay_open, + self.mode.common.settings.get("public_mode"), + self.mode.web.password, + ) self.success.emit() @@ -97,30 +122,40 @@ class AutoStartTimer(QtCore.QThread): """ Waits for a prescribed time before allowing a share to start """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) + def __init__(self, mode, canceled=False): super(AutoStartTimer, self).__init__() self.mode = mode self.canceled = canceled - self.mode.common.log('AutoStartTimer', '__init__') + self.mode.common.log("AutoStartTimer", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) try: # Sleep until scheduled time while autostart_timer_datetime_delta > 0 and self.canceled == False: time.sleep(0.1) now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) # Timer has now finished if self.canceled == False: - self.mode.server_status.server_button.setText(strings._('gui_please_wait')) - self.mode.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.mode.server_status.server_button.setText( + strings._("gui_please_wait") + ) + self.mode.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) self.success.emit() except ValueError as e: self.error.emit(e.args[0]) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 2bcbf1a6..58fc01d0 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -24,10 +24,12 @@ from onionshare.onion import * from .widgets import Alert + class TorConnectionDialog(QtWidgets.QProgressDialog): """ Connecting to Tor dialog. """ + open_settings = QtCore.pyqtSignal() def __init__(self, common, qtapp, onion, custom_settings=False): @@ -40,18 +42,20 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): else: self.settings = self.common.settings - self.common.log('TorConnectionDialog', '__init__') + self.common.log("TorConnectionDialog", "__init__") self.qtapp = qtapp self.onion = onion self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.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) # Label - self.setLabelText(strings._('connecting_to_tor')) + self.setLabelText(strings._("connecting_to_tor")) # Progress bar ticks from 0 to 100 self.setRange(0, 100) @@ -59,10 +63,10 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setMinimumDuration(100) # Start displaying the status at 0 - self._tor_status_update(0, '') + self._tor_status_update(0, "") def start(self): - self.common.log('TorConnectionDialog', 'start') + self.common.log("TorConnectionDialog", "start") t = TorConnectionThread(self.common, self.settings, self, self.onion) t.tor_status_update.connect(self._tor_status_update) @@ -81,17 +85,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _tor_status_update(self, progress, summary): self.setValue(int(progress)) - self.setLabelText("{}
{}".format(strings._('connecting_to_tor'), summary)) + self.setLabelText( + "{}
{}".format(strings._("connecting_to_tor"), summary) + ) def _connected_to_tor(self): - 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): - self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor') + self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor") self.active = False self.onion.cleanup() @@ -99,12 +105,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): - self.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(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + "{}\n\n{}".format(msg, strings._("gui_tor_connection_error_settings")), + QtWidgets.QMessageBox.Warning, + ) # Open settings self.open_settings.emit() @@ -114,6 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): # Cancel connecting to Tor QtCore.QTimer.singleShot(1, self.cancel) + class TorConnectionThread(QtCore.QThread): tor_status_update = QtCore.pyqtSignal(str, str) connected_to_tor = QtCore.pyqtSignal() @@ -125,7 +136,7 @@ class TorConnectionThread(QtCore.QThread): self.common = common - self.common.log('TorConnectionThread', '__init__') + self.common.log("TorConnectionThread", "__init__") self.settings = settings @@ -133,7 +144,7 @@ class TorConnectionThread(QtCore.QThread): self.onion = onion def run(self): - self.common.log('TorConnectionThread', 'run') + self.common.log("TorConnectionThread", "run") # Connect to the Onion try: @@ -144,11 +155,15 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: - self.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: - self.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 1e37b73a..a8bd7979 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -27,21 +27,26 @@ from onionshare.onion import Onion from onionshare import strings + class UpdateCheckerCheckError(Exception): """ Error checking for updates because of some Tor connection issue, or because the OnionShare website is down. """ + pass + class UpdateCheckerInvalidLatestVersion(Exception): """ Successfully downloaded the latest version, but it doesn't appear to be a valid version string. """ + def __init__(self, latest_version): self.latest_version = latest_version + class UpdateChecker(QtCore.QObject): """ Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest @@ -50,6 +55,7 @@ class UpdateChecker(QtCore.QObject): Only check at most once per day, unless force is True. """ + update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal() @@ -60,12 +66,12 @@ class UpdateChecker(QtCore.QObject): self.common = common - self.common.log('UpdateChecker', '__init__') + self.common.log("UpdateChecker", "__init__") self.onion = onion self.config = config def check(self, force=False, config=False): - self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) + self.common.log("UpdateChecker", "check", "force={}".format(force)) # Load the settings settings = Settings(self.common, config) settings.load() @@ -77,7 +83,7 @@ class UpdateChecker(QtCore.QObject): check_for_updates = False # See if it's been 1 day since the last check - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp) now = datetime.datetime.now() @@ -90,45 +96,61 @@ class UpdateChecker(QtCore.QObject): # Check for updates if check_for_updates: - self.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(self.common.version, self.common.platform) + 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 - path = '/latest-version.txt' + path = "/latest-version.txt" if force: - path += '?force=1' + path += "?force=1" - if Version(self.onion.tor_version) >= Version('0.3.2.9'): - onion_domain = 'lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion' + if Version(self.onion.tor_version) >= Version("0.3.2.9"): + onion_domain = ( + "lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion" + ) else: - onion_domain = 'elx57ue5uyfplgva.onion' + onion_domain = "elx57ue5uyfplgva.onion" - self.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) s = socks.socksocket() - s.settimeout(15) # 15 second timeout + s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(onion_domain) - http_request += 'User-Agent: {}\r\n'.format(user_agent) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) + http_request = "GET {} HTTP/1.0\r\n".format(path) + http_request += "Host: {}\r\n".format(onion_domain) + http_request += "User-Agent: {}\r\n".format(user_agent) + http_request += "\r\n" + s.sendall(http_request.encode("utf-8")) http_response = s.recv(1024) - latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') + latest_version = ( + http_response[http_response.find(b"\r\n\r\n") :] + .strip() + .decode("utf-8") + ) - self.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: - self.common.log('UpdateChecker', 'check', '{}'.format(e)) + self.common.log("UpdateChecker", "check", "{}".format(e)) self.update_error.emit() raise UpdateCheckerCheckError @@ -140,22 +162,32 @@ class UpdateChecker(QtCore.QObject): raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) - timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp() + timestamp = ( + datetime.datetime.now() + .replace(microsecond=0) + .replace(second=0) + .timestamp() + ) # Re-load the settings first before saving, just in case they've changed since we started our thread settings.load() - settings.set('autoupdate_timestamp', timestamp) + settings.set("autoupdate_timestamp", timestamp) settings.save() # Do we need to update? - update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) + update_url = "https://github.com/micahflee/onionshare/releases/tag/v{}".format( + latest_version + ) installed_version = self.common.version if installed_version < latest_version: - self.update_available.emit(update_url, installed_version, latest_version) + self.update_available.emit( + update_url, installed_version, latest_version + ) return # No updates are available self.update_not_available.emit() + class UpdateThread(QtCore.QThread): update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() @@ -167,13 +199,13 @@ class UpdateThread(QtCore.QThread): self.common = common - self.common.log('UpdateThread', '__init__') + self.common.log("UpdateThread", "__init__") self.onion = onion self.config = config self.force = force def run(self): - self.common.log('UpdateThread', 'run') + self.common.log("UpdateThread", "run") u = UpdateChecker(self.common, self.onion, self.config) u.update_available.connect(self._update_available) @@ -182,28 +214,28 @@ class UpdateThread(QtCore.QThread): u.update_invalid_version.connect(self._update_invalid_version) try: - u.check(config=self.config,force=self.force) + u.check(config=self.config, force=self.force) except Exception as e: # If update check fails, silently ignore - self.common.log('UpdateThread', 'run', '{}'.format(e)) + self.common.log("UpdateThread", "run", "{}".format(e)) pass def _update_available(self, update_url, installed_version, latest_version): - self.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): - 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): - self.common.log('UpdateThread', '_update_error') + self.common.log("UpdateThread", "_update_error") self.active = False self.update_error.emit() def _update_invalid_version(self, latest_version): - self.common.log('UpdateThread', '_update_invalid_version') + self.common.log("UpdateThread", "_update_invalid_version") self.active = False self.update_invalid_version.emit(latest_version) diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py index 600165aa..d16485fe 100644 --- a/onionshare_gui/widgets.py +++ b/onionshare_gui/widgets.py @@ -19,19 +19,30 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui + class Alert(QtWidgets.QMessageBox): """ An alert box dialog. """ - def __init__(self, common, 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) self.common = common - self.common.log('Alert', '__init__') + self.common.log("Alert", "__init__") self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.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) @@ -49,11 +60,12 @@ class AddFileDialog(QtWidgets.QFileDialog): This is because the macOS sandbox requires native dialogs, and this is a Qt5 dialog. """ + def __init__(self, common, *args, **kwargs): QtWidgets.QFileDialog.__init__(self, *args, **kwargs) self.common = common - self.common.log('AddFileDialog', '__init__') + self.common.log("AddFileDialog", "__init__") self.setOption(self.DontUseNativeDialog, True) self.setOption(self.ReadOnly, True) @@ -65,5 +77,5 @@ class AddFileDialog(QtWidgets.QFileDialog): list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def accept(self): - self.common.log('AddFileDialog', 'accept') + self.common.log("AddFileDialog", "accept") QtWidgets.QDialog.accept(self) diff --git a/setup.py b/setup.py index 608d4088..9af72fc1 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ along with this program. If not, see . import os, sys, platform, tempfile from distutils.core import setup + def file_list(path): files = [] for filename in os.listdir(path): @@ -29,7 +30,8 @@ def file_list(path): files.append(os.path.join(path, filename)) return files -version = open('share/version.txt').read().strip() + +version = open("share/version.txt").read().strip() description = ( """OnionShare lets you securely and anonymously send and receive files. It """ """works by starting a web server, making it accessible as a Tor onion """ @@ -37,61 +39,99 @@ description = ( """files from you, or upload files to you. It does _not_ require setting up """ """a separate server or using a third party file-sharing service.""" ) -long_description = description + "\n\n" + ( - """If you want to send files to someone, OnionShare hosts them on your own """ - """computer and uses a Tor onion service to make them temporarily accessible """ - """over the internet. The receiving user just needs to open the web address """ - """in Tor Browser to download the files. If you want to receive files, """ - """OnionShare hosts an anonymous dropbox directly on your computer and uses """ - """a Tor onion service to make it temporarily accessible over the internet. """ - """Other users can upload files to you from by loading the web address in """ - """Tor Browser.""" +long_description = ( + description + + "\n\n" + + ( + """If you want to send files to someone, OnionShare hosts them on your own """ + """computer and uses a Tor onion service to make them temporarily accessible """ + """over the internet. The receiving user just needs to open the web address """ + """in Tor Browser to download the files. If you want to receive files, """ + """OnionShare hosts an anonymous dropbox directly on your computer and uses """ + """a Tor onion service to make it temporarily accessible over the internet. """ + """Other users can upload files to you from by loading the web address in """ + """Tor Browser.""" + ) ) -author = 'Micah Lee' -author_email = 'micah@micahflee.com' -url = 'https://github.com/micahflee/onionshare' -license = 'GPL v3' -keywords = 'onion, share, onionshare, tor, anonymous, web server' +author = "Micah Lee" +author_email = "micah@micahflee.com" +url = "https://github.com/micahflee/onionshare" +license = "GPL v3" +keywords = "onion, share, onionshare, tor, anonymous, web server" classifiers = [ - "Programming Language :: Python :: 3", - "Framework :: Flask", - "Topic :: Communications :: File Sharing", - "Topic :: Security :: Cryptography", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Intended Audience :: End Users/Desktop", - "Operating System :: OS Independent", - "Environment :: Web Environment" - ] -data_files=[ - (os.path.join(sys.prefix, 'share/applications'), ['install/org.onionshare.OnionShare.desktop']), - (os.path.join(sys.prefix, 'share/icons/hicolor/scalable/apps'), ['install/org.onionshare.OnionShare.svg']), - (os.path.join(sys.prefix, 'share/metainfo'), ['install/org.onionshare.OnionShare.appdata.xml']), - (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), - (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), - (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')), - (os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')), - (os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')), - (os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')), - (os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js')) - ] -if not platform.system().endswith('BSD') and platform.system() != 'DragonFly': - data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])) + "Programming Language :: Python :: 3", + "Framework :: Flask", + "Topic :: Communications :: File Sharing", + "Topic :: Security :: Cryptography", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Intended Audience :: End Users/Desktop", + "Operating System :: OS Independent", + "Environment :: Web Environment", +] +data_files = [ + ( + os.path.join(sys.prefix, "share/applications"), + ["install/org.onionshare.OnionShare.desktop"], + ), + ( + os.path.join(sys.prefix, "share/icons/hicolor/scalable/apps"), + ["install/org.onionshare.OnionShare.svg"], + ), + ( + os.path.join(sys.prefix, "share/metainfo"), + ["install/org.onionshare.OnionShare.appdata.xml"], + ), + (os.path.join(sys.prefix, "share/onionshare"), file_list("share")), + (os.path.join(sys.prefix, "share/onionshare/images"), file_list("share/images")), + (os.path.join(sys.prefix, "share/onionshare/locale"), file_list("share/locale")), + ( + os.path.join(sys.prefix, "share/onionshare/templates"), + file_list("share/templates"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/css"), + file_list("share/static/css"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/img"), + file_list("share/static/img"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/js"), + file_list("share/static/js"), + ), +] +if not platform.system().endswith("BSD") and platform.system() != "DragonFly": + data_files.append( + ( + "/usr/share/nautilus-python/extensions/", + ["install/scripts/onionshare-nautilus.py"], + ) + ) setup( - name='onionshare', version=version, - description=description, long_description=long_description, - author=author, author_email=author_email, maintainer=author, maintainer_email=author_email, - url=url, license=license, keywords=keywords, classifiers=classifiers, + name="onionshare", + version=version, + description=description, + long_description=long_description, + author=author, + author_email=author_email, + maintainer=author, + maintainer_email=author_email, + url=url, + license=license, + keywords=keywords, + classifiers=classifiers, packages=[ - 'onionshare', - 'onionshare.web', - 'onionshare_gui', - 'onionshare_gui.mode', - 'onionshare_gui.mode.share_mode', - 'onionshare_gui.mode.receive_mode', - 'onionshare_gui.mode.website_mode' + "onionshare", + "onionshare.web", + "onionshare_gui", + "onionshare_gui.mode", + "onionshare_gui.mode.share_mode", + "onionshare_gui.mode.receive_mode", + "onionshare_gui.mode.website_mode", ], include_package_data=True, - scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'], - data_files=data_files + scripts=["install/scripts/onionshare", "install/scripts/onionshare-gui"], + data_files=data_files, ) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 3e82769a..6d6340d1 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -20,17 +20,17 @@ from onionshare_gui.mode.website_mode import WebsiteMode class GuiBaseTest(object): @staticmethod def set_up(test_settings): - '''Create GUI with given settings''' + """Create GUI with given settings""" # Create our test file - testfile = open('/tmp/test.txt', 'w') - testfile.write('onionshare') + testfile = open("/tmp/test.txt", "w") + testfile.write("onionshare") testfile.close() # Create a test dir and files - if not os.path.exists('/tmp/testdir'): - testdir = os.mkdir('/tmp/testdir') - testfile = open('/tmp/testdir/test', 'w') - testfile.write('onionshare') + if not os.path.exists("/tmp/testdir"): + testdir = os.mkdir("/tmp/testdir") + testfile = open("/tmp/testdir/test", "w") + testfile.write("onionshare") testfile.close() common = Common() @@ -39,7 +39,7 @@ class GuiBaseTest(object): strings.load_strings(common) # Get all of the settings in test_settings - test_settings['data_dir'] = '/tmp/OnionShare' + test_settings["data_dir"] = "/tmp/OnionShare" for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val @@ -51,53 +51,55 @@ class GuiBaseTest(object): app = OnionShare(common, testonion, True, 0) web = Web(common, False, True) - open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) + open("/tmp/settings.json", "w").write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', True) + gui = OnionShareGui( + common, + testonion, + qtapp, + app, + ["/tmp/test.txt", "/tmp/testdir"], + "/tmp/settings.json", + True, + ) return gui @staticmethod def tear_down(): - '''Clean up after tests''' + """Clean up after tests""" try: - os.remove('/tmp/test.txt') - os.remove('/tmp/settings.json') - os.remove('/tmp/large_file') - os.remove('/tmp/download.zip') - os.remove('/tmp/webpage') - shutil.rmtree('/tmp/testdir') - shutil.rmtree('/tmp/OnionShare') + os.remove("/tmp/test.txt") + os.remove("/tmp/settings.json") + os.remove("/tmp/large_file") + os.remove("/tmp/download.zip") + os.remove("/tmp/webpage") + shutil.rmtree("/tmp/testdir") + shutil.rmtree("/tmp/OnionShare") except: pass - def gui_loaded(self): - '''Test that the GUI actually is shown''' + """Test that the GUI actually is shown""" self.assertTrue(self.gui.show) - def windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - + """Test that the window title is OnionShare""" + self.assertEqual(self.gui.windowTitle(), "OnionShare") def settings_button_is_visible(self): - '''Test that the settings button is visible''' + """Test that the settings button is visible""" self.assertTrue(self.gui.settings_button.isVisible()) - def settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' + """Test that the settings button is hidden when the server starts""" self.assertFalse(self.gui.settings_button.isVisible()) - def server_status_bar_is_visible(self): - '''Test that the status bar is visible''' + """Test that the status bar is visible""" self.assertTrue(self.gui.status_bar.isVisible()) - def click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' + """Test that we can switch Mode by clicking the button""" if type(mode) == ReceiveMode: QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) @@ -108,16 +110,14 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(self.gui.website_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_WEBSITE) - def click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' + """Test that we can toggle Download or Upload history by clicking the toggle button""" currently_visible = mode.history.isVisible() QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertEqual(mode.history.isVisible(), not currently_visible) - def history_indicator(self, mode, public_mode, indicator_count="1"): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + """Test that we can make sure the history is toggled off, do an action, and the indiciator works""" # Make sure history is toggled off if mode.history.isVisible(): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) @@ -128,12 +128,16 @@ class GuiBaseTest(object): if type(mode) == ReceiveMode: # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + files = {"file[]": open("/tmp/test.txt", "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password), + ) QtTest.QTest.qWait(2000) if type(mode) == ShareMode: @@ -142,7 +146,10 @@ class GuiBaseTest(object): if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password), + ) QtTest.QTest.qWait(2000) # Indicator should be visible, have a value of "1" @@ -153,19 +160,16 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - def history_is_not_visible(self, mode): - '''Test that the History section is not visible''' + """Test that the History section is not visible""" self.assertFalse(mode.history.isVisible()) - def history_is_visible(self, mode): - '''Test that the History section is visible''' + """Test that the History section is visible""" self.assertTrue(mode.history.isVisible()) - def server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' + """Test we can start the service""" # Should be in SERVER_WORKING state QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 1) @@ -175,115 +179,143 @@ class GuiBaseTest(object): self.assertFalse(mode.toggle_history.indicator_label.isVisible()) def server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + """Test that the Server Status indicator shows we are Starting""" + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_working"), + ) def server_status_indicator_says_scheduled(self, mode): - '''Test that the Server Status indicator shows we are Scheduled''' - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_scheduled')) + """Test that the Server Status indicator shows we are Scheduled""" + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_scheduled"), + ) def server_is_started(self, mode, startup_time=2000): - '''Test that the server has started''' + """Test that the server has started""" QtTest.QTest.qWait(startup_time) # Should now be in SERVER_STARTED state self.assertEqual(mode.server_status.status, 2) - def web_server_is_running(self): - '''Test that the web server has started''' + """Test that the web server has started""" try: - r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port)) self.assertTrue(True) except requests.exceptions.ConnectionError: self.assertTrue(False) - def have_a_password(self, mode, public_mode): - '''Test that we have a valid password''' + """Test that we have a valid password""" if not public_mode: - self.assertRegex(mode.server_status.web.password, r'(\w+)-(\w+)') + self.assertRegex(mode.server_status.web.password, r"(\w+)-(\w+)") else: - self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)') + self.assertIsNone(mode.server_status.web.password, r"(\w+)-(\w+)") def add_button_visible(self, mode): - '''Test that the add button should be visible''' + """Test that the add button should be visible""" self.assertTrue(mode.server_status.file_selection.add_button.isVisible()) def url_description_shown(self, mode): - '''Test that the URL label is showing''' + """Test that the URL label is showing""" self.assertTrue(mode.server_status.url_description.isVisible()) - def have_copy_url_button(self, mode, public_mode): - '''Test that the Copy URL button is shown and that the clipboard is correct''' + """Test that the Copy URL button is shown and that the clipboard is correct""" self.assertTrue(mode.server_status.copy_url_button.isVisible()) - QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + mode.server_status.copy_url_button, QtCore.Qt.LeftButton + ) clipboard = self.gui.qtapp.clipboard() if public_mode: - self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port)) + self.assertEqual( + clipboard.text(), "http://127.0.0.1:{}".format(self.gui.app.port) + ) else: - self.assertEqual(clipboard.text(), 'http://onionshare:{}@127.0.0.1:{}'.format(mode.server_status.web.password, self.gui.app.port)) - + self.assertEqual( + clipboard.text(), + "http://onionshare:{}@127.0.0.1:{}".format( + mode.server_status.web.password, self.gui.app.port + ), + ) def server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' + """Test that the Server Status indicator shows we are started""" if type(mode) == ReceiveMode: - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_receive_started"), + ) if type(mode) == ShareMode: - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_started"), + ) def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' + """Test that the web page contains a string""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.get( + url, auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password) + ) self.assertTrue(string in r.text) - def history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' + """Test that the relevant widgets are present in the history view after activity has taken place""" self.assertFalse(mode.history.empty.isVisible()) self.assertTrue(mode.history.not_empty.isVisible()) - def counter_incremented(self, mode, count): - '''Test that the counter has incremented''' + """Test that the counter has incremented""" self.assertEqual(mode.history.completed_count, count) - def server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open) or (type(mode) == WebsiteMode): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + """Test that the server stops when we click Stop""" + if ( + type(mode) == ReceiveMode + or (type(mode) == ShareMode and stay_open) + or (type(mode) == WebsiteMode) + ): + QtTest.QTest.mouseClick( + mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(mode.server_status.status, 0) - def web_server_is_stopped(self): - '''Test that the web server also stopped''' + """Test that the web server also stopped""" QtTest.QTest.qWait(2000) try: - r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port)) self.assertTrue(False) except requests.exceptions.ConnectionError: self.assertTrue(True) - def server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' + """Test that the Server Status indicator shows we closed""" if type(mode) == ReceiveMode: - self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + self.assertEqual( + self.gui.receive_mode.server_status_label.text(), + strings._("gui_status_indicator_receive_stopped"), + ) if type(mode) == ShareMode: if stay_open: - self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + self.assertEqual( + self.gui.share_mode.server_status_label.text(), + strings._("gui_status_indicator_share_stopped"), + ) else: - self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + self.assertEqual( + self.gui.share_mode.server_status_label.text(), + strings._("closing_automatically"), + ) def clear_all_history_items(self, mode, count): if count == 0: @@ -292,41 +324,40 @@ class GuiBaseTest(object): # Auto-stop timer tests def set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' + """Test that the timeout can be set""" timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) mode.server_status.autostop_timer_widget.setDateTime(timer) self.assertTrue(mode.server_status.autostop_timer_widget.dateTime(), timer) def autostop_timer_widget_hidden(self, mode): - '''Test that the auto-stop timer widget is hidden when share has started''' + """Test that the auto-stop timer widget is hidden when share has started""" self.assertFalse(mode.server_status.autostop_timer_container.isVisible()) - def server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' + """Test that the server has timed out after the timer ran out""" QtTest.QTest.qWait(wait) # We should have timed out now self.assertEqual(mode.server_status.status, 0) # Auto-start timer tests def set_autostart_timer(self, mode, timer): - '''Test that the timer can be set''' + """Test that the timer can be set""" schedule = QtCore.QDateTime.currentDateTime().addSecs(timer) mode.server_status.autostart_timer_widget.setDateTime(schedule) self.assertTrue(mode.server_status.autostart_timer_widget.dateTime(), schedule) def autostart_timer_widget_hidden(self, mode): - '''Test that the auto-start timer widget is hidden when share has started''' + """Test that the auto-start timer widget is hidden when share has started""" self.assertFalse(mode.server_status.autostart_timer_container.isVisible()) def scheduled_service_started(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' + """Test that the server has timed out after the timer ran out""" QtTest.QTest.qWait(wait) # We should have started now self.assertEqual(mode.server_status.status, 2) def cancel_the_share(self, mode): - '''Test that we can cancel a share before it's started up ''' + """Test that we can cancel a share before it's started up """ self.server_working_on_start_button_pressed(mode) self.server_status_indicator_says_scheduled(mode) self.add_delete_buttons_hidden() @@ -334,7 +365,9 @@ class GuiBaseTest(object): self.set_autostart_timer(mode, 10) QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) QtTest.QTest.qWait(2000) - QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseRelease( + mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(mode.server_status.status, 0) self.server_is_stopped(mode, False) self.web_server_is_stopped() diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 80e05250..34db1a94 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -4,25 +4,44 @@ from datetime import datetime, timedelta from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest + class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False): - '''Test that we can upload the file''' + def upload_file( + self, + public_mode, + file_to_upload, + expected_basename, + identical_files_at_once=False, + ): + """Test that we can upload the file""" # Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused QtTest.QTest.qWait(2000) - files = {'file[]': open(file_to_upload, 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + files = {"file[]": open(file_to_upload, "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) if identical_files_at_once: # Send a duplicate upload to test for collisions r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) if identical_files_at_once: # Send a duplicate upload to test for collisions - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) QtTest.QTest.qWait(2000) @@ -35,7 +54,9 @@ class GuiReceiveTest(GuiBaseTest): time_dir = now.strftime("%H.%M.%S-1") else: time_dir = now.strftime("%H.%M.%S") - receive_mode_dir = os.path.join(self.gui.common.settings.get('data_dir'), date_dir, time_dir) + receive_mode_dir = os.path.join( + self.gui.common.settings.get("data_dir"), date_dir, time_dir + ) expected_filename = os.path.join(receive_mode_dir, expected_basename) if os.path.exists(expected_filename): exists = True @@ -45,31 +66,37 @@ class GuiReceiveTest(GuiBaseTest): self.assertTrue(exists) def upload_file_should_fail(self, public_mode): - '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + """Test that we can't upload the file when permissions are wrong, and expected content is shown""" + files = {"file[]": open("/tmp/test.txt", "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) QtCore.QTimer.singleShot(1000, self.accept_dialog) - self.assertTrue('Error uploading, please inform the OnionShare user' in r.text) + self.assertTrue("Error uploading, please inform the OnionShare user" in r.text) def upload_dir_permissions(self, mode=0o755): - '''Manipulate the permissions on the upload dir in between tests''' - os.chmod('/tmp/OnionShare', mode) + """Manipulate the permissions on the upload dir in between tests""" + os.chmod("/tmp/OnionShare", mode) def try_without_auth_in_non_public_mode(self): - r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) + r = requests.post("http://127.0.0.1:{}/upload".format(self.gui.app.port)) self.assertEqual(r.status_code, 401) - r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/close".format(self.gui.app.port)) self.assertEqual(r.status_code, 401) # 'Grouped' tests follow from here def run_all_receive_mode_setup_tests(self, public_mode): - '''Set up a share in Receive mode and start it''' + """Set up a share in Receive mode and start it""" self.click_mode(self.gui.receive_mode) self.history_is_not_visible(self.gui.receive_mode) self.click_toggle_history(self.gui.receive_mode) @@ -83,24 +110,28 @@ class GuiReceiveTest(GuiBaseTest): self.url_description_shown(self.gui.receive_mode) self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.web_page( + self.gui.receive_mode, + "Select the files you want to send, then click", + public_mode, + ) def run_all_receive_mode_tests(self, public_mode): - '''Upload files in receive mode and stop the share''' + """Upload files in receive mode and stop the share""" self.run_all_receive_mode_setup_tests(public_mode) if not public_mode: self.try_without_auth_in_non_public_mode() - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.counter_incremented(self.gui.receive_mode, 2) - self.upload_file(public_mode, '/tmp/testdir/test', 'test') + self.upload_file(public_mode, "/tmp/testdir/test", "test") self.counter_incremented(self.gui.receive_mode, 3) - self.upload_file(public_mode, '/tmp/testdir/test', 'test') + self.upload_file(public_mode, "/tmp/testdir/test", "test") self.counter_incremented(self.gui.receive_mode, 4) # Test uploading the same file twice at the same time, and make sure no collisions - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt', True) + self.upload_file(public_mode, "/tmp/test.txt", "test.txt", True) self.counter_incremented(self.gui.receive_mode, 6) self.history_indicator(self.gui.receive_mode, public_mode, "2") self.server_is_stopped(self.gui.receive_mode, False) @@ -111,7 +142,7 @@ class GuiReceiveTest(GuiBaseTest): self.history_indicator(self.gui.receive_mode, public_mode, "2") def run_all_receive_mode_unwritable_dir_tests(self, public_mode): - '''Attempt to upload (unwritable) files in receive mode and stop the share''' + """Attempt to upload (unwritable) files in receive mode and stop the share""" self.run_all_receive_mode_setup_tests(public_mode) self.upload_dir_permissions(0o400) self.upload_file_should_fail(public_mode) @@ -131,8 +162,8 @@ class GuiReceiveTest(GuiBaseTest): def run_all_clear_all_button_tests(self, public_mode): """Test the Clear All history button""" self.run_all_receive_mode_setup_tests(public_mode) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.history_widgets_present(self.gui.receive_mode) self.clear_all_history_items(self.gui.receive_mode, 0) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.clear_all_history_items(self.gui.receive_mode, 2) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 6925defa..630d0562 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -6,89 +6,137 @@ import tempfile from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest + class GuiShareTest(GuiBaseTest): # Persistence tests def have_same_password(self, password): - '''Test that we have the same password''' + """Test that we have the same password""" self.assertEqual(self.gui.share_mode.server_status.web.password, password) # Share-specific tests def file_selection_widget_has_files(self, num=2): - '''Test that the number of items in the list is as expected''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num) - + """Test that the number of items in the list is as expected""" + self.assertEqual( + self.gui.share_mode.server_status.file_selection.get_num_files(), num + ) def deleting_all_files_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + """Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button""" + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect( + self.gui.share_mode.server_status.file_selection.file_list.item(0) + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.viewport(), + QtCore.Qt.LeftButton, + pos=rect.center(), + ) # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + self.assertTrue( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) # Click delete, delete button should still be visible since we have one more file - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.delete_button, + QtCore.Qt.LeftButton, + ) - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect( + self.gui.share_mode.server_status.file_selection.file_list.item(0) + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.viewport(), + QtCore.Qt.LeftButton, + pos=rect.center(), + ) + self.assertTrue( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.delete_button, + QtCore.Qt.LeftButton, + ) # No more files, the delete button should be hidden - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + self.assertFalse( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) def add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + """Test that we can also delete a file by clicking on its [X] widget""" + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/etc/hosts" + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.item( + 0 + ).item_button, + QtCore.Qt.LeftButton, + ) self.file_selection_widget_has_files(0) - def file_selection_widget_read_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + """Re-add some files to the list so we can share""" + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/etc/hosts" + ) + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/test.txt" + ) self.file_selection_widget_has_files(2) - def add_large_file(self): - '''Add a large file to the share''' - size = 1024*1024*155 - with open('/tmp/large_file', 'wb') as fout: + """Add a large file to the share""" + size = 1024 * 1024 * 155 + with open("/tmp/large_file", "wb") as fout: fout.write(os.urandom(size)) - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file') - + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/large_file" + ) def add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + """Test that the add and delete buttons are hidden when the server starts""" + self.assertFalse( + self.gui.share_mode.server_status.file_selection.add_button.isVisible() + ) + self.assertFalse( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) def download_share(self, public_mode): - '''Test that we can download the share''' + """Test that we can download the share""" url = "http://127.0.0.1:{}/download".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) tmp_file = tempfile.NamedTemporaryFile() - with open(tmp_file.name, 'wb') as f: + with open(tmp_file.name, "wb") as f: f.write(r.content) zip = zipfile.ZipFile(tmp_file.name) QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + self.assertEqual("onionshare", zip.read("test.txt").decode("utf-8")) def individual_file_is_viewable_or_not(self, public_mode, stay_open): - '''Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)''' + """Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)""" url = "http://127.0.0.1:{}".format(self.gui.app.port) download_file_url = "http://127.0.0.1:{}/test.txt".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) if stay_open: self.assertTrue('a href="test.txt"' in r.text) @@ -96,32 +144,44 @@ class GuiShareTest(GuiBaseTest): if public_mode: r = requests.get(download_file_url) else: - r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + download_file_url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) tmp_file = tempfile.NamedTemporaryFile() - with open(tmp_file.name, 'wb') as f: + with open(tmp_file.name, "wb") as f: f.write(r.content) - with open(tmp_file.name, 'r') as f: - self.assertEqual('onionshare', f.read()) + with open(tmp_file.name, "r") as f: + self.assertEqual("onionshare", f.read()) else: self.assertFalse('a href="/test.txt"' in r.text) if public_mode: r = requests.get(download_file_url) else: - r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + download_file_url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) self.assertEqual(r.status_code, 404) self.download_share(public_mode) QtTest.QTest.qWait(2000) def hit_401(self, public_mode): - '''Test that the server stops after too many 401s, or doesn't when in public_mode''' + """Test that the server stops after too many 401s, or doesn't when in public_mode""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) for _ in range(20): password_guess = self.gui.common.build_password() - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password_guess)) + r = requests.get( + url, auth=requests.auth.HTTPBasicAuth("onionshare", password_guess) + ) # A nasty hack to avoid the Alert dialog that blocks the rest of the test if not public_mode: @@ -134,7 +194,6 @@ class GuiShareTest(GuiBaseTest): else: self.web_server_is_stopped() - # 'Grouped' tests follow from here def run_all_share_mode_setup_tests(self): @@ -148,7 +207,6 @@ class GuiShareTest(GuiBaseTest): self.add_a_file_and_delete_using_its_delete_widget() self.file_selection_widget_read_files() - def run_all_share_mode_started_tests(self, public_mode, startup_time=2000): """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) @@ -162,10 +220,9 @@ class GuiShareTest(GuiBaseTest): self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) - def run_all_share_mode_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.web_page(self.gui.share_mode, "Total size", public_mode) self.download_share(public_mode) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) @@ -179,7 +236,7 @@ class GuiShareTest(GuiBaseTest): def run_all_share_mode_individual_file_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.web_page(self.gui.share_mode, "Total size", public_mode) self.individual_file_is_viewable_or_not(public_mode, stay_open) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) @@ -222,7 +279,6 @@ class GuiShareTest(GuiBaseTest): self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): """Same as end-to-end share tests but also test the password is the same on multiple shared""" self.run_all_share_mode_setup_tests() @@ -231,7 +287,6 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_download_tests(public_mode, stay_open) self.have_same_password(password) - def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() @@ -258,12 +313,16 @@ class GuiShareTest(GuiBaseTest): self.set_autostart_timer(self.gui.share_mode, 15) self.set_timeout(self.gui.share_mode, 5) QtCore.QTimer.singleShot(4000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.server_is_stopped(self.gui.share_mode, False) def run_all_share_mode_unreadable_file_tests(self): - '''Attempt to share an unreadable file''' + """Attempt to share an unreadable file""" self.run_all_share_mode_setup_tests() QtCore.QTimer.singleShot(1000, self.accept_dialog) - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/nonexistent.txt') + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/nonexistent.txt" + ) self.file_selection_widget_has_files(2) diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index 798c619a..f6b67112 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -13,13 +13,16 @@ from onionshare.web import Web from onionshare_gui import Application, OnionShare, OnionShareGui from .GuiShareTest import GuiShareTest + class GuiWebsiteTest(GuiShareTest): @staticmethod def set_up(test_settings): - '''Create GUI with given settings''' + """Create GUI with given settings""" # Create our test file - testfile = open('/tmp/index.html', 'w') - testfile.write('

This is a test website hosted by OnionShare

') + testfile = open("/tmp/index.html", "w") + testfile.write( + "

This is a test website hosted by OnionShare

" + ) testfile.close() common = Common() @@ -28,7 +31,7 @@ class GuiWebsiteTest(GuiShareTest): strings.load_strings(common) # Get all of the settings in test_settings - test_settings['data_dir'] = '/tmp/OnionShare' + test_settings["data_dir"] = "/tmp/OnionShare" for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val @@ -40,44 +43,62 @@ class GuiWebsiteTest(GuiShareTest): app = OnionShare(common, testonion, True, 0) web = Web(common, False, True) - open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) + open("/tmp/settings.json", "w").write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/index.html'], '/tmp/settings.json', True) + gui = OnionShareGui( + common, + testonion, + qtapp, + app, + ["/tmp/index.html"], + "/tmp/settings.json", + True, + ) return gui @staticmethod def tear_down(): - '''Clean up after tests''' + """Clean up after tests""" try: - os.remove('/tmp/index.html') - os.remove('/tmp/settings.json') + os.remove("/tmp/index.html") + os.remove("/tmp/settings.json") except: pass def view_website(self, public_mode): - '''Test that we can download the share''' + """Test that we can download the share""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.website_mode.server_status.web.password + ), + ) QtTest.QTest.qWait(2000) - self.assertTrue('This is a test website hosted by OnionShare' in r.text) + self.assertTrue("This is a test website hosted by OnionShare" in r.text) def check_csp_header(self, public_mode, csp_header_disabled): - '''Test that the CSP header is present when enabled or vice versa''' + """Test that the CSP header is present when enabled or vice versa""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.website_mode.server_status.web.password + ), + ) QtTest.QTest.qWait(2000) if csp_header_disabled: - self.assertFalse('Content-Security-Policy' in r.headers) + self.assertFalse("Content-Security-Policy" in r.headers) else: - self.assertTrue('Content-Security-Policy' in r.headers) + self.assertTrue("Content-Security-Policy" in r.headers) def run_all_website_mode_setup_tests(self): """Tests in website mode prior to starting a share""" @@ -100,16 +121,16 @@ class GuiWebsiteTest(GuiShareTest): self.have_copy_url_button(self.gui.website_mode, public_mode) self.server_status_indicator_says_started(self.gui.website_mode) - def run_all_website_mode_download_tests(self, public_mode): """Tests in website mode after viewing the site""" self.run_all_website_mode_setup_tests() self.run_all_website_mode_started_tests(public_mode, startup_time=2000) self.view_website(public_mode) - self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_disabled')) + self.check_csp_header( + public_mode, self.gui.common.settings.get("csp_header_disabled") + ) self.history_widgets_present(self.gui.website_mode) self.server_is_stopped(self.gui.website_mode, False) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.website_mode, False) self.add_button_visible(self.gui.website_mode) - diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 35bdd9c6..1aa6da25 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -23,17 +23,17 @@ class OnionStub(object): class SettingsGuiBaseTest(object): @staticmethod def set_up(): - '''Create the GUI''' + """Create the GUI""" # Default settings for the settings GUI tests test_settings = { - "no_bridges": False, - "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", + "no_bridges": False, + "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", } # Create our test file - testfile = open('/tmp/test.txt', 'w') - testfile.write('onionshare') + testfile = open("/tmp/test.txt", "w") + testfile.write("onionshare") testfile.close() common = Common() @@ -51,22 +51,22 @@ class SettingsGuiBaseTest(object): if key not in test_settings: test_settings[key] = val - open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) + open("/tmp/settings.json", "w").write(json.dumps(test_settings)) - gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json', True) + gui = SettingsDialog(common, testonion, qtapp, "/tmp/settings.json", True) return gui @staticmethod def tear_down(): - '''Clean up after tests''' - os.remove('/tmp/settings.json') + """Clean up after tests""" + os.remove("/tmp/settings.json") def run_settings_gui_tests(self): self.gui.show() # Window is shown self.assertTrue(self.gui.isVisible()) - self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title')) + self.assertEqual(self.gui.windowTitle(), strings._("gui_settings_window_title")) # Check for updates button is hidden self.assertFalse(self.gui.check_for_updates_button.isVisible()) @@ -74,13 +74,21 @@ class SettingsGuiBaseTest(object): # public mode is off self.assertFalse(self.gui.public_mode_checkbox.isChecked()) # enable public mode - QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.public_mode_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.public_mode_checkbox.height() / 2), + ) self.assertTrue(self.gui.public_mode_checkbox.isChecked()) # autostop timer is off self.assertFalse(self.gui.autostop_timer_checkbox.isChecked()) # enable autostop timer - QtTest.QTest.mouseClick(self.gui.autostop_timer_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.autostop_timer_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.autostop_timer_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.autostop_timer_checkbox.height() / 2), + ) self.assertTrue(self.gui.autostop_timer_checkbox.isChecked()) # legacy mode checkbox and related widgets @@ -96,32 +104,70 @@ class SettingsGuiBaseTest(object): self.assertFalse(self.gui.hidservauth_copy_button.isVisible()) # enable legacy mode - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.use_legacy_v2_onions_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.use_legacy_v2_onions_checkbox.height() / 2 + ), + ) self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked()) self.assertTrue(self.gui.save_private_key_checkbox.isVisible()) self.assertTrue(self.gui.use_stealth_widget.isVisible()) # enable persistent mode - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.save_private_key_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.save_private_key_checkbox.height() / 2 + ), + ) self.assertTrue(self.gui.save_private_key_checkbox.isChecked()) # enable stealth mode - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.stealth_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2), + ) self.assertTrue(self.gui.stealth_checkbox.isChecked()) # now that stealth is enabled, we can't turn off legacy mode self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) # disable stealth, persistence - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.save_private_key_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.save_private_key_checkbox.height() / 2 + ), + ) + QtTest.QTest.mouseClick( + self.gui.stealth_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2), + ) # legacy mode checkbox is enabled again self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) # uncheck legacy mode - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.use_legacy_v2_onions_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.use_legacy_v2_onions_checkbox.height() / 2 + ), + ) # legacy options hidden again self.assertTrue(self.gui.save_private_key_widget.isVisible()) self.assertFalse(self.gui.use_stealth_widget.isVisible()) # re-enable legacy mode - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.use_legacy_v2_onions_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.use_legacy_v2_onions_checkbox.height() / 2 + ), + ) else: # legacy mode setting is hidden @@ -131,8 +177,16 @@ class SettingsGuiBaseTest(object): self.assertTrue(self.gui.use_stealth_widget.isVisible()) # enable them all again so that we can see the setting stick in settings.json - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.save_private_key_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.save_private_key_checkbox.height() / 2), + ) + QtTest.QTest.mouseClick( + self.gui.stealth_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.stealth_checkbox.height() / 2), + ) else: # None of the onion settings should appear self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible()) @@ -144,12 +198,17 @@ class SettingsGuiBaseTest(object): # stay open toggled off, on self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked()) - QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2)) + QtTest.QTest.mouseClick( + self.gui.close_after_first_download_checkbox, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.close_after_first_download_checkbox.height() / 2 + ), + ) self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked()) # receive mode - self.gui.data_dir_lineedit.setText('/tmp/OnionShareSettingsTest') - + self.gui.data_dir_lineedit.setText("/tmp/OnionShareSettingsTest") # bundled mode is enabled self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled()) @@ -161,7 +220,11 @@ class SettingsGuiBaseTest(object): self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) # switch to obfs4 - QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.tor_bridges_use_obfs4_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.tor_bridges_use_obfs4_radio.height() / 2), + ) self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked()) # custom bridges are hidden @@ -175,7 +238,11 @@ class SettingsGuiBaseTest(object): self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked()) # enable automatic mode - QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.connection_type_automatic_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.connection_type_automatic_radio.height() / 2), + ) self.assertTrue(self.gui.connection_type_automatic_radio.isChecked()) # bundled is off self.assertFalse(self.gui.connection_type_bundled_radio.isChecked()) @@ -187,7 +254,13 @@ class SettingsGuiBaseTest(object): self.assertFalse(self.gui.authenticate_password_radio.isVisible()) # enable control port mode - QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.connection_type_control_port_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.connection_type_control_port_radio.height() / 2 + ), + ) self.assertTrue(self.gui.connection_type_control_port_radio.isChecked()) # automatic is off self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) @@ -196,7 +269,13 @@ class SettingsGuiBaseTest(object): self.assertTrue(self.gui.authenticate_password_radio.isVisible()) # enable socket mode - QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.connection_type_socket_file_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint( + 2, self.gui.connection_type_socket_file_radio.height() / 2 + ), + ) self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked()) # control port is off self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) @@ -205,20 +284,30 @@ class SettingsGuiBaseTest(object): self.assertTrue(self.gui.authenticate_password_radio.isVisible()) # re-enable bundled mode - QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.connection_type_bundled_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.connection_type_bundled_radio.height() / 2), + ) # go back to custom bridges - QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2)) + QtTest.QTest.mouseClick( + self.gui.tor_bridges_use_custom_radio, + QtCore.Qt.LeftButton, + pos=QtCore.QPoint(2, self.gui.tor_bridges_use_custom_radio.height() / 2), + ) self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible()) self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked()) - self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3') + self.gui.tor_bridges_use_custom_textbox.setPlainText( + "94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3" + ) # Test that the Settings Dialog can save the settings and close itself QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton) self.assertFalse(self.gui.isVisible()) # Test our settings are reflected in the settings json - with open('/tmp/settings.json') as f: + with open("/tmp/settings.json") as f: data = json.load(f) self.assertTrue(data["public_mode"]) @@ -238,4 +327,7 @@ class SettingsGuiBaseTest(object): self.assertFalse(data["close_after_first_download"]) self.assertEqual(data["connection_type"], "bundled") self.assertFalse(data["tor_bridges_use_obfs4"]) - self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n") + self.assertEqual( + data["tor_bridges_use_custom_bridges"], + "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n", + ) diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 3f9952d0..434a525d 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -16,20 +16,21 @@ from onionshare_gui.mode.receive_mode import ReceiveMode from .GuiBaseTest import GuiBaseTest + class TorGuiBaseTest(GuiBaseTest): @staticmethod def set_up(test_settings): - '''Create GUI with given settings''' + """Create GUI with given settings""" # Create our test file - testfile = open('/tmp/test.txt', 'w') - testfile.write('onionshare') + testfile = open("/tmp/test.txt", "w") + testfile.write("onionshare") testfile.close() # Create a test dir and files - if not os.path.exists('/tmp/testdir'): - testdir = os.mkdir('/tmp/testdir') - testfile = open('/tmp/testdir/test.txt', 'w') - testfile.write('onionshare') + if not os.path.exists("/tmp/testdir"): + testdir = os.mkdir("/tmp/testdir") + testfile = open("/tmp/testdir/test.txt", "w") + testfile.write("onionshare") testfile.close() common = Common() @@ -38,8 +39,8 @@ class TorGuiBaseTest(GuiBaseTest): strings.load_strings(common) # Get all of the settings in test_settings - test_settings['connection_type'] = 'automatic' - test_settings['data_dir'] = '/tmp/OnionShare' + test_settings["connection_type"] = "automatic" + test_settings["data_dir"] = "/tmp/OnionShare" for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val @@ -51,13 +52,21 @@ class TorGuiBaseTest(GuiBaseTest): app = OnionShare(common, testonion, False, 0) web = Web(common, False, False) - open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) + open("/tmp/settings.json", "w").write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', False) + gui = OnionShareGui( + common, + testonion, + qtapp, + app, + ["/tmp/test.txt", "/tmp/testdir"], + "/tmp/settings.json", + False, + ) return gui def history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + """Test that we can make sure the history is toggled off, do an action, and the indiciator works""" # Make sure history is toggled off if mode.history.isVisible(): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) @@ -70,15 +79,17 @@ class TorGuiBaseTest(GuiBaseTest): (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() session = requests.session() session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port) if type(mode) == ReceiveMode: # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} + files = {"file[]": open("/tmp/test.txt", "rb")} if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.password) + path = "http://{}/{}/upload".format( + self.gui.app.onion_host, mode.web.password + ) else: - path = 'http://{}/upload'.format(self.gui.app.onion_host) + path = "http://{}/upload".format(self.gui.app.onion_host) response = session.post(path, files=files) QtTest.QTest.qWait(4000) @@ -87,7 +98,9 @@ class TorGuiBaseTest(GuiBaseTest): if public_mode: path = "http://{}/download".format(self.gui.app.onion_host) else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.password) + path = "http://{}/{}/download".format( + self.gui.app.onion_host, mode.web.password + ) response = session.get(path) QtTest.QTest.qWait(4000) @@ -100,61 +113,72 @@ class TorGuiBaseTest(GuiBaseTest): self.assertFalse(mode.toggle_history.indicator_label.isVisible()) def have_an_onion_service(self): - '''Test that we have a valid Onion URL''' - self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + """Test that we have a valid Onion URL""" + self.assertRegex(self.gui.app.onion_host, r"[a-z2-7].onion") def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' + """Test that the web page contains a string""" (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() s.settimeout(60) s.connect((self.gui.app.onion_host, 80)) if not public_mode: - path = '/{}'.format(mode.server_status.web.password) + path = "/{}".format(mode.server_status.web.password) else: - path = '/' - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - with open('/tmp/webpage', 'wb') as file_to_write: + path = "/" + http_request = "GET {} HTTP/1.0\r\n".format(path) + http_request += "Host: {}\r\n".format(self.gui.app.onion_host) + http_request += "\r\n" + s.sendall(http_request.encode("utf-8")) + with open("/tmp/webpage", "wb") as file_to_write: while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) + data = s.recv(1024) + if not data: + break + file_to_write.write(data) file_to_write.close() - f = open('/tmp/webpage') + f = open("/tmp/webpage") self.assertTrue(string in f.read()) f.close() def have_copy_url_button(self, mode, public_mode): - '''Test that the Copy URL button is shown and that the clipboard is correct''' + """Test that the Copy URL button is shown and that the clipboard is correct""" self.assertTrue(mode.server_status.copy_url_button.isVisible()) - QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + mode.server_status.copy_url_button, QtCore.Qt.LeftButton + ) clipboard = self.gui.qtapp.clipboard() if public_mode: - self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host)) + self.assertEqual( + clipboard.text(), "http://{}".format(self.gui.app.onion_host) + ) else: - self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.password)) - + self.assertEqual( + clipboard.text(), + "http://{}/{}".format( + self.gui.app.onion_host, mode.server_status.web.password + ), + ) # Stealth tests def copy_have_hidserv_auth_button(self, mode): - '''Test that the Copy HidservAuth button is shown''' + """Test that the Copy HidservAuth button is shown""" self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) def hidserv_auth_string(self): - '''Test the validity of the HidservAuth string''' - self.assertRegex(self.gui.app.auth_string, r'HidServAuth {} [a-zA-Z1-9]'.format(self.gui.app.onion_host)) - - + """Test the validity of the HidservAuth string""" + self.assertRegex( + self.gui.app.auth_string, + r"HidServAuth {} [a-zA-Z1-9]".format(self.gui.app.onion_host), + ) # Miscellaneous tests def tor_killed_statusbar_message_shown(self, mode): - '''Test that the status bar message shows Tor was disconnected''' + """Test that the status bar message shows Tor was disconnected""" self.gui.app.onion.c = None QtTest.QTest.qWait(1000) - self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) + self.assertTrue( + mode.status_bar.currentMessage(), strings._("gui_tor_connection_lost") + ) diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py index 601f34b6..18a00643 100644 --- a/tests/TorGuiReceiveTest.py +++ b/tests/TorGuiReceiveTest.py @@ -3,28 +3,29 @@ import requests from PyQt5 import QtTest from .TorGuiBaseTest import TorGuiBaseTest -class TorGuiReceiveTest(TorGuiBaseTest): +class TorGuiReceiveTest(TorGuiBaseTest): def upload_file(self, public_mode, file_to_upload, expected_file): - '''Test that we can upload the file''' + """Test that we can upload the file""" (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() session = requests.session() session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - files = {'file[]': open(file_to_upload, 'rb')} + session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port) + files = {"file[]": open(file_to_upload, "rb")} if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.password) + path = "http://{}/{}/upload".format( + self.gui.app.onion_host, self.gui.receive_mode.web.password + ) else: - path = 'http://{}/upload'.format(self.gui.app.onion_host) + path = "http://{}/upload".format(self.gui.app.onion_host) response = session.post(path, files=files) QtTest.QTest.qWait(4000) self.assertTrue(os.path.isfile(expected_file)) - # 'Grouped' tests follow from here def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - '''Run a full suite of tests in Receive mode''' + """Run a full suite of tests in Receive mode""" self.click_mode(self.gui.receive_mode) self.history_is_not_visible(self.gui.receive_mode) self.click_toggle_history(self.gui.receive_mode) @@ -39,15 +40,19 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.url_description_shown(self.gui.receive_mode) self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt') + self.web_page( + self.gui.receive_mode, + "Select the files you want to send, then click", + public_mode, + ) + self.upload_file(public_mode, "/tmp/test.txt", "/tmp/OnionShare/test.txt") self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt') + self.upload_file(public_mode, "/tmp/test.txt", "/tmp/OnionShare/test-2.txt") self.counter_incremented(self.gui.receive_mode, 2) - self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test') + self.upload_file(public_mode, "/tmp/testdir/test", "/tmp/OnionShare/test") self.counter_incremented(self.gui.receive_mode, 3) - self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2') + self.upload_file(public_mode, "/tmp/testdir/test", "/tmp/OnionShare/test-2") self.counter_incremented(self.gui.receive_mode, 4) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index cfce9d4e..e86a6927 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -4,48 +4,50 @@ from PyQt5 import QtTest from .TorGuiBaseTest import TorGuiBaseTest from .GuiShareTest import GuiShareTest + class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): def download_share(self, public_mode): - '''Test downloading a share''' + """Test downloading a share""" # Set up connecting to the onion (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() session = requests.session() session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + session.proxies["http"] = "socks5h://{}:{}".format(socks_address, socks_port) # Download files if public_mode: path = "http://{}/download".format(self.gui.app.onion_host) else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.password) + path = "http://{}/{}/download".format( + self.gui.app.onion_host, self.gui.share_mode.web.password + ) response = session.get(path, stream=True) QtTest.QTest.qWait(4000) if response.status_code == 200: - with open('/tmp/download.zip', 'wb') as file_to_write: + with open("/tmp/download.zip", "wb") as file_to_write: for chunk in response.iter_content(chunk_size=128): file_to_write.write(chunk) file_to_write.close() - zip = zipfile.ZipFile('/tmp/download.zip') + zip = zipfile.ZipFile("/tmp/download.zip") QtTest.QTest.qWait(4000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - + self.assertEqual("onionshare", zip.read("test.txt").decode("utf-8")) # Persistence tests def have_same_onion(self, onion): - '''Test that we have the same onion''' + """Test that we have the same onion""" self.assertEqual(self.gui.app.onion_host, onion) # legacy v2 onion test def have_v2_onion(self): - '''Test that the onion is a v2 style onion''' - self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + """Test that the onion is a v2 style onion""" + self.assertRegex(self.gui.app.onion_host, r"[a-z2-7].onion") self.assertEqual(len(self.gui.app.onion_host), 22) # 'Grouped' tests follow from here def run_all_share_mode_started_tests(self, public_mode): - '''Tests in share mode after starting a share''' + """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_status_indicator_says_starting(self.gui.share_mode) self.add_delete_buttons_hidden() @@ -58,10 +60,9 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) - def run_all_share_mode_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.web_page(self.gui.share_mode, "Total size", public_mode) self.download_share(public_mode) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) @@ -72,7 +73,6 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.server_is_started(self.gui.share_mode, startup_time=45000) self.history_indicator(self.gui.share_mode, public_mode) - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): """Same as end-to-end share tests but also test the password is the same on multiple shared""" self.run_all_share_mode_setup_tests() @@ -83,7 +83,6 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.have_same_onion(onion) self.have_same_password(password) - def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() diff --git a/tests/conftest.py b/tests/conftest.py index 7aca2b2c..ac81d14d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import sys + # Force tests to look for resources in the source code tree sys.onionshare_dev_mode = True @@ -10,6 +11,7 @@ import pytest from onionshare import common, web, settings, strings + def pytest_addoption(parser): parser.addoption( "--rungui", action="store_true", default=False, help="run GUI tests" @@ -27,7 +29,7 @@ def pytest_collection_modifyitems(config, items): if "tor" in item.keywords: item.add_marker(skip_tor) - if not config.getoption('--rungui'): + if not config.getoption("--rungui"): # --rungui given in cli: do not skip GUI tests skip_gui = pytest.mark.skip(reason="need --rungui option to run") for item in items: @@ -43,8 +45,8 @@ def temp_dir_1024(): tmp_dir = tempfile.mkdtemp() tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) + with open(tmp_file, "wb") as f: + f.write(b"*" * 1024) return tmp_dir @@ -58,8 +60,8 @@ def temp_dir_1024_delete(): with tempfile.TemporaryDirectory() as tmp_dir: tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) + with open(tmp_file, "wb") as f: + f.write(b"*" * 1024) yield tmp_dir @@ -68,7 +70,7 @@ def temp_file_1024(): """ Create a temporary file of a particular size (1024 bytes). """ with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) + tmp_file.write(b"*" * 1024) return tmp_file.name @@ -81,18 +83,18 @@ def temp_file_1024_delete(): """ with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) + tmp_file.write(b"*" * 1024) tmp_file.flush() yield tmp_file.name # pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') +@pytest.yield_fixture(scope="session") def custom_zw(): zw = web.share_mode.ZipWriter( common.Common(), zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' + processed_size_callback=lambda _: "custom_callback", ) yield zw zw.close() @@ -100,7 +102,7 @@ def custom_zw(): # pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') +@pytest.yield_fixture(scope="session") def default_zw(): zw = web.share_mode.ZipWriter(common.Common()) yield zw @@ -111,76 +113,77 @@ def default_zw(): @pytest.fixture def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + monkeypatch.setattr("locale.getdefaultlocale", lambda: ("en_US", "UTF-8")) @pytest.fixture def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + monkeypatch.setattr("locale.getdefaultlocale", lambda: ("fr_FR", "UTF-8")) @pytest.fixture def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + monkeypatch.setattr("locale.getdefaultlocale", lambda: ("xx_XX", "UTF-8")) @pytest.fixture def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + monkeypatch.setattr("locale.getdefaultlocale", lambda: ("ru_RU", "UTF-8")) @pytest.fixture def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') + monkeypatch.setattr("platform.system", lambda: "Darwin") @pytest.fixture # (scope="session") def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') + monkeypatch.setattr("platform.system", lambda: "Linux") @pytest.fixture def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') + monkeypatch.setattr("platform.system", lambda: "Windows") @pytest.fixture def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) + monkeypatch.setattr("sys.argv", [sys.prefix]) @pytest.fixture def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) + monkeypatch.setattr("sys.frozen", True, raising=False) @pytest.fixture def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + monkeypatch.setattr("sys._MEIPASS", os.path.expanduser("~"), raising=False) @pytest.fixture # (scope="session") def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + monkeypatch.setattr("sys.onionshare_dev_mode", True, raising=False) @pytest.fixture def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) + monkeypatch.setattr("time.time", lambda: 100) @pytest.fixture def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + monkeypatch.setattr("time.strftime", lambda _: "Jun 06 2013 11:05:00") + @pytest.fixture def common_obj(): return common.Common() + @pytest.fixture def settings_obj(sys_onionshare_dev_mode, platform_linux): _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' + _common.version = "DUMMY_VERSION_1.2.3" strings.load_strings(_common) return settings.Settings(_common) diff --git a/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py index f06ea37b..388a424b 100644 --- a/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py +++ b/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py @@ -4,13 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - "public_mode": True - } + test_settings = {"close_after_first_download": False, "public_mode": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -18,11 +16,12 @@ class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, True) self.hit_401(True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_401_triggers_ratelimit_test.py b/tests/local_onionshare_401_triggers_ratelimit_test.py index 4100657b..cdeb34db 100644 --- a/tests/local_onionshare_401_triggers_ratelimit_test.py +++ b/tests/local_onionshare_401_triggers_ratelimit_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class Local401RateLimitTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False - } + test_settings = {"close_after_first_download": False} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,11 +16,12 @@ class Local401RateLimitTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) self.hit_401(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py index e43c88ba..9a38e24a 100644 --- a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py +++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py @@ -5,12 +5,11 @@ from PyQt5 import QtCore, QtTest from .GuiShareTest import GuiShareTest + class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False - } + test_settings = {"close_after_first_download": False} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -18,7 +17,7 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) @@ -30,5 +29,6 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest self.server_is_started(self.gui.share_mode, 0) self.web_server_is_running() + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_clear_all_button_test.py b/tests/local_onionshare_receive_mode_clear_all_button_test.py index f93d4fe1..d69c3e59 100644 --- a/tests/local_onionshare_receive_mode_clear_all_button_test.py +++ b/tests/local_onionshare_receive_mode_clear_all_button_test.py @@ -4,11 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -16,10 +16,11 @@ class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_clear_all_button_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py index 4cde86c1..f958a132 100644 --- a/tests/local_onionshare_receive_mode_timer_test.py +++ b/tests/local_onionshare_receive_mode_timer_test.py @@ -4,13 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostop_timer": True, - } + test_settings = {"public_mode": False, "autostop_timer": True} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -18,10 +16,11 @@ class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_timer_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index 26feacc3..f1451ba0 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -4,12 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } + test_settings = {"receive_allow_receiver_shutdown": True} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_unwritable_dir_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py index 601c4bd2..6f0997f2 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py @@ -4,13 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } + test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -18,10 +16,11 @@ class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_unwritable_dir_tests(True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index 1f3a8331..818bd593 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -4,13 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } + test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -18,10 +16,11 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index 16036893..38888655 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -4,12 +4,11 @@ import unittest from .GuiReceiveTest import GuiReceiveTest + class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } + test_settings = {"receive_allow_receiver_shutdown": True} cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): GuiReceiveTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_settings_dialog_legacy_tor_test.py b/tests/local_onionshare_settings_dialog_legacy_tor_test.py index 54653c1b..72d33241 100644 --- a/tests/local_onionshare_settings_dialog_legacy_tor_test.py +++ b/tests/local_onionshare_settings_dialog_legacy_tor_test.py @@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): SettingsGuiBaseTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui_legacy_tor(self): self.gui.onion = OnionStub(True, False) self.gui.reload_settings() diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py index 06e3cc9e..b8c06243 100644 --- a/tests/local_onionshare_settings_dialog_no_tor_test.py +++ b/tests/local_onionshare_settings_dialog_no_tor_test.py @@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): SettingsGuiBaseTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui_no_tor(self): self.gui.onion = OnionStub(False, False) self.gui.reload_settings() diff --git a/tests/local_onionshare_settings_dialog_v3_tor_test.py b/tests/local_onionshare_settings_dialog_v3_tor_test.py index 88a0438e..d5abeabc 100644 --- a/tests/local_onionshare_settings_dialog_v3_tor_test.py +++ b/tests/local_onionshare_settings_dialog_v3_tor_test.py @@ -16,7 +16,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): SettingsGuiBaseTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui_v3_tor(self): self.gui.onion = OnionStub(True, True) self.gui.reload_settings() diff --git a/tests/local_onionshare_share_mode_autostart_and_autostop_timer_mismatch_test.py b/tests/local_onionshare_share_mode_autostart_and_autostop_timer_mismatch_test.py index 0bb3bfa3..2a25bef1 100644 --- a/tests/local_onionshare_share_mode_autostart_and_autostop_timer_mismatch_test.py +++ b/tests/local_onionshare_share_mode_autostart_and_autostop_timer_mismatch_test.py @@ -4,6 +4,7 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): @@ -19,10 +20,11 @@ class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_autostop_autostart_mismatch_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_autostart_timer_test.py b/tests/local_onionshare_share_mode_autostart_timer_test.py index 4fd5f649..776cff4f 100644 --- a/tests/local_onionshare_share_mode_autostart_timer_test.py +++ b/tests/local_onionshare_share_mode_autostart_timer_test.py @@ -4,13 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostart_timer": True, - } + test_settings = {"public_mode": False, "autostart_timer": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -18,10 +16,11 @@ class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_autostart_timer_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py b/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py index d8e82aed..1c2040df 100644 --- a/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py +++ b/tests/local_onionshare_share_mode_autostart_timer_too_short_test.py @@ -5,13 +5,11 @@ from PyQt5 import QtCore, QtTest from .GuiShareTest import GuiShareTest + class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostart_timer": True, - } + test_settings = {"public_mode": False, "autostart_timer": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -19,7 +17,7 @@ class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() @@ -27,8 +25,11 @@ class LocalShareModeAutoStartTimerTooShortTest(unittest.TestCase, GuiShareTest): self.set_autostart_timer(self.gui.share_mode, 2) QtTest.QTest.qWait(3000) QtCore.QTimer.singleShot(4000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(self.gui.share_mode.server_status.status, 0) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_cancel_share_test.py b/tests/local_onionshare_share_mode_cancel_share_test.py index 5b526999..d6ee051b 100644 --- a/tests/local_onionshare_share_mode_cancel_share_test.py +++ b/tests/local_onionshare_share_mode_cancel_share_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "autostart_timer": True, - } + test_settings = {"autostart_timer": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,11 +16,12 @@ class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() self.cancel_the_share(self.gui.share_mode) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_clear_all_button_test.py b/tests/local_onionshare_share_mode_clear_all_button_test.py index caed342d..1c11fe81 100644 --- a/tests/local_onionshare_share_mode_clear_all_button_test.py +++ b/tests/local_onionshare_share_mode_clear_all_button_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } + test_settings = {"close_after_first_download": False} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_clear_all_button_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index b7ff336c..6661eae7 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": True, - } + test_settings = {"public_mode": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index 50d82326..04213865 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } + test_settings = {"close_after_first_download": False} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index 387d63c1..1c0b69e9 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -4,11 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -16,10 +16,11 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py b/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py index 4e026e16..18b3283a 100644 --- a/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py +++ b/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } + test_settings = {"close_after_first_download": False} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTe GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_individual_file_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_individual_file_view_test.py b/tests/local_onionshare_share_mode_individual_file_view_test.py index 2bdccaec..d41b2010 100644 --- a/tests/local_onionshare_share_mode_individual_file_view_test.py +++ b/tests/local_onionshare_share_mode_individual_file_view_test.py @@ -4,12 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": True, - } + test_settings = {"close_after_first_download": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_individual_file_tests(False, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py index 5717a2d8..a0458d03 100644 --- a/tests/local_onionshare_share_mode_large_download_test.py +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -4,11 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -16,10 +16,11 @@ class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_large_file_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_password_persistent_test.py b/tests/local_onionshare_share_mode_password_persistent_test.py index 5b515ca1..067815f7 100644 --- a/tests/local_onionshare_share_mode_password_persistent_test.py +++ b/tests/local_onionshare_share_mode_password_persistent_test.py @@ -4,6 +4,7 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): @@ -20,10 +21,11 @@ class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_persistent_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index 0526be08..da200f97 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -4,13 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostop_timer": True, - } + test_settings = {"public_mode": False, "autostop_timer": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -18,10 +16,11 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_timer_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_timer_too_short_test.py b/tests/local_onionshare_share_mode_timer_too_short_test.py index 09b82af3..63c2fdc2 100644 --- a/tests/local_onionshare_share_mode_timer_too_short_test.py +++ b/tests/local_onionshare_share_mode_timer_too_short_test.py @@ -5,13 +5,11 @@ from PyQt5 import QtCore, QtTest from .GuiShareTest import GuiShareTest + class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostop_timer": True, - } + test_settings = {"public_mode": False, "autostop_timer": True} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -19,7 +17,7 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() @@ -27,8 +25,11 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): self.set_timeout(self.gui.share_mode, 2) QtTest.QTest.qWait(3000) QtCore.QTimer.singleShot(4000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(self.gui.share_mode.server_status.status, 0) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_share_mode_unreadable_file_test.py b/tests/local_onionshare_share_mode_unreadable_file_test.py index 8b5dd4e7..80f0fdb8 100644 --- a/tests/local_onionshare_share_mode_unreadable_file_test.py +++ b/tests/local_onionshare_share_mode_unreadable_file_test.py @@ -4,11 +4,11 @@ import unittest from .GuiShareTest import GuiShareTest + class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = GuiShareTest.set_up(test_settings) @classmethod @@ -16,10 +16,11 @@ class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest): GuiShareTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_unreadable_file_tests() + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_website_mode_csp_enabled_test.py b/tests/local_onionshare_website_mode_csp_enabled_test.py index fbdc07ea..f9fdb983 100644 --- a/tests/local_onionshare_website_mode_csp_enabled_test.py +++ b/tests/local_onionshare_website_mode_csp_enabled_test.py @@ -4,12 +4,11 @@ import unittest from .GuiWebsiteTest import GuiWebsiteTest + class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest): @classmethod def setUpClass(cls): - test_settings = { - "csp_header_disabled": False, - } + test_settings = {"csp_header_disabled": False} cls.gui = GuiWebsiteTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest): GuiWebsiteTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): - #self.run_all_common_setup_tests() + # self.run_all_common_setup_tests() self.run_all_website_mode_download_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py index fc560f70..ba00e780 100644 --- a/tests/local_onionshare_website_mode_test.py +++ b/tests/local_onionshare_website_mode_test.py @@ -4,12 +4,11 @@ import unittest from .GuiWebsiteTest import GuiWebsiteTest + class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): @classmethod def setUpClass(cls): - test_settings = { - "csp_header_disabled": True - } + test_settings = {"csp_header_disabled": True} cls.gui = GuiWebsiteTest.set_up(test_settings) @classmethod @@ -17,10 +16,11 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): GuiWebsiteTest.tear_down() @pytest.mark.gui - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): - #self.run_all_common_setup_tests() + # self.run_all_common_setup_tests() self.run_all_website_mode_download_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index f970b8af..7a87c690 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -8,9 +8,7 @@ from .TorGuiShareTest import TorGuiShareTest class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": True - } + test_settings = {"close_after_first_download": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -19,7 +17,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) @@ -27,5 +25,6 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): self.server_is_stopped(self.gui.share_mode, False) self.web_server_is_stopped() + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py index c2611ad5..31b1a8f6 100644 --- a/tests/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -4,13 +4,11 @@ import unittest from .TorGuiReceiveTest import TorGuiReceiveTest + class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } + test_settings = {"public_mode": True, "receive_allow_receiver_shutdown": True} cls.gui = TorGuiReceiveTest.set_up(test_settings) @classmethod @@ -19,10 +17,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(True, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py index a1935562..ca695843 100644 --- a/tests/onionshare_receive_mode_upload_test.py +++ b/tests/onionshare_receive_mode_upload_test.py @@ -4,12 +4,11 @@ import unittest from .TorGuiReceiveTest import TorGuiReceiveTest + class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): @classmethod def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } + test_settings = {"receive_allow_receiver_shutdown": True} cls.gui = TorGuiReceiveTest.set_up(test_settings) @classmethod @@ -18,10 +17,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index 2a248bae..0483fbe1 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -4,12 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "autostart_timer": True, - } + test_settings = {"autostart_timer": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -18,11 +17,12 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() self.cancel_the_share(self.gui.share_mode) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py index 93c23978..72554f8b 100644 --- a/tests/onionshare_share_mode_download_public_mode_test.py +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -4,12 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": True, - } + test_settings = {"public_mode": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -18,10 +17,11 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py index 3fcebc63..923eebf3 100644 --- a/tests/onionshare_share_mode_download_stay_open_test.py +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -4,12 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } + test_settings = {"close_after_first_download": False} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -18,10 +17,11 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py index 65f68df6..2bebd098 100644 --- a/tests/onionshare_share_mode_download_test.py +++ b/tests/onionshare_share_mode_download_test.py @@ -4,11 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -17,10 +17,11 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 0e461f7e..c5d44733 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -4,6 +4,7 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): @@ -22,10 +23,11 @@ class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_persistent_tests(False, True) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index ded0b7a8..3ee743d5 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -4,13 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "use_legacy_v2_onions": True, - "use_stealth": True, - } + test_settings = {"use_legacy_v2_onions": True, "use_stealth": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -19,7 +17,7 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() @@ -27,5 +25,6 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): self.hidserv_auth_string() self.copy_have_hidserv_auth_button(self.gui.share_mode) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py index 1ae2b0fa..78a70bbf 100644 --- a/tests/onionshare_share_mode_timer_test.py +++ b/tests/onionshare_share_mode_timer_test.py @@ -4,13 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "public_mode": False, - "autostop_timer": True, - } + test_settings = {"public_mode": False, "autostop_timer": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -19,10 +17,11 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_timer_tests(False) + if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py index 7362a170..4702ab3e 100644 --- a/tests/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests/onionshare_share_mode_tor_connection_killed_test.py @@ -4,16 +4,16 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - } + test_settings = {} cls.gui = TorGuiShareTest.set_up(test_settings) @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() diff --git a/tests/onionshare_share_mode_v2_onion_test.py b/tests/onionshare_share_mode_v2_onion_test.py index 29c97491..152457ba 100644 --- a/tests/onionshare_share_mode_v2_onion_test.py +++ b/tests/onionshare_share_mode_v2_onion_test.py @@ -4,12 +4,11 @@ import unittest from .TorGuiShareTest import TorGuiShareTest + class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): - test_settings = { - "use_legacy_v2_onions": True, - } + test_settings = {"use_legacy_v2_onions": True} cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod @@ -18,11 +17,12 @@ class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.gui @pytest.mark.tor - @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + @pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) self.have_v2_onion() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 321afbb7..387fbf4a 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -20,7 +20,7 @@ import tempfile import os -class MockSubprocess(): +class MockSubprocess: def __init__(self): self.last_call = None diff --git a/tests/test_onionshare.py b/tests/test_onionshare.py index f141fed7..64b16b1f 100644 --- a/tests/test_onionshare.py +++ b/tests/test_onionshare.py @@ -27,14 +27,14 @@ from onionshare.common import Common class MyOnion: def __init__(self, stealth=False): - self.auth_string = 'TestHidServAuth' - self.private_key = '' + self.auth_string = "TestHidServAuth" + self.private_key = "" self.stealth = stealth self.scheduled_key = None @staticmethod def start_onion_service(self, await_publication=True, save_scheduled_key=False): - return 'test_service_id.onion' + return "test_service_id.onion" @pytest.fixture @@ -65,18 +65,17 @@ class TestOnionShare: onionshare_obj.set_stealth(False) onionshare_obj.start_onion_service() assert 17600 <= onionshare_obj.port <= 17650 - assert onionshare_obj.onion_host == 'test_service_id.onion' + assert onionshare_obj.onion_host == "test_service_id.onion" def test_start_onion_service_stealth(self, onionshare_obj): onionshare_obj.set_stealth(True) onionshare_obj.start_onion_service() - assert onionshare_obj.auth_string == 'TestHidServAuth' + assert onionshare_obj.auth_string == "TestHidServAuth" def test_start_onion_service_local_only(self, onionshare_obj): onionshare_obj.local_only = True onionshare_obj.start_onion_service() - assert onionshare_obj.onion_host == '127.0.0.1:{}'.format( - onionshare_obj.port) + assert onionshare_obj.onion_host == "127.0.0.1:{}".format(onionshare_obj.port) def test_cleanup(self, onionshare_obj, temp_dir_1024, temp_file_1024): onionshare_obj.cleanup_filenames = [temp_dir_1024, temp_file_1024] diff --git a/tests/test_onionshare_common.py b/tests/test_onionshare_common.py index d5e67381..1f230295 100644 --- a/tests/test_onionshare_common.py +++ b/tests/test_onionshare_common.py @@ -29,37 +29,40 @@ import zipfile import pytest -LOG_MSG_REGEX = re.compile(r""" +LOG_MSG_REGEX = re.compile( + r""" ^\[Jun\ 06\ 2013\ 11:05:00\] \ TestModule\.\.dummy_func - \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE) -PASSWORD_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') + \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", + re.VERBOSE, +) +PASSWORD_REGEX = re.compile(r"^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$") # TODO: Improve the Common tests to test it all as a single class class TestBuildPassword: - @pytest.mark.parametrize('test_input,expected', ( - # VALID, two lowercase words, separated by a hyphen - ('syrup-enzyme', True), - ('caution-friday', True), - - # VALID, two lowercase words, with one hyphenated compound word - ('drop-down-thimble', True), - ('unmixed-yo-yo', True), - - # VALID, two lowercase hyphenated compound words, separated by hyphen - ('yo-yo-drop-down', True), - ('felt-tip-t-shirt', True), - ('hello-world', True), - - # INVALID - ('Upper-Case', False), - ('digits-123', False), - ('too-many-hyphens-', False), - ('symbols-!@#$%', False) - )) + @pytest.mark.parametrize( + "test_input,expected", + ( + # VALID, two lowercase words, separated by a hyphen + ("syrup-enzyme", True), + ("caution-friday", True), + # VALID, two lowercase words, with one hyphenated compound word + ("drop-down-thimble", True), + ("unmixed-yo-yo", True), + # VALID, two lowercase hyphenated compound words, separated by hyphen + ("yo-yo-drop-down", True), + ("felt-tip-t-shirt", True), + ("hello-world", True), + # INVALID + ("Upper-Case", False), + ("digits-123", False), + ("too-many-hyphens-", False), + ("symbols-!@#$%", False), + ), + ) def test_build_password_regex(self, test_input, expected): """ Test that `PASSWORD_REGEX` accounts for the following patterns @@ -92,79 +95,87 @@ class TestDirSize: class TestEstimatedTimeRemaining: - @pytest.mark.parametrize('test_input,expected', ( - ((2, 676, 12), '8h14m16s'), - ((14, 1049, 30), '1h26m15s'), - ((21, 450, 1), '33m42s'), - ((31, 1115, 80), '11m39s'), - ((336, 989, 32), '2m12s'), - ((603, 949, 38), '36s'), - ((971, 1009, 83), '1s') - )) + @pytest.mark.parametrize( + "test_input,expected", + ( + ((2, 676, 12), "8h14m16s"), + ((14, 1049, 30), "1h26m15s"), + ((21, 450, 1), "33m42s"), + ((31, 1115, 80), "11m39s"), + ((336, 989, 32), "2m12s"), + ((603, 949, 38), "36s"), + ((971, 1009, 83), "1s"), + ), + ) def test_estimated_time_remaining( - self, common_obj, test_input, expected, time_time_100): + self, common_obj, test_input, expected, time_time_100 + ): assert common_obj.estimated_time_remaining(*test_input) == expected - @pytest.mark.parametrize('test_input', ( - (10, 20, 100), # if `time_elapsed == 0` - (0, 37, 99) # if `download_rate == 0` - )) + @pytest.mark.parametrize( + "test_input", + ( + (10, 20, 100), # if `time_elapsed == 0` + (0, 37, 99), # if `download_rate == 0` + ), + ) def test_raises_zero_division_error(self, common_obj, test_input, time_time_100): with pytest.raises(ZeroDivisionError): common_obj.estimated_time_remaining(*test_input) class TestFormatSeconds: - @pytest.mark.parametrize('test_input,expected', ( - (0, '0s'), - (26, '26s'), - (60, '1m'), - (947.35, '15m47s'), - (1847, '30m47s'), - (2193.94, '36m34s'), - (3600, '1h'), - (13426.83, '3h43m47s'), - (16293, '4h31m33s'), - (18392.14, '5h6m32s'), - (86400, '1d'), - (129674, '1d12h1m14s'), - (56404.12, '15h40m4s') - )) + @pytest.mark.parametrize( + "test_input,expected", + ( + (0, "0s"), + (26, "26s"), + (60, "1m"), + (947.35, "15m47s"), + (1847, "30m47s"), + (2193.94, "36m34s"), + (3600, "1h"), + (13426.83, "3h43m47s"), + (16293, "4h31m33s"), + (18392.14, "5h6m32s"), + (86400, "1d"), + (129674, "1d12h1m14s"), + (56404.12, "15h40m4s"), + ), + ) def test_format_seconds(self, common_obj, test_input, expected): assert common_obj.format_seconds(test_input) == expected # TODO: test negative numbers? - @pytest.mark.parametrize('test_input', ( - 'string', lambda: None, [], {}, set() - )) + @pytest.mark.parametrize("test_input", ("string", lambda: None, [], {}, set())) def test_invalid_input_types(self, common_obj, test_input): with pytest.raises(TypeError): common_obj.format_seconds(test_input) class TestGetAvailablePort: - @pytest.mark.parametrize('port_min,port_max', ( - (random.randint(1024, 1500), - random.randint(1800, 2048)) for _ in range(50) - )) + @pytest.mark.parametrize( + "port_min,port_max", + ((random.randint(1024, 1500), random.randint(1800, 2048)) for _ in range(50)), + ) def test_returns_an_open_port(self, common_obj, port_min, port_max): """ get_available_port() should return an open port within the range """ port = common_obj.get_available_port(port_min, port_max) assert port_min <= port <= port_max with socket.socket() as tmpsock: - tmpsock.bind(('127.0.0.1', port)) + tmpsock.bind(("127.0.0.1", port)) class TestGetPlatform: def test_darwin(self, platform_darwin, common_obj): - assert common_obj.platform == 'Darwin' + assert common_obj.platform == "Darwin" def test_linux(self, platform_linux, common_obj): - assert common_obj.platform == 'Linux' + assert common_obj.platform == "Linux" def test_windows(self, platform_windows, common_obj): - assert common_obj.platform == 'Windows' + assert common_obj.platform == "Windows" # TODO: double-check these tests @@ -173,94 +184,114 @@ class TestGetResourcePath: prefix = os.path.join( os.path.dirname( os.path.dirname( - os.path.abspath( - inspect.getfile( - inspect.currentframe())))), 'share') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) + os.path.abspath(inspect.getfile(inspect.currentframe())) + ) + ), + "share", + ) + assert common_obj.get_resource_path( + os.path.join(prefix, "test_filename") + ) == os.path.join(prefix, "test_filename") def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix): - prefix = os.path.join(sys.prefix, 'share/onionshare') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) + prefix = os.path.join(sys.prefix, "share/onionshare") + assert common_obj.get_resource_path( + os.path.join(prefix, "test_filename") + ) == os.path.join(prefix, "test_filename") def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass): - prefix = os.path.join(sys._MEIPASS, 'share') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) + prefix = os.path.join(sys._MEIPASS, "share") + assert common_obj.get_resource_path( + os.path.join(prefix, "test_filename") + ) == os.path.join(prefix, "test_filename") class TestGetTorPaths: # @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ? - def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass): + def test_get_tor_paths_darwin( + self, platform_darwin, common_obj, sys_frozen, sys_meipass + ): base_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - common_obj.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') - assert (common_obj.get_tor_paths() == - (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) + os.path.dirname(os.path.dirname(common_obj.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") + assert common_obj.get_tor_paths() == ( + tor_path, + tor_geo_ip_file_path, + tor_geo_ipv6_file_path, + obfs4proxy_file_path, + ) # @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ? def test_get_tor_paths_linux(self, platform_linux, common_obj): - assert (common_obj.get_tor_paths() == - ('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy')) + assert common_obj.get_tor_paths() == ( + "/usr/bin/tor", + "/usr/share/tor/geoip", + "/usr/share/tor/geoip6", + "/usr/bin/obfs4proxy", + ) # @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ? def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen): base_path = os.path.join( - os.path.dirname( - os.path.dirname( - common_obj.get_resource_path(''))), 'tor') - tor_path = os.path.join( - os.path.join(base_path, 'Tor'), 'tor.exe') + os.path.dirname(os.path.dirname(common_obj.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') + 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') + 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') - assert (common_obj.get_tor_paths() == - (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) + os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip6" + ) + assert common_obj.get_tor_paths() == ( + tor_path, + tor_geo_ip_file_path, + tor_geo_ipv6_file_path, + obfs4proxy_file_path, + ) class TestHumanReadableFilesize: - @pytest.mark.parametrize('test_input,expected', ( - (1024 ** 0, '1.0 B'), - (1024 ** 1, '1.0 KiB'), - (1024 ** 2, '1.0 MiB'), - (1024 ** 3, '1.0 GiB'), - (1024 ** 4, '1.0 TiB'), - (1024 ** 5, '1.0 PiB'), - (1024 ** 6, '1.0 EiB'), - (1024 ** 7, '1.0 ZiB'), - (1024 ** 8, '1.0 YiB') - )) + @pytest.mark.parametrize( + "test_input,expected", + ( + (1024 ** 0, "1.0 B"), + (1024 ** 1, "1.0 KiB"), + (1024 ** 2, "1.0 MiB"), + (1024 ** 3, "1.0 GiB"), + (1024 ** 4, "1.0 TiB"), + (1024 ** 5, "1.0 PiB"), + (1024 ** 6, "1.0 EiB"), + (1024 ** 7, "1.0 ZiB"), + (1024 ** 8, "1.0 YiB"), + ), + ) def test_human_readable_filesize(self, common_obj, test_input, expected): assert common_obj.human_readable_filesize(test_input) == expected class TestLog: - @pytest.mark.parametrize('test_input', ( - ('[Jun 06 2013 11:05:00]' - ' TestModule..dummy_func' - ' at 0xdeadbeef>'), - ('[Jun 06 2013 11:05:00]' - ' TestModule..dummy_func' - ' at 0xdeadbeef>: TEST_MSG') - )) + @pytest.mark.parametrize( + "test_input", + ( + ( + "[Jun 06 2013 11:05:00]" + " TestModule..dummy_func" + " at 0xdeadbeef>" + ), + ( + "[Jun 06 2013 11:05:00]" + " TestModule..dummy_func" + " at 0xdeadbeef>: TEST_MSG" + ), + ), + ) def test_log_msg_regex(self, test_input): assert bool(LOG_MSG_REGEX.match(test_input)) @@ -272,10 +303,10 @@ class TestLog: # From: https://stackoverflow.com/questions/1218933 with io.StringIO() as buf, contextlib.redirect_stdout(buf): - common_obj.log('TestModule', dummy_func) - common_obj.log('TestModule', dummy_func, 'TEST_MSG') + common_obj.log("TestModule", dummy_func) + common_obj.log("TestModule", dummy_func, "TEST_MSG") output = buf.getvalue() - line_one, line_two, _ = output.split('\n') + line_one, line_two, _ = output.split("\n") assert LOG_MSG_REGEX.match(line_one) assert LOG_MSG_REGEX.match(line_two) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 12200b70..0bce2f94 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -28,86 +28,86 @@ from onionshare import common, settings, strings @pytest.fixture def os_path_expanduser(monkeypatch): - monkeypatch.setattr('os.path.expanduser', lambda path: path) + monkeypatch.setattr("os.path.expanduser", lambda path: path) @pytest.fixture def settings_obj(sys_onionshare_dev_mode, platform_linux): _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' + _common.version = "DUMMY_VERSION_1.2.3" return settings.Settings(_common) class TestSettings: def test_init(self, settings_obj): expected_settings = { - 'version': 'DUMMY_VERSION_1.2.3', - 'connection_type': 'bundled', - 'control_port_address': '127.0.0.1', - 'control_port_port': 9051, - 'socks_address': '127.0.0.1', - 'socks_port': 9050, - 'socket_file_path': '/var/run/tor/control', - 'auth_type': 'no_auth', - 'auth_password': '', - 'close_after_first_download': True, - 'autostop_timer': False, - 'autostart_timer': False, - 'use_stealth': False, - 'use_autoupdate': True, - 'autoupdate_timestamp': None, - 'no_bridges': True, - 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_azure': False, - 'tor_bridges_use_custom_bridges': '', - 'use_legacy_v2_onions': False, - 'save_private_key': False, - 'private_key': '', - 'password': '', - 'hidservauth_string': '', - 'data_dir': os.path.expanduser('~/OnionShare'), - 'public_mode': False, - 'csp_header_disabled': False + "version": "DUMMY_VERSION_1.2.3", + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "auth_type": "no_auth", + "auth_password": "", + "close_after_first_download": True, + "autostop_timer": False, + "autostart_timer": False, + "use_stealth": False, + "use_autoupdate": True, + "autoupdate_timestamp": None, + "no_bridges": True, + "tor_bridges_use_obfs4": False, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_custom_bridges": "", + "use_legacy_v2_onions": False, + "save_private_key": False, + "private_key": "", + "password": "", + "hidservauth_string": "", + "data_dir": os.path.expanduser("~/OnionShare"), + "public_mode": False, + "csp_header_disabled": False, } for key in settings_obj._settings: # Skip locale, it will not always default to the same thing - if key != 'locale': + if key != "locale": assert settings_obj._settings[key] == settings_obj.default_settings[key] assert settings_obj._settings[key] == expected_settings[key] def test_fill_in_defaults(self, settings_obj): - del settings_obj._settings['version'] + del settings_obj._settings["version"] settings_obj.fill_in_defaults() - assert settings_obj._settings['version'] == 'DUMMY_VERSION_1.2.3' + assert settings_obj._settings["version"] == "DUMMY_VERSION_1.2.3" def test_load(self, settings_obj): custom_settings = { - 'version': 'CUSTOM_VERSION', - 'socks_port': 9999, - 'use_stealth': True + "version": "CUSTOM_VERSION", + "socks_port": 9999, + "use_stealth": True, } tmp_file, tmp_file_path = tempfile.mkstemp() - with open(tmp_file, 'w') as f: + with open(tmp_file, "w") as f: json.dump(custom_settings, f) settings_obj.filename = tmp_file_path settings_obj.load() - assert settings_obj._settings['version'] == 'CUSTOM_VERSION' - assert settings_obj._settings['socks_port'] == 9999 - assert settings_obj._settings['use_stealth'] is True + assert settings_obj._settings["version"] == "CUSTOM_VERSION" + assert settings_obj._settings["socks_port"] == 9999 + assert settings_obj._settings["use_stealth"] is True os.remove(tmp_file_path) assert os.path.exists(tmp_file_path) is False def test_save(self, monkeypatch, settings_obj): - monkeypatch.setattr(strings, '_', lambda _: '') + monkeypatch.setattr(strings, "_", lambda _: "") - settings_filename = 'default_settings.json' + settings_filename = "default_settings.json" tmp_dir = tempfile.gettempdir() settings_path = os.path.join(tmp_dir, settings_filename) settings_obj.filename = settings_path settings_obj.save() - with open(settings_path, 'r') as f: + with open(settings_path, "r") as f: settings = json.load(f) assert settings_obj._settings == settings @@ -116,69 +116,64 @@ class TestSettings: assert os.path.exists(settings_path) is False def test_get(self, settings_obj): - assert settings_obj.get('version') == 'DUMMY_VERSION_1.2.3' - assert settings_obj.get('connection_type') == 'bundled' - assert settings_obj.get('control_port_address') == '127.0.0.1' - assert settings_obj.get('control_port_port') == 9051 - assert settings_obj.get('socks_address') == '127.0.0.1' - assert settings_obj.get('socks_port') == 9050 - assert settings_obj.get('socket_file_path') == '/var/run/tor/control' - assert settings_obj.get('auth_type') == 'no_auth' - assert settings_obj.get('auth_password') == '' - assert settings_obj.get('close_after_first_download') is True - assert settings_obj.get('use_stealth') is False - assert settings_obj.get('use_autoupdate') is True - assert settings_obj.get('autoupdate_timestamp') is None - assert settings_obj.get('autoupdate_timestamp') is None - assert settings_obj.get('no_bridges') is True - assert settings_obj.get('tor_bridges_use_obfs4') is False - assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False - assert settings_obj.get('tor_bridges_use_custom_bridges') == '' - + assert settings_obj.get("version") == "DUMMY_VERSION_1.2.3" + assert settings_obj.get("connection_type") == "bundled" + assert settings_obj.get("control_port_address") == "127.0.0.1" + assert settings_obj.get("control_port_port") == 9051 + assert settings_obj.get("socks_address") == "127.0.0.1" + assert settings_obj.get("socks_port") == 9050 + assert settings_obj.get("socket_file_path") == "/var/run/tor/control" + assert settings_obj.get("auth_type") == "no_auth" + assert settings_obj.get("auth_password") == "" + assert settings_obj.get("close_after_first_download") is True + assert settings_obj.get("use_stealth") is False + assert settings_obj.get("use_autoupdate") is True + assert settings_obj.get("autoupdate_timestamp") is None + assert settings_obj.get("autoupdate_timestamp") is None + assert settings_obj.get("no_bridges") is True + assert settings_obj.get("tor_bridges_use_obfs4") is False + assert settings_obj.get("tor_bridges_use_meek_lite_azure") is False + assert settings_obj.get("tor_bridges_use_custom_bridges") == "" def test_set_version(self, settings_obj): - settings_obj.set('version', 'CUSTOM_VERSION') - assert settings_obj._settings['version'] == 'CUSTOM_VERSION' + settings_obj.set("version", "CUSTOM_VERSION") + assert settings_obj._settings["version"] == "CUSTOM_VERSION" def test_set_control_port_port(self, settings_obj): - settings_obj.set('control_port_port', 999) - assert settings_obj._settings['control_port_port'] == 999 + settings_obj.set("control_port_port", 999) + assert settings_obj._settings["control_port_port"] == 999 - settings_obj.set('control_port_port', 'NON_INTEGER') - assert settings_obj._settings['control_port_port'] == 9051 + settings_obj.set("control_port_port", "NON_INTEGER") + assert settings_obj._settings["control_port_port"] == 9051 def test_set_socks_port(self, settings_obj): - settings_obj.set('socks_port', 888) - assert settings_obj._settings['socks_port'] == 888 + settings_obj.set("socks_port", 888) + assert settings_obj._settings["socks_port"] == 888 - settings_obj.set('socks_port', 'NON_INTEGER') - assert settings_obj._settings['socks_port'] == 9050 + settings_obj.set("socks_port", "NON_INTEGER") + assert settings_obj._settings["socks_port"] == 9050 - def test_filename_darwin( - self, - monkeypatch, - os_path_expanduser, - platform_darwin): + def test_filename_darwin(self, monkeypatch, os_path_expanduser, platform_darwin): obj = settings.Settings(common.Common()) - assert (obj.filename == - '~/Library/Application Support/OnionShare/onionshare.json') + assert ( + obj.filename == "~/Library/Application Support/OnionShare/onionshare.json" + ) - def test_filename_linux( - self, - monkeypatch, - os_path_expanduser, - platform_linux): + def test_filename_linux(self, monkeypatch, os_path_expanduser, platform_linux): obj = settings.Settings(common.Common()) - assert obj.filename == '~/.config/onionshare/onionshare.json' + assert obj.filename == "~/.config/onionshare/onionshare.json" - def test_filename_windows( - self, - monkeypatch, - platform_windows): - monkeypatch.setenv('APPDATA', 'C:') + def test_filename_windows(self, monkeypatch, platform_windows): + monkeypatch.setenv("APPDATA", "C:") obj = settings.Settings(common.Common()) - assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json' + assert obj.filename.replace("/", "\\") == "C:\\OnionShare\\onionshare.json" def test_set_custom_bridge(self, settings_obj): - settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E') - assert settings_obj._settings['tor_bridges_use_custom_bridges'] == 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E' + settings_obj.set( + "tor_bridges_use_custom_bridges", + "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E", + ) + assert ( + settings_obj._settings["tor_bridges_use_custom_bridges"] + == "Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E" + ) diff --git a/tests/test_onionshare_strings.py b/tests/test_onionshare_strings.py index 3ac22a11..7ad65191 100644 --- a/tests/test_onionshare_strings.py +++ b/tests/test_onionshare_strings.py @@ -32,31 +32,34 @@ from onionshare.settings import Settings # return path # common.get_resource_path = get_resource_path + def test_underscore_is_function(): assert callable(strings._) and isinstance(strings._, types.FunctionType) class TestLoadStrings: def test_load_strings_defaults_to_english( - self, common_obj, locale_en, sys_onionshare_dev_mode): + self, common_obj, locale_en, sys_onionshare_dev_mode + ): """ load_strings() loads English by default """ common_obj.settings = Settings(common_obj) strings.load_strings(common_obj) - assert strings._('preparing_files') == "Compressing files." - + assert strings._("preparing_files") == "Compressing files." def test_load_strings_loads_other_languages( - self, common_obj, locale_fr, sys_onionshare_dev_mode): + self, common_obj, locale_fr, sys_onionshare_dev_mode + ): """ load_strings() loads other languages in different locales """ common_obj.settings = Settings(common_obj) - common_obj.settings.set('locale', 'fr') + common_obj.settings.set("locale", "fr") strings.load_strings(common_obj) - assert strings._('preparing_files') == "Compression des fichiers." + assert strings._("preparing_files") == "Compression des fichiers." def test_load_invalid_locale( - self, common_obj, locale_invalid, sys_onionshare_dev_mode): + self, common_obj, locale_invalid, sys_onionshare_dev_mode + ): """ load_strings() raises a KeyError for an invalid locale """ with pytest.raises(KeyError): common_obj.settings = Settings(common_obj) - common_obj.settings.set('locale', 'XX') + common_obj.settings.set("locale", "XX") strings.load_strings(common_obj) diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py index 313dbcea..b971b31a 100644 --- a/tests/test_onionshare_web.py +++ b/tests/test_onionshare_web.py @@ -37,8 +37,8 @@ from onionshare import strings from onionshare.web import Web from onionshare.settings import Settings -DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') -RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') +DEFAULT_ZW_FILENAME_REGEX = re.compile(r"^onionshare_[a-z2-7]{6}.zip$") +RANDOM_STR_REGEX = re.compile(r"^[a-z2-7]+$") def web_obj(common_obj, mode, num_files=0): @@ -53,12 +53,12 @@ def web_obj(common_obj, mode, num_files=0): web.app.testing = True # Share mode - if mode == 'share': + if mode == "share": # Add files files = [] for i in range(num_files): with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) + tmp_file.write(b"*" * 1024) files.append(tmp_file.name) web.share_mode.set_file_info(files) # Receive mode @@ -70,122 +70,130 @@ def web_obj(common_obj, mode, num_files=0): class TestWeb: def test_share_mode(self, common_obj): - web = web_obj(common_obj, 'share', 3) - assert web.mode is 'share' + web = web_obj(common_obj, "share", 3) + assert web.mode is "share" with web.app.test_client() as c: # Load / without auth - res = c.get('/') + res = c.get("/") res.get_data() assert res.status_code == 401 # Load / with invalid auth - res = c.get('/', headers=self._make_auth_headers('invalid')) + res = c.get("/", headers=self._make_auth_headers("invalid")) res.get_data() assert res.status_code == 401 # Load / with valid auth - res = c.get('/', headers=self._make_auth_headers(web.password)) + res = c.get("/", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 # Download - res = c.get('/download', headers=self._make_auth_headers(web.password)) + res = c.get("/download", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 - assert res.mimetype == 'application/zip' + assert res.mimetype == "application/zip" def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024): - web = web_obj(common_obj, 'share', 3) + web = web_obj(common_obj, "share", 3) web.stay_open = False assert web.running == True with web.app.test_client() as c: # Download the first time - res = c.get('/download', headers=self._make_auth_headers(web.password)) + res = c.get("/download", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 - assert res.mimetype == 'application/zip' + assert res.mimetype == "application/zip" assert web.running == False - def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024): - web = web_obj(common_obj, 'share', 3) + def test_share_mode_close_after_first_download_off( + self, common_obj, temp_file_1024 + ): + web = web_obj(common_obj, "share", 3) web.stay_open = True assert web.running == True with web.app.test_client() as c: # Download the first time - res = c.get('/download', headers=self._make_auth_headers(web.password)) + res = c.get("/download", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 - assert res.mimetype == 'application/zip' + assert res.mimetype == "application/zip" assert web.running == True def test_receive_mode(self, common_obj): - web = web_obj(common_obj, 'receive') - assert web.mode is 'receive' + web = web_obj(common_obj, "receive") + assert web.mode is "receive" with web.app.test_client() as c: # Load / without auth - res = c.get('/') + res = c.get("/") res.get_data() assert res.status_code == 401 # Load / with invalid auth - res = c.get('/', headers=self._make_auth_headers('invalid')) + res = c.get("/", headers=self._make_auth_headers("invalid")) res.get_data() assert res.status_code == 401 # Load / with valid auth - res = c.get('/', headers=self._make_auth_headers(web.password)) + res = c.get("/", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 def test_public_mode_on(self, common_obj): - web = web_obj(common_obj, 'receive') - common_obj.settings.set('public_mode', True) + web = web_obj(common_obj, "receive") + common_obj.settings.set("public_mode", True) with web.app.test_client() as c: # Loading / should work without auth - res = c.get('/') + res = c.get("/") data1 = res.get_data() assert res.status_code == 200 def test_public_mode_off(self, common_obj): - web = web_obj(common_obj, 'receive') - common_obj.settings.set('public_mode', False) + web = web_obj(common_obj, "receive") + common_obj.settings.set("public_mode", False) with web.app.test_client() as c: # Load / without auth - res = c.get('/') + res = c.get("/") res.get_data() assert res.status_code == 401 # But static resources should work without auth - res = c.get('{}/css/style.css'.format(web.static_url_path)) + res = c.get("{}/css/style.css".format(web.static_url_path)) res.get_data() assert res.status_code == 200 # Load / with valid auth - res = c.get('/', headers=self._make_auth_headers(web.password)) + res = c.get("/", headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 def _make_auth_headers(self, password): - auth = base64.b64encode(b'onionshare:'+password.encode()).decode() + auth = base64.b64encode(b"onionshare:" + password.encode()).decode() h = Headers() - h.add('Authorization', 'Basic ' + auth) + h.add("Authorization", "Basic " + auth) return h class TestZipWriterDefault: - @pytest.mark.parametrize('test_input', ( - 'onionshare_{}.zip'.format(''.join( - random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6) - )) for _ in range(50) - )) + @pytest.mark.parametrize( + "test_input", + ( + "onionshare_{}.zip".format( + "".join( + random.choice("abcdefghijklmnopqrstuvwxyz234567") for _ in range(6) + ) + ) + for _ in range(50) + ), + ) def test_default_zw_filename_regex(self, test_input): assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input)) @@ -200,15 +208,14 @@ class TestZipWriterDefault: assert default_zw.z._allowZip64 is True def test_zipfile_mode(self, default_zw): - assert default_zw.z.mode == 'w' + assert default_zw.z.mode == "w" def test_callback(self, default_zw): assert default_zw.processed_size_callback(None) is None def test_add_file(self, default_zw, temp_file_1024_delete): default_zw.add_file(temp_file_1024_delete) - zipfile_info = default_zw.z.getinfo( - os.path.basename(temp_file_1024_delete)) + zipfile_info = default_zw.z.getinfo(os.path.basename(temp_file_1024_delete)) assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED assert zipfile_info.file_size == 1024 @@ -220,12 +227,15 @@ class TestZipWriterDefault: class TestZipWriterCustom: - @pytest.mark.parametrize('test_input', ( - Common.random_string( - random.randint(2, 50), - random.choice((None, random.randint(2, 50))) - ) for _ in range(50) - )) + @pytest.mark.parametrize( + "test_input", + ( + Common.random_string( + random.randint(2, 50), random.choice((None, random.randint(2, 50))) + ) + for _ in range(50) + ), + ) def test_random_string_regex(self, test_input): assert bool(RANDOM_STR_REGEX.match(test_input)) @@ -233,4 +243,4 @@ class TestZipWriterCustom: assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename)) def test_custom_callback(self, custom_zw): - assert custom_zw.processed_size_callback(None) == 'custom_callback' + assert custom_zw.processed_size_callback(None) == "custom_callback"