mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-10 03:37:28 -03:00
Merge branch 'develop' into upgrade-flask
This commit is contained in:
commit
4ff23fa9bd
52 changed files with 1441 additions and 457 deletions
|
@ -31,6 +31,7 @@ Finalize localization:
|
|||
- [ ] Merge all the translations from weblate
|
||||
- [ ] In `docs` run `poetry run ./check-weblate.py [API_KEY]` to see which translations are >90% in the app and docs
|
||||
- [ ] Edit `cli/onionshare_cli/settings.py`, make sure `self.available_locales` lists only locales that are >90% translated
|
||||
- [ ] From the `desktop` folder in the virtual env, run `./scripts/countries-update-list.py` to make sure the localized country list for censorship circumvention is available in all available languages
|
||||
- [ ] Edit `docs/source/conf.py`, make sure `languages` lists only languages that are >90% translated
|
||||
- [ ] Edit `docs/build.sh` and make sure `LOCALES=` lists the same languages as above, in `docs/source/conf.py`
|
||||
- [ ] Make sure the latest documentation is built and committed:
|
||||
|
|
|
@ -36,7 +36,7 @@ fi
|
|||
mkdir -p build/source
|
||||
mkdir -p dist
|
||||
cd build/source
|
||||
git clone https://github.com/onionshare/onionshare.git
|
||||
git clone --single-branch --branch $TAG --depth 1 https://github.com/onionshare/onionshare.git
|
||||
cd onionshare
|
||||
|
||||
# Verify tag
|
||||
|
@ -65,7 +65,7 @@ git checkout $TAG
|
|||
# Delete .git, compress, and PGP sign
|
||||
cd ..
|
||||
rm -rf onionshare/.git
|
||||
tar -cf onionshare-$VERSION.tar.gz onionshare/
|
||||
tar -czf onionshare-$VERSION.tar.gz onionshare/
|
||||
|
||||
# Move source package to dist
|
||||
cd ../..
|
||||
|
|
|
@ -22,6 +22,12 @@ import requests
|
|||
from .meek import MeekNotRunning
|
||||
|
||||
|
||||
class CensorshipCircumventionError(Exception):
|
||||
"""
|
||||
There was a problem connecting to the Tor CensorshipCircumvention API.
|
||||
"""
|
||||
|
||||
|
||||
class CensorshipCircumvention(object):
|
||||
"""
|
||||
Connect to the Tor Moat APIs to retrieve censorship
|
||||
|
@ -47,7 +53,7 @@ class CensorshipCircumvention(object):
|
|||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Meek with CensorShipCircumvention API",
|
||||
"Using Meek with CensorshipCircumvention API",
|
||||
)
|
||||
self.api_proxies = self.meek.meek_proxies
|
||||
if onion:
|
||||
|
@ -58,7 +64,7 @@ class CensorshipCircumvention(object):
|
|||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"__init__",
|
||||
"Using Tor with CensorShipCircumvention API",
|
||||
"Using Tor with CensorshipCircumvention API",
|
||||
)
|
||||
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
||||
self.api_proxies = {
|
||||
|
@ -84,31 +90,34 @@ class CensorshipCircumvention(object):
|
|||
if country:
|
||||
data = {"country": country}
|
||||
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"status_code={r.status_code}",
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_map",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def request_settings(self, country=False, transports=False):
|
||||
"""
|
||||
|
@ -127,45 +136,53 @@ class CensorshipCircumvention(object):
|
|||
endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
|
||||
data = {}
|
||||
if country:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"Trying to obtain bridges for country={country}",
|
||||
)
|
||||
data = {"country": country}
|
||||
if transports:
|
||||
data.append({"transports": transports})
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"status_code={r.status_code}",
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
return False
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
# There are no settings - perhaps this country doesn't require censorship circumvention?
|
||||
# This is not really an error, so we can just check if False and assume direct Tor
|
||||
# connection will work.
|
||||
if not "settings" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
"No settings found for this country",
|
||||
)
|
||||
return False
|
||||
# There are no settings - perhaps this country doesn't require censorship circumvention?
|
||||
# This is not really an error, so we can just check if False and assume direct Tor
|
||||
# connection will work.
|
||||
if not "settings" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_settings",
|
||||
"No settings found for this country",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def request_builtin_bridges(self):
|
||||
"""
|
||||
|
@ -174,27 +191,103 @@ class CensorshipCircumvention(object):
|
|||
if not self.api_proxies:
|
||||
return False
|
||||
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
try:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.api_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"status_code={r.status_code}",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"errors={result['errors']}",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise CensorshipCircumventionError(e)
|
||||
|
||||
def save_settings(self, settings, bridge_settings):
|
||||
"""
|
||||
Checks the bridges and saves them in settings.
|
||||
"""
|
||||
bridges_ok = False
|
||||
self.settings = settings
|
||||
|
||||
# @TODO there might be several bridge types recommended.
|
||||
# Should we attempt to iterate over each type if one of them fails to connect?
|
||||
# But if so, how to stop it starting 3 separate Tor connection threads?
|
||||
# for bridges in request_bridges["settings"]:
|
||||
bridges = bridge_settings["settings"][0]["bridges"]
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
f"Obtained bridges: {bridges}",
|
||||
)
|
||||
if r.status_code != 200:
|
||||
bridge_strings = bridges["bridge_strings"]
|
||||
bridge_type = bridges["type"]
|
||||
bridge_source = bridges["source"]
|
||||
|
||||
# If the recommended bridge source is to use the built-in
|
||||
# bridges, set that in our settings, as if the user had
|
||||
# selected the built-in bridges for a specific PT themselves.
|
||||
#
|
||||
if bridge_source == "builtin":
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"status_code={r.status_code}",
|
||||
"save_settings",
|
||||
"Will be using built-in bridges",
|
||||
)
|
||||
return False
|
||||
|
||||
result = r.json()
|
||||
|
||||
if "errors" in result:
|
||||
self.settings.set("bridges_type", "built-in")
|
||||
if bridge_type == "obfs4":
|
||||
self.settings.set("bridges_builtin_pt", "obfs4")
|
||||
if bridge_type == "snowflake":
|
||||
self.settings.set("bridges_builtin_pt", "snowflake")
|
||||
if bridge_type == "meek":
|
||||
self.settings.set("bridges_builtin_pt", "meek-azure")
|
||||
bridges_ok = True
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"censorship_obtain_builtin_bridges",
|
||||
f"errors={result['errors']}",
|
||||
"save_settings",
|
||||
"Will be using custom bridges",
|
||||
)
|
||||
# Any other type of bridge we can treat as custom.
|
||||
self.settings.set("bridges_type", "custom")
|
||||
|
||||
# Sanity check the bridges provided from the Tor API before saving
|
||||
bridges_checked = self.common.check_bridges_valid(bridge_strings)
|
||||
|
||||
if bridges_checked:
|
||||
self.settings.set("bridges_custom", "\n".join(bridges_checked))
|
||||
bridges_ok = True
|
||||
|
||||
# If we got any good bridges, save them to settings and return.
|
||||
if bridges_ok:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Saving settings with automatically-obtained bridges",
|
||||
)
|
||||
self.settings.set("bridges_enabled", True)
|
||||
self.settings.save()
|
||||
return True
|
||||
else:
|
||||
self.common.log(
|
||||
"CensorshipCircumvention",
|
||||
"save_settings",
|
||||
"Could not use any of the obtained bridges.",
|
||||
)
|
||||
return False
|
||||
|
||||
return result
|
||||
|
|
|
@ -28,6 +28,7 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
import shutil
|
||||
import re
|
||||
from pkg_resources import resource_filename
|
||||
|
||||
import colorama
|
||||
|
@ -312,7 +313,6 @@ class Common:
|
|||
"""
|
||||
Returns the absolute path of a resource
|
||||
"""
|
||||
self.log("Common", "get_resource_path", f"filename={filename}")
|
||||
path = resource_filename("onionshare_cli", os.path.join("resources", filename))
|
||||
self.log("Common", "get_resource_path", f"filename={filename}, path={path}")
|
||||
return path
|
||||
|
@ -467,6 +467,40 @@ class Common:
|
|||
r = random.SystemRandom()
|
||||
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||
|
||||
def check_bridges_valid(self, bridges):
|
||||
"""
|
||||
Does a regex check against a supplied list of bridges, to make sure they
|
||||
are valid strings depending on the bridge type.
|
||||
"""
|
||||
valid_bridges = []
|
||||
self.log("Common", "check_bridges_valid", "Checking bridge syntax")
|
||||
for bridge in bridges:
|
||||
if bridge != "":
|
||||
# Check the syntax of the custom bridge to make sure it looks legitimate
|
||||
ipv4_pattern = re.compile(
|
||||
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
|
||||
)
|
||||
ipv6_pattern = re.compile(
|
||||
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
|
||||
)
|
||||
meek_lite_pattern = re.compile(
|
||||
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
|
||||
)
|
||||
snowflake_pattern = re.compile(
|
||||
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
||||
)
|
||||
if (
|
||||
ipv4_pattern.match(bridge)
|
||||
or ipv6_pattern.match(bridge)
|
||||
or meek_lite_pattern.match(bridge)
|
||||
or snowflake_pattern.match(bridge)
|
||||
):
|
||||
valid_bridges.append(bridge)
|
||||
if valid_bridges:
|
||||
return valid_bridges
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_flatpak(self):
|
||||
"""
|
||||
Returns True if OnionShare is running in a Flatpak sandbox
|
||||
|
@ -479,6 +513,7 @@ class Common:
|
|||
"""
|
||||
return os.environ.get("SNAP_INSTANCE_NAME") == "onionshare"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def random_string(num_bytes, output_len=None):
|
||||
"""
|
||||
|
|
|
@ -954,20 +954,6 @@ class Onion(object):
|
|||
"update_builtin_bridges",
|
||||
f"Obtained bridges: {builtin_bridges}",
|
||||
)
|
||||
if builtin_bridges["meek"]:
|
||||
# Meek bridge needs to be defined as "meek_lite", not "meek",
|
||||
# for it to work with obfs4proxy.
|
||||
# We also refer to this bridge type as 'meek-azure' in our settings.
|
||||
# So first, rename the key in the dict
|
||||
builtin_bridges["meek-azure"] = builtin_bridges.pop("meek")
|
||||
new_meek_bridges = []
|
||||
# Now replace the values. They also need the url/front params appended
|
||||
for item in builtin_bridges["meek-azure"]:
|
||||
newline = item.replace("meek", "meek_lite")
|
||||
new_meek_bridges.append(
|
||||
f"{newline} url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com"
|
||||
)
|
||||
builtin_bridges["meek-azure"] = new_meek_bridges
|
||||
# Save the new settings
|
||||
self.settings.set("bridges_builtin", builtin_bridges)
|
||||
self.settings.save()
|
||||
|
|
|
@ -103,6 +103,7 @@ class Settings(object):
|
|||
"socket_file_path": "/var/run/tor/control",
|
||||
"auth_type": "no_auth",
|
||||
"auth_password": "",
|
||||
"auto_connect": False,
|
||||
"use_autoupdate": True,
|
||||
"autoupdate_timestamp": None,
|
||||
"bridges_enabled": False,
|
||||
|
|
|
@ -44,8 +44,9 @@ class SendBaseModeWeb:
|
|||
self.download_filesize = None
|
||||
self.zip_writer = None
|
||||
|
||||
# Store the tempfile objects here, so when they're garbage collected the files are deleted
|
||||
self.gzip_files = []
|
||||
# Create a temporary dir to store gzip files in
|
||||
self.gzip_tmp_dir = tempfile.TemporaryDirectory(dir=self.common.build_tmp_dir())
|
||||
self.gzip_counter = 0
|
||||
|
||||
# If autostop_sharing, only allow one download at a time
|
||||
self.download_in_progress = False
|
||||
|
@ -193,15 +194,12 @@ 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:
|
||||
self.gzip_files.append(
|
||||
tempfile.NamedTemporaryFile("wb+", dir=self.common.build_tmp_dir())
|
||||
gzip_filename = os.path.join(
|
||||
self.gzip_tmp_dir.name, str(self.gzip_counter)
|
||||
)
|
||||
gzip_file = self.gzip_files[-1]
|
||||
self._gzip_compress(filesystem_path, gzip_file.name, 6, None)
|
||||
self.gzip_individual_files[filesystem_path] = gzip_file.name
|
||||
|
||||
# Cleanup this temp file
|
||||
self.web.cleanup_tempfiles.append(gzip_file)
|
||||
self.gzip_counter += 1
|
||||
self._gzip_compress(filesystem_path, gzip_filename, 6, None)
|
||||
self.gzip_individual_files[filesystem_path] = gzip_filename
|
||||
|
||||
file_to_download = self.gzip_individual_files[filesystem_path]
|
||||
filesize = os.path.getsize(self.gzip_individual_files[filesystem_path])
|
||||
|
|
|
@ -171,7 +171,6 @@ class Web:
|
|||
self.socketio.init_app(self.app)
|
||||
self.chat_mode = ChatModeWeb(self.common, self)
|
||||
|
||||
self.cleanup_tempfiles = []
|
||||
self.cleanup_tempdirs = []
|
||||
|
||||
def get_mode(self):
|
||||
|
@ -405,13 +404,8 @@ class Web:
|
|||
"""
|
||||
self.common.log("Web", "cleanup")
|
||||
|
||||
# Close all of the tempfile.NamedTemporaryFile
|
||||
for file in self.cleanup_tempfiles:
|
||||
file.close()
|
||||
|
||||
# Clean up the tempfile.NamedTemporaryDirectory objects
|
||||
for dir in self.cleanup_tempdirs:
|
||||
dir.cleanup()
|
||||
|
||||
self.cleanup_tempfiles = []
|
||||
self.cleanup_tempdirs = []
|
||||
|
|
|
@ -37,6 +37,7 @@ class TestSettings:
|
|||
"bridges_builtin": {},
|
||||
"persistent_tabs": [],
|
||||
"theme": 0,
|
||||
"auto_connect": False,
|
||||
}
|
||||
for key in settings_obj._settings:
|
||||
# Skip locale, it will not always default to the same thing
|
||||
|
|
|
@ -50,7 +50,6 @@ def web_obj(temp_dir, common_obj, mode, num_files=0):
|
|||
web = Web(common_obj, False, mode_settings, mode)
|
||||
web.running = True
|
||||
|
||||
web.cleanup_tempfiles == []
|
||||
web.cleanup_tempdirs == []
|
||||
web.app.testing = True
|
||||
|
||||
|
@ -308,17 +307,13 @@ class TestWeb:
|
|||
def test_cleanup(self, common_obj, temp_dir_1024):
|
||||
web = web_obj(temp_dir_1024, common_obj, "share", 3)
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile()
|
||||
temp_dir = tempfile.TemporaryDirectory()
|
||||
|
||||
web.cleanup_tempfiles = [temp_file]
|
||||
web.cleanup_tempdirs = [temp_dir]
|
||||
web.cleanup()
|
||||
|
||||
assert os.path.exists(temp_file.name) is False
|
||||
assert os.path.exists(temp_dir.name) is False
|
||||
|
||||
assert web.cleanup_tempfiles == []
|
||||
assert web.cleanup_tempdirs == []
|
||||
|
||||
|
||||
|
|
681
desktop/onionshare/connection_tab.py
Normal file
681
desktop/onionshare/connection_tab.py
Normal file
|
@ -0,0 +1,681 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OnionShare | https://onionshare.org/
|
||||
|
||||
Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare_cli.censorship import (
|
||||
CensorshipCircumvention,
|
||||
CensorshipCircumventionError,
|
||||
)
|
||||
from onionshare_cli.meek import (
|
||||
MeekNotRunning,
|
||||
MeekNotFound,
|
||||
)
|
||||
from onionshare_cli.settings import Settings
|
||||
|
||||
from . import strings
|
||||
from .gui_common import GuiCommon, ToggleCheckbox
|
||||
from .tor_connection import TorConnectionWidget
|
||||
from .update_checker import UpdateThread
|
||||
from .widgets import Alert
|
||||
|
||||
|
||||
class AutoConnectTab(QtWidgets.QWidget):
|
||||
"""
|
||||
Initial Tab that appears in the very beginning to ask user if
|
||||
should auto connect.
|
||||
"""
|
||||
|
||||
close_this_tab = QtCore.Signal()
|
||||
tor_is_connected = QtCore.Signal()
|
||||
tor_is_disconnected = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, tab_id, status_bar, window, parent=None):
|
||||
super(AutoConnectTab, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectTab", "__init__")
|
||||
|
||||
self.status_bar = status_bar
|
||||
self.tab_id = tab_id
|
||||
self.window = window
|
||||
self.parent = parent
|
||||
|
||||
# Was auto connected?
|
||||
self.curr_settings = Settings(common)
|
||||
self.curr_settings.load()
|
||||
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
|
||||
|
||||
# Rocket ship animation images
|
||||
self.anim_stars = AnimStars(self, self.window)
|
||||
self.anim_ship = AnimShip(self, self.window)
|
||||
self.anim_smoke = AnimSmoke(self, self.window)
|
||||
|
||||
# Onionshare logo
|
||||
self.image_label = QtWidgets.QLabel()
|
||||
self.image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(
|
||||
QtGui.QImage(
|
||||
GuiCommon.get_resource_path(
|
||||
os.path.join(
|
||||
"images", f"{common.gui.color_mode}_logo_text_bg.png"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.image_label.setFixedSize(322, 65)
|
||||
image_layout = QtWidgets.QVBoxLayout()
|
||||
image_layout.addWidget(self.image_label)
|
||||
self.image = QtWidgets.QWidget()
|
||||
self.image.setLayout(image_layout)
|
||||
|
||||
# First launch widget
|
||||
self.first_launch_widget = AutoConnectFirstLaunchWidget(
|
||||
self.common, self.curr_settings
|
||||
)
|
||||
self.first_launch_widget.toggle_auto_connect.connect(self.toggle_auto_connect)
|
||||
self.first_launch_widget.connect_clicked.connect(
|
||||
self.first_launch_widget_connect_clicked
|
||||
)
|
||||
self.first_launch_widget.open_tor_settings.connect(self.open_tor_settings)
|
||||
self.first_launch_widget.show()
|
||||
|
||||
# Use bridge widget
|
||||
self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common)
|
||||
self.use_bridge_widget.connect_clicked.connect(self.use_bridge_connect_clicked)
|
||||
self.use_bridge_widget.try_again_clicked.connect(
|
||||
self.first_launch_widget_connect_clicked
|
||||
)
|
||||
self.use_bridge_widget.open_tor_settings.connect(self.open_tor_settings)
|
||||
self.use_bridge_widget.hide()
|
||||
|
||||
# Tor connection widget
|
||||
self.tor_con = TorConnectionWidget(self.common, self.status_bar)
|
||||
self.tor_con.success.connect(self.tor_con_success)
|
||||
self.tor_con.fail.connect(self.tor_con_fail)
|
||||
self.tor_con.update_progress.connect(self.anim_stars.update)
|
||||
self.tor_con.update_progress.connect(self.anim_ship.update)
|
||||
self.tor_con.update_progress.connect(self.anim_smoke.update)
|
||||
self.tor_con.hide()
|
||||
|
||||
# Layout
|
||||
content_layout = QtWidgets.QVBoxLayout()
|
||||
content_layout.addStretch()
|
||||
content_layout.addWidget(self.image)
|
||||
content_layout.addWidget(self.first_launch_widget)
|
||||
content_layout.addWidget(self.use_bridge_widget)
|
||||
content_layout.addWidget(self.tor_con)
|
||||
content_layout.addStretch()
|
||||
content_layout.setAlignment(QtCore.Qt.AlignCenter)
|
||||
content_widget = QtWidgets.QWidget()
|
||||
content_widget.setLayout(content_layout)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout()
|
||||
self.layout.addWidget(content_widget)
|
||||
self.layout.addStretch()
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def check_autoconnect(self):
|
||||
"""
|
||||
After rendering, check if autoconnect was clicked, then start connecting
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "autoconnect_checking")
|
||||
if self.auto_connect_enabled:
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.setChecked(True)
|
||||
self.first_launch_widget_connect_clicked()
|
||||
|
||||
def toggle_auto_connect(self):
|
||||
"""
|
||||
Auto connect checkbox clicked
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "autoconnect_checkbox_clicked")
|
||||
self.curr_settings.set(
|
||||
"auto_connect",
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.isChecked(),
|
||||
)
|
||||
self.curr_settings.save()
|
||||
|
||||
def open_tor_settings(self):
|
||||
self.parent.open_settings_tab(from_autoconnect=True, active_tab="tor")
|
||||
|
||||
def first_launch_widget_connect_clicked(self):
|
||||
"""
|
||||
Connect button in first launch widget clicked. Try to connect to tor.
|
||||
"""
|
||||
self.common.log("AutoConnectTab", "first_launch_widget_connect_clicked")
|
||||
self.first_launch_widget.hide_buttons()
|
||||
|
||||
self.tor_con.show()
|
||||
self.tor_con.start(self.curr_settings)
|
||||
|
||||
def _got_bridges(self):
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
# Try and connect again
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"_got_bridges",
|
||||
"Got bridges. Trying to reconnect to Tor",
|
||||
)
|
||||
self.tor_con.show()
|
||||
self.tor_con.start(self.curr_settings)
|
||||
|
||||
def _got_no_bridges(self):
|
||||
# If we got no bridges, try connecting again using built-in obfs4 bridges
|
||||
self.curr_settings.set("bridges_type", "built-in")
|
||||
self.curr_settings.set("bridges_builtin_pt", "obfs4")
|
||||
self.curr_settings.set("bridges_enabled", True)
|
||||
self.curr_settings.save()
|
||||
|
||||
self._got_bridges()
|
||||
|
||||
def _censorship_progress_update(self, progress, summary):
|
||||
self.use_bridge_widget.progress.setValue(int(progress))
|
||||
self.use_bridge_widget.progress_label.setText(
|
||||
f"<strong>{strings._('gui_autoconnect_circumventing_censorship')}</strong><br>{summary}"
|
||||
)
|
||||
|
||||
def network_connection_error(self):
|
||||
"""
|
||||
Display an error if there simply seems no network connection.
|
||||
"""
|
||||
self.use_bridge_widget.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_failed_to_connect_to_tor")
|
||||
)
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
self.use_bridge_widget.error_label.show()
|
||||
self.use_bridge_widget.country_combobox.setEnabled(True)
|
||||
self.use_bridge_widget.show_buttons()
|
||||
self.use_bridge_widget.show()
|
||||
|
||||
def use_bridge_connect_clicked(self):
|
||||
"""
|
||||
Connect button in use bridge widget clicked.
|
||||
"""
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"use_bridge_connect_clicked",
|
||||
"Trying to automatically obtain bridges",
|
||||
)
|
||||
self.use_bridge_widget.hide_buttons()
|
||||
self.use_bridge_widget.progress.show()
|
||||
self.use_bridge_widget.progress_label.show()
|
||||
|
||||
if self.use_bridge_widget.detect_automatic_radio.isChecked():
|
||||
country = False
|
||||
else:
|
||||
country = self.use_bridge_widget.country_combobox.currentData().lower()
|
||||
|
||||
self._censorship_progress_update(
|
||||
50, strings._("gui_autoconnect_circumventing_censorship_starting_meek")
|
||||
)
|
||||
try:
|
||||
self.common.gui.meek.start()
|
||||
self.censorship_circumvention = CensorshipCircumvention(
|
||||
self.common, self.common.gui.meek
|
||||
)
|
||||
self._censorship_progress_update(
|
||||
75,
|
||||
strings._(
|
||||
"gui_autoconnect_circumventing_censorship_requesting_bridges"
|
||||
),
|
||||
)
|
||||
bridge_settings = self.censorship_circumvention.request_settings(
|
||||
country=country
|
||||
)
|
||||
self.common.gui.meek.cleanup()
|
||||
|
||||
if bridge_settings and self.censorship_circumvention.save_settings(
|
||||
self.curr_settings, bridge_settings
|
||||
):
|
||||
self._censorship_progress_update(
|
||||
100,
|
||||
strings._("gui_autoconnect_circumventing_censorship_got_bridges"),
|
||||
)
|
||||
self._got_bridges()
|
||||
else:
|
||||
self._got_no_bridges()
|
||||
except (
|
||||
MeekNotRunning,
|
||||
MeekNotFound,
|
||||
) as e:
|
||||
self._got_no_bridges()
|
||||
except CensorshipCircumventionError as e:
|
||||
self.common.log(
|
||||
"AutoConnectTab",
|
||||
"use_bridge_connect_clicked",
|
||||
"Request to the Tor Censorship Circumvention API failed. No network connection?",
|
||||
)
|
||||
self.network_connection_error()
|
||||
|
||||
def check_for_updates(self):
|
||||
"""
|
||||
Check for OnionShare updates in a new thread, if enabled.
|
||||
"""
|
||||
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
|
||||
),
|
||||
)
|
||||
|
||||
self.update_thread = UpdateThread(self.common, self.common.gui.onion)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def tor_con_success(self):
|
||||
"""
|
||||
Finished testing tor connection.
|
||||
"""
|
||||
self.tor_con.hide()
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.use_bridge_widget.show_buttons()
|
||||
self.use_bridge_widget.progress.hide()
|
||||
self.use_bridge_widget.progress_label.hide()
|
||||
|
||||
if self.common.gui.onion.is_authenticated() and not self.tor_con.wasCanceled():
|
||||
# Tell the tabs that Tor is connected
|
||||
self.tor_is_connected.emit()
|
||||
# After connecting to Tor, check for updates
|
||||
self.check_for_updates()
|
||||
# Close the tab
|
||||
self.close_this_tab.emit()
|
||||
|
||||
def tor_con_fail(self, msg):
|
||||
"""
|
||||
Finished testing tor connection.
|
||||
"""
|
||||
self.tor_con.hide()
|
||||
|
||||
# If we're on first launch, switch to use bridge
|
||||
if self.first_launch_widget.isVisible():
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.first_launch_widget.hide()
|
||||
self.use_bridge_widget.show()
|
||||
else:
|
||||
self.use_bridge_widget.show_buttons()
|
||||
|
||||
def reload_settings(self):
|
||||
"""
|
||||
Reload the latest Tor settings, and reset to show the
|
||||
first-launch widget if it had been hidden.
|
||||
"""
|
||||
self.curr_settings.load()
|
||||
self.auto_connect_enabled = self.curr_settings.get("auto_connect")
|
||||
self.first_launch_widget.enable_autoconnect_checkbox.setChecked(
|
||||
self.auto_connect_enabled
|
||||
)
|
||||
self.use_bridge_widget.hide()
|
||||
self.first_launch_widget.show_buttons()
|
||||
self.first_launch_widget.show()
|
||||
|
||||
|
||||
class Anim(QtWidgets.QLabel):
|
||||
"""
|
||||
Rocket ship animation base class
|
||||
"""
|
||||
|
||||
force_update = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent, window, w, h, filename):
|
||||
super(Anim, self).__init__(parent=parent)
|
||||
|
||||
self.window = window
|
||||
self.window.window_resized.connect(self.update_same_percent)
|
||||
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.percent = 0
|
||||
self.used_percentages = []
|
||||
|
||||
self.setPixmap(
|
||||
QtGui.QPixmap.fromImage(
|
||||
QtGui.QImage(
|
||||
GuiCommon.get_resource_path(os.path.join("images", filename))
|
||||
)
|
||||
)
|
||||
)
|
||||
self.setFixedSize(self.w, self.h)
|
||||
self.update(0)
|
||||
|
||||
self.force_update.connect(self.update)
|
||||
|
||||
def update_same_percent(self):
|
||||
self.update(self.percent)
|
||||
|
||||
def update(self, percent):
|
||||
self.percent = percent
|
||||
self.move()
|
||||
self.setGeometry(int(self.x), int(self.y), int(self.w), int(self.h))
|
||||
|
||||
def move(self):
|
||||
# Implement in child
|
||||
pass
|
||||
|
||||
|
||||
class AnimStars(Anim):
|
||||
"""
|
||||
Rocket ship animation part: stars
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimStars, self).__init__(
|
||||
parent, window, 740, 629, "tor-connect-stars.png"
|
||||
)
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w
|
||||
self.y = 0
|
||||
# Stars don't move until 10%, then move down
|
||||
if self.percent >= 10:
|
||||
self.y += self.percent * 6.6
|
||||
|
||||
|
||||
class AnimShip(Anim):
|
||||
"""
|
||||
Rocket ship animation part: ship
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimShip, self).__init__(parent, window, 239, 545, "tor-connect-ship.png")
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w - 150
|
||||
self.y = self.window.height() - self.h - 40
|
||||
# Ship moves up
|
||||
self.y -= self.percent * 6.6
|
||||
|
||||
|
||||
class AnimSmoke(Anim):
|
||||
"""
|
||||
Rocket ship animation part: smoke
|
||||
"""
|
||||
|
||||
def __init__(self, parent, window):
|
||||
super(AnimSmoke, self).__init__(
|
||||
parent, window, 522, 158, "tor-connect-smoke.png"
|
||||
)
|
||||
|
||||
def move(self):
|
||||
self.x = self.window.width() - self.w
|
||||
self.y = self.window.height() - self.h + 50
|
||||
# Smoke moves up until 50%, then moves down
|
||||
self.y -= self.percent * 6.6
|
||||
if self.percent >= 50:
|
||||
self.y += self.percent * 6.7
|
||||
|
||||
|
||||
class AutoConnectFirstLaunchWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
When you first launch OnionShare, this is the widget that is displayed
|
||||
"""
|
||||
|
||||
toggle_auto_connect = QtCore.Signal()
|
||||
connect_clicked = QtCore.Signal()
|
||||
open_tor_settings = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, settings):
|
||||
super(AutoConnectFirstLaunchWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectFirstLaunchWidget", "__init__")
|
||||
|
||||
self.settings = settings
|
||||
|
||||
# Description and checkbox
|
||||
description_label = QtWidgets.QLabel(strings._("gui_autoconnect_description"))
|
||||
self.enable_autoconnect_checkbox = ToggleCheckbox(
|
||||
strings._("gui_enable_autoconnect_checkbox")
|
||||
)
|
||||
self.enable_autoconnect_checkbox.setChecked(self.settings.get("auto_connect"))
|
||||
self.enable_autoconnect_checkbox.clicked.connect(self._toggle_auto_connect)
|
||||
self.enable_autoconnect_checkbox.setFixedWidth(400)
|
||||
self.enable_autoconnect_checkbox.setStyleSheet(
|
||||
common.gui.css["enable_autoconnect"]
|
||||
)
|
||||
description_layout = QtWidgets.QVBoxLayout()
|
||||
description_layout.addWidget(description_label)
|
||||
description_layout.addWidget(self.enable_autoconnect_checkbox)
|
||||
description_widget = QtWidgets.QWidget()
|
||||
description_widget.setLayout(description_layout)
|
||||
|
||||
# Buttons
|
||||
self.connect_button = QtWidgets.QPushButton(strings._("gui_autoconnect_start"))
|
||||
self.connect_button.clicked.connect(self._connect_clicked)
|
||||
self.connect_button.setFixedWidth(150)
|
||||
self.connect_button.setStyleSheet(common.gui.css["autoconnect_start_button"])
|
||||
self.configure_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_configure")
|
||||
)
|
||||
self.configure_button.clicked.connect(self._open_tor_settings)
|
||||
self.configure_button.setFlat(True)
|
||||
self.configure_button.setStyleSheet(
|
||||
common.gui.css["autoconnect_configure_button"]
|
||||
)
|
||||
cta_layout = QtWidgets.QHBoxLayout()
|
||||
cta_layout.addWidget(self.connect_button)
|
||||
cta_layout.addWidget(self.configure_button)
|
||||
cta_widget = QtWidgets.QWidget()
|
||||
cta_widget.setLayout(cta_layout)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(description_widget)
|
||||
layout.addWidget(cta_widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
def hide_buttons(self):
|
||||
self.connect_button.hide()
|
||||
self.configure_button.hide()
|
||||
|
||||
def show_buttons(self):
|
||||
self.connect_button.show()
|
||||
self.configure_button.show()
|
||||
|
||||
def _toggle_auto_connect(self):
|
||||
self.toggle_auto_connect.emit()
|
||||
|
||||
def _connect_clicked(self):
|
||||
self.connect_clicked.emit()
|
||||
|
||||
def _open_tor_settings(self):
|
||||
self.open_tor_settings.emit()
|
||||
|
||||
|
||||
class AutoConnectUseBridgeWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
If connecting fails, this is the widget that helps the user bypass censorship
|
||||
"""
|
||||
|
||||
connect_clicked = QtCore.Signal()
|
||||
try_again_clicked = QtCore.Signal()
|
||||
open_tor_settings = QtCore.Signal()
|
||||
|
||||
def __init__(self, common):
|
||||
super(AutoConnectUseBridgeWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("AutoConnectUseBridgeWidget", "__init__")
|
||||
|
||||
# Heading label when we fail to connect to Tor.
|
||||
self.connection_status_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_failed_to_connect_to_tor")
|
||||
)
|
||||
self.connection_status_label.setTextFormat(QtCore.Qt.RichText)
|
||||
self.connection_status_label.setStyleSheet(
|
||||
common.gui.css["autoconnect_failed_to_connect_label"]
|
||||
)
|
||||
|
||||
# Description
|
||||
self.description_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_bridge_description")
|
||||
)
|
||||
self.description_label.setTextFormat(QtCore.Qt.RichText)
|
||||
self.description_label.setWordWrap(True)
|
||||
|
||||
# Detection preference
|
||||
self.detect_automatic_radio = QtWidgets.QRadioButton(
|
||||
strings._("gui_autoconnect_bridge_detect_automatic")
|
||||
)
|
||||
self.detect_automatic_radio.toggled.connect(self._detect_automatic_toggled)
|
||||
self.detect_manual_radio = QtWidgets.QRadioButton(
|
||||
strings._("gui_autoconnect_bridge_detect_manual")
|
||||
)
|
||||
self.detect_manual_radio.toggled.connect(self._detect_manual_toggled)
|
||||
detect_layout = QtWidgets.QVBoxLayout()
|
||||
detect_layout.addWidget(self.detect_automatic_radio)
|
||||
detect_layout.addWidget(self.detect_manual_radio)
|
||||
|
||||
# Country list
|
||||
locale = self.common.settings.get("locale")
|
||||
if not locale:
|
||||
locale = "en"
|
||||
|
||||
with open(
|
||||
GuiCommon.get_resource_path(os.path.join("countries", f"{locale}.json"))
|
||||
) as f:
|
||||
countries = json.loads(f.read())
|
||||
|
||||
self.country_combobox = QtWidgets.QComboBox()
|
||||
self.country_combobox.setStyleSheet(
|
||||
common.gui.css["autoconnect_countries_combobox"]
|
||||
)
|
||||
for country_code in countries:
|
||||
self.country_combobox.addItem(countries[country_code], country_code)
|
||||
|
||||
# Task label
|
||||
self.task_label = QtWidgets.QLabel()
|
||||
self.task_label.setStyleSheet(common.gui.css["autoconnect_task_label"])
|
||||
self.task_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.task_label.hide()
|
||||
|
||||
# Buttons
|
||||
self.connect_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_bridge_start")
|
||||
)
|
||||
self.connect_button.clicked.connect(self._connect_clicked)
|
||||
self.connect_button.setFixedWidth(150)
|
||||
self.connect_button.setStyleSheet(common.gui.css["autoconnect_start_button"])
|
||||
|
||||
self.try_again_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_try_again_without_a_bridge")
|
||||
)
|
||||
self.try_again_button.clicked.connect(self._try_again_clicked)
|
||||
self.try_again_button.setStyleSheet(common.gui.css["autoconnect_start_button"])
|
||||
|
||||
self.configure_button = QtWidgets.QPushButton(
|
||||
strings._("gui_autoconnect_configure")
|
||||
)
|
||||
self.configure_button.clicked.connect(self._open_tor_settings)
|
||||
self.configure_button.setFlat(True)
|
||||
self.configure_button.setStyleSheet(
|
||||
common.gui.css["autoconnect_configure_button"]
|
||||
)
|
||||
|
||||
# Error label
|
||||
self.error_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_could_not_connect_to_tor_api")
|
||||
)
|
||||
self.error_label.setStyleSheet(self.common.gui.css["tor_settings_error"])
|
||||
self.error_label.setWordWrap(True)
|
||||
self.error_label.hide()
|
||||
|
||||
self.progress = QtWidgets.QProgressBar()
|
||||
self.progress.setRange(0, 100)
|
||||
self.progress_label = QtWidgets.QLabel(
|
||||
strings._("gui_autoconnect_circumventing_censorship")
|
||||
)
|
||||
self.progress_label.setAlignment(QtCore.Qt.AlignHCenter)
|
||||
self.progress.hide()
|
||||
self.progress_label.hide()
|
||||
|
||||
cta_layout = QtWidgets.QHBoxLayout()
|
||||
cta_layout.addWidget(self.connect_button)
|
||||
cta_layout.addWidget(self.try_again_button)
|
||||
cta_layout.addWidget(self.configure_button)
|
||||
cta_layout.addStretch()
|
||||
cta_widget = QtWidgets.QWidget()
|
||||
cta_widget.setLayout(cta_layout)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.connection_status_label)
|
||||
layout.addWidget(self.description_label)
|
||||
layout.addLayout(detect_layout)
|
||||
layout.addWidget(self.country_combobox)
|
||||
layout.addWidget(self.task_label)
|
||||
layout.addWidget(cta_widget)
|
||||
layout.addWidget(self.progress)
|
||||
layout.addWidget(self.progress_label)
|
||||
layout.addWidget(self.error_label)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.detect_automatic_radio.setChecked(True)
|
||||
|
||||
def hide_buttons(self):
|
||||
self.connect_button.hide()
|
||||
self.try_again_button.hide()
|
||||
self.configure_button.hide()
|
||||
self.description_label.hide()
|
||||
self.error_label.hide()
|
||||
self.detect_automatic_radio.hide()
|
||||
self.detect_manual_radio.hide()
|
||||
|
||||
def show_buttons(self):
|
||||
self.connect_button.show()
|
||||
self.try_again_button.show()
|
||||
self.description_label.show()
|
||||
self.configure_button.show()
|
||||
self.detect_automatic_radio.show()
|
||||
self.detect_manual_radio.show()
|
||||
|
||||
def _detect_automatic_toggled(self):
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.country_combobox.hide()
|
||||
|
||||
def _detect_manual_toggled(self):
|
||||
self.country_combobox.setEnabled(True)
|
||||
self.country_combobox.show()
|
||||
|
||||
def _connect_clicked(self):
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.hide_buttons()
|
||||
self.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_trying_to_connect_to_tor")
|
||||
)
|
||||
self.connect_clicked.emit()
|
||||
|
||||
def _try_again_clicked(self):
|
||||
self.connection_status_label.setText(
|
||||
strings._("gui_autoconnect_trying_to_connect_to_tor")
|
||||
)
|
||||
self.country_combobox.setEnabled(False)
|
||||
self.country_combobox.hide()
|
||||
self.hide_buttons()
|
||||
self.try_again_clicked.emit()
|
||||
|
||||
def _open_tor_settings(self):
|
||||
self.open_tor_settings.emit()
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
import os
|
||||
import shutil
|
||||
from pkg_resources import resource_filename
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from . import strings
|
||||
from onionshare_cli.onion import (
|
||||
|
@ -39,6 +40,7 @@ from onionshare_cli.onion import (
|
|||
TorTooOldStealth,
|
||||
PortNotAvailable,
|
||||
)
|
||||
from onionshare_cli.meek import Meek
|
||||
|
||||
|
||||
class GuiCommon:
|
||||
|
@ -77,6 +79,9 @@ class GuiCommon:
|
|||
os.makedirs(self.events_dir, 0o700, True)
|
||||
self.events_filename = os.path.join(self.events_dir, "events")
|
||||
|
||||
# Instantiate Meek, which is used to bypass censorship
|
||||
self.meek = Meek(self.common, get_tor_paths=self.get_tor_paths)
|
||||
|
||||
self.css = self.get_css(qtapp.color_mode)
|
||||
self.color_mode = qtapp.color_mode
|
||||
|
||||
|
@ -116,6 +121,15 @@ class GuiCommon:
|
|||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}""",
|
||||
"settings_subtab_bar": """
|
||||
QTabBar::tab {
|
||||
background: transparent;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
border-bottom: 3px solid;
|
||||
border-color: #4E064F;
|
||||
padding: 3px
|
||||
}""",
|
||||
"mode_new_tab_button": """
|
||||
QPushButton {
|
||||
font-weight: bold;
|
||||
|
@ -149,6 +163,52 @@ class GuiCommon:
|
|||
QStatusBar::item {
|
||||
border: 0px;
|
||||
}""",
|
||||
"autoconnect_start_button": """
|
||||
QPushButton {
|
||||
background-color: #5fa416;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}""",
|
||||
"autoconnect_configure_button": """
|
||||
QPushButton {
|
||||
padding: 9px 29px;
|
||||
color: #3f7fcf;
|
||||
text-align: left;
|
||||
}""",
|
||||
"enable_autoconnect": """
|
||||
QCheckBox {
|
||||
margin-top: 30px;
|
||||
background: #FCFCFC;
|
||||
color: #000000;
|
||||
border: 1px solid #DDDBDA;
|
||||
border-radius: 8px;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
QCheckBox::indicator {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}""",
|
||||
"autoconnect_countries_combobox": """
|
||||
QComboBox {
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
QComboBox:disabled {
|
||||
color: #666666;
|
||||
}
|
||||
""",
|
||||
"autoconnect_task_label": """
|
||||
QLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
""",
|
||||
"autoconnect_failed_to_connect_label": """
|
||||
QLabel {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}""",
|
||||
# Common styles between modes and their child widgets
|
||||
"mode_settings_toggle_advanced": """
|
||||
QPushButton {
|
||||
|
@ -508,3 +568,50 @@ class GuiCommon:
|
|||
elif type(e) is PortNotAvailable:
|
||||
return strings._("error_port_not_available")
|
||||
return None
|
||||
|
||||
|
||||
class ToggleCheckbox(QtWidgets.QCheckBox):
|
||||
def __init__(self, text):
|
||||
super(ToggleCheckbox, self).__init__(text)
|
||||
# Set default parameters
|
||||
self.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
self.w = 50
|
||||
self.h = 24
|
||||
self.bg_color = "#D4D4D4"
|
||||
self.circle_color = "#BDBDBD"
|
||||
self.active_color = "#4E0D4E"
|
||||
self.inactive_color = ""
|
||||
|
||||
def hitButton(self, pos):
|
||||
return self.toggleRect.contains(pos)
|
||||
|
||||
def paintEvent(self, e):
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
opt = QtWidgets.QStyleOptionButton()
|
||||
opt.init(self)
|
||||
self.initStyleOption(opt)
|
||||
s = self.style()
|
||||
s.drawControl(QtWidgets.QStyle.CE_CheckBox, opt, painter, self)
|
||||
|
||||
rect = QtCore.QRect(
|
||||
s.subElementRect(QtWidgets.QStyle.SE_CheckBoxContents, opt, self)
|
||||
)
|
||||
x = (
|
||||
rect.width() - rect.x() - self.w + 20
|
||||
) # 20 is the padding between text and toggle
|
||||
y = self.height() / 2 - self.h / 2 + 16 # 16 is the padding top for the checkbox
|
||||
self.toggleRect = QtCore.QRect(x, y, self.w, self.h)
|
||||
painter.setBrush(QtGui.QColor(self.bg_color))
|
||||
painter.drawRoundedRect(x, y, self.w, self.h, self.h / 2, self.h / 2)
|
||||
if not self.isChecked():
|
||||
painter.setBrush(QtGui.QColor(self.circle_color))
|
||||
painter.drawEllipse(x, y - 3, self.h + 6, self.h + 6)
|
||||
else:
|
||||
painter.setBrush(QtGui.QColor(self.active_color))
|
||||
painter.drawEllipse(
|
||||
x + self.w - (self.h + 6), y - 3, self.h + 6, self.h + 6
|
||||
)
|
||||
|
||||
painter.end()
|
||||
|
|
|
@ -23,10 +23,10 @@ import time
|
|||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from . import strings
|
||||
from .tor_connection import TorConnectionDialog
|
||||
from .widgets import Alert
|
||||
from .update_checker import UpdateThread
|
||||
from .connection_tab import AutoConnectTab
|
||||
from .tab_widget import TabWidget
|
||||
from .settings_tab import SettingsTab
|
||||
from .gui_common import GuiCommon
|
||||
from .threads import OnionCleanupThread
|
||||
|
||||
|
@ -36,6 +36,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
MainWindow is the OnionShare main window, which contains the GUI elements, including all open tabs
|
||||
"""
|
||||
|
||||
window_resized = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, filenames):
|
||||
super(MainWindow, self).__init__()
|
||||
|
||||
|
@ -53,7 +55,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
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.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
||||
self.help_action.triggered.connect(lambda: SettingsTab.open_help())
|
||||
exit_action = menu.addAction(strings._("systray_menu_exit"))
|
||||
exit_action.triggered.connect(self.close)
|
||||
|
||||
|
@ -106,24 +108,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
)
|
||||
self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator)
|
||||
|
||||
# Tor settings button
|
||||
self.tor_settings_button = QtWidgets.QPushButton()
|
||||
self.tor_settings_button.setDefault(False)
|
||||
self.tor_settings_button.setFixedSize(40, 50)
|
||||
self.tor_settings_button.setIcon(
|
||||
QtGui.QIcon(
|
||||
GuiCommon.get_resource_path(
|
||||
"images/{}_tor_settings.png".format(self.common.gui.color_mode)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.tor_settings_button.clicked.connect(self.open_tor_settings)
|
||||
self.tor_settings_button.setStyleSheet(self.common.gui.css["settings_button"])
|
||||
self.status_bar.addPermanentWidget(self.tor_settings_button)
|
||||
|
||||
if os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1":
|
||||
self.tor_settings_button.hide()
|
||||
|
||||
# Settings button
|
||||
self.settings_button = QtWidgets.QPushButton()
|
||||
self.settings_button.setDefault(False)
|
||||
|
@ -140,13 +124,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.status_bar.addPermanentWidget(self.settings_button)
|
||||
|
||||
# Tabs
|
||||
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar)
|
||||
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar, self)
|
||||
self.tabs.bring_to_front.connect(self.bring_to_front)
|
||||
|
||||
# If we have saved persistent tabs, try opening those
|
||||
if len(self.common.settings.get("persistent_tabs")) > 0:
|
||||
for mode_settings_id in self.common.settings.get("persistent_tabs"):
|
||||
self.tabs.load_tab(mode_settings_id)
|
||||
# If not connected to tor in beginning, show autoconnect tab
|
||||
if not self.common.gui.onion.connected_to_tor:
|
||||
self.tabs.new_tab_clicked()
|
||||
else:
|
||||
# Start with opening the first tab
|
||||
self.tabs.new_tab_clicked()
|
||||
|
@ -160,18 +147,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.setCentralWidget(central_widget)
|
||||
self.show()
|
||||
|
||||
# Start the "Connecting to Tor" dialog, which calls onion.connect()
|
||||
tor_con = TorConnectionDialog(self.common)
|
||||
tor_con.canceled.connect(self.tor_connection_canceled)
|
||||
tor_con.success.connect(self.tabs.tor_is_connected)
|
||||
tor_con.open_tor_settings.connect(self.tor_connection_open_tor_settings)
|
||||
if not self.common.gui.local_only:
|
||||
tor_con.start()
|
||||
self.settings_have_changed()
|
||||
|
||||
# After connecting to Tor, check for updates
|
||||
self.check_for_updates()
|
||||
|
||||
# Create the close warning dialog -- the dialog widget needs to be in the constructor
|
||||
# in order to test it
|
||||
self.close_dialog = QtWidgets.QMessageBox()
|
||||
|
@ -186,6 +161,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
)
|
||||
self.close_dialog.setDefaultButton(self.close_dialog.reject_button)
|
||||
|
||||
# Check for autoconnect
|
||||
self.tabs.check_autoconnect_tab()
|
||||
|
||||
def tor_connection_canceled(self):
|
||||
"""
|
||||
If the user cancels before Tor finishes connecting, ask if they want to
|
||||
|
@ -246,15 +224,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
"""
|
||||
Open the TorSettingsTab
|
||||
"""
|
||||
self.common.log("MainWindow", "open_tor_settings")
|
||||
self.tabs.open_tor_settings_tab()
|
||||
self._open_settings(active_tab="tor")
|
||||
|
||||
def open_settings(self):
|
||||
"""
|
||||
Open the SettingsTab
|
||||
Open the general SettingsTab
|
||||
"""
|
||||
self.common.log("MainWindow", "open_settings")
|
||||
self.tabs.open_settings_tab()
|
||||
self._open_settings(active_tab="general")
|
||||
|
||||
def _open_settings(self, active_tab):
|
||||
self.common.log("MainWindow", f"open settings with active tab: {active_tab}")
|
||||
from_autoconnect = False
|
||||
for tab_id in self.tabs.tabs:
|
||||
if type(self.tabs.tabs[tab_id]) is AutoConnectTab:
|
||||
from_autoconnect = True
|
||||
break
|
||||
self.tabs.open_settings_tab(from_autoconnect, active_tab=active_tab)
|
||||
|
||||
def settings_have_changed(self):
|
||||
self.common.log("OnionShareGui", "settings_have_changed")
|
||||
|
@ -267,25 +252,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
tab = self.tabs.widget(index)
|
||||
tab.settings_have_changed()
|
||||
|
||||
def check_for_updates(self):
|
||||
"""
|
||||
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"):
|
||||
|
||||
def update_available(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.common.gui.onion)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def bring_to_front(self):
|
||||
self.common.log("MainWindow", "bring_to_front")
|
||||
self.raise_()
|
||||
|
@ -355,3 +321,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
|
||||
# Wait 1 second for threads to close gracefully, so tests finally pass
|
||||
time.sleep(1)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.window_resized.emit()
|
||||
return super(MainWindow, self).resizeEvent(event)
|
1
desktop/onionshare/resources/countries/ar.json
Normal file
1
desktop/onionshare/resources/countries/ar.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/bn.json
Normal file
1
desktop/onionshare/resources/countries/bn.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/de.json
Normal file
1
desktop/onionshare/resources/countries/de.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/el.json
Normal file
1
desktop/onionshare/resources/countries/el.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/en.json
Normal file
1
desktop/onionshare/resources/countries/en.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"AF": "Afghanistan", "AX": "\u00c5land Islands", "AL": "Albania", "DZ": "Algeria", "AS": "American Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarctica", "AG": "Antigua & Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AT": "Austria", "AZ": "Azerbaijan", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BY": "Belarus", "BE": "Belgium", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia & Herzegovina", "BW": "Botswana", "BV": "Bouvet Island", "BR": "Brazil", "IO": "British Indian Ocean Territory", "VG": "British Virgin Islands", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "KH": "Cambodia", "CM": "Cameroon", "CA": "Canada", "CV": "Cape Verde", "BQ": "Caribbean Netherlands", "KY": "Cayman Islands", "CF": "Central African Republic", "TD": "Chad", "CL": "Chile", "CN": "China", "CX": "Christmas Island", "CC": "Cocos (Keeling) Islands", "CO": "Colombia", "KM": "Comoros", "CG": "Congo - Brazzaville", "CD": "Congo - Kinshasa", "CK": "Cook Islands", "CR": "Costa Rica", "CI": "C\u00f4te d\u2019Ivoire", "HR": "Croatia", "CU": "Cuba", "CW": "Cura\u00e7ao", "CY": "Cyprus", "CZ": "Czechia", "DK": "Denmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominican Republic", "EC": "Ecuador", "EG": "Egypt", "SV": "El Salvador", "GQ": "Equatorial Guinea", "ER": "Eritrea", "EE": "Estonia", "SZ": "Eswatini", "ET": "Ethiopia", "FK": "Falkland Islands", "FO": "Faroe Islands", "FJ": "Fiji", "FI": "Finland", "FR": "France", "GF": "French Guiana", "PF": "French Polynesia", "TF": "French Southern Territories", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "DE": "Germany", "GH": "Ghana", "GI": "Gibraltar", "GR": "Greece", "GL": "Greenland", "GD": "Grenada", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard & McDonald Islands", "HN": "Honduras", "HK": "Hong Kong SAR China", "HU": "Hungary", "IS": "Iceland", "IN": "India", "ID": "Indonesia", "IR": "Iran", "IQ": "Iraq", "IE": "Ireland", "IM": "Isle of Man", "IL": "Israel", "IT": "Italy", "JM": "Jamaica", "JP": "Japan", "JO": "Jordan", "KZ": "Kazakhstan", "KE": "Kenya", "KI": "Kiribati", "KW": "Kuwait", "KG": "Kyrgyzstan", "LA": "Laos", "LV": "Latvia", "LB": "Lebanon", "LS": "Lesotho", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Lithuania", "LU": "Luxembourg", "MO": "Macao SAR China", "MG": "Madagascar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldives", "ML": "Mali", "MT": "Malta", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MA": "Morocco", "MZ": "Mozambique", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal", "NL": "Netherlands", "NC": "New Caledonia", "NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "NF": "Norfolk Island", "KP": "North Korea", "MK": "North Macedonia", "NO": "Norway", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua New Guinea", "PY": "Paraguay", "PE": "Peru", "PH": "Philippines", "PN": "Pitcairn Islands", "PL": "Poland", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Romania", "RU": "Russia", "RW": "Rwanda", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 & Pr\u00edncipe", "SA": "Saudi Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychelles", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SB": "Solomon Islands", "SO": "Somalia", "ZA": "South Africa", "GS": "South Georgia & South Sandwich Islands", "KR": "South Korea", "SS": "South Sudan", "ES": "Spain", "LK": "Sri Lanka", "BL": "St. Barth\u00e9lemy", "SH": "St. Helena", "KN": "St. Kitts & Nevis", "LC": "St. Lucia", "MF": "St. Martin", "PM": "St. Pierre & Miquelon", "VC": "St. Vincent & Grenadines", "SD": "Sudan", "SR": "Suriname", "SJ": "Svalbard & Jan Mayen", "SE": "Sweden", "CH": "Switzerland", "SY": "Syria", "TW": "Taiwan", "TJ": "Tajikistan", "TZ": "Tanzania", "TH": "Thailand", "TL": "Timor-Leste", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad & Tobago", "TN": "Tunisia", "TR": "Turkey", "TM": "Turkmenistan", "TC": "Turks & Caicos Islands", "VI": "U.S. Virgin Islands", "UG": "Uganda", "UA": "Ukraine", "AE": "United Arab Emirates", "GB": "United Kingdom", "US": "United States", "UY": "Uruguay", "UZ": "Uzbekistan", "VU": "Vanuatu", "VA": "Vatican City", "VE": "Venezuela", "VN": "Vietnam", "WF": "Wallis & Futuna", "EH": "Western Sahara", "YE": "Yemen", "ZM": "Zambia", "ZW": "Zimbabwe"}
|
1
desktop/onionshare/resources/countries/es.json
Normal file
1
desktop/onionshare/resources/countries/es.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/fi.json
Normal file
1
desktop/onionshare/resources/countries/fi.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"AF": "Afganistan", "AX": "Ahvenanmaa", "NL": "Alankomaat", "AL": "Albania", "DZ": "Algeria", "AS": "Amerikan Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua ja Barbuda", "AE": "Arabiemiirikunnat", "AR": "Argentiina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AZ": "Azerbaid\u017ean", "BS": "Bahama", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgia", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia ja Hertsegovina", "BW": "Botswana", "BV": "Bouvet\u2019nsaari", "BR": "Brasilia", "IO": "Brittil\u00e4inen Intian valtameren alue", "VG": "Brittil\u00e4iset Neitsytsaaret", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "KY": "Caymansaaret", "CL": "Chile", "CK": "Cookinsaaret", "CR": "Costa Rica", "CW": "Cura\u00e7ao", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominikaaninen tasavalta", "EC": "Ecuador", "EG": "Egypti", "SV": "El Salvador", "ER": "Eritrea", "ES": "Espanja", "ZA": "Etel\u00e4-Afrikka", "GS": "Etel\u00e4-Georgia ja Etel\u00e4iset Sandwichsaaret", "KR": "Etel\u00e4-Korea", "SS": "Etel\u00e4-Sudan", "ET": "Etiopia", "FK": "Falklandinsaaret", "FJ": "Fid\u017ei", "PH": "Filippiinit", "FO": "F\u00e4rsaaret", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "GH": "Ghana", "GI": "Gibraltar", "GD": "Grenada", "GL": "Gr\u00f6nlanti", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard ja McDonaldinsaaret", "HN": "Honduras", "HK": "Hongkong \u2013 Kiinan e.h.a.", "SJ": "Huippuvuoret ja Jan Mayen", "ID": "Indonesia", "IN": "Intia", "IQ": "Irak", "IR": "Iran", "IE": "Irlanti", "IS": "Islanti", "GB": "Iso-Britannia", "IL": "Israel", "IT": "Italia", "TL": "It\u00e4-Timor", "AT": "It\u00e4valta", "JM": "Jamaika", "JP": "Japani", "YE": "Jemen", "JO": "Jordania", "CX": "Joulusaari", "KH": "Kambod\u017ea", "CM": "Kamerun", "CA": "Kanada", "CV": "Kap Verde", "BQ": "Karibian Alankomaat", "KZ": "Kazakstan", "KE": "Kenia", "CF": "Keski-Afrikan tasavalta", "CN": "Kiina", "KG": "Kirgisia", "KI": "Kiribati", "CO": "Kolumbia", "KM": "Komorit", "CD": "Kongon demokraattinen tasavalta", "CG": "Kongon tasavalta", "CC": "Kookossaaret (Keelingsaaret)", "GR": "Kreikka", "HR": "Kroatia", "CU": "Kuuba", "KW": "Kuwait", "CY": "Kypros", "LA": "Laos", "LV": "Latvia", "LS": "Lesotho", "LB": "Libanon", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Liettua", "LU": "Luxemburg", "EH": "L\u00e4nsi-Sahara", "MO": "Macao \u2013 Kiinan e.h.a.", "MG": "Madagaskar", "MW": "Malawi", "MV": "Malediivit", "MY": "Malesia", "ML": "Mali", "MT": "Malta", "IM": "Mansaari", "MA": "Marokko", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Meksiko", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MZ": "Mosambik", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "NF": "Norfolkinsaari", "NO": "Norja", "CI": "Norsunluurannikko", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua-Uusi-Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn", "KP": "Pohjois-Korea", "MK": "Pohjois-Makedonia", "PT": "Portugali", "PR": "Puerto Rico", "PL": "Puola", "GQ": "P\u00e4iv\u00e4ntasaajan Guinea", "QA": "Qatar", "FR": "Ranska", "TF": "Ranskan etel\u00e4iset alueet", "GF": "Ranskan Guayana", "PF": "Ranskan Polynesia", "RE": "R\u00e9union", "RO": "Romania", "RW": "Ruanda", "SE": "Ruotsi", "SH": "Saint Helena", "KN": "Saint Kitts ja Nevis", "LC": "Saint Lucia", "VC": "Saint Vincent ja Grenadiinit", "BL": "Saint-Barth\u00e9lemy", "MF": "Saint-Martin", "PM": "Saint-Pierre ja Miquelon", "DE": "Saksa", "SB": "Salomonsaaret", "ZM": "Sambia", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 ja Pr\u00edncipe", "SA": "Saudi-Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychellit", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SO": "Somalia", "LK": "Sri Lanka", "SD": "Sudan", "FI": "Suomi", "SR": "Suriname", "CH": "Sveitsi", "SZ": "Swazimaa", "SY": "Syyria", "TJ": "Tad\u017eikistan", "TW": "Taiwan", "TZ": "Tansania", "DK": "Tanska", "TH": "Thaimaa", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad ja Tobago", "TD": "T\u0161ad", "CZ": "T\u0161ekki", "TN": "Tunisia", "TR": "Turkki", "TM": "Turkmenistan", "TC": "Turks- ja Caicossaaret", "UG": "Uganda", "UA": "Ukraina", "HU": "Unkari", "UY": "Uruguay", "NC": "Uusi-Kaledonia", "NZ": "Uusi-Seelanti", "UZ": "Uzbekistan", "BY": "Valko-Ven\u00e4j\u00e4", "VU": "Vanuatu", "VA": "Vatikaani", "VE": "Venezuela", "RU": "Ven\u00e4j\u00e4", "VN": "Vietnam", "EE": "Viro", "WF": "Wallis ja Futuna", "US": "Yhdysvallat", "VI": "Yhdysvaltain Neitsytsaaret", "ZW": "Zimbabwe"}
|
1
desktop/onionshare/resources/countries/fr.json
Normal file
1
desktop/onionshare/resources/countries/fr.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/gl.json
Normal file
1
desktop/onionshare/resources/countries/gl.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/is.json
Normal file
1
desktop/onionshare/resources/countries/is.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/ja.json
Normal file
1
desktop/onionshare/resources/countries/ja.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/lt.json
Normal file
1
desktop/onionshare/resources/countries/lt.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/nb_NO.json
Normal file
1
desktop/onionshare/resources/countries/nb_NO.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"AF": "Afghanistan", "AL": "Albania", "DZ": "Algerie", "AS": "Amerikansk Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua og Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AZ": "Aserbajdsjan", "AU": "Australia", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgia", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia-Hercegovina", "BW": "Botswana", "BV": "Bouvet\u00f8ya", "BR": "Brasil", "BN": "Brunei", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi", "CA": "Canada", "KY": "Cayman\u00f8yene", "CL": "Chile", "CX": "Christmas\u00f8ya", "CO": "Colombia", "CK": "Cook\u00f8yene", "CR": "Costa Rica", "CU": "Cuba", "CW": "Cura\u00e7ao", "DK": "Danmark", "VI": "De amerikanske jomfru\u00f8yene", "VG": "De britiske jomfru\u00f8yene", "AE": "De forente arabiske emirater", "TF": "De franske s\u00f8rterritorier", "DO": "Den dominikanske republikk", "CF": "Den sentralafrikanske republikk", "IO": "Det britiske territoriet i Indiahavet", "DJ": "Djibouti", "DM": "Dominica", "EC": "Ecuador", "EG": "Egypt", "GQ": "Ekvatorial-Guinea", "SV": "El Salvador", "CI": "Elfenbenskysten", "ER": "Eritrea", "EE": "Estland", "SZ": "Eswatini", "ET": "Etiopia", "FK": "Falklands\u00f8yene", "FJ": "Fiji", "PH": "Filippinene", "FI": "Finland", "FR": "Frankrike", "GF": "Fransk Guyana", "PF": "Fransk Polynesia", "FO": "F\u00e6r\u00f8yene", "GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "GH": "Ghana", "GI": "Gibraltar", "GD": "Grenada", "GL": "Gr\u00f8nland", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard- og McDonald\u00f8yene", "GR": "Hellas", "HN": "Honduras", "HK": "Hongkong S.A.R. Kina", "BY": "Hviterussland", "IN": "India", "ID": "Indonesia", "IQ": "Irak", "IR": "Iran", "IE": "Irland", "IS": "Island", "IL": "Israel", "IT": "Italia", "JM": "Jamaica", "JP": "Japan", "YE": "Jemen", "JO": "Jordan", "KH": "Kambodsja", "CM": "Kamerun", "CV": "Kapp Verde", "BQ": "Karibisk Nederland", "KZ": "Kasakhstan", "KE": "Kenya", "CN": "Kina", "KG": "Kirgisistan", "KI": "Kiribati", "CC": "Kokos\u00f8yene", "KM": "Komorene", "CG": "Kongo-Brazzaville", "CD": "Kongo-Kinshasa", "HR": "Kroatia", "KW": "Kuwait", "CY": "Kypros", "LA": "Laos", "LV": "Latvia", "LS": "Lesotho", "LB": "Libanon", "LR": "Liberia", "LY": "Libya", "LI": "Liechtenstein", "LT": "Litauen", "LU": "Luxemburg", "MO": "Macao S.A.R. Kina", "MG": "Madagaskar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldivene", "ML": "Mali", "MT": "Malta", "IM": "Man", "MA": "Marokko", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "ME": "Montenegro", "MS": "Montserrat", "MZ": "Mosambik", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NL": "Nederland", "NP": "Nepal", "NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "KP": "Nord-Korea", "MK": "Nord-Makedonia", "NF": "Norfolk\u00f8ya", "NO": "Norge", "NC": "Ny-Caledonia", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua Ny-Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn\u00f8yene", "PL": "Polen", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Romania", "RU": "Russland", "RW": "Rwanda", "KN": "Saint Kitts og Nevis", "BL": "Saint-Barth\u00e9lemy", "MF": "Saint-Martin", "PM": "Saint-Pierre-et-Miquelon", "SB": "Salomon\u00f8yene", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 og Pr\u00edncipe", "SA": "Saudi-Arabia", "SN": "Senegal", "RS": "Serbia", "SC": "Seychellene", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakia", "SI": "Slovenia", "SO": "Somalia", "ES": "Spania", "LK": "Sri Lanka", "SH": "St. Helena", "LC": "St. Lucia", "VC": "St. Vincent og Grenadinene", "GB": "Storbritannia", "SD": "Sudan", "SR": "Surinam", "SJ": "Svalbard og Jan Mayen", "CH": "Sveits", "SE": "Sverige", "SY": "Syria", "ZA": "S\u00f8r-Afrika", "GS": "S\u00f8r-Georgia og S\u00f8r-Sandwich\u00f8yene", "KR": "S\u00f8r-Korea", "SS": "S\u00f8r-Sudan", "TJ": "Tadsjikistan", "TW": "Taiwan", "TZ": "Tanzania", "TH": "Thailand", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad og Tobago", "TD": "Tsjad", "CZ": "Tsjekkia", "TN": "Tunisia", "TM": "Turkmenistan", "TC": "Turks- og Caicos\u00f8yene", "TR": "Tyrkia", "DE": "Tyskland", "UG": "Uganda", "UA": "Ukraina", "HU": "Ungarn", "UY": "Uruguay", "US": "USA", "UZ": "Usbekistan", "VU": "Vanuatu", "VA": "Vatikanstaten", "VE": "Venezuela", "EH": "Vest-Sahara", "VN": "Vietnam", "WF": "Wallis og Futuna", "ZM": "Zambia", "ZW": "Zimbabwe", "TL": "\u00d8st-Timor", "AT": "\u00d8sterrike", "AX": "\u00c5land"}
|
1
desktop/onionshare/resources/countries/pl.json
Normal file
1
desktop/onionshare/resources/countries/pl.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/pt_BR.json
Normal file
1
desktop/onionshare/resources/countries/pt_BR.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/pt_PT.json
Normal file
1
desktop/onionshare/resources/countries/pt_PT.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/ru.json
Normal file
1
desktop/onionshare/resources/countries/ru.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/sv.json
Normal file
1
desktop/onionshare/resources/countries/sv.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"AF": "Afghanistan", "AL": "Albanien", "DZ": "Algeriet", "VI": "Amerikanska Jungfru\u00f6arna", "AS": "Amerikanska Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarktis", "AG": "Antigua och Barbuda", "AR": "Argentina", "AM": "Armenien", "AW": "Aruba", "AU": "Australien", "AZ": "Azerbajdzjan", "BS": "Bahamas", "BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BE": "Belgien", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnien och Hercegovina", "BW": "Botswana", "BV": "Bouvet\u00f6n", "BR": "Brasilien", "VG": "Brittiska Jungfru\u00f6arna", "IO": "Brittiska territoriet i Indiska oceanen", "BN": "Brunei", "BG": "Bulgarien", "BF": "Burkina Faso", "BI": "Burundi", "KY": "Cayman\u00f6arna", "CF": "Centralafrikanska republiken", "CL": "Chile", "CO": "Colombia", "CK": "Cook\u00f6arna", "CR": "Costa Rica", "CW": "Cura\u00e7ao", "CY": "Cypern", "CI": "C\u00f4te d\u2019Ivoire", "DK": "Danmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominikanska republiken", "EC": "Ecuador", "EG": "Egypten", "GQ": "Ekvatorialguinea", "SV": "El Salvador", "ER": "Eritrea", "EE": "Estland", "ET": "Etiopien", "FK": "Falklands\u00f6arna", "FJ": "Fiji", "PH": "Filippinerna", "FI": "Finland", "FR": "Frankrike", "GF": "Franska Guyana", "PF": "Franska Polynesien", "TF": "Franska sydterritorierna", "FO": "F\u00e4r\u00f6arna", "AE": "F\u00f6renade Arabemiraten", "GA": "Gabon", "GM": "Gambia", "GE": "Georgien", "GH": "Ghana", "GI": "Gibraltar", "GR": "Grekland", "GD": "Grenada", "GL": "Gr\u00f6nland", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard\u00f6n och McDonald\u00f6arna", "HN": "Honduras", "HK": "Hongkong", "IN": "Indien", "ID": "Indonesien", "IQ": "Irak", "IR": "Iran", "IE": "Irland", "IS": "Island", "IM": "Isle of Man", "IL": "Israel", "IT": "Italien", "JM": "Jamaica", "JP": "Japan", "YE": "Jemen", "JO": "Jordanien", "CX": "Jul\u00f6n", "KH": "Kambodja", "CM": "Kamerun", "CA": "Kanada", "CV": "Kap Verde", "BQ": "Karibiska Nederl\u00e4nderna", "KZ": "Kazakstan", "KE": "Kenya", "CN": "Kina", "KG": "Kirgizistan", "KI": "Kiribati", "CC": "Kokos\u00f6arna", "KM": "Komorerna", "CG": "Kongo-Brazzaville", "CD": "Kongo-Kinshasa", "HR": "Kroatien", "CU": "Kuba", "KW": "Kuwait", "LA": "Laos", "LS": "Lesotho", "LV": "Lettland", "LB": "Libanon", "LR": "Liberia", "LY": "Libyen", "LI": "Liechtenstein", "LT": "Litauen", "LU": "Luxemburg", "MO": "Macao", "MG": "Madagaskar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldiverna", "ML": "Mali", "MT": "Malta", "MA": "Marocko", "MQ": "Martinique", "MR": "Mauretanien", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexiko", "MZ": "Mo\u00e7ambique", "MD": "Moldavien", "MC": "Monaco", "MN": "Mongoliet", "ME": "Montenegro", "MS": "Montserrat", "MM": "Myanmar (Burma)", "NA": "Namibia", "NR": "Nauru", "NL": "Nederl\u00e4nderna", "NP": "Nepal", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue", "KP": "Nordkorea", "MK": "Nordmakedonien", "NF": "Norfolk\u00f6n", "NO": "Norge", "NC": "Nya Kaledonien", "NZ": "Nya Zeeland", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PA": "Panama", "PG": "Papua Nya Guinea", "PY": "Paraguay", "PE": "Peru", "PN": "Pitcairn\u00f6arna", "PL": "Polen", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "R\u00e9union", "RO": "Rum\u00e4nien", "RW": "Rwanda", "RU": "Ryssland", "BL": "S:t Barth\u00e9lemy", "SH": "S:t Helena", "KN": "S:t Kitts och Nevis", "LC": "S:t Lucia", "PM": "S:t Pierre och Miquelon", "VC": "S:t Vincent och Grenadinerna", "MF": "Saint-Martin", "SB": "Salomon\u00f6arna", "WS": "Samoa", "SM": "San Marino", "ST": "S\u00e3o Tom\u00e9 och Pr\u00edncipe", "SA": "Saudiarabien", "CH": "Schweiz", "SN": "Senegal", "RS": "Serbien", "SC": "Seychellerna", "SL": "Sierra Leone", "SG": "Singapore", "SX": "Sint Maarten", "SK": "Slovakien", "SI": "Slovenien", "SO": "Somalia", "ES": "Spanien", "LK": "Sri Lanka", "GB": "Storbritannien", "SD": "Sudan", "SR": "Surinam", "SJ": "Svalbard och Jan Mayen", "SE": "Sverige", "SZ": "Swaziland", "ZA": "Sydafrika", "GS": "Sydgeorgien och Sydsandwich\u00f6arna", "KR": "Sydkorea", "SS": "Sydsudan", "SY": "Syrien", "TJ": "Tadzjikistan", "TW": "Taiwan", "TZ": "Tanzania", "TD": "Tchad", "TH": "Thailand", "CZ": "Tjeckien", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad och Tobago", "TN": "Tunisien", "TR": "Turkiet", "TM": "Turkmenistan", "TC": "Turks- och Caicos\u00f6arna", "DE": "Tyskland", "UG": "Uganda", "UA": "Ukraina", "HU": "Ungern", "UY": "Uruguay", "US": "USA", "UZ": "Uzbekistan", "VU": "Vanuatu", "VA": "Vatikanstaten", "VE": "Venezuela", "VN": "Vietnam", "BY": "Vitryssland", "EH": "V\u00e4stsahara", "WF": "Wallis- och Futuna\u00f6arna", "ZM": "Zambia", "ZW": "Zimbabwe", "AX": "\u00c5land", "AT": "\u00d6sterrike", "TL": "\u00d6sttimor"}
|
1
desktop/onionshare/resources/countries/tr.json
Normal file
1
desktop/onionshare/resources/countries/tr.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/uk.json
Normal file
1
desktop/onionshare/resources/countries/uk.json
Normal file
File diff suppressed because one or more lines are too long
1
desktop/onionshare/resources/countries/zh_Hans.json
Normal file
1
desktop/onionshare/resources/countries/zh_Hans.json
Normal file
File diff suppressed because one or more lines are too long
BIN
desktop/onionshare/resources/images/dark_logo_text_bg.png
Normal file
BIN
desktop/onionshare/resources/images/dark_logo_text_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
BIN
desktop/onionshare/resources/images/light_logo_text_bg.png
Normal file
BIN
desktop/onionshare/resources/images/light_logo_text_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-ship.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-ship.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-smoke.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-smoke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
desktop/onionshare/resources/images/tor-connect-stars.png
Normal file
BIN
desktop/onionshare/resources/images/tor-connect-stars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -10,7 +10,7 @@
|
|||
"gui_add_files": "Add Files",
|
||||
"gui_add_folder": "Add Folder",
|
||||
"gui_remove": "Remove",
|
||||
"gui_dragdrop_sandbox_flatpak": "To make the Flatpak sandbox more secure, drag and drop is not supported. Use the Add Files and Add Folder buttons to browse for files instead.",
|
||||
"gui_dragdrop_sandbox_flatpak": "To make the Flatpak sandbox more secure, drag and drop is not supported. Use the \"Add Files\" and \"Add Folder\" buttons to select files instead.",
|
||||
"gui_file_selection_remove_all": "Remove All",
|
||||
"gui_choose_items": "Choose",
|
||||
"gui_share_start_server": "Start sharing",
|
||||
|
@ -27,9 +27,9 @@
|
|||
"gui_copy_url": "Copy Address",
|
||||
"gui_copy_client_auth": "Copy Private Key",
|
||||
"gui_canceled": "Canceled",
|
||||
"gui_copied_url_title": "Copied OnionShare Address",
|
||||
"gui_copied_url_title": "OnionShare Address Copied",
|
||||
"gui_copied_url": "OnionShare address copied to clipboard",
|
||||
"gui_copied_client_auth_title": "Copied Private Key",
|
||||
"gui_copied_client_auth_title": "Private Key Copied",
|
||||
"gui_copied_client_auth": "Private Key copied to clipboard",
|
||||
"gui_show_qr_code": "Show QR Code",
|
||||
"gui_qr_code_dialog_title": "OnionShare QR Code",
|
||||
|
@ -42,7 +42,25 @@
|
|||
"gui_please_wait": "Starting… Click to cancel.",
|
||||
"zip_progress_bar_format": "Compressing: %p%",
|
||||
"gui_tor_settings_window_title": "Tor Settings",
|
||||
"gui_autoconnect_description": "OnionShare relies on the Tor Network, run by thousands of volunteers around the world.",
|
||||
"gui_enable_autoconnect_checkbox": "Connect to Tor automatically",
|
||||
"gui_autoconnect_failed_to_connect_to_tor": "Failed to Connect to Tor",
|
||||
"gui_autoconnect_trying_to_connect_to_tor": "Trying to Connect to Tor...",
|
||||
"gui_autoconnect_bridge_description": "Are you connected to the internet?<br><br>It's also possible that your internet is being censored. You might be able to bypass this using a bridge.",
|
||||
"gui_autoconnect_bridge_detect_automatic": "Automatically determine my country from my IP address",
|
||||
"gui_autoconnect_bridge_detect_manual": "Manually select my country",
|
||||
"gui_autoconnect_start": "Connect to Tor",
|
||||
"gui_autoconnect_configure": "Network Settings",
|
||||
"gui_autoconnect_bridge_start": "Use a Bridge",
|
||||
"gui_autoconnect_try_again_without_a_bridge": "Try again without a Bridge",
|
||||
"gui_autoconnect_circumventing_censorship": "Trying to resolve connectivity issues",
|
||||
"gui_autoconnect_circumventing_censorship_starting_circumvention": "Starting censorship circumvention process",
|
||||
"gui_autoconnect_circumventing_censorship_starting_meek": "Starting Meek for domain-fronting",
|
||||
"gui_autoconnect_circumventing_censorship_requesting_bridges": "Requesting bridges from the Tor Censorship Circumvention API",
|
||||
"gui_autoconnect_circumventing_censorship_got_bridges": "Got bridges! Trying to reconnect to Tor",
|
||||
"gui_autoconnect_could_not_connect_to_tor_api": "Could not connect to the Tor API. Make sure you are connected to the internet before trying again.",
|
||||
"gui_settings_window_title": "Settings",
|
||||
"gui_general_settings_window_title": "General",
|
||||
"gui_settings_autoupdate_label": "Check for new version",
|
||||
"gui_settings_autoupdate_option": "Notify me when a new version is available",
|
||||
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
||||
|
@ -62,7 +80,7 @@
|
|||
"gui_settings_authenticate_password_option": "Password",
|
||||
"gui_settings_password_label": "Password",
|
||||
"gui_settings_tor_bridges": "Connect using a Tor bridge?",
|
||||
"gui_settings_tor_bridges_label": "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.",
|
||||
"gui_settings_tor_bridges_label": "Bridges helps your traffic enter the Tor Network where Tor access is blocked. Depending on where you are, one bridge may work better than another.",
|
||||
"gui_settings_bridge_use_checkbox": "Use a bridge",
|
||||
"gui_settings_bridge_radio_builtin": "Select a built-in bridge",
|
||||
"gui_settings_bridge_none_radio_option": "Don't use a bridge",
|
||||
|
@ -131,14 +149,14 @@
|
|||
"history_requests_tooltip": "{} web requests",
|
||||
"error_cannot_create_data_dir": "Could not create OnionShare data folder: {}",
|
||||
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>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.</b>",
|
||||
"gui_open_folder_error": "Failed to open folder with xdg-open. The file is here: {}",
|
||||
"gui_open_folder_error": "Could not open the folder with xdg-open. The file is here: {}",
|
||||
"gui_settings_language_label": "Language",
|
||||
"gui_settings_theme_label": "Theme",
|
||||
"gui_settings_theme_auto": "Auto",
|
||||
"gui_settings_theme_light": "Light",
|
||||
"gui_settings_theme_dark": "Dark",
|
||||
"gui_settings_language_changed_notice": "Restart OnionShare for the new language to be applied.",
|
||||
"gui_color_mode_changed_notice": "Restart OnionShare for the new color mode to be applied.",
|
||||
"gui_settings_language_changed_notice": "Restart OnionShare to change to the new language.",
|
||||
"gui_color_mode_changed_notice": "Restart OnionShare to see the new colors.",
|
||||
"systray_menu_exit": "Quit",
|
||||
"systray_page_loaded_title": "Page Loaded",
|
||||
"systray_page_loaded_message": "OnionShare address loaded",
|
||||
|
@ -157,10 +175,10 @@
|
|||
"gui_all_modes_progress_starting": "{0:s}, %p% (calculating)",
|
||||
"gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||
"gui_share_mode_no_files": "No Files Sent Yet",
|
||||
"gui_share_mode_autostop_timer_waiting": "Waiting to finish sending",
|
||||
"gui_share_mode_autostop_timer_waiting": "Finishing sending…",
|
||||
"gui_website_mode_no_files": "No Website Shared Yet",
|
||||
"gui_receive_mode_no_files": "No Files Received Yet",
|
||||
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
|
||||
"gui_receive_mode_autostop_timer_waiting": "Finishing receiving…",
|
||||
"days_first_letter": "d",
|
||||
"hours_first_letter": "h",
|
||||
"minutes_first_letter": "m",
|
||||
|
@ -180,14 +198,14 @@
|
|||
"gui_tab_name_website": "Website",
|
||||
"gui_tab_name_chat": "Chat",
|
||||
"gui_close_tab_warning_title": "Are you sure?",
|
||||
"gui_close_tab_warning_persistent_description": "This tab is persistent. If you close it you'll lose the onion address that it's using. Are you sure you want to close it?",
|
||||
"gui_close_tab_warning_share_description": "You're in the process of sending files. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_receive_description": "You're in the process of receiving files. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_website_description": "You're actively hosting a website. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_persistent_description": "Close persistent tab and lose the onion address it is using?",
|
||||
"gui_close_tab_warning_share_description": "Close tab that is sending files?",
|
||||
"gui_close_tab_warning_receive_description": "Close tab that is receiving files?",
|
||||
"gui_close_tab_warning_website_description": "Close tab that is hosting a website?",
|
||||
"gui_close_tab_warning_close": "Close",
|
||||
"gui_close_tab_warning_cancel": "Cancel",
|
||||
"gui_quit_warning_title": "Are you sure?",
|
||||
"gui_quit_warning_description": "Sharing is active in some of your tabs. If you quit, all of your tabs will close. Are you sure you want to quit?",
|
||||
"gui_quit_warning_description": "Quit and close all tabs, even though sharing is active in some of them?",
|
||||
"gui_quit_warning_quit": "Quit",
|
||||
"gui_quit_warning_cancel": "Cancel",
|
||||
"mode_settings_advanced_toggle_show": "Show advanced settings",
|
||||
|
@ -219,18 +237,18 @@
|
|||
"settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.",
|
||||
"settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?",
|
||||
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}",
|
||||
"gui_rendezvous_cleanup": "Waiting for Tor circuits to close to be sure your files have successfully transferred.\n\nThis might take a few minutes.",
|
||||
"gui_rendezvous_cleanup": "Waiting for Tor circuits to close to be sure your files have transferred.\n\nThis might take a few minutes.",
|
||||
"gui_rendezvous_cleanup_quit_early": "Quit Early",
|
||||
"error_port_not_available": "OnionShare port not available",
|
||||
"history_receive_read_message_button": "Read Message",
|
||||
"error_tor_protocol_error": "There was an error with Tor: {}",
|
||||
"moat_contact_label": "Contacting BridgeDB...",
|
||||
"moat_contact_label": "Contacting BridgeDB…",
|
||||
"moat_captcha_label": "Solve the CAPTCHA to request a bridge.",
|
||||
"moat_captcha_placeholder": "Enter the characters from the image",
|
||||
"moat_captcha_submit": "Submit",
|
||||
"moat_captcha_reload": "Reload",
|
||||
"moat_bridgedb_error": "Error contacting BridgeDB.",
|
||||
"moat_captcha_error": "The solution is not correct. Please try again.",
|
||||
"moat_solution_empty_error": "You must enter the characters from the image",
|
||||
"moat_bridgedb_error": "Could not contact BridgeDB.",
|
||||
"moat_captcha_error": "Incorrect solution. Please try again.",
|
||||
"moat_solution_empty_error": "Enter the characters from the image",
|
||||
"mode_tor_not_connected_label": "OnionShare is not connected to the Tor network"
|
||||
}
|
||||
}
|
72
desktop/onionshare/settings_parent_tab.py
Normal file
72
desktop/onionshare/settings_parent_tab.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare_cli.mode_settings import ModeSettings
|
||||
|
||||
from . import strings
|
||||
from .tab import Tab
|
||||
from .threads import EventHandlerThread
|
||||
from .gui_common import GuiCommon
|
||||
from .tor_settings_tab import TorSettingsTab
|
||||
from .settings_tab import SettingsTab
|
||||
from .connection_tab import AutoConnectTab
|
||||
|
||||
|
||||
class SettingsParentTab(QtWidgets.QTabWidget):
|
||||
"""
|
||||
The settings tab widget containing the tor settings
|
||||
and app settings subtabs
|
||||
"""
|
||||
|
||||
bring_to_front = QtCore.Signal()
|
||||
close_this_tab = QtCore.Signal()
|
||||
|
||||
def __init__(
|
||||
self, common, tab_id, parent=None, active_tab="general", from_autoconnect=False
|
||||
):
|
||||
super(SettingsParentTab, self).__init__()
|
||||
self.parent = parent
|
||||
self.common = common
|
||||
self.common.log("SettingsParentTab", "__init__")
|
||||
|
||||
# Keep track of tabs in a dictionary that maps tab_id to tab.
|
||||
# Each tab has a unique, auto-incremented id (tab_id). This is different than the
|
||||
# tab's index, which changes as tabs are re-arranged.
|
||||
self.tabs = {
|
||||
"general": 0,
|
||||
"tor": 1,
|
||||
}
|
||||
self.tab_id = tab_id
|
||||
self.current_tab_id = self.tabs[active_tab]
|
||||
|
||||
# Use a custom tab bar
|
||||
tab_bar = TabBar(self.common)
|
||||
self.setTabBar(tab_bar)
|
||||
settings_tab = SettingsTab(self.common, self.tabs["general"], parent=self)
|
||||
self.tor_settings_tab = TorSettingsTab(
|
||||
self.common,
|
||||
self.tabs["tor"],
|
||||
self.parent.are_tabs_active(),
|
||||
self.parent.status_bar,
|
||||
parent=self,
|
||||
from_autoconnect=from_autoconnect,
|
||||
)
|
||||
self.addTab(settings_tab, strings._("gui_general_settings_window_title"))
|
||||
self.addTab(self.tor_settings_tab, strings._("gui_tor_settings_window_title"))
|
||||
|
||||
# Set up the tab widget
|
||||
self.setMovable(False)
|
||||
self.setTabsClosable(False)
|
||||
self.setUsesScrollButtons(False)
|
||||
self.setCurrentIndex(self.current_tab_id)
|
||||
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
"""
|
||||
A custom tab bar
|
||||
"""
|
||||
|
||||
move_new_tab_button = QtCore.Signal()
|
||||
|
||||
def __init__(self, common):
|
||||
super(TabBar, self).__init__()
|
||||
self.setStyleSheet(common.gui.css["settings_subtab_bar"])
|
|
@ -33,9 +33,7 @@ class SettingsTab(QtWidgets.QWidget):
|
|||
Settings dialog.
|
||||
"""
|
||||
|
||||
close_this_tab = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, tab_id):
|
||||
def __init__(self, common, tab_id, parent=None):
|
||||
super(SettingsTab, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
@ -43,6 +41,7 @@ class SettingsTab(QtWidgets.QWidget):
|
|||
|
||||
self.system = platform.system()
|
||||
self.tab_id = tab_id
|
||||
self.parent = parent
|
||||
|
||||
# Automatic updates options
|
||||
|
||||
|
@ -283,7 +282,7 @@ class SettingsTab(QtWidgets.QWidget):
|
|||
|
||||
# Save the new settings
|
||||
settings.save()
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
def help_clicked(self):
|
||||
"""
|
||||
|
|
|
@ -26,8 +26,8 @@ from . import strings
|
|||
from .tab import Tab
|
||||
from .threads import EventHandlerThread
|
||||
from .gui_common import GuiCommon
|
||||
from .tor_settings_tab import TorSettingsTab
|
||||
from .settings_tab import SettingsTab
|
||||
from .settings_parent_tab import SettingsParentTab
|
||||
from .connection_tab import AutoConnectTab
|
||||
|
||||
|
||||
class TabWidget(QtWidgets.QTabWidget):
|
||||
|
@ -37,13 +37,14 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
|
||||
bring_to_front = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, system_tray, status_bar):
|
||||
def __init__(self, common, system_tray, status_bar, window):
|
||||
super(TabWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("TabWidget", "__init__")
|
||||
|
||||
self.system_tray = system_tray
|
||||
self.status_bar = status_bar
|
||||
self.window = window
|
||||
|
||||
# Keep track of tabs in a dictionary that maps tab_id to tab.
|
||||
# Each tab has a unique, auto-incremented id (tab_id). This is different than the
|
||||
|
@ -96,8 +97,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
# Clean up each tab
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
self.tabs[tab_id].cleanup()
|
||||
|
||||
|
@ -136,8 +137,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
|
||||
# If it's Settings or Tor Settings, ignore
|
||||
if (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
# Blank the server status indicator
|
||||
self.status_bar.server_status_image_label.clear()
|
||||
|
@ -159,8 +160,16 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
pass
|
||||
|
||||
def new_tab_clicked(self):
|
||||
# Create a new tab
|
||||
self.add_tab()
|
||||
# if already connected to tor or local only, create a new tab
|
||||
# Else open the initial connection tab
|
||||
if self.common.gui.local_only or self.common.gui.onion.is_authenticated():
|
||||
self.add_tab()
|
||||
else:
|
||||
self.open_connection_tab()
|
||||
|
||||
def check_autoconnect_tab(self):
|
||||
if type(self.tabs[0]) is AutoConnectTab:
|
||||
self.tabs[0].check_autoconnect()
|
||||
|
||||
def load_tab(self, mode_settings_id):
|
||||
# Load the tab's mode settings
|
||||
|
@ -198,44 +207,51 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
# Bring the window to front, in case this is being added by an event
|
||||
self.bring_to_front.emit()
|
||||
|
||||
def open_settings_tab(self):
|
||||
def open_connection_tab(self):
|
||||
self.common.log("TabWidget", "open_connection_tab")
|
||||
|
||||
# See if a connection tab is already open, and if so switch to it
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is AutoConnectTab:
|
||||
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
|
||||
return
|
||||
|
||||
connection_tab = AutoConnectTab(
|
||||
self.common, self.current_tab_id, self.status_bar, self.window, parent=self
|
||||
)
|
||||
connection_tab.close_this_tab.connect(self.close_connection_tab)
|
||||
connection_tab.tor_is_connected.connect(self.tor_is_connected)
|
||||
connection_tab.tor_is_disconnected.connect(self.tor_is_disconnected)
|
||||
self.tabs[self.current_tab_id] = connection_tab
|
||||
self.current_tab_id += 1
|
||||
index = self.addTab(connection_tab, strings._("gui_autoconnect_start"))
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def open_settings_tab(self, from_autoconnect=False, active_tab="general"):
|
||||
self.common.log("TabWidget", "open_settings_tab")
|
||||
|
||||
# See if a settings tab is already open, and if so switch to it
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is SettingsTab:
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
|
||||
return
|
||||
|
||||
settings_tab = SettingsTab(self.common, self.current_tab_id)
|
||||
settings_tab = SettingsParentTab(
|
||||
self.common,
|
||||
self.current_tab_id,
|
||||
active_tab=active_tab,
|
||||
parent=self,
|
||||
from_autoconnect=from_autoconnect,
|
||||
)
|
||||
settings_tab.close_this_tab.connect(self.close_settings_tab)
|
||||
self.tor_settings_tab = settings_tab.tor_settings_tab
|
||||
self.tor_settings_tab.tor_is_connected.connect(self.tor_is_connected)
|
||||
self.tor_settings_tab.tor_is_disconnected.connect(self.tor_is_disconnected)
|
||||
self.tabs[self.current_tab_id] = settings_tab
|
||||
self.current_tab_id += 1
|
||||
index = self.addTab(settings_tab, strings._("gui_settings_window_title"))
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def open_tor_settings_tab(self):
|
||||
self.common.log("TabWidget", "open_tor_settings_tab")
|
||||
|
||||
# See if a settings tab is already open, and if so switch to it
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
self.setCurrentIndex(self.indexOf(self.tabs[tab_id]))
|
||||
return
|
||||
|
||||
self.tor_settings_tab = TorSettingsTab(
|
||||
self.common, self.current_tab_id, self.are_tabs_active(), self.status_bar
|
||||
)
|
||||
self.tor_settings_tab.close_this_tab.connect(self.close_tor_settings_tab)
|
||||
self.tor_settings_tab.tor_is_connected.connect(self.tor_is_connected)
|
||||
self.tor_settings_tab.tor_is_disconnected.connect(self.tor_is_disconnected)
|
||||
self.tabs[self.current_tab_id] = self.tor_settings_tab
|
||||
self.current_tab_id += 1
|
||||
index = self.addTab(
|
||||
self.tor_settings_tab, strings._("gui_tor_settings_window_title")
|
||||
)
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def change_title(self, tab_id, title):
|
||||
shortened_title = title
|
||||
if len(shortened_title) > 11:
|
||||
|
@ -279,8 +295,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
persistent_tabs = []
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
tab = self.widget(self.indexOf(self.tabs[tab_id]))
|
||||
if tab.settings.get("persistent", "enabled"):
|
||||
|
@ -296,12 +312,12 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
tab_id = tab.tab_id
|
||||
|
||||
if (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
self.common.log("TabWidget", "closing a settings tab")
|
||||
|
||||
if type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
self.tor_settings_tab = None
|
||||
|
||||
# Remove the tab
|
||||
|
@ -334,18 +350,41 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
else:
|
||||
self.common.log("TabWidget", "user does not want to close the tab")
|
||||
|
||||
def close_settings_tab(self):
|
||||
self.common.log("TabWidget", "close_settings_tab")
|
||||
def close_connection_tab(self):
|
||||
self.common.log("TabWidget", "close_connection_tab")
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is SettingsTab:
|
||||
if type(self.tabs[tab_id]) is AutoConnectTab:
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.close_tab(index)
|
||||
return
|
||||
|
||||
def close_tor_settings_tab(self):
|
||||
self.common.log("TabWidget", "close_tor_settings_tab")
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
def close_settings_tab(self):
|
||||
self.common.log("TabWidget", "close_settings_tab")
|
||||
for tab_id in list(self.tabs):
|
||||
if type(self.tabs[tab_id]) is AutoConnectTab:
|
||||
# If we are being returned to the AutoConnectTab, but
|
||||
# the user has fixed their Tor settings in the TorSettings
|
||||
# tab, *and* they have enabled autoconnect, then
|
||||
# we should close the AutoConnect Tab.
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.common.log(
|
||||
"TabWidget",
|
||||
"close_settings_tab",
|
||||
"Tor is connected and we can auto-connect, so closing the tab",
|
||||
)
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.close_tab(index)
|
||||
else:
|
||||
self.tabs[tab_id].reload_settings()
|
||||
self.common.log(
|
||||
"TabWidget",
|
||||
"close_settings_tab",
|
||||
"Reloading settings in case they changed in the TorSettingsTab. Not auto-connecting",
|
||||
)
|
||||
break
|
||||
# List of indices may have changed due to the above, so we loop over it again as another copy
|
||||
for tab_id in list(self.tabs):
|
||||
if type(self.tabs[tab_id]) is SettingsParentTab:
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.close_tab(index)
|
||||
return
|
||||
|
@ -356,8 +395,8 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
"""
|
||||
for tab_id in self.tabs:
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsTab
|
||||
or type(self.tabs[tab_id]) is TorSettingsTab
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
|
@ -379,23 +418,23 @@ class TabWidget(QtWidgets.QTabWidget):
|
|||
|
||||
def tor_is_connected(self):
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is SettingsTab:
|
||||
self.tabs[tab_id].tor_is_connected()
|
||||
else:
|
||||
if not type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
mode.tor_connection_started()
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
mode.tor_connection_started()
|
||||
|
||||
def tor_is_disconnected(self):
|
||||
for tab_id in self.tabs:
|
||||
if type(self.tabs[tab_id]) is SettingsTab:
|
||||
self.tabs[tab_id].tor_is_disconnected()
|
||||
else:
|
||||
if not type(self.tabs[tab_id]) is TorSettingsTab:
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
mode.tor_connection_stopped()
|
||||
if not (
|
||||
type(self.tabs[tab_id]) is SettingsParentTab
|
||||
or type(self.tabs[tab_id]) is AutoConnectTab
|
||||
):
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
mode.tor_connection_stopped()
|
||||
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
|
|
|
@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
import time
|
||||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
from PySide2 import QtCore, QtWidgets
|
||||
|
||||
from onionshare_cli.onion import (
|
||||
BundledTorCanceled,
|
||||
|
@ -39,122 +39,6 @@ from onionshare_cli.onion import (
|
|||
)
|
||||
|
||||
from . import strings
|
||||
from .gui_common import GuiCommon
|
||||
from .widgets import Alert
|
||||
|
||||
|
||||
class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
"""
|
||||
Connecting to Tor dialog.
|
||||
"""
|
||||
|
||||
open_tor_settings = QtCore.Signal()
|
||||
success = QtCore.Signal()
|
||||
|
||||
def __init__(
|
||||
self, common, custom_settings=False, testing_settings=False, onion=None
|
||||
):
|
||||
super(TorConnectionDialog, self).__init__(None)
|
||||
|
||||
self.common = common
|
||||
self.testing_settings = testing_settings
|
||||
|
||||
if custom_settings:
|
||||
self.settings = custom_settings
|
||||
else:
|
||||
self.settings = self.common.settings
|
||||
|
||||
self.common.log("TorConnectionDialog", "__init__")
|
||||
|
||||
if self.testing_settings:
|
||||
self.title = strings._("gui_settings_connection_type_test_button")
|
||||
self.onion = onion
|
||||
else:
|
||||
self.title = "OnionShare"
|
||||
self.onion = self.common.gui.onion
|
||||
|
||||
self.setWindowTitle(self.title)
|
||||
|
||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||
self.setModal(True)
|
||||
self.setFixedSize(400, 150)
|
||||
|
||||
# Label
|
||||
self.setLabelText(strings._("connecting_to_tor"))
|
||||
|
||||
# Progress bar ticks from 0 to 100
|
||||
self.setRange(0, 100)
|
||||
# Don't show if connection takes less than 100ms (for non-bundled tor)
|
||||
self.setMinimumDuration(100)
|
||||
|
||||
# Start displaying the status at 0
|
||||
self._tor_status_update(0, "")
|
||||
|
||||
def start(self):
|
||||
self.common.log("TorConnectionDialog", "start")
|
||||
|
||||
t = TorConnectionThread(self.common, self.settings, self)
|
||||
t.tor_status_update.connect(self._tor_status_update)
|
||||
t.connected_to_tor.connect(self._connected_to_tor)
|
||||
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
|
||||
t.error_connecting_to_tor.connect(self._error_connecting_to_tor)
|
||||
t.start()
|
||||
|
||||
# The main thread needs to remain active, and checking for Qt events,
|
||||
# until the thread is finished. Otherwise it won't be able to handle
|
||||
# accepting signals.
|
||||
self.active = True
|
||||
while self.active:
|
||||
time.sleep(0.1)
|
||||
self.common.gui.qtapp.processEvents()
|
||||
|
||||
def _tor_status_update(self, progress, summary):
|
||||
self.setValue(int(progress))
|
||||
self.setLabelText(
|
||||
f"<strong>{strings._('connecting_to_tor')}</strong><br>{summary}"
|
||||
)
|
||||
|
||||
def _connected_to_tor(self):
|
||||
self.common.log("TorConnectionDialog", "_connected_to_tor")
|
||||
self.active = False
|
||||
# Close the dialog after connecting
|
||||
self.setValue(self.maximum())
|
||||
self.success.emit()
|
||||
|
||||
def _canceled_connecting_to_tor(self):
|
||||
self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
|
||||
self.active = False
|
||||
self.onion.cleanup()
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel)
|
||||
|
||||
def _error_connecting_to_tor(self, msg):
|
||||
self.common.log("TorConnectionDialog", "_error_connecting_to_tor")
|
||||
self.active = False
|
||||
|
||||
if self.testing_settings:
|
||||
# If testing, just display the error but don't open settings
|
||||
def alert():
|
||||
Alert(self.common, msg, QtWidgets.QMessageBox.Warning, title=self.title)
|
||||
|
||||
else:
|
||||
# If not testing, open settings after displaying the error
|
||||
def alert():
|
||||
Alert(
|
||||
self.common,
|
||||
f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}",
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
title=self.title,
|
||||
)
|
||||
|
||||
# Open settings
|
||||
self.open_tor_settings.emit()
|
||||
|
||||
QtCore.QTimer.singleShot(1, alert)
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel)
|
||||
|
||||
|
||||
class TorConnectionWidget(QtWidgets.QWidget):
|
||||
|
@ -165,6 +49,7 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
|||
open_tor_settings = QtCore.Signal()
|
||||
success = QtCore.Signal()
|
||||
fail = QtCore.Signal(str)
|
||||
update_progress = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, common, status_bar):
|
||||
super(TorConnectionWidget, self).__init__(None)
|
||||
|
@ -186,14 +71,10 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
|||
progress_layout.addWidget(self.progress)
|
||||
progress_layout.addWidget(self.cancel_button)
|
||||
|
||||
inner_layout = QtWidgets.QVBoxLayout()
|
||||
inner_layout.addWidget(self.label)
|
||||
inner_layout.addLayout(progress_layout)
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.label)
|
||||
layout.addLayout(progress_layout)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.addStretch()
|
||||
layout.addLayout(inner_layout)
|
||||
layout.addStretch()
|
||||
self.setLayout(layout)
|
||||
|
||||
# Start displaying the status at 0
|
||||
|
@ -233,12 +114,14 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
|||
def cancel_clicked(self):
|
||||
self.was_canceled = True
|
||||
self.fail.emit("")
|
||||
self._reset()
|
||||
|
||||
def wasCanceled(self):
|
||||
return self.was_canceled
|
||||
|
||||
def _tor_status_update(self, progress, summary):
|
||||
self.progress.setValue(int(progress))
|
||||
self.update_progress.emit(int(progress))
|
||||
self.label.setText(
|
||||
f"<strong>{strings._('connecting_to_tor')}</strong><br>{summary}"
|
||||
)
|
||||
|
@ -250,8 +133,10 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
|||
|
||||
# Close the dialog after connecting
|
||||
self.progress.setValue(self.progress.maximum())
|
||||
self.update_progress.emit(int(self.progress.maximum()))
|
||||
|
||||
self.success.emit()
|
||||
self._reset()
|
||||
|
||||
def _canceled_connecting_to_tor(self):
|
||||
self.common.log("TorConnectionWidget", "_canceled_connecting_to_tor")
|
||||
|
@ -260,11 +145,18 @@ class TorConnectionWidget(QtWidgets.QWidget):
|
|||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel_clicked)
|
||||
self._reset()
|
||||
|
||||
def _error_connecting_to_tor(self, msg):
|
||||
self.common.log("TorConnectionWidget", "_error_connecting_to_tor")
|
||||
self.active = False
|
||||
self.fail.emit(msg)
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.label.setText("")
|
||||
self.progress.setValue(0)
|
||||
self.update_progress.emit(0)
|
||||
|
||||
|
||||
class TorConnectionThread(QtCore.QThread):
|
||||
|
|
|
@ -21,7 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
from PySide2 import QtCore, QtWidgets, QtGui
|
||||
import sys
|
||||
import platform
|
||||
import re
|
||||
import os
|
||||
|
||||
from onionshare_cli.meek import Meek
|
||||
|
@ -43,7 +42,15 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
tor_is_connected = QtCore.Signal()
|
||||
tor_is_disconnected = QtCore.Signal()
|
||||
|
||||
def __init__(self, common, tab_id, are_tabs_active, status_bar):
|
||||
def __init__(
|
||||
self,
|
||||
common,
|
||||
tab_id,
|
||||
are_tabs_active,
|
||||
status_bar,
|
||||
from_autoconnect=False,
|
||||
parent=None,
|
||||
):
|
||||
super(TorSettingsTab, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
@ -54,6 +61,8 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
|
||||
self.system = platform.system()
|
||||
self.tab_id = tab_id
|
||||
self.parent = parent
|
||||
self.from_autoconnect = from_autoconnect
|
||||
|
||||
# Connection type: either automatic, control port, or socket file
|
||||
|
||||
|
@ -303,6 +312,21 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
)
|
||||
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
|
||||
|
||||
# Quickstart settings
|
||||
self.autoconnect_checkbox = QtWidgets.QCheckBox(
|
||||
strings._("gui_enable_autoconnect_checkbox")
|
||||
)
|
||||
self.autoconnect_checkbox.toggled.connect(self.autoconnect_toggled)
|
||||
left_column_settings = QtWidgets.QVBoxLayout()
|
||||
connection_type_radio_group.setFixedHeight(300)
|
||||
left_column_settings.addWidget(connection_type_radio_group)
|
||||
left_column_settings.addSpacing(20)
|
||||
left_column_settings.addWidget(self.autoconnect_checkbox)
|
||||
left_column_settings.addStretch()
|
||||
left_column_settings.setContentsMargins(0, 0, 0, 0)
|
||||
left_column_setting_widget = QtWidgets.QWidget()
|
||||
left_column_setting_widget.setLayout(left_column_settings)
|
||||
|
||||
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
|
||||
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
|
||||
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
|
||||
|
@ -322,7 +346,7 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
|
||||
# Settings are in columns
|
||||
columns_layout = QtWidgets.QHBoxLayout()
|
||||
columns_layout.addWidget(connection_type_radio_group)
|
||||
columns_layout.addWidget(left_column_setting_widget)
|
||||
columns_layout.addSpacing(20)
|
||||
columns_layout.addLayout(connection_type_layout, stretch=1)
|
||||
columns_wrapper = QtWidgets.QWidget()
|
||||
|
@ -391,6 +415,10 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
self.old_settings = Settings(self.common)
|
||||
self.old_settings.load()
|
||||
|
||||
# Check if autoconnect was enabled
|
||||
if self.old_settings.get("auto_connect"):
|
||||
self.autoconnect_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
connection_type = self.old_settings.get("connection_type")
|
||||
if connection_type == "bundled":
|
||||
if self.connection_type_bundled_radio.isEnabled():
|
||||
|
@ -477,6 +505,12 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.bridge_settings.hide()
|
||||
|
||||
def autoconnect_toggled(self):
|
||||
"""
|
||||
Auto connect checkbox clicked
|
||||
"""
|
||||
self.common.log("TorSettingsTab", "autoconnect_checkbox_clicked")
|
||||
|
||||
def active_tabs_changed(self, are_tabs_active):
|
||||
if are_tabs_active:
|
||||
self.main_widget.hide()
|
||||
|
@ -664,7 +698,9 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
|
||||
# the Onion object
|
||||
reboot_onion = False
|
||||
if not self.common.gui.local_only:
|
||||
if not self.common.gui.local_only and not (
|
||||
self.from_autoconnect and not settings.get("auto_connect")
|
||||
):
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.common.log(
|
||||
"TorSettingsTab", "save_clicked", "Connected to Tor"
|
||||
|
@ -717,9 +753,9 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
self.tor_con.show()
|
||||
self.tor_con.start(settings)
|
||||
else:
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
else:
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
def tor_con_success(self):
|
||||
"""
|
||||
|
@ -750,7 +786,7 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
# Tell the tabs that Tor is connected
|
||||
self.tor_is_connected.emit()
|
||||
# Close the tab
|
||||
self.close_this_tab.emit()
|
||||
self.parent.close_this_tab.emit()
|
||||
|
||||
self.tor_con_type = None
|
||||
|
||||
|
@ -777,6 +813,9 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
settings = Settings(self.common)
|
||||
settings.load() # To get the last update timestamp
|
||||
|
||||
# autoconnect
|
||||
settings.set("auto_connect", self.autoconnect_checkbox.isChecked())
|
||||
|
||||
# Tor connection
|
||||
if self.connection_type_bundled_radio.isChecked():
|
||||
settings.set("connection_type", "bundled")
|
||||
|
@ -835,35 +874,10 @@ class TorSettingsTab(QtWidgets.QWidget):
|
|||
if self.bridge_custom_radio.isChecked():
|
||||
settings.set("bridges_type", "custom")
|
||||
|
||||
new_bridges = []
|
||||
bridges = self.bridge_custom_textbox.toPlainText().split("\n")
|
||||
bridges_valid = False
|
||||
for bridge in bridges:
|
||||
if bridge != "":
|
||||
# Check the syntax of the custom bridge to make sure it looks legitimate
|
||||
ipv4_pattern = re.compile(
|
||||
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
|
||||
)
|
||||
ipv6_pattern = re.compile(
|
||||
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
|
||||
)
|
||||
meek_lite_pattern = re.compile(
|
||||
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
|
||||
)
|
||||
snowflake_pattern = re.compile(
|
||||
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
|
||||
)
|
||||
if (
|
||||
ipv4_pattern.match(bridge)
|
||||
or ipv6_pattern.match(bridge)
|
||||
or meek_lite_pattern.match(bridge)
|
||||
or snowflake_pattern.match(bridge)
|
||||
):
|
||||
new_bridges.append(bridge)
|
||||
bridges_valid = True
|
||||
|
||||
bridges_valid = self.common.check_bridges_valid(bridges)
|
||||
if bridges_valid:
|
||||
new_bridges = "\n".join(new_bridges) + "\n"
|
||||
new_bridges = "\n".join(bridges_valid) + "\n"
|
||||
settings.set("bridges_custom", new_bridges)
|
||||
else:
|
||||
self.error_label.setText(
|
||||
|
|
42
desktop/scripts/countries-update-list.py
Executable file
42
desktop/scripts/countries-update-list.py
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
import onionshare_cli
|
||||
|
||||
|
||||
def main():
|
||||
# Clone the country-list repo
|
||||
tmp_dir = tempfile.TemporaryDirectory()
|
||||
subprocess.run(
|
||||
["git", "clone", "https://github.com/umpirsky/country-list.git"],
|
||||
cwd=tmp_dir.name,
|
||||
)
|
||||
repo_dir = os.path.join(tmp_dir.name, "country-list")
|
||||
|
||||
# Get the list of enabled languages
|
||||
common = onionshare_cli.common.Common()
|
||||
settings = onionshare_cli.settings.Settings(common)
|
||||
available_locales = list(settings.available_locales)
|
||||
|
||||
# Make a dictionary that makes a language's ISO 3166-1 to its name in all enabled languages
|
||||
os.makedirs(os.path.join("onionshare", "resources", "countries"), exist_ok=True)
|
||||
for locale in available_locales:
|
||||
with open(os.path.join(repo_dir, "data", locale, "country.json")) as f:
|
||||
countries = json.loads(f.read())
|
||||
|
||||
# Remove countries we don't have images for
|
||||
for key in ["JE", "MH", "FM", "MP", "PS", "TV", "UM"]:
|
||||
del countries[key]
|
||||
|
||||
with open(
|
||||
os.path.join("onionshare", "resources", "countries", f"{locale}.json"),
|
||||
"w",
|
||||
) as f:
|
||||
f.write(json.dumps(countries))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -35,10 +35,10 @@ from bridges import UpdateTorBridges
|
|||
|
||||
|
||||
def main():
|
||||
tarball_url = "https://dist.torproject.org/torbrowser/11.0.4/tor-browser-linux64-11.0.4_en-US.tar.xz"
|
||||
tarball_filename = "tor-browser-linux64-11.0.4_en-US.tar.xz"
|
||||
tarball_url = "https://dist.torproject.org/torbrowser/11.0.9/tor-browser-linux64-11.0.9_en-US.tar.xz"
|
||||
tarball_filename = "tor-browser-linux64-11.0.9_en-US.tar.xz"
|
||||
expected_tarball_sha256 = (
|
||||
"05a5fd6d633ca84c33bbd3e2f8ffca2d2fa2105032a430b07d3c0cf062d9e15f"
|
||||
"baa5ccafb5c68f1c46f9ae983b9b0a0419f66d41e0483ba5aacb3462fa0a8032"
|
||||
)
|
||||
|
||||
# Build paths
|
||||
|
|
|
@ -36,10 +36,10 @@ from bridges import UpdateTorBridges
|
|||
|
||||
|
||||
def main():
|
||||
dmg_url = "https://dist.torproject.org/torbrowser/11.0.4/TorBrowser-11.0.4-osx64_en-US.dmg"
|
||||
dmg_filename = "TorBrowser-11.0.4-osx64_en-US.dmg"
|
||||
dmg_url = "https://dist.torproject.org/torbrowser/11.0.9/TorBrowser-11.0.9-osx64_en-US.dmg"
|
||||
dmg_filename = "TorBrowser-11.0.9-osx64_en-US.dmg"
|
||||
expected_dmg_sha256 = (
|
||||
"309a67c8a82ae266756d7cf5ea00e94d9242e59d55eaff97dcd6201da3c8449c"
|
||||
"e34629a178a92983924a5a89c7a988285d2d27f21832413a7f7e33af7871c8d6"
|
||||
)
|
||||
|
||||
# Build paths
|
||||
|
|
|
@ -35,10 +35,10 @@ from bridges import UpdateTorBridges
|
|||
|
||||
|
||||
def main():
|
||||
exe_url = "https://dist.torproject.org/torbrowser/11.0.4/torbrowser-install-11.0.4_en-US.exe"
|
||||
exe_filename = "torbrowser-install-11.0.4_en-US.exe"
|
||||
exe_url = "https://dist.torproject.org/torbrowser/11.0.9/torbrowser-install-11.0.9_en-US.exe"
|
||||
exe_filename = "torbrowser-install-11.0.9_en-US.exe"
|
||||
expected_exe_sha256 = (
|
||||
"c7073f58f49a225bcf7668a5630e94f5f5e96fb7bed095feebf3bf8417bd3d07"
|
||||
"e938433028b6ffb5d312db6268b19e419626b071f08209684c8e5b9f3d3df2bc"
|
||||
)
|
||||
# Build paths
|
||||
root_path = os.path.dirname(
|
||||
|
|
|
@ -6,19 +6,19 @@ Advanced Usage
|
|||
Save Tabs
|
||||
---------
|
||||
|
||||
Everything in OnionShare is temporary by default. If you close an OnionShare tab, its address no longer exists and it can't be used again.
|
||||
Sometimes you might want an OnionShare service to be persistent.
|
||||
This is useful if you want to host a website available from the same OnionShare address even if you reboot your computer.
|
||||
Everything in OnionShare is temporary by default. When OnionShare tabs are closed, addresses no longer exist and can't be used again.
|
||||
Your OnionShare service can also be persistent.
|
||||
If you host a website, persistence means it will be available on the same OnionShare address even if you reboot your computer.
|
||||
|
||||
To make any tab persistent, check the "Save this tab, and automatically open it when I open OnionShare" box before starting the server.
|
||||
When a tab is saved a purple pin icon appears to the left of its server status.
|
||||
To make any tab persistent, check the "Save this tab, and automatically open it when I open OnionShare" box before starting your server.
|
||||
A purple pin icon appears to the left of its server status to tell you the tab is saved.
|
||||
|
||||
.. image:: _static/screenshots/advanced-save-tabs.png
|
||||
|
||||
When you quit OnionShare and then open it again, your saved tabs will start opened.
|
||||
You'll have to manually start each service, but when you do they will start with the same OnionShare address and private key.
|
||||
When opening OnionShare, your saved tabs from the prior session will start opened.
|
||||
Each service then can be started manually, and will be available on the same OnionShare address and be protected by the same private key.
|
||||
|
||||
If you save a tab, a copy of that tab's onion service secret key will be stored on your computer with your OnionShare settings.
|
||||
If you save a tab, a copy of its onion service secret key is stored on your computer.
|
||||
|
||||
.. _turn_off_private_key:
|
||||
|
||||
|
@ -27,37 +27,40 @@ Turn Off Private Key
|
|||
|
||||
By default, all OnionShare services are protected with a private key, which Tor calls "client authentication".
|
||||
|
||||
When browsing to an OnionShare service in Tor Browser, Tor Browser will prompt for the private key to be entered.
|
||||
The Tor Browser will ask you to enter your private key when you load an OnionShare service.
|
||||
If you want allow the public to use your service, it's better to disable the private key altogether.
|
||||
|
||||
Sometimes you might want your OnionShare service to be accessible to the public, like if you want to set up an OnionShare receive service so the public can securely and anonymously send you files.
|
||||
In this case, it's better to disable the private key altogether.
|
||||
|
||||
To turn off the private key for any tab, check the "This is a public OnionShare service (disables private key)" box before starting the server. Then the server will be public and won't need a private key to view in Tor Browser.
|
||||
To turn off the private key for any tab, check the "This is a public OnionShare service (disables private key)" box before starting the server.
|
||||
Then the server will be public and a private key is not needed to load it in the Tor Browser.
|
||||
|
||||
.. _custom_titles:
|
||||
|
||||
Custom Titles
|
||||
-------------
|
||||
|
||||
By default, when people load an OnionShare service in Tor Browser they see the default title for the type of service. For example, the default title of a chat service is "OnionShare Chat".
|
||||
When people load OnionShare services in the Tor Browser they see the default title for each type of service.
|
||||
For example, the default title for chat services is "OnionShare Chat".
|
||||
|
||||
If you want to choose a custom title, set the "Custom title" setting before starting a server.
|
||||
If you edit the "Custom title" setting before starting a server you can change it.
|
||||
|
||||
Scheduled Times
|
||||
---------------
|
||||
|
||||
OnionShare supports scheduling exactly when a service should start and stop.
|
||||
Before starting a server, click "Show advanced settings" in its tab and then check the boxes next to either "Start onion service at scheduled time", "Stop onion service at scheduled time", or both, and set the respective desired dates and times.
|
||||
Before starting a server, click "Show advanced settings" in its tab and then check the boxes next to either
|
||||
"Start onion service at scheduled time", "Stop onion service at scheduled time", or both, and set the respective desired dates and times.
|
||||
|
||||
If you scheduled a service to start in the future, when you click the "Start sharing" button you will see a timer counting down until it starts.
|
||||
If you scheduled it to stop in the future, after it's started you will see a timer counting down to when it will stop automatically.
|
||||
Services scheduled to start in the future display a countdown timer when when the "Start sharing" button is clicked.
|
||||
Services scheduled to stop in the future display a countdown timer when started.
|
||||
|
||||
**Scheduling an OnionShare service to automatically start can be used as a dead man's switch**, where your service will be made public at a given time in the future if anything happens to you.
|
||||
**Scheduling an OnionShare service to automatically start can be used as a dead man's switch**.
|
||||
This means your service is made public at a given time in the future if you are not there to prevent it.
|
||||
If nothing happens to you, you can cancel the service before it's scheduled to start.
|
||||
|
||||
.. image:: _static/screenshots/advanced-schedule-start-timer.png
|
||||
|
||||
**Scheduling an OnionShare service to automatically stop can be useful to limit exposure**, like if you want to share secret documents while making sure they're not available on the internet for more than a few days.
|
||||
**Scheduling an OnionShare service to automatically stop limits its exposure**.
|
||||
If you want to share secret info or something that will be outdated, you can do so for selected limited time.
|
||||
|
||||
.. image:: _static/screenshots/advanced-schedule-stop-timer.png
|
||||
|
||||
|
@ -78,14 +81,14 @@ Then run it like this::
|
|||
|
||||
onionshare-cli --help
|
||||
|
||||
For information about installing it on different operating systems, see the `CLI readme file <https://github.com/onionshare/onionshare/blob/develop/cli/README.md>`_ in the git repository.
|
||||
Info about installing it on different operating systems can be found in the `CLI README file <https://github.com/onionshare/onionshare/blob/develop/cli/README.md>`_ in the Git repository.
|
||||
|
||||
If you installed OnionShare using the Linux Snapcraft package, you can also just run ``onionshare.cli`` to access the command-line interface version.
|
||||
If you installed OnionShare using the Snap package, you can also just run ``onionshare.cli`` to access the command-line interface version.
|
||||
|
||||
Usage
|
||||
^^^^^
|
||||
|
||||
You can browse the command-line documentation by running ``onionshare --help``::
|
||||
Browse the command-line documentation by running ``onionshare --help``::
|
||||
|
||||
$ onionshare-cli --help
|
||||
╭───────────────────────────────────────────╮
|
||||
|
@ -119,7 +122,7 @@ You can browse the command-line documentation by running ``onionshare --help``::
|
|||
filename List of files or folders to share
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-h, --help Show this help message and exit
|
||||
--receive Receive files
|
||||
--website Publish website
|
||||
--chat Start chat server
|
||||
|
@ -133,8 +136,8 @@ You can browse the command-line documentation by running ``onionshare --help``::
|
|||
--auto-start-timer SECONDS
|
||||
Start onion service at scheduled time (N seconds from now)
|
||||
--auto-stop-timer SECONDS
|
||||
Stop onion service at schedule time (N seconds from now)
|
||||
--no-autostop-sharing Share files: Continue sharing after files have been sent (default is to stop sharing)
|
||||
Stop onion service at scheduled time (N seconds from now)
|
||||
--no-autostop-sharing Share files: Continue sharing after files have been sent (the default is to stop sharing)
|
||||
--data-dir data_dir Receive files: Save files received to this directory
|
||||
--webhook-url webhook_url
|
||||
Receive files: URL to receive webhook notifications
|
||||
|
|
|
@ -12,13 +12,13 @@ Linux
|
|||
-----
|
||||
|
||||
There are various ways to install OnionShare for Linux, but the recommended way is to use either the `Flatpak <https://flatpak.org/>`_ or the `Snap <https://snapcraft.io/>`_ package.
|
||||
Flatpak and Snap ensure that you'll always use the newest version and run OnionShare inside of a sandbox.
|
||||
Flatpak and Snapcraft ensure that you'll always use the newest version and run OnionShare inside of a sandbox.
|
||||
|
||||
Snap support is built-in to Ubuntu and Fedora comes with Flatpak support, but which you use is up to you. Both work in all Linux distributions.
|
||||
Snapcraft support is built-in to Ubuntu and Fedora comes with Flatpak support, but which you use is up to you. Both work in all Linux distributions.
|
||||
|
||||
**Install OnionShare using Flatpak**: https://flathub.org/apps/details/org.onionshare.OnionShare
|
||||
|
||||
**Install OnionShare using Snap**: https://snapcraft.io/onionshare
|
||||
**Install OnionShare using Snapcraft**: https://snapcraft.io/onionshare
|
||||
|
||||
You can also download and install PGP-signed ``.flatpak`` or ``.snap`` packages from https://onionshare.org/dist/ if you prefer.
|
||||
|
||||
|
@ -27,7 +27,7 @@ You can also download and install PGP-signed ``.flatpak`` or ``.snap`` packages
|
|||
Command-line only
|
||||
-----------------
|
||||
|
||||
You can install just the command line version of OnionShare on any operating system using the Python package manager ``pip``. See :ref:`cli` for more information.
|
||||
You can install just the command-line version of OnionShare on any operating system using the Python package manager ``pip``. :ref:`cli` has more info.
|
||||
|
||||
.. _verifying_sigs:
|
||||
|
||||
|
@ -40,7 +40,8 @@ For Windows and macOS, this step is optional and provides defense in depth: the
|
|||
Signing key
|
||||
^^^^^^^^^^^
|
||||
|
||||
Packages are signed by Micah Lee, the core developer, using his PGP public key with fingerprint ``927F419D7EC82C2F149C1BD1403C2657CD994F73``. You can download Micah's key `from the keys.openpgp.org keyserver <https://keys.openpgp.org/vks/v1/by-fingerprint/927F419D7EC82C2F149C1BD1403C2657CD994F73>`_.
|
||||
Packages are signed by Micah Lee, the core developer, using his PGP public key with fingerprint ``927F419D7EC82C2F149C1BD1403C2657CD994F73``.
|
||||
You can download Micah's key `from the keys.openpgp.org keyserver <https://keys.openpgp.org/vks/v1/by-fingerprint/927F419D7EC82C2F149C1BD1403C2657CD994F73>`_.
|
||||
|
||||
You must have GnuPG installed to verify signatures. For macOS you probably want `GPGTools <https://gpgtools.org/>`_, and for Windows you probably want `Gpg4win <https://www.gpg4win.org/>`_.
|
||||
|
||||
|
@ -73,6 +74,6 @@ The expected output looks like this::
|
|||
gpg: There is no indication that the signature belongs to the owner.
|
||||
Primary key fingerprint: 927F 419D 7EC8 2C2F 149C 1BD1 403C 2657 CD99 4F73
|
||||
|
||||
If you don't see ``Good signature from``, there might be a problem with the integrity of the file (malicious or otherwise), and you should not install the package. (The ``WARNING:`` shown above, is not a problem with the package, it only means you haven't defined a level of "trust" of Micah's PGP key.)
|
||||
If you don't see ``Good signature from``, there might be a problem with the integrity of the file (malicious or otherwise), and you should not install the package. (The ``WARNING:`` shown above, is not a problem with the package, it only means you haven't defined a level of "trust" of Micah's (the core developer) PGP key.)
|
||||
|
||||
If you want to learn more about verifying PGP signatures, the guides for `Qubes OS <https://www.qubes-os.org/security/verifying-signatures/>`_ and the `Tor Project <https://support.torproject.org/tbb/how-to-verify-signature/>`_ may be useful.
|
||||
|
|
|
@ -8,17 +8,38 @@ Like all software, OnionShare may contain bugs or vulnerabilities.
|
|||
What OnionShare protects against
|
||||
--------------------------------
|
||||
|
||||
**Third parties don't have access to anything that happens in OnionShare.** Using OnionShare means hosting services directly on your computer. When sharing files with OnionShare, they are not uploaded to any server. If you make an OnionShare chat room, your computer acts as a server for that too. This avoids the traditional model of having to trust the computers of others.
|
||||
**Third parties don't have access to anything that happens in OnionShare.**
|
||||
Using OnionShare means hosting services directly on your computer.
|
||||
When sharing your files with OnionShare, they are not uploaded to any third-party server.
|
||||
If you make an OnionShare chat room, your computer acts as a server for that too.
|
||||
This avoids the traditional model of having to trust the computers of others.
|
||||
|
||||
**Network eavesdroppers can't spy on anything that happens in OnionShare in transit.** The connection between the Tor onion service and Tor Browser is end-to-end encrypted. This means network attackers can't eavesdrop on anything except encrypted Tor traffic. Even if an eavesdropper is a malicious rendezvous node used to connect the Tor Browser with OnionShare's onion service, the traffic is encrypted using the onion service's private key.
|
||||
**Network eavesdroppers can't spy on anything that happens in OnionShare in transit.**
|
||||
The connection between the Tor onion service and Tor Browser is end-to-end encrypted.
|
||||
This means network attackers can't eavesdrop on anything except encrypted Tor traffic.
|
||||
Even if an eavesdropper is a malicious rendezvous node used to connect the Tor Browser with OnionShare's onion service,
|
||||
the traffic is encrypted using the onion service's private key.
|
||||
|
||||
**Anonymity of OnionShare users are protected by Tor.** OnionShare and Tor Browser protect the anonymity of the users. As long as the OnionShare user anonymously communicates the OnionShare address with the Tor Browser users, the Tor Browser users and eavesdroppers can't learn the identity of the OnionShare user.
|
||||
**Anonymity of OnionShare users are protected by Tor.**
|
||||
OnionShare and Tor Browser protect the anonymity of the users.
|
||||
As long as the OnionShare user anonymously communicates the OnionShare address with the Tor Browser users,
|
||||
the Tor Browser users and eavesdroppers can't learn the identity of the OnionShare user.
|
||||
|
||||
**If an attacker learns about the onion service, it still can't access anything.** Prior attacks against the Tor network to enumerate onion services allowed the attacker to discover private ``.onion`` addresses. If an attack discovers a private OnionShare address, they will also need to guess the private key used for client authentication in order to access it (unless the OnionShare user chooses make their service public by turning off the private key -- see :ref:`turn_off_private_key`).
|
||||
**If an attacker learns about the onion service, it still can't access anything.**
|
||||
Prior attacks against the Tor network to enumerate onion services allowed attackers to discover private ``.onion`` addresses.
|
||||
To access an OnionShare service from its address, the private key used for client authentication must be guessed (unless the service is already made public by turning off the private key -- see :ref:`turn_off_private_key`).
|
||||
|
||||
What OnionShare doesn't protect against
|
||||
---------------------------------------
|
||||
|
||||
**Communicating the OnionShare address and private key might not be secure.** Communicating the OnionShare address to people is the responsibility of the OnionShare user. If sent insecurely (such as through an email message monitored by an attacker), an eavesdropper can tell that OnionShare is being used. If the eavesdropper loads the address in Tor Browser while the service is still up, they can access it. To avoid this, the address must be communicated securely, via encrypted text message (probably with disappearing messages enabled), encrypted email, or in person. This isn't necessary when using OnionShare for something that isn't secret.
|
||||
**Communicating the OnionShare address and private key might not be secure.**
|
||||
Communicating the OnionShare address to people is the responsibility of the OnionShare user.
|
||||
If sent insecurely (such as through an e-mail message monitored by an attacker), an eavesdropper can tell that OnionShare is being used.
|
||||
Eavesdroppers can access services that are still up by loading their addresses and/or lost key in the Tor Browser.
|
||||
Avoid this by communicating the address securely, via encrypted text message (probably with disappearing messages enabled), encrypted e-mail, or in person.
|
||||
This isn't necessary when using OnionShare for something that isn't secret.
|
||||
|
||||
**Communicating the OnionShare address and private key might not be anonymous.** Extra precautions must be taken to ensure the OnionShare address is communicated anonymously. A new email or chat account, only accessed over Tor, can be used to share the address. This isn't necessary unless anonymity is a goal.
|
||||
**Communicating the OnionShare address and private key might not be anonymous.**
|
||||
Extra precaution must be taken to ensure the OnionShare address is communicated anonymously.
|
||||
A new e-mail or chat account, only accessed over Tor, can be used to share the address.
|
||||
This isn't necessary unless anonymity is a goal.
|
||||
|
|
Loading…
Reference in a new issue