mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-29 09:16:24 -05:00
Merge branch 'black_formatting' into develop
This commit is contained in:
commit
cc2cead50b
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
@ -74,19 +75,18 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||
# (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')
|
||||
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 = []
|
||||
|
@ -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='<int>', dest='autostart_timer', default=0, help="Schedule this share to start N seconds from now")
|
||||
parser.add_argument('--auto-stop-timer', metavar='<int>', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds")
|
||||
parser.add_argument('--connect-timeout', metavar='<int>', 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="<int>",
|
||||
dest="autostart_timer",
|
||||
default=0,
|
||||
help="Schedule this share to start N seconds from now",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-stop-timer",
|
||||
metavar="<int>",
|
||||
dest="autostop_timer",
|
||||
default=0,
|
||||
help="Stop sharing after a given amount of seconds",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--connect-timeout",
|
||||
metavar="<int>",
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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,28 +367,39 @@ 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'):
|
||||
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
|
||||
@ -297,18 +407,20 @@ class Onion(object):
|
||||
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):
|
||||
"""
|
||||
|
@ -24,15 +24,17 @@ from . import common, strings
|
||||
from .onion import TorTooOld, TorErrorProtocolError
|
||||
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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -287,21 +360,27 @@ class ReceiveModeRequest(Request):
|
||||
|
||||
# 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(
|
||||
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))
|
||||
))
|
||||
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:
|
||||
|
@ -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.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)
|
||||
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.
|
||||
"""
|
||||
|
@ -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('/<path:path>')
|
||||
|
||||
@self.web.app.route("/", defaults={"path": ""})
|
||||
@self.web.app.route("/<path:path>")
|
||||
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,26 +170,29 @@ 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',
|
||||
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,
|
||||
@ -178,17 +200,21 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
breadcrumbs_leaf=breadcrumbs_leaf,
|
||||
filename=os.path.basename(self.download_filename),
|
||||
filesize=self.filesize,
|
||||
filesize_human=self.common.human_readable_filesize(self.download_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))
|
||||
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?
|
||||
|
@ -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 + '/<path:filename>',
|
||||
endpoint='static', view_func=self.app.send_static_file)
|
||||
self.static_url_path + "/<path:filename>",
|
||||
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
|
||||
|
@ -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('/<path:path>')
|
||||
|
||||
@self.web.app.route("/", defaults={"path": ""})
|
||||
@self.web.app.route("/<path:path>")
|
||||
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',
|
||||
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))
|
||||
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])
|
||||
|
@ -32,21 +32,25 @@ 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):
|
||||
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()
|
||||
|
@ -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)
|
||||
@ -103,11 +117,21 @@ class Mode(QtWidgets.QWidget):
|
||||
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):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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")
|
||||
)
|
||||
else:
|
||||
text = strings._(string_range_name).format(
|
||||
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('<img src="{0:s}" /> {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(
|
||||
'<img src="{0:s}" /> {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('<img src="{0:s}" /> {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(
|
||||
'<img src="{0:s}" /> {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('<img src="{0:s}" /> {1:d}'.format(image, self.requests_count))
|
||||
self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.requests_count))
|
||||
self.requests_label.setText(
|
||||
'<img src="{0:s}" /> {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()
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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_()
|
||||
|
||||
|
@ -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
|
||||
@ -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()
|
||||
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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])
|
||||
|
@ -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("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary))
|
||||
self.setLabelText(
|
||||
"<strong>{}</strong><br>{}".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):
|
||||
|
@ -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,24 +96,32 @@ 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)
|
||||
@ -116,19 +130,27 @@ class UpdateChecker(QtCore.QObject):
|
||||
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)
|
||||
@ -185,25 +217,25 @@ class UpdateThread(QtCore.QThread):
|
||||
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)
|
||||
|
@ -19,19 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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)
|
||||
|
106
setup.py
106
setup.py
@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
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,7 +39,10 @@ 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" + (
|
||||
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 """
|
||||
@ -47,11 +52,12 @@ long_description = description + "\n\n" + (
|
||||
"""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",
|
||||
@ -60,38 +66,72 @@ classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Operating System :: OS Independent",
|
||||
"Environment :: Web Environment"
|
||||
"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'))
|
||||
(
|
||||
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']))
|
||||
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,
|
||||
)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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'''
|
||||
"""Add a large file to the share"""
|
||||
size = 1024 * 1024 * 155
|
||||
with open('/tmp/large_file', 'wb') as fout:
|
||||
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)
|
||||
|
@ -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('<html><body><p>This is a test website hosted by OnionShare</p></body></html>')
|
||||
testfile = open("/tmp/index.html", "w")
|
||||
testfile.write(
|
||||
"<html><body><p>This is a test website hosted by OnionShare</p></body></html>"
|
||||
)
|
||||
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)
|
||||
|
||||
|
@ -23,7 +23,7 @@ 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 = {
|
||||
@ -32,8 +32,8 @@ class SettingsGuiBaseTest(object):
|
||||
}
|
||||
|
||||
# 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",
|
||||
)
|
||||
|
@ -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)
|
||||
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")
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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_website_mode_download_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
@ -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_website_mode_download_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -20,7 +20,7 @@ import tempfile
|
||||
import os
|
||||
|
||||
|
||||
class MockSubprocess():
|
||||
class MockSubprocess:
|
||||
def __init__(self):
|
||||
self.last_call = None
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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\.<function\ TestLog\.test_output\.<locals>\.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', (
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected",
|
||||
(
|
||||
# VALID, two lowercase words, separated by a hyphen
|
||||
('syrup-enzyme', True),
|
||||
('caution-friday', True),
|
||||
|
||||
("syrup-enzyme", True),
|
||||
("caution-friday", True),
|
||||
# VALID, two lowercase words, with one hyphenated compound word
|
||||
('drop-down-thimble', True),
|
||||
('unmixed-yo-yo', True),
|
||||
|
||||
("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),
|
||||
|
||||
("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)
|
||||
))
|
||||
("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', (
|
||||
@pytest.mark.parametrize(
|
||||
"test_input",
|
||||
(
|
||||
(10, 20, 100), # if `time_elapsed == 0`
|
||||
(0, 37, 99) # if `download_rate == 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.<function TestLog.test_output.<locals>.dummy_func'
|
||||
' at 0xdeadbeef>'),
|
||||
('[Jun 06 2013 11:05:00]'
|
||||
' TestModule.<function TestLog.test_output.<locals>.dummy_func'
|
||||
' at 0xdeadbeef>: TEST_MSG')
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
"test_input",
|
||||
(
|
||||
(
|
||||
"[Jun 06 2013 11:05:00]"
|
||||
" TestModule.<function TestLog.test_output.<locals>.dummy_func"
|
||||
" at 0xdeadbeef>"
|
||||
),
|
||||
(
|
||||
"[Jun 06 2013 11:05:00]"
|
||||
" TestModule.<function TestLog.test_output.<locals>.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)
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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', (
|
||||
@pytest.mark.parametrize(
|
||||
"test_input",
|
||||
(
|
||||
Common.random_string(
|
||||
random.randint(2, 50),
|
||||
random.choice((None, random.randint(2, 50)))
|
||||
) for _ in range(50)
|
||||
))
|
||||
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"
|
||||
|
Loading…
Reference in New Issue
Block a user