Merge branch 'censorship' into develop

This commit is contained in:
Micah Lee 2022-03-06 15:02:28 -08:00
commit d59328d99c
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
41 changed files with 1340 additions and 354 deletions

View File

@ -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:

View File

@ -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

View File

@ -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):
"""

View File

@ -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,

View File

@ -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

View 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()

View File

@ -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()

View File

@ -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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"}

File diff suppressed because one or more lines are too long

View 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"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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: {}",
@ -233,4 +251,4 @@
"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"
}
}

View 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"])

View File

@ -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):
"""

View File

@ -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):

View File

@ -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):

View File

@ -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(

View 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()