From 04a5f8a286a1214c0e84f6c9564348570c7f4e71 Mon Sep 17 00:00:00 2001 From: x80486 Date: Wed, 29 May 2019 20:48:48 -0400 Subject: [PATCH 01/64] Update/Tweak AppStream Metadata XML and Desktop Entry according to the latest standard(s) --- MANIFEST.in | 5 +-- ... => org.onionshare.OnionShare.appdata.xml} | 33 +++++++++++-------- ...ktop => org.onionshare.OnionShare.desktop} | 2 +- setup.py | 5 +-- 4 files changed, 27 insertions(+), 18 deletions(-) rename install/{onionshare.appdata.xml => org.onionshare.OnionShare.appdata.xml} (53%) rename install/{onionshare.desktop => org.onionshare.OnionShare.desktop} (95%) diff --git a/MANIFEST.in b/MANIFEST.in index 71af3740..6861423d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,12 +2,13 @@ include LICENSE include README.md include BUILD.md include share/* +include share/icons/hicolor/scalable/apps/org.onionshare.OnionShare.svg include share/images/* include share/locale/* include share/templates/* include share/static/* -include install/onionshare.desktop -include install/onionshare.appdata.xml +include install/org.onionshare.OnionShare.desktop +include install/org.onionshare.OnionShare.appdata.xml include install/onionshare80.xpm include install/scripts/onionshare-nautilus.py include tests/*.py diff --git a/install/onionshare.appdata.xml b/install/org.onionshare.OnionShare.appdata.xml similarity index 53% rename from install/onionshare.appdata.xml rename to install/org.onionshare.OnionShare.appdata.xml index 2302a2e8..6ae1b5b6 100644 --- a/install/onionshare.appdata.xml +++ b/install/org.onionshare.OnionShare.appdata.xml @@ -1,27 +1,27 @@ - - onionshare.desktop + + org.onionshare.OnionShare CC0-1.0 GPL-3.0 OnionShare Securely and anonymously share a file of any size

- OnionShare lets you securely and anonymously send and receive files. It works by starting a - web server, making it accessible as a Tor onion service, and generating an unguessable web - address so others can download files from you, or upload files to you. It does not - require setting up a separate server or using a third party file-sharing service. + OnionShare lets you securely and anonymously send and receive files. It works by starting a web server, + making it accessible as a Tor onion service, and generating an unguessable web address so others can + download files from you, or upload files to you. It does not require setting up a separate server + or using a third party file-sharing service.

- If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor - onion service to make them temporarily accessible over the internet. The receiving user just - needs to open the web address in Tor Browser to download the files. If you want to receive files, - OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to - make it temporarily accessible over the internet. Other users can upload files to you from by - loading the web address in Tor Browser. + If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor onion + service to make them temporarily accessible over the internet. The receiving user just needs to open the web + address in Tor Browser to download the files. If you want to receive files, OnionShare hosts an anonymous + dropbox directly on your computer and uses a Tor onion service to make it temporarily accessible over the + internet. Other users can upload files to you from by loading the web address in Tor Browser.

+ org.onionshare.OnionShare.desktop https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-server.png @@ -40,6 +40,13 @@ Uploading files to OnionShare user using Tor Browser + https://github.com/micahflee/onionshare/issues/ + https://github.com/micahflee/onionshare/wiki/ https://onionshare.org/ - micah@micahflee.com + Micah Lee + micah@micahflee.com + + + +
diff --git a/install/onionshare.desktop b/install/org.onionshare.OnionShare.desktop similarity index 95% rename from install/onionshare.desktop rename to install/org.onionshare.OnionShare.desktop index 697668db..536d73c6 100644 --- a/install/onionshare.desktop +++ b/install/org.onionshare.OnionShare.desktop @@ -7,7 +7,7 @@ Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk Exec=/usr/bin/onionshare-gui Terminal=false Type=Application -Icon=onionshare80 +Icon=org.onionshare.OnionShare Categories=Network;FileTransfer; Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting; Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting; diff --git a/setup.py b/setup.py index 347ff366..fa2294a9 100644 --- a/setup.py +++ b/setup.py @@ -63,8 +63,9 @@ classifiers = [ "Environment :: Web Environment" ] data_files=[ - (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']), - (os.path.join(sys.prefix, 'share/metainfo'), ['install/onionshare.appdata.xml']), + (os.path.join(sys.prefix, 'share/applications'), ['install/org.onionshare.OnionShare.desktop']), + (os.path.join(sys.prefix, 'share/icons/hicolor/scalable/apps'), ['install/org.onionshare.OnionShare.svg']), + (os.path.join(sys.prefix, 'share/metainfo'), ['install/org.onionshare.OnionShare.appdata.xml']), (os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']), (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), From dd8e9d37ae051b15d3cd6414afed3ff7ad1796c1 Mon Sep 17 00:00:00 2001 From: wh1t3fang <35537694+wh1t3fang@users.noreply.github.com> Date: Wed, 12 Jun 2019 05:10:24 +0000 Subject: [PATCH 02/64] Added python-flask-httpauth depend added flask httpauth depend to the install instructions for debian. Otherwise, this exception appears. ImportError: No module named 'flask_httpauth' --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index e3f3fec7..81eb504e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,7 +14,7 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python +apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python-flask-httpauth ``` For Fedora-like distros: From 30dc17df27d4d79fed6ee8be552ad2a1da72edd1 Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 5 Jun 2019 13:47:41 +0200 Subject: [PATCH 03/64] Start code sharing between WebsiteMode and ShareMode --- onionshare/web/base_mode.py | 45 ++++++++++++++++++++++++++++++++++ onionshare/web/share_mode.py | 27 +++----------------- onionshare/web/website_mode.py | 16 +++--------- 3 files changed, 52 insertions(+), 36 deletions(-) create mode 100644 onionshare/web/base_mode.py diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py new file mode 100644 index 00000000..fb1043d7 --- /dev/null +++ b/onionshare/web/base_mode.py @@ -0,0 +1,45 @@ +import os +import sys +import tempfile +import mimetypes +from flask import Response, request, render_template, make_response + +from .. import strings + +class BaseModeWeb(object): + """ + All of the web logic shared between share and website mode + """ + def __init__(self, common, web): + super(BaseModeWeb, self).__init__() + self.common = common + self.web = web + + # Information about the file to be shared + self.file_info = [] + self.is_zipped = False + self.download_filename = None + self.download_filesize = None + self.gzip_filename = None + self.gzip_filesize = None + self.zip_writer = None + + # Dictionary mapping file paths to filenames on disk + self.files = {} + + self.visit_count = 0 + self.download_count = 0 + + # If "Stop After First Download" is checked (stay_open == False), only allow + # one download at a time. + self.download_in_progress = False + + # Reset assets path + self.web.app.static_folder=self.common.get_resource_path('static') + + + def init(self): + """ + Add custom initialization here. + """ + pass diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 0dfa7e0a..779d0a4b 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -6,38 +6,17 @@ import mimetypes import gzip from flask import Response, request, render_template, make_response +from .base_mode import BaseModeWeb from .. import strings -class ShareModeWeb(object): +class ShareModeWeb(BaseModeWeb): """ All of the web logic for share mode """ - def __init__(self, common, web): - self.common = common + def init(self): self.common.log('ShareModeWeb', '__init__') - self.web = web - - # Information about the file to be shared - self.file_info = [] - self.is_zipped = False - self.download_filename = None - self.download_filesize = None - self.gzip_filename = None - self.gzip_filesize = None - self.zip_writer = None - - self.download_count = 0 - - # If "Stop After First Download" is checked (stay_open == False), only allow - # one download at a time. - self.download_in_progress = False - - # Reset assets path - self.web.app.static_folder=self.common.get_resource_path('static') - - self.define_routes() def define_routes(self): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index d2cd6db9..f61da569 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -4,26 +4,18 @@ import tempfile import mimetypes from flask import Response, request, render_template, make_response, send_from_directory +from .base_mode import BaseModeWeb from .. import strings -class WebsiteModeWeb(object): +class WebsiteModeWeb(BaseModeWeb): """ All of the web logic for share mode """ - def __init__(self, common, web): - self.common = common + def init(self): + self.common.log('WebsiteModeWeb', '__init__') - self.web = web - - # Dictionary mapping file paths to filenames on disk - self.files = {} - self.visit_count = 0 - - # Reset assets path - self.web.app.static_folder=self.common.get_resource_path('static') - self.define_routes() def define_routes(self): From 4551716a9edd4f105c136bd174d7e814a1697e99 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 12:33:34 +0200 Subject: [PATCH 04/64] Refactor set_file_list between website and share mode --- onionshare/web/base_mode.py | 31 ++++++++++++++++++++++++++++--- onionshare/web/share_mode.py | 15 ++------------- onionshare/web/website_mode.py | 20 ++++++-------------- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py index fb1043d7..8843d198 100644 --- a/onionshare/web/base_mode.py +++ b/onionshare/web/base_mode.py @@ -26,7 +26,9 @@ class BaseModeWeb(object): # Dictionary mapping file paths to filenames on disk self.files = {} - + self.cleanup_filenames = [] + self.file_info = {'files': [], 'dirs': []} + self.visit_count = 0 self.download_count = 0 @@ -34,8 +36,7 @@ class BaseModeWeb(object): # one download at a time. self.download_in_progress = False - # Reset assets path - self.web.app.static_folder=self.common.get_resource_path('static') + self.define_routes() def init(self): @@ -43,3 +44,27 @@ class BaseModeWeb(object): Add custom initialization here. """ pass + + def set_file_info(self, filenames, processed_size_callback=None): + """ + Build a data structure that describes the list of files + """ + if self.web.mode == 'website': + self.common.log("WebsiteModeWeb", "set_file_info") + self.web.cancel_compression = True + + # This is only the root files and dirs, as opposed to all of them + self.root_files = {} + + # If there's just one folder, replace filenames with a list of files inside that folder + if len(filenames) == 1 and os.path.isdir(filenames[0]): + filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + + self.build_file_list(filenames) + + elif self.web.mode == 'share': + self.common.log("ShareModeWeb", "set_file_info") + self.web.cancel_compression = False + self.build_zipfile_list(filenames, processed_size_callback) + + return True diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 779d0a4b..68763357 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -177,19 +177,8 @@ class ShareModeWeb(BaseModeWeb): r.headers.set('Content-Type', content_type) return r - def set_file_info(self, filenames, processed_size_callback=None): - """ - Using the list of filenames being shared, fill in details that the web - page will need to display. This includes zipping up the file in order to - get the zip file's name and size. - """ - self.common.log("ShareModeWeb", "set_file_info") - self.web.cancel_compression = False - - self.cleanup_filenames = [] - - # build file info list - self.file_info = {'files': [], 'dirs': []} + def build_zipfile_list(self, filenames, processed_size_callback=None): + self.common.log("ShareModeWeb", "build_file_list") for filename in filenames: info = { 'filename': filename, diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index f61da569..4c024849 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -10,12 +10,14 @@ from .. import strings class WebsiteModeWeb(BaseModeWeb): """ - All of the web logic for share mode + All of the web logic for website mode """ def init(self): - self.common.log('WebsiteModeWeb', '__init__') + # Reset assets path + self.web.app.static_folder=self.common.get_resource_path('share/static') + self.define_routes() def define_routes(self): @@ -127,22 +129,12 @@ class WebsiteModeWeb(BaseModeWeb): static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) - def set_file_info(self, filenames): + def build_file_list(self, filenames): """ Build a data structure that describes the list of files that make up the static website. """ - self.common.log("WebsiteModeWeb", "set_file_info") - - # This is a dictionary that maps HTTP routes to filenames on disk - self.files = {} - - # This is only the root files and dirs, as opposed to all of them - self.root_files = {} - - # If there's just one folder, replace filenames with a list of files inside that folder - if len(filenames) == 1 and os.path.isdir(filenames[0]): - filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + self.common.log("WebsiteModeWeb", "build_file_list") # Loop through the files for filename in filenames: From 324c6579c24372b617cbf9549459025d305e6112 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 12:41:12 +0200 Subject: [PATCH 05/64] Move directory_listing function --- onionshare/web/base_mode.py | 36 +++++++++++++++++++++++++++++++++- onionshare/web/website_mode.py | 33 +------------------------------ 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py index 8843d198..46e63e1a 100644 --- a/onionshare/web/base_mode.py +++ b/onionshare/web/base_mode.py @@ -4,7 +4,7 @@ import tempfile import mimetypes from flask import Response, request, render_template, make_response -from .. import strings + from .. import strings class BaseModeWeb(object): """ @@ -45,6 +45,40 @@ class BaseModeWeb(object): """ pass + + def directory_listing(self, path, filenames, filesystem_path=None): + # If filesystem_path is None, this is the root directory listing + files = [] + dirs = [] + + for filename in filenames: + if filesystem_path: + this_filesystem_path = os.path.join(filesystem_path, filename) + else: + this_filesystem_path = self.files[filename] + + is_dir = os.path.isdir(this_filesystem_path) + + if is_dir: + dirs.append({ + 'basename': filename + }) + else: + size = os.path.getsize(this_filesystem_path) + size_human = self.common.human_readable_filesize(size) + files.append({ + 'basename': filename, + 'size_human': size_human + }) + + r = make_response(render_template('listing.html', + path=path, + files=files, + dirs=dirs, + static_url_path=self.web.static_url_path)) + return self.web.add_security_headers(r) + + def set_file_info(self, filenames, processed_size_callback=None): """ Build a data structure that describes the list of files diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 4c024849..287acbd9 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -97,38 +97,7 @@ class WebsiteModeWeb(BaseModeWeb): # If the path isn't found, throw a 404 return self.web.error404() - def directory_listing(self, path, filenames, filesystem_path=None): - # If filesystem_path is None, this is the root directory listing - files = [] - dirs = [] - - for filename in filenames: - if filesystem_path: - this_filesystem_path = os.path.join(filesystem_path, filename) - else: - this_filesystem_path = self.files[filename] - - is_dir = os.path.isdir(this_filesystem_path) - - if is_dir: - dirs.append({ - 'basename': filename - }) - else: - size = os.path.getsize(this_filesystem_path) - size_human = self.common.human_readable_filesize(size) - files.append({ - 'basename': filename, - 'size_human': size_human - }) - - r = make_response(render_template('listing.html', - path=path, - files=files, - dirs=dirs, - static_url_path=self.web.static_url_path)) - return self.web.add_security_headers(r) - + def build_file_list(self, filenames): """ Build a data structure that describes the list of files that make up From e6f114c677775f01bc95f7496eea4aecb59c9d64 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 21:47:49 +0200 Subject: [PATCH 06/64] Refactor directory_listing function --- onionshare/web/base_mode.py | 43 +++++++++++++++------------------- onionshare/web/share_mode.py | 11 ++------- onionshare/web/website_mode.py | 31 ++++++++++++++++++++---- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py index 46e63e1a..ff7e11be 100644 --- a/onionshare/web/base_mode.py +++ b/onionshare/web/base_mode.py @@ -4,7 +4,7 @@ import tempfile import mimetypes from flask import Response, request, render_template, make_response - from .. import strings +from .. import strings class BaseModeWeb(object): """ @@ -46,36 +46,31 @@ class BaseModeWeb(object): pass - def directory_listing(self, path, filenames, filesystem_path=None): + def directory_listing(self, path='', filenames=[], filesystem_path=None): # If filesystem_path is None, this is the root directory listing files = [] dirs = [] + r = '' - for filename in filenames: - if filesystem_path: - this_filesystem_path = os.path.join(filesystem_path, filename) - else: - this_filesystem_path = self.files[filename] + if self.web.mode == 'website': + files, dirs = build_directory_listing(filenames) - is_dir = os.path.isdir(this_filesystem_path) + r = make_response(render_template('listing.html', + path=path, + files=files, + dirs=dirs, + static_url_path=self.web.static_url_path)) - if is_dir: - dirs.append({ - 'basename': filename - }) - else: - size = os.path.getsize(this_filesystem_path) - size_human = self.common.human_readable_filesize(size) - files.append({ - 'basename': filename, - 'size_human': size_human - }) + elif self.web.mode == 'share': + r = make_response(render_template( + 'send.html', + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped, + static_url_path=self.web.static_url_path)) - r = make_response(render_template('listing.html', - path=path, - files=files, - dirs=dirs, - static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 68763357..cb3bba50 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -44,15 +44,8 @@ class ShareModeWeb(BaseModeWeb): else: self.filesize = self.download_filesize - r = make_response(render_template( - 'send.html', - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped, - static_url_path=self.web.static_url_path)) - return self.web.add_security_headers(r) + return self.directory_listing() + @self.web.app.route("/download") def download(): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 287acbd9..5183bebc 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -14,12 +14,9 @@ class WebsiteModeWeb(BaseModeWeb): """ def init(self): self.common.log('WebsiteModeWeb', '__init__') - - # Reset assets path - self.web.app.static_folder=self.common.get_resource_path('share/static') - self.define_routes() + def define_routes(self): """ The web app routes for sharing a website @@ -56,6 +53,7 @@ class WebsiteModeWeb(BaseModeWeb): # Render it dirname = os.path.dirname(self.files[index_path]) basename = os.path.basename(self.files[index_path]) + return send_from_directory(dirname, basename) else: @@ -80,6 +78,7 @@ class WebsiteModeWeb(BaseModeWeb): return self.web.error404() else: # Special case loading / + if path == '': index_path = 'index.html' if index_path in self.files: @@ -97,7 +96,29 @@ class WebsiteModeWeb(BaseModeWeb): # If the path isn't found, throw a 404 return self.web.error404() - + def build_directory_listing(self, filenames): + for filename in filenames: + if filesystem_path: + this_filesystem_path = os.path.join(filesystem_path, filename) + else: + this_filesystem_path = self.files[filename] + + is_dir = os.path.isdir(this_filesystem_path) + + if is_dir: + dirs.append({ + 'basename': filename + }) + else: + size = os.path.getsize(this_filesystem_path) + size_human = self.common.human_readable_filesize(size) + files.append({ + 'basename': filename, + 'size_human': size_human + }) + return files, dirs + + def build_file_list(self, filenames): """ Build a data structure that describes the list of files that make up From 347b25d5a06576f5e2a4107441579e6aa78a0483 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 22:56:48 +0200 Subject: [PATCH 07/64] Revert "Generate a new static_url_path each time the server is stopped and started again" This change creates problems with how website mode renders assets. This reverts commit ae110026e72bc7bd38aa515f52fb52aa3236e8b1. --- onionshare/web/web.py | 18 +++++------------- onionshare_gui/threads.py | 3 --- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 1d2a3fec..17dd8c15 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -51,12 +51,16 @@ class Web(object): self.common = common self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode)) + # The static URL path has a 128-bit random number in it to avoid having name + # collisions with files that might be getting shared + self.static_url_path = '/static_{}'.format(self.common.random_string(16)) + # The flask app self.app = Flask(__name__, + static_url_path=self.static_url_path, static_folder=self.common.get_resource_path('static'), template_folder=self.common.get_resource_path('templates')) self.app.secret_key = self.common.random_string(8) - self.generate_static_url_path() self.auth = HTTPBasicAuth() self.auth.error_handler(self.error401) @@ -225,18 +229,6 @@ class Web(object): self.password = self.common.build_password() self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password)) - def generate_static_url_path(self): - # The static URL path has a 128-bit random number in it to avoid having name - # collisions with files that might be getting shared - self.static_url_path = '/static_{}'.format(self.common.random_string(16)) - self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path)) - - # Update the flask route to handle the new static URL path - self.app.static_url_path = self.static_url_path - self.app.add_url_rule( - self.static_url_path + '/', - endpoint='static', view_func=self.app.send_static_file) - def verbose_mode(self): """ Turn on verbose mode, which will log flask errors to a file. diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 57e0f0af..bee1b6bc 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -42,9 +42,6 @@ class OnionThread(QtCore.QThread): def run(self): self.mode.common.log('OnionThread', 'run') - # Make a new static URL path for each new share - self.mode.web.generate_static_url_path() - # Choose port and password early, because we need them to exist in advance for scheduled shares self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') if not self.mode.app.port: From 7b8a83854dac9c840924a430b45ff716119e5b56 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 22:58:33 +0200 Subject: [PATCH 08/64] Remove reset of web app path in receive mode --- onionshare/web/receive_mode.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 3f848d2f..b444deb2 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -18,9 +18,6 @@ class ReceiveModeWeb(object): self.web = web - # Reset assets path - self.web.app.static_folder=self.common.get_resource_path('static') - self.can_upload = True self.upload_count = 0 self.uploads_in_progress = [] @@ -34,7 +31,7 @@ class ReceiveModeWeb(object): @self.web.app.route("/") def index(): self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response(render_template('receive.html', + r = make_response(render_template('receive.html', static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) From 5c0c45a6debb19e127999d8d20cc97ebb9d05138 Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 14 Jun 2019 18:21:12 +0200 Subject: [PATCH 09/64] Clean up rendering logic between share and website mode --- onionshare/web/base_mode.py | 149 +++++++++++++++++++++++++++++---- onionshare/web/share_mode.py | 9 +- onionshare/web/website_mode.py | 114 +------------------------ share/templates/send.html | 17 ++-- 4 files changed, 151 insertions(+), 138 deletions(-) diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py index ff7e11be..905414f6 100644 --- a/onionshare/web/base_mode.py +++ b/onionshare/web/base_mode.py @@ -2,7 +2,7 @@ import os import sys import tempfile import mimetypes -from flask import Response, request, render_template, make_response +from flask import Response, request, render_template, make_response, send_from_directory from .. import strings @@ -26,6 +26,8 @@ class BaseModeWeb(object): # Dictionary mapping file paths to filenames on disk self.files = {} + # This is only the root files and dirs, as opposed to all of them + self.root_files = {} self.cleanup_filenames = [] self.file_info = {'files': [], 'dirs': []} @@ -46,15 +48,15 @@ class BaseModeWeb(object): pass - def directory_listing(self, path='', filenames=[], filesystem_path=None): + def directory_listing(self, filenames, path='', filesystem_path=None): # If filesystem_path is None, this is the root directory listing files = [] dirs = [] r = '' - if self.web.mode == 'website': - files, dirs = build_directory_listing(filenames) + files, dirs = self.build_directory_listing(filenames, filesystem_path) + if self.web.mode == 'website': r = make_response(render_template('listing.html', path=path, files=files, @@ -65,6 +67,8 @@ class BaseModeWeb(object): r = make_response(render_template( 'send.html', file_info=self.file_info, + files=files, + dirs=dirs, filename=os.path.basename(self.download_filename), filesize=self.filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), @@ -74,26 +78,141 @@ class BaseModeWeb(object): return self.web.add_security_headers(r) + def build_directory_listing(self, filenames, filesystem_path): + files = [] + dirs = [] + + for filename in filenames: + if filesystem_path: + this_filesystem_path = os.path.join(filesystem_path, filename) + else: + this_filesystem_path = self.files[filename] + + is_dir = os.path.isdir(this_filesystem_path) + + if is_dir: + dirs.append({ + 'basename': filename + }) + else: + size = os.path.getsize(this_filesystem_path) + size_human = self.common.human_readable_filesize(size) + files.append({ + 'basename': filename, + 'size_human': size_human + }) + return files, dirs + + def set_file_info(self, filenames, processed_size_callback=None): """ Build a data structure that describes the list of files """ - if self.web.mode == 'website': - self.common.log("WebsiteModeWeb", "set_file_info") - self.web.cancel_compression = True - # This is only the root files and dirs, as opposed to all of them - self.root_files = {} + # If there's just one folder, replace filenames with a list of files inside that folder + if len(filenames) == 1 and os.path.isdir(filenames[0]): + filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] - # If there's just one folder, replace filenames with a list of files inside that folder - if len(filenames) == 1 and os.path.isdir(filenames[0]): - filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + self.build_file_list(filenames) - self.build_file_list(filenames) - - elif self.web.mode == 'share': + if self.web.mode == 'share': self.common.log("ShareModeWeb", "set_file_info") self.web.cancel_compression = False self.build_zipfile_list(filenames, processed_size_callback) + elif self.web.mode == 'website': + self.common.log("WebsiteModeWeb", "set_file_info") + self.web.cancel_compression = True + return True + + + def build_file_list(self, filenames): + """ + Build a data structure that describes the list of files that make up + the static website. + """ + self.common.log("BaseModeWeb", "build_file_list") + + # Loop through the files + for filename in filenames: + basename = os.path.basename(filename.rstrip('/')) + + # If it's a filename, add it + if os.path.isfile(filename): + self.files[basename] = filename + self.root_files[basename] = filename + + # If it's a directory, add it recursively + elif os.path.isdir(filename): + self.root_files[basename + '/'] = filename + + for root, _, nested_filenames in os.walk(filename): + # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", + # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". + # The normalized_root should be "some_folder/foobar" + normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') + + # Add the dir itself + self.files[normalized_root + '/'] = root + + # Add the files in this dir + for nested_filename in nested_filenames: + self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) + + return True + + def render_logic(self, path=''): + if path in self.files: + filesystem_path = self.files[path] + + # If it's a directory + if os.path.isdir(filesystem_path): + # Is there an index.html? + index_path = os.path.join(path, 'index.html') + if self.web.mode == 'website' and index_path in self.files: + # Render it + dirname = os.path.dirname(self.files[index_path]) + basename = os.path.basename(self.files[index_path]) + + return send_from_directory(dirname, basename) + + else: + # Otherwise, render directory listing + filenames = [] + for filename in os.listdir(filesystem_path): + if os.path.isdir(os.path.join(filesystem_path, filename)): + filenames.append(filename + '/') + else: + filenames.append(filename) + filenames.sort() + return self.directory_listing(filenames, path, filesystem_path) + + # If it's a file + elif os.path.isfile(filesystem_path): + dirname = os.path.dirname(filesystem_path) + basename = os.path.basename(filesystem_path) + return send_from_directory(dirname, basename) + + # If it's not a directory or file, throw a 404 + else: + return self.web.error404() + else: + # Special case loading / + + if path == '': + index_path = 'index.html' + if self.web.mode == 'website' and index_path in self.files: + # Render it + dirname = os.path.dirname(self.files[index_path]) + basename = os.path.basename(self.files[index_path]) + return send_from_directory(dirname, basename) + else: + # Root directory listing + filenames = list(self.root_files) + filenames.sort() + return self.directory_listing(filenames, path) + + else: + # If the path isn't found, throw a 404 + return self.web.error404() diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index cb3bba50..afcbdcd9 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -23,8 +23,9 @@ class ShareModeWeb(BaseModeWeb): """ The web app routes for sharing files """ - @self.web.app.route("/") - def index(): + @self.web.app.route('/', defaults={'path': ''}) + @self.web.app.route('/') + def index(path): """ Render the template for the onionshare landing page. """ @@ -44,7 +45,7 @@ class ShareModeWeb(BaseModeWeb): else: self.filesize = self.download_filesize - return self.directory_listing() + return self.render_logic(path) @self.web.app.route("/download") @@ -171,7 +172,7 @@ class ShareModeWeb(BaseModeWeb): return r def build_zipfile_list(self, filenames, processed_size_callback=None): - self.common.log("ShareModeWeb", "build_file_list") + self.common.log("ShareModeWeb", "build_zipfile_list") for filename in filenames: info = { 'filename': filename, diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 5183bebc..b8e4dfdf 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -2,7 +2,7 @@ import os import sys import tempfile import mimetypes -from flask import Response, request, render_template, make_response, send_from_directory +from flask import Response, request, render_template, make_response from .base_mode import BaseModeWeb from .. import strings @@ -42,114 +42,4 @@ class WebsiteModeWeb(BaseModeWeb): 'action': 'visit' }) - if path in self.files: - filesystem_path = self.files[path] - - # If it's a directory - if os.path.isdir(filesystem_path): - # Is there an index.html? - index_path = os.path.join(path, 'index.html') - if index_path in self.files: - # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - - return send_from_directory(dirname, basename) - - else: - # Otherwise, render directory listing - filenames = [] - for filename in os.listdir(filesystem_path): - if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') - else: - filenames.append(filename) - filenames.sort() - return self.directory_listing(path, filenames, filesystem_path) - - # If it's a file - elif os.path.isfile(filesystem_path): - dirname = os.path.dirname(filesystem_path) - basename = os.path.basename(filesystem_path) - return send_from_directory(dirname, basename) - - # If it's not a directory or file, throw a 404 - else: - return self.web.error404() - else: - # Special case loading / - - if path == '': - index_path = 'index.html' - if index_path in self.files: - # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - return send_from_directory(dirname, basename) - else: - # Root directory listing - filenames = list(self.root_files) - filenames.sort() - return self.directory_listing(path, filenames) - - else: - # If the path isn't found, throw a 404 - return self.web.error404() - - def build_directory_listing(self, filenames): - for filename in filenames: - if filesystem_path: - this_filesystem_path = os.path.join(filesystem_path, filename) - else: - this_filesystem_path = self.files[filename] - - is_dir = os.path.isdir(this_filesystem_path) - - if is_dir: - dirs.append({ - 'basename': filename - }) - else: - size = os.path.getsize(this_filesystem_path) - size_human = self.common.human_readable_filesize(size) - files.append({ - 'basename': filename, - 'size_human': size_human - }) - return files, dirs - - - def build_file_list(self, filenames): - """ - Build a data structure that describes the list of files that make up - the static website. - """ - self.common.log("WebsiteModeWeb", "build_file_list") - - # Loop through the files - for filename in filenames: - basename = os.path.basename(filename.rstrip('/')) - - # If it's a filename, add it - if os.path.isfile(filename): - self.files[basename] = filename - self.root_files[basename] = filename - - # If it's a directory, add it recursively - elif os.path.isdir(filename): - self.root_files[basename + '/'] = filename - - for root, _, nested_filenames in os.walk(filename): - # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", - # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". - # The normalized_root should be "some_folder/foobar" - normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') - - # Add the dir itself - self.files[normalized_root + '/'] = root - - # Add the files in this dir - for nested_filename in nested_filenames: - self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) - - return True + return self.render_logic(path) diff --git a/share/templates/send.html b/share/templates/send.html index e0076c0f..490fddf4 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -28,24 +28,27 @@ Size - {% for info in file_info.dirs %} + {% for info in dirs %} - {{ info.basename }} + + {{ info.basename }} + - {{ info.size_human }} - + — {% endfor %} - {% for info in file_info.files %} + + {% for info in files %} - {{ info.basename }} + + {{ info.basename }} + {{ info.size_human }} - {% endfor %} From b0b59075661f79efa9d657ce8523df00ad328356 Mon Sep 17 00:00:00 2001 From: Vinicius Zavam Date: Tue, 23 Jul 2019 15:09:11 +0000 Subject: [PATCH 10/64] DragonFly is *BSD; bringing back #716 --- onionshare/common.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 9b871f04..27e8efc2 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -41,7 +41,7 @@ class Common(object): # The platform OnionShare is running on self.platform = platform.system() - if self.platform.endswith('BSD'): + if self.platform.endswith('BSD') or self.platform == 'DragonFly': self.platform = 'BSD' # The current version of OnionShare diff --git a/setup.py b/setup.py index 347ff366..8aa0f4a0 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ data_files=[ (os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')), (os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js')) ] -if platform.system() != 'OpenBSD': +if not platform.system().endswith('BSD') and platform.system() != 'DragonFly': data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])) setup( From a66f8e1a6a88fb15ea037f3f814ac80e71349dc9 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 27 Jul 2019 08:01:35 +0000 Subject: [PATCH 11/64] weblate translation updates --- share/locale/ar.json | 280 +++++++++++++++++++++++-------------------- share/locale/ca.json | 86 ++++++------- share/locale/da.json | 6 +- share/locale/de.json | 8 +- share/locale/el.json | 156 ++++++++++++------------ share/locale/es.json | 2 +- share/locale/fr.json | 34 +++--- share/locale/hu.json | 4 +- share/locale/it.json | 50 ++++---- share/locale/ms.json | 34 +++--- share/locale/nl.json | 4 +- share/locale/ro.json | 12 +- share/locale/sv.json | 10 +- share/locale/sw.json | 175 +++++++++++++++++++++++++++ share/locale/te.json | 2 +- share/locale/tr.json | 196 +++++++++++++++--------------- 16 files changed, 628 insertions(+), 431 deletions(-) create mode 100644 share/locale/sw.json diff --git a/share/locale/ar.json b/share/locale/ar.json index d125e5fd..103b6077 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,20 +1,20 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "جاري ضغط الملفات.", + "preparing_files": "يجري ضغط الملفات.", "give_this_url": "أعط هذا العنوان للمتلقي:", "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", "give_this_url_receive": "اعط هذا العنوان للمرسل:", "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف الخادم", "not_a_file": "{0:s} ليس ملفا صالحا.", - "not_a_readable_file": "{0:s} ملف غير قابل للقراءة.", - "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", + "not_a_readable_file": "تعذّرت قراءة الملف {0:s}.", + "no_available_port": "لا يوجد منفذ متاح لتشغيل onion service", "other_page_loaded": "تم تحميل العنوان", - "close_on_autostop_timer": "", - "closing_automatically": "توقف بسبب انتهاء التحميل", + "close_on_autostop_timer": "تمّ الإيقاف بسبب بلوغ مؤقت الإيقاف أجله", + "closing_automatically": "تم الإيقاف بسبب تمام النقل", "timeout_download_still_running": "انتظار اكتمال التحميل", - "large_filesize": "تحذير: ارسال مشاركة كبيرة قد يستغرق ساعات", - "systray_menu_exit": "خروج", + "large_filesize": "تحذير: رفع مشاركة كبيرة قد يستغرق ساعات", + "systray_menu_exit": "أنهِ", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -31,142 +31,142 @@ "help_verbose": "", "help_filename": "قائمة الملفات أو المجلدات للمشاركة", "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "إضافة", - "gui_delete": "حذف", - "gui_choose_items": "إختر", + "gui_drag_and_drop": "اسحب الملفات و الأدلة و أسقطها\nلبدء رفعها لمشاركتها", + "gui_add": "أضِف", + "gui_delete": "احذف", + "gui_choose_items": "اختر", "gui_share_start_server": "ابدأ المشاركة", "gui_share_stop_server": "أوقف المشاركة", - "gui_share_stop_server_autostop_timer": "", + "gui_share_stop_server_autostop_timer": "أوقف مشاركة ({})", "gui_share_stop_server_autostop_timer_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "أوقف وضع الإستلام", - "gui_receive_stop_server_autostop_timer": "", + "gui_receive_start_server": "فعّل طور التلقّي", + "gui_receive_stop_server": "أوقف طور التلقّي", + "gui_receive_stop_server_autostop_timer": "أوقف طور التلقّي (باقي {})", "gui_receive_stop_server_autostop_timer_tooltip": "", - "gui_copy_url": "نسخ العنوان", + "gui_copy_url": "انسخ العنوان", "gui_copy_hidservauth": "انسخ HidServAuth", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "ألغى", - "gui_copied_url_title": "", - "gui_copied_url": "تم نسخ عنوان OnionShare إلى الحافظة", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_canceled": "تم الإلغاء", + "gui_copied_url_title": "تم نسخ مسار OnionShare", + "gui_copied_url": "تم نسخ مسار OnionShare إلى الحافظة", + "gui_copied_hidservauth_title": "تم نسخ HidServAuth", + "gui_copied_hidservauth": "تم نسخ سطر HidServAuth إلى الحافظة", + "gui_please_wait": "يجري البدء… اضغط هنا للإلغاء.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_quit_title": "", - "gui_share_quit_warning": "إنك بصدد إرسال ملفات.هل أنت متأكد أنك تريد الخروج مِن OnionShare؟", - "gui_receive_quit_warning": "إنك بصدد تلقي ملفات.هل أنت متأكد أنك تريد الخروج مِن OnionShare؟", - "gui_quit_warning_quit": "خروج", - "gui_quit_warning_dont_quit": "إلغاء", - "error_rate_limit": "", - "zip_progress_bar_format": "جاري الضغط: %p%", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "gui_quit_title": "مهلًا", + "gui_share_quit_warning": "يجري حاليا رفع ملفات. أمتأكد أنك تريد إنهاء OnionShare؟", + "gui_receive_quit_warning": "يجري حالبا تلقّي ملفات. أمتأكد أنك تريد إنهاء OnionShare؟", + "gui_quit_warning_quit": "أنهِ", + "gui_quit_warning_dont_quit": "ألغِ", + "error_rate_limit": "أجرى شخص ما محاولات كثيرة خاطئة على مسارك، مما قد يعني أنه يحاول تخمينه، لذلك فلقد أوقف OnionShare الخادوم. عاود المشاركة و أرسل إلى المتلقّي مسار المشاركة الجديد.", + "zip_progress_bar_format": "يجري الضغط: %p%", + "error_stealth_not_supported": "لاستعمال استيثاق العميل تلزمك إصدارة تور ‪0.2.9.1-alpha‬ أو (متصفّح تور 6.5) و python3-stem الإصدارة 1.5.0، أو ما بعدها.", + "error_ephemeral_not_supported": "يتطلّب OnionShare كلّا من إصدارة تور 0.2.7.1 و الإصدارة 1.4.0 من python3-stem.", "gui_settings_window_title": "الإعدادات", "gui_settings_whats_this": "ما هذا؟", - "gui_settings_stealth_option": "استخدام ترخيص العميل", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "التحقق من الإصدار الجديد", - "gui_settings_autoupdate_option": "قم بإشعاري عند توفر إصدار جديد", - "gui_settings_autoupdate_timestamp": "آخر فحص: {}", - "gui_settings_autoupdate_timestamp_never": "أبدا", - "gui_settings_autoupdate_check_button": "تحقق من وجود نسخة جديدة", + "gui_settings_stealth_option": "فعّل استيثاق العميل", + "gui_settings_stealth_hidservauth_string": "بحفظ مفتاحك السّرّيّ لاستعماله لاحقًا صار بوسعك النقر هنا لنسخ HidServAuth.", + "gui_settings_autoupdate_label": "التماس وجود إصدارة أحدث", + "gui_settings_autoupdate_option": "أخطرني عند وجود إصدارة أحدث", + "gui_settings_autoupdate_timestamp": "تاريخ آخر التماس: {}", + "gui_settings_autoupdate_timestamp_never": "بتاتًا", + "gui_settings_autoupdate_check_button": "التمس وجود إصدارة أحدث", "gui_settings_general_label": "الإعدادات العامة", "gui_settings_sharing_label": "إعدادات المشاركة", - "gui_settings_close_after_first_download_option": "إيقاف المشاركة بعد اكتمال إرسال الملفات", + "gui_settings_close_after_first_download_option": "أوقف المشاركة بعد تمام تنزيل المتلقّي الملفات", "gui_settings_connection_type_label": "كيف ينبغي أن يتصل OnionShare بشبكة تور؟", - "gui_settings_connection_type_bundled_option": "استخدام إصدار تور المدمج في صلب OnionShare", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "الاتصال باستخدام منفذ التحكم", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "اختبار الاتصال بشبكة تور", + "gui_settings_connection_type_bundled_option": "باستخدام إصدارة تور المضمّنة في OnionShare", + "gui_settings_connection_type_automatic_option": "بمحاولة الضبط التلقائي لاستخدام متصفّح تور", + "gui_settings_connection_type_control_port_option": "عبر منفذ التحكم", + "gui_settings_connection_type_socket_file_option": "عبر ملف مقبس", + "gui_settings_connection_type_test_button": "اختبر الاتصال بشبكة تور", "gui_settings_control_port_label": "منفذ التحكم", - "gui_settings_socket_file_label": "ملف مأخذ التوصيل", - "gui_settings_socks_label": "منفذ مأخذ التوصيل", - "gui_settings_authenticate_label": "إعدادات المصادقة على تور", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "كلمة السر", + "gui_settings_socket_file_label": "ملف المقبس", + "gui_settings_socks_label": "منفذ SOCKS", + "gui_settings_authenticate_label": "إعدادات استيثاق تور", + "gui_settings_authenticate_no_auth_option": "بلا استيثاق و لا حتّى بالكوكيز", + "gui_settings_authenticate_password_option": "بكلمة سرّ", "gui_settings_password_label": "كلمة السر", - "gui_settings_tor_bridges": "دعم جسر تور", - "gui_settings_tor_bridges_no_bridges_radio_option": "لا تستخدم الجسور", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "استخدام جسور مخصصة", - "gui_settings_tor_bridges_custom_label": "يمكنكم الحصول على جسور مِن https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "حفظ", - "gui_settings_button_cancel": "إلغاء", + "gui_settings_tor_bridges": "دعم جسور تور", + "gui_settings_tor_bridges_no_bridges_radio_option": "بلا جسور", + "gui_settings_tor_bridges_obfs4_radio_option": "باستخدام وسائل نقل obfs4 المضمّنة", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "استخدام وسائل نقل obfs4 المضمّنة يتطلّب obfs4proxy", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "باستخدام وسائل نقل meek_lite ‮(‪Azure في‬)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "استخدام وسائل نقل meek_lite ‮(‪Azure‬)‬ يتطلّب obfs4proxy", + "gui_settings_meek_lite_expensive_warning": "تنبيه: استخدام جسور meek_lite يكلّف مشروع تور للغاية..

استخدمها عند الضرورة فقط لتعذّر الاتّصال بتور مباشرة، أو عبر وسائل نقل obfs4 أو الجسور الاعتيادية.", + "gui_settings_tor_bridges_custom_radio_option": "استخدام جسورًا مطوّعة", + "gui_settings_tor_bridges_custom_label": "يمكنكم الحصول على عناوين جسور مِن https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "الجسور التي أضفت عاوينها كلّها لا تعمل.\nتحقّق منها أو أضف غيرها.", + "gui_settings_button_save": "احفظ", + "gui_settings_button_cancel": "ألغِ", "gui_settings_button_help": "مساعدة", - "gui_settings_autostop_timer_checkbox": "", - "gui_settings_autostop_timer": "إيقاف المشاركة في:", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "لا يمكن الاتصال بوحدة تحكم تور في {}:{}.", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "متصل بوحدة تحكم تور، ولكنه يتطلب كلمة سرية للمصادقة.", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "هناك خطأ مع تور: {}", - "error_tor_protocol_error_unknown": "حدث خطأ مجهول مع تور", + "gui_settings_autostop_timer_checkbox": "استخدم مؤقِّت الإيقاف", + "gui_settings_autostop_timer": "أوقف المشاركة في:", + "settings_error_unknown": "تعذّر الاتصال بمتحكّم تور لأنّ تضبيطاتك غير صحيحة.", + "settings_error_automatic": "تعذّر الاتّصال بمتحكم تور. تأكد من اشتغال متصفّح تور في الخلفية (و هو متاح في torproject.org)", + "settings_error_socket_port": "تعذّر الاتصال بمتحكّم تور في {}:{}.", + "settings_error_socket_file": "تعذّر الاتّصال بمتحكّم تور عبر ملف المقبس {}.", + "settings_error_auth": "تمّ الاتّصال مع {}:{} لكن تعذّر الاستيثاق. ربما هو ليس متحكّم تور؟", + "settings_error_missing_password": "تمّ الاتّصال بمتحكّم تور لكنه يطلب كلمة سرّ للاستيثاق.", + "settings_error_unreadable_cookie_file": "تمّ الاتّصال بمتحكّم تور لكن إمّا أنّ كلمة السّر غير صحيحة أو أنّ المستخدم غير مصرّح له بقراءة ملف الكوكي.", + "settings_error_bundled_tor_not_supported": "استعمال إصدارة تور المضمّنة في OnionShare لا يعمل في طور التطوير في وِندوز و لا ماك أوإس.", + "settings_error_bundled_tor_timeout": "استغرق الاتّصال بتور وقتا أطول من اللازم. إمّا أنك غير متصّل بالإنترنت أو أنّ ساعة النظام غير مضبوطة.", + "settings_error_bundled_tor_broken": "تعذّر على OnionShare الاتصّال بتور في الخلفية:\n{}", + "settings_test_success": "تمّ الاتّصال بمتحكّم تور:\n\nإصدارة تور: {}\nيدعم خدمات تور الزائلة: {}\nيدعم استيثاق العميل: {}\nيدعم الجيل الأحدث من عناوين ‪.onion‬: {}", + "error_tor_protocol_error": "ثمّة عطل في تور: {}", + "error_tor_protocol_error_unknown": "طرأ عطل مجهول في تور", "error_invalid_private_key": "نوع المفتاح الخاص هذا غير معتمد", - "connecting_to_tor": "جارٍ الاتصال بشبكة تور", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "إنك تقوم بتشغيل آخر نسخة مِن OnionShare.", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "نعم,", - "gui_tor_connection_ask_quit": "خروج", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", + "connecting_to_tor": "يجري الاتصال بشبكة تور", + "update_available": "توجد إصدارة أحدث من OnionShare. يمكنك تنزيلها الآن.

إصدارتك {} و الأحدث {}.", + "update_error_check_error": "تعذّر التماس إصدارة أحدث: موقع OnionShare على الوِب يبلغ أنّ الإصدارة الأحدث هي العبارة غير المفهومة '{}'…", + "update_error_invalid_latest_version": "تعذّر التماس إصدارة أحدث: إما أنّك غير متّصل بتور أو أنّ موقع OnionShare به عطل.", + "update_not_available": "أنت تشغّل أحدث إصدارة مِنْ OnionShare.", + "gui_tor_connection_ask": "أتريد فتح الإعدادات لضبط الاتّصال بتور؟", + "gui_tor_connection_ask_open_settings": "نعم", + "gui_tor_connection_ask_quit": "أنهِ", + "gui_tor_connection_error_settings": "جرّب تغيير كيفية اتّصال OnionShare بشبكة تور في الإعدادات.", + "gui_tor_connection_canceled": "تعذّر الاتّصال بتور.\n\nتحقّق من اتّصالك بالإنترنت ثم أعد تشغيل OnionShare و اضبط اتّصاله بتور.", "gui_tor_connection_lost": "غير متصل بشبكة تور.", - "gui_server_started_after_autostop_timer": "", - "gui_server_autostop_timer_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "استخدم العناوين الموروثة", - "gui_save_private_key_checkbox": "استخدم عنوانا ثابتا", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", + "gui_server_started_after_autostop_timer": "بلغ مؤقِّت الإيقاف أجله قبل اشتغال الخادوم. أنشئ مشاركة جديدة.", + "gui_server_autostop_timer_expired": "بلغ مؤقّت الإيقاف أجله بالفعل. حدّثه للبدء بالمشاركة.", + "share_via_onionshare": "شاركه باستعمال OnionShare", + "gui_use_legacy_v2_onions_checkbox": "استخدم صيغة العناوين التاريخية", + "gui_save_private_key_checkbox": "استخدم عنوانًا دائمًا", + "gui_share_url_description": "أيّ شخص لديه مسار OnionShare هذا سيكون بوسعه تنزيل تلك الملفات باستعمال متصفّح تور: ", + "gui_receive_url_description": "أيّ شخص لديه مسار OnionShare هذا سيكون بوسعه رفع ملفات إلى حاسوبك باستعمال متصفّح تور: ", + "gui_url_label_persistent": "هذه المشاركة لن توقف تلقائيًّا.

كل مشاركة لاحقة ستستخدم العنوان نفسه. لاستعمال عناوين لمرة واحدة عطّل خيار \"استخدم عنوانًا دائمًا\" في الإعدادات.", + "gui_url_label_stay_open": "هذه المشاركة لن تتوقف تلقائيا.", + "gui_url_label_onetime": "هذه المشاركة ستتوقف تلقائيًّا بعد تمام أوّل تنزيلة.", + "gui_url_label_onetime_and_persistent": "هذه المشاركة لن توقف تلقائيًّا.

كل مشاركة لاحقة ستستخدم العنوان نفسه. لاستعمال عناوين لمرة واحدة عطّل خيار \"استخدم عنوانًا دائمًا\" في الإعدادات.", "gui_status_indicator_share_stopped": "جاهز للمشاركة", - "gui_status_indicator_share_working": "يبدأ…", - "gui_status_indicator_share_started": "المشاركة جارية", - "gui_status_indicator_receive_stopped": "جاهز للتلقي", - "gui_status_indicator_receive_working": "يبدأ…", - "gui_status_indicator_receive_started": "جاري الإستلام", + "gui_status_indicator_share_working": "يجري البدء…", + "gui_status_indicator_share_started": "تجري المشاركة", + "gui_status_indicator_receive_stopped": "جاهز للتلقّي", + "gui_status_indicator_receive_working": "يجري البدء…", + "gui_status_indicator_receive_started": "يجري التلقّي", "gui_file_info": "{} ملفات، {}", "gui_file_info_single": "{} ملف، {}", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "history_in_progress_tooltip": "تجري معالجة {}", + "history_completed_tooltip": "تمّت معالجة {}", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", + "gui_receive_mode_warning": "طور التلقّي يسمح للآخرين برفع ملفات إلى حاسوبك.

بعض الملفات قد تكون قادرة على السيطرة على نظامك إذا ما فتحتها. لا تفتح ملفات إلا من أشخاص تثق بهم، أو إنْ كنت واثقًا ممّا تفعل.", + "receive_mode_upload_starting": "يجري بدء رفع حجم مجمله {}", "receive_mode_received_file": "تم تلقي: {}", - "gui_mode_share_button": "مشاركة الملفات", + "gui_mode_share_button": "مشاركة ملفات", "gui_mode_receive_button": "تلقّي ملفات", - "gui_settings_receiving_label": "إعدادات الاستلام", + "gui_settings_receiving_label": "إعدادات التلقّي", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "استعراض", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "الوضع العام", + "gui_settings_public_mode_checkbox": "الطور العلني", "systray_close_server_title": "", "systray_close_server_message": "", "systray_page_loaded_title": "تم تحميل الصفحة", @@ -179,29 +179,51 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", + "gui_open_folder_error_nautilus": "تعذّر فتح الدليل لأنَّ نوتِلَس ليس متاحًا. الملف موجود في: {}", "gui_settings_language_label": "اللغة المفضلة", - "gui_settings_language_changed_notice": "", + "gui_settings_language_changed_notice": "أعد تشغيل OnionShare لتطبيق خيار اللغة.", "timeout_upload_still_running": "انتظار اكتمال الرفع", - "gui_add_files": "إضافة ملفات", - "gui_add_folder": "إضافة مجلد", - "gui_settings_onion_label": "إعدادات البصل", - "gui_connect_to_tor_for_onion_settings": "اربط الاتصال بشبكة تور لترى إعدادات خدمة البصل", - "gui_settings_data_dir_label": "حفظ الملفات على", - "gui_settings_data_dir_browse_button": "تصفح", - "systray_page_loaded_message": "تم تحميل عنوان OnionShare", + "gui_add_files": "أضف ملفات", + "gui_add_folder": "أضف دليلا", + "gui_settings_onion_label": "إعدادات البصلة", + "gui_connect_to_tor_for_onion_settings": "يجب الاتّصال بشبكة تور لأجل مطالعة إعدادات خدمة البصلة", + "gui_settings_data_dir_label": "احفظ الملفات في", + "gui_settings_data_dir_browse_button": "تصفّح", + "systray_page_loaded_message": "تم تحميل مسار OnionShare", "systray_share_started_title": "بدأت المشاركة", - "systray_share_started_message": "بدأت عملية إرسال الملفات إلى شخص ما", - "systray_share_completed_title": "اكتملت المشاركة", - "systray_share_completed_message": "انتهت عملية إرسال الملفات", - "systray_share_canceled_title": "ألغيت المشاركة", + "systray_share_started_message": "بدأ إرسال الملفات إلى شخص ما", + "systray_share_completed_title": "تمّت المشاركة", + "systray_share_completed_message": "تمّ إرسال الملفات", + "systray_share_canceled_title": "تمّ إلغاء المشاركة", "systray_share_canceled_message": "شخص ما ألغى استقبال ملفاتك", - "systray_receive_started_title": "جاري الاستلام", - "systray_receive_started_message": "شخص ما يرسل لك ملفات", - "gui_all_modes_history": "السجل الزمني", + "systray_receive_started_title": "بدأ التلقّي", + "systray_receive_started_message": "شخص ما يرسل إليك ملفات", + "gui_all_modes_history": "التأريخ", "gui_all_modes_clear_history": "مسح الكل", - "gui_share_mode_no_files": "لم ترسل أية ملفات بعد", - "gui_share_mode_autostop_timer_waiting": "في انتظار الانتهاء من الإرسال", - "gui_receive_mode_no_files": "لم تتلق أية ملفات بعد", - "gui_receive_mode_autostop_timer_waiting": "في انتظار الانتهاء من الاستلام" + "gui_share_mode_no_files": "لَمْ تُرسَل أيّة ملفات بعد", + "gui_share_mode_autostop_timer_waiting": "في انتظار إتمام الإرسال", + "gui_receive_mode_no_files": "لَمْ تُتَلقَّ أيّة ملفات بعد", + "gui_receive_mode_autostop_timer_waiting": "في انتظار إتمام التلقّي", + "gui_stop_server_autostop_timer_tooltip": "أجل المؤقت {}", + "gui_start_server_autostart_timer_tooltip": "أجل المؤقت {}", + "gui_waiting_to_start": "مُجدولة بدايتها بعد {}. اضغط هنا لإلغائها.", + "gui_settings_autostart_timer_checkbox": "استخدم مؤقِّت البدء", + "gui_settings_autostart_timer": "ابدأ المشاركة في:", + "gui_server_autostart_timer_expired": "الوقت المُجدول فات بالفعل. حدّثه للبدء بالمشاركة.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "وقت الإيقاف لا يجوز أن يكون هو نفسه وقت البدء و لا قبله. اضبطه للبدء بالمشاركة.", + "gui_status_indicator_share_scheduled": "تمّت الجدولة…", + "gui_status_indicator_receive_scheduled": "تمّت الجدولة…", + "error_cannot_create_data_dir": "تعذَّر إنشاء دليل بيانات OnionShare: {}", + "gui_all_modes_transfer_started": "بدأ في {}", + "gui_all_modes_transfer_finished_range": "تمّ نقل {} - {}", + "gui_all_modes_transfer_finished": "تمّ نقل {}", + "gui_all_modes_transfer_canceled_range": "تمّ إلغاء {} - {}", + "gui_all_modes_transfer_canceled": "تمّ إلغاء {}", + "gui_all_modes_progress_complete": "انقضت %p%، {0:s}", + "gui_all_modes_progress_starting": "(يجري الحساب) {0:s}، %p%", + "gui_all_modes_progress_eta": "{0:s}، الزمن الباقي المقدّر: {1:s}، %p%", + "days_first_letter": "يوم", + "hours_first_letter": "ساعة", + "minutes_first_letter": "دقيقة", + "seconds_first_letter": "ثانية" } diff --git a/share/locale/ca.json b/share/locale/ca.json index b88dcead..4380dc18 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -13,7 +13,7 @@ "close_on_autostop_timer": "S'ha aturat perquè s'ha acabat el temporitzador d'aturada automàtica", "closing_automatically": "S'ha aturat perquè ha acabat la transferència", "timeout_download_still_running": "S'està esperant que acabi la descàrrega", - "large_filesize": "Compte: La transferència d'arxius molt grans podria trigar hores", + "large_filesize": "Compte: La transferència de fitxers molt grans podria trigar hores", "systray_menu_exit": "Surt", "systray_download_started_title": "S'ha iniciat la descàrrega amb OnionShare", "systray_download_started_message": "Algú ha començat a descarregar els teus arxius", @@ -31,15 +31,15 @@ "help_verbose": "Envia els errors d'OnionShare a stdout i els errors web al disc", "help_filename": "Llista d'arxius o carpetes a compartir", "help_config": "Ubicació de la configuració JSON personalitzada", - "gui_drag_and_drop": "Arrossega fitxers i carpetes\nper començar a compartir", + "gui_drag_and_drop": "Arrossega fitxers i carpetes\nper a començar a compartir", "gui_add": "Afegeix", "gui_delete": "Esborra", - "gui_choose_items": "Escull", + "gui_choose_items": "Trieu", "gui_share_start_server": "Comparteix", "gui_share_stop_server": "Deixa de compartir", "gui_share_stop_server_autostop_timer": "Deixa de compartir (queden {}s)", "gui_share_stop_server_autostop_timer_tooltip": "El temporitzador acaba a {}", - "gui_receive_start_server": "Inicia en mode de recepció", + "gui_receive_start_server": "Inicia el mode de recepció", "gui_receive_stop_server": "Atura el mode de recepció", "gui_receive_stop_server_autostop_timer": "Atura el mode de recepció (queden {})", "gui_receive_stop_server_autostop_timer_tooltip": "El temporitzador acaba a {}", @@ -47,36 +47,36 @@ "gui_copy_hidservauth": "Copia el HidServAuth", "gui_downloads": "Historial de descàrregues", "gui_no_downloads": "No n'hi ha cap", - "gui_canceled": "Canceŀlat", + "gui_canceled": "S'ha cancel·lat", "gui_copied_url_title": "S'ha copiat l'adreça OnionShare", "gui_copied_url": "S'ha copiat l'adreça OnionShare al porta-retalls", "gui_copied_hidservauth_title": "S'ha copiat el HidServAuth", "gui_copied_hidservauth": "S'ha copiat la línia HidServAuth al porta-retalls", - "gui_please_wait": "S'està iniciant… Clica per a canceŀlar.", + "gui_please_wait": "S'està iniciant… Feu clic per a cancel·lar.", "gui_download_upload_progress_complete": "Han passat %p%, {0:s}.", "gui_download_upload_progress_starting": "{0:s}, %p% (s'està calculant)", "gui_download_upload_progress_eta": "{0:s}, temps restant: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_quit_title": "Espera un moment", - "gui_share_quit_warning": "Encara s'estan enviant fitxers. Segur que vols sortir de l'OnionShare?", - "gui_receive_quit_warning": "Encara s'estan rebent fitxers. Segur que vols sortir de l'OnionShare?", + "gui_quit_title": "Espereu un moment", + "gui_share_quit_warning": "Encara s'estan enviant fitxers. Segur que voleu sortir de l'OnionShare?", + "gui_receive_quit_warning": "Encara s'estan rebent fitxers. Segur que voleu sortir de l'OnionShare?", "gui_quit_warning_quit": "Surt", - "gui_quit_warning_dont_quit": "Canceŀla", - "error_rate_limit": "Algú ha fet massa intents a la teva adreça, cosa que podria significar que l'estan intentant endevinar. Per això OnionShare s'ha aturat sola. Pots tornar a començar i enviar a la destinatària la nova adreça.", + "gui_quit_warning_dont_quit": "Cancel·la", + "error_rate_limit": "Algú ha fet massa intents incorrectes en la vostra adreça, cosa que podria significar que l'estan intentant endevinar. Per això OnionShare ha aturat el servidor. Torneu a començar i envieu de nou la nova adreça.", "zip_progress_bar_format": "S'està comprimint: %p%", - "error_stealth_not_supported": "Per fer servir l'autorització de client, necessites les versions iguals o superiors a Tor 0.2.9.1-alpha (o Tor Browser 6.5) i python3-stem 1.5.0.", + "error_stealth_not_supported": "Per a fer servir l'autorització de client, necessiteu versions iguals o superiors a Tor 0.2.9.1-alpha (o Tor Browser 6.5) i python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare necessita almenys les versions Tor 0.2.7.1 i python3-stem 1.4.0.", "gui_settings_window_title": "Configuració", "gui_settings_whats_this": "Què és això?", "gui_settings_stealth_option": "Fes servir autorització de client", - "gui_settings_stealth_hidservauth_string": "Ara que ja has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu \"HidServAuth\".", - "gui_settings_autoupdate_label": "Comprova si hi ha noves versions", + "gui_settings_stealth_hidservauth_string": "Ara que ja heu desat la clau privada per a reutilitzar-la, podeu fer clic per a copiar el HidServAuth.", + "gui_settings_autoupdate_label": "Comprova si hi ha versions noves", "gui_settings_autoupdate_option": "Notifica'm si hi ha una actualització disponible", "gui_settings_autoupdate_timestamp": "Última comprovació: {}", "gui_settings_autoupdate_timestamp_never": "Mai", "gui_settings_autoupdate_check_button": "Comprova si hi ha una versió més nova", "gui_settings_general_label": "Configuració general", - "gui_settings_sharing_label": "Configuració de compartir", + "gui_settings_sharing_label": "Configuració de compartició", "gui_settings_close_after_first_download_option": "Deixa de compartir després d'enviar fitxers", "gui_settings_connection_type_label": "Com hauria de connectar-se OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare", @@ -88,60 +88,60 @@ "gui_settings_socket_file_label": "Fitxer de socket", "gui_settings_socks_label": "Port SOCKS", "gui_settings_authenticate_label": "Configuració d'autenticació a Tor", - "gui_settings_authenticate_no_auth_option": "Sense autenticació o autenticació per cookies", + "gui_settings_authenticate_no_auth_option": "Sense autenticació, o autenticació amb galetes", "gui_settings_authenticate_password_option": "Contrasenya", "gui_settings_password_label": "Contrasenya", "gui_settings_tor_bridges": "Ponts de Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "No facis servir ponts", - "gui_settings_tor_bridges_obfs4_radio_option": "Fes servir el transport connectable obfs4", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Fes servir el transport connectable obfs4 (necessita obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Fes servir el transport connectable meek_lite (Azure)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Fes servir el transport connectable meek_lite (Azure, necessita obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Compte: els ponts meek_lite costen molts recursos al Tor Project per funcionar.

Sisplau, fes-los servir només si no pots connectar-te a Tor directament, a través de obfs4, o a través de ponts normals.", + "gui_settings_tor_bridges_obfs4_radio_option": "Fes servir el transport integrat obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Fes servir el transport integrat obfs4 (necessita obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Fes servir el transport integrat meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Fes servir el transport integrat meek_lite (Azure, necessita obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Compte: fer funcionar els ponts meek_lite suposa un cost molt gran per al Tor Project .

Feu-los servir només si no podeu connectar-vos a Tor directament, a través d'obfs4, o a través de ponts normals.", "gui_settings_tor_bridges_custom_radio_option": "Fes servir ponts personalitzats", - "gui_settings_tor_bridges_custom_label": "Pots trobar-ne a https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Cap dels ponts que has afegit ha funcionat.\nComprova'ls o prova d'afegir-ne de nous.", + "gui_settings_tor_bridges_custom_label": "Podeu trobar-ne a https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Cap dels ponts que heu afegit funciona.\nComproveu-los o proveu d'afegir-ne de nous.", "gui_settings_button_save": "Desa", - "gui_settings_button_cancel": "Canceŀla", + "gui_settings_button_cancel": "Cancel·la", "gui_settings_button_help": "Ajuda", "gui_settings_autostop_timer_checkbox": "Utilitza un temporitzador d'aturada", "gui_settings_autostop_timer": "Atura a:", "settings_error_unknown": "No s'ha pogut connectar a Tor perquè la configuració és inconsistent.", - "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a torproject.org)", + "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Heu iniciat el Tor Browser? (disponible a torproject.org)", "settings_error_socket_port": "No s'ha pogut establir la connexió al controlador de Tor a {}:{}.", "settings_error_socket_file": "No s'ha pogut connectar al controlador de Tor fent servir el fitxer de socket {}.", "settings_error_auth": "S'ha establert la connexió a {}:{} però ha fallat l'autenticació. Pot ser que no sigui un controlador de Tor?", "settings_error_missing_password": "S'ha establer la connexió al controlador de Tor, però necessita una contrasenya d'autenticació.", - "settings_error_unreadable_cookie_file": "S'ha establert la connexió al controlador de Tor, però hi ha hagut un error de permisos. Pot ser que la contrasenya sigui errònia o que faltin permisos de lectura en el fitxer de galetes.", + "settings_error_unreadable_cookie_file": "S'ha establert la connexió al controlador de Tor, però pot ser que la contrasenya sigui errònia o que faltin permisos de lectura en el fitxer de galetes.", "settings_error_bundled_tor_not_supported": "La versió de Tor inclosa a OnionShare no funciona en mode de desenvolupador a Windows ni MacOS.", - "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegura't que estàs connectat a internet i que tens en hora el rellotge del sistema.", - "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}", - "settings_test_success": "Connectat al controlador de Tor.\n\nVersió de Tor: {}\nSuporta serveis onion efímers: {}.\nSuporta autenticació del client: {}.\nSuporta adreces .onion de nova generació: {}.", + "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegureu-vos que esteu connectat a internet i que teniu en hora el rellotge del sistema.", + "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}", + "settings_test_success": "S'ha connectat al controlador de Tor.\n\nVersió de Tor: {}\nCompatible amb serveis onion efímers: {}.\nCompatible amb autenticació del client: {}.\nCompatible amb adreces .onion de nova generació: {}.", "error_tor_protocol_error": "Hi ha hagut un error amb Tor: {}", "error_tor_protocol_error_unknown": "Hi ha hagut un error desconegut amb Tor", "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", - "connecting_to_tor": "Connectant a la xarxa Tor", - "update_available": "Ha sortit una nova versió d'OnionShare.Feu clic aquí per obtenir-la.

Esteu usant {} i la més recent és {}.", + "connecting_to_tor": "S'està connectant a la xarxa Tor", + "update_available": "Hi ha una nova versió d'OnionShare.Feu clic aquí per a obtenir-la.

Esteu usant {} i la més recent és {}.", "update_error_check_error": "No s'ha pogut comprovar si hi ha versions més noves. La web d'OnionShare diu que l'última versió és '{}' però no s'ha pogut reconèixer…", - "update_error_invalid_latest_version": "No s'ha pogut comprovar si hi ha una versió més nova. Pot ser que no estiguis connectat/da a Tor o que la web d'OnionShare estigui caiguda?", - "update_not_available": "Aquesta és la versió més nova d'OnionShare.", - "gui_tor_connection_ask": "Vols anar a la configuració per provar d'arreglar la connexió a Tor?", + "update_error_invalid_latest_version": "No s'ha pogut comprovar si hi ha una versió més nova. Pot ser que no estigueu connectat a Tor o que el web d'OnionShare estigui caigut?", + "update_not_available": "Aquesta és l'última versió d'OnionShare.", + "gui_tor_connection_ask": "Voleu anar a la configuració per a provar d'arreglar la connexió a Tor?", "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Surt", - "gui_tor_connection_error_settings": "Prova de canviar la configuració de com OnionShare es connecta a la xarxa Tor.", - "gui_tor_connection_canceled": "No s'ha pogut establir la connexió amb la xarxa Tor.\n\nAssegura't que tens connexió a internet, torna a obrir OnionShare i prepara la connexió a Tor.", + "gui_tor_connection_error_settings": "Proveu de canviar la configuració de com OnionShare es connecta a la xarxa Tor.", + "gui_tor_connection_canceled": "No s'ha pogut establir la connexió amb la xarxa Tor.\n\nAssegureu-vos que teniu connexió a internet, torneu a obrir OnionShare i prepareu la connexió a Tor.", "gui_tor_connection_lost": "S'ha perdut la connexió amb Tor.", - "gui_server_started_after_autostop_timer": "El temporitzador de finalització automàtica ha acabat abans que s'iniciés el servidor.\nTorna a compartir-ho.", + "gui_server_started_after_autostop_timer": "El temporitzador de finalització automàtica ha acabat abans que s'iniciés el servidor.\nTorneu a compartir-ho.", "gui_server_autostop_timer_expired": "El temporitzador de finalització automàtica ja s'ha acabat.\nReinicieu-lo per a poder compartir.", "share_via_onionshare": "Comparteix-ho amb OnionShare", "gui_use_legacy_v2_onions_checkbox": "Fes servir adreces amb un format antic", "gui_save_private_key_checkbox": "Fes servir una adreça persistent", "gui_share_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot descarregar fitxers teus fent servir el Navegador de Tor: ", "gui_receive_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot pujar fitxers al teu ordinador fent servir el Navegador de Tor: ", - "gui_url_label_persistent": "Aquesta sessió no es tancarà.

Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».", + "gui_url_label_persistent": "Aquesta sessió no es tancarà.

Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si voleu crear una adreça diferent per a cada recurs, desactiveu l'opció «Feu servir una adreça persistent».", "gui_url_label_stay_open": "Aquesta recurs no es deixarà de compartir sol.", "gui_url_label_onetime": "Aquest recurs deixarà de compartir-se després de la primera descàrrega.", - "gui_url_label_onetime_and_persistent": "Aquest recurs no es deixarà de compartir sol.

Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».", + "gui_url_label_onetime_and_persistent": "Aquest recurs no es deixarà de compartir sol.

Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si voleu crear una adreça diferent per a cada recurs, desactiveu l'opció «Feu servir una adreça persistent».", "gui_status_indicator_share_stopped": "A punt per compartir", "gui_status_indicator_share_working": "S'està iniciant…", "gui_status_indicator_share_started": "S'està compartint", @@ -185,8 +185,8 @@ "timeout_upload_still_running": "S'està esperant que acabi la pujada", "gui_add_files": "Afegeix fitxers", "gui_add_folder": "Afegeix una carpeta", - "gui_settings_onion_label": "Servei ceba", - "gui_connect_to_tor_for_onion_settings": "Connecta't a Tor per configurar els serveis ocults", + "gui_settings_onion_label": "Configuració Onion", + "gui_connect_to_tor_for_onion_settings": "Connecteu-vos a Tor per a configurar els serveis onion", "error_cannot_create_data_dir": "No s'ha pogut crear la carpeta de dades d'OnionShare: {}", "receive_mode_data_dir": "Els arxius que rebis apareixeran aquí: {}", "gui_settings_data_dir_label": "Desa els fitxers a", @@ -216,11 +216,11 @@ "gui_receive_mode_autostop_timer_waiting": "S'està esperant que finalitzi la recepció", "gui_stop_server_autostop_timer_tooltip": "El temporitzador d'aturada automàtica finalitza a les {}", "gui_start_server_autostart_timer_tooltip": "El temporitzador d'inici automàtic finalitza a les {}", - "gui_waiting_to_start": "S'ha programat per iniciar en {}. Feu clic per cancel·lar.", + "gui_waiting_to_start": "S'ha programat per a iniciar en {}. Feu clic per a cancel·lar.", "gui_settings_autostart_timer_checkbox": "Usa un temporitzador d'inici automàtic", "gui_settings_autostart_timer": "Inicia la compartició:", "gui_server_autostart_timer_expired": "L'hora programada ja ha passat. Actualitzeu-la per a començar la compartició.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "L'hora d'aturada automàtica no pot ser igual ni anterior que l'hora d'inici automàtic. Modifique-ho per a començar la compartició.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "L'hora d'aturada automàtica no pot ser igual ni anterior que l'hora d'inici automàtic. Modifiqueu-ho per a començar la compartició.", "gui_status_indicator_share_scheduled": "Programat…", "gui_status_indicator_receive_scheduled": "Programat…", "days_first_letter": "d", diff --git a/share/locale/da.json b/share/locale/da.json index b3a2234a..33fd2541 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -95,7 +95,7 @@ "settings_error_bundled_tor_not_supported": "Brug af Tor-versionen som kom med OnionShare virker ikke i udviklertilstand på Windows eller macOS.", "settings_error_bundled_tor_timeout": "For længe om at oprette forbindelse til Tor. Måske har du ikke forbindelse til internettet, eller går dit systems ur forkert?", "settings_error_bundled_tor_broken": "OnionShare kunne ikke oprette forbindelse til Tor i baggrunden:\n{}", - "settings_test_success": "Forbundet til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}.\nUnderstøtter klientautentifikation: {}.\nUnderstøtter næste generations .onion-adresser: {}.", + "settings_test_success": "Forbundet til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}.\nUnderstøtter klientautentifikation: {}.\nUnderstøtter næstegenerations .onion-adresser: {}.", "error_tor_protocol_error": "Der opstod en fejl med Tor: {}", "connecting_to_tor": "Opretter forbindelse til Tor-netværket", "update_available": "Der findes en ny OnionShare. Klik her for at hente den.

Du bruger {} og den seneste er {}.", @@ -173,8 +173,8 @@ "gui_settings_language_label": "Foretrukne sprog", "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft.", "gui_settings_meek_lite_expensive_warning": "Advarsel: meek_lite-broerne er meget dyre at køre for Tor-projektet.

Brug dem kun hvis du ikke er i stand til at oprette forbindelse til Tor direkte, via obfs4-transporter eller andre normale broer.", - "gui_share_url_description": "Alle med OnionShare-adressen kan downloade dine filer med Tor Browser: ", - "gui_receive_url_description": "Alle med OnionShare-adressen kan uploade filer til din computer med Tor Browser: ", + "gui_share_url_description": "Alle med OnionShare-adressen kan downloade dine filer, med Tor Browser: ", + "gui_receive_url_description": "Alle med OnionShare-adressen kan uploade filer til din computer, med Tor Browser: ", "history_in_progress_tooltip": "{} igangværende", "history_completed_tooltip": "{} færdige", "info_in_progress_uploads_tooltip": "{} igangværende upload(s)", diff --git a/share/locale/de.json b/share/locale/de.json index 44839231..0a0eb9c1 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -3,7 +3,7 @@ "give_this_url": "Gib diese URL an den Empfänger:", "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine gültige Datei.", - "other_page_loaded": "URL geladen", + "other_page_loaded": "Daten geladen", "closing_automatically": "Gestoppt, da die Übertragung erfolgreich beendet wurde", "large_filesize": "Warnung: Das Hochladen von großen Dateien kann sehr lange dauern", "help_local_only": "Tor nicht verwenden (nur für Entwicklung)", @@ -49,7 +49,7 @@ "give_this_url_receive": "Gib diese URL dem Sender:", "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", "not_a_readable_file": "{0:s} ist eine schreibgeschützte Datei.", - "no_available_port": "Es konnte kein freier Port gefunden werden, um den Verteilservice zu starten", + "no_available_port": "Es konnte kein freier Port gefunden werden, um den Verteildienst zu starten", "close_on_autostop_timer": "Angehalten da der Stoptimer abgelaufen ist", "systray_upload_started_title": "OnionShare Upload wurde gestartet", "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", @@ -79,7 +79,7 @@ "help_stealth": "Nutze Klientauthorisierung (fortgeschritten)", "gui_receive_start_server": "Empfangsmodus starten", "gui_receive_stop_server": "Empfangsmodus stoppen", - "gui_receive_stop_server_autostop_timer": "Empfängermodus stoppen (stoppt automatisch in {} Sekunden)", + "gui_receive_stop_server_autostop_timer": "Empfängermodus stoppen ({} verbleibend)", "gui_receive_stop_server_autostop_timer_tooltip": "Zeit läuft in {} ab", "gui_no_downloads": "Bisher keine Downloads", "gui_copied_url_title": "OnionShare-Adresse kopiert", @@ -218,7 +218,7 @@ "gui_settings_autostart_timer": "Teilen starten bei:", "gui_waiting_to_start": "Geplant in {} zu starten. Klicken zum Abbrechen.", "gui_stop_server_autostop_timer_tooltip": "Stoptimer endet um {}", - "gui_start_server_autostart_timer_tooltip": "Starttimer endet um {}", + "gui_start_server_autostart_timer_tooltip": "Automatischer Stoptimer endet um {}", "gui_server_autostart_timer_expired": "Die geplante Zeit ist bereits vergangen. Bitte aktualisieren um das Teilen zu starten.", "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Die Stopzeit kann nicht gleich oder früher als die Startzeit sein. Bitte aktutalisieren um das Teilen zu starten.", "gui_status_indicator_share_scheduled": "Geplant…", diff --git a/share/locale/el.json b/share/locale/el.json index 00a063c9..90655c18 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -35,41 +35,41 @@ "gui_add": "Προσθήκη", "gui_delete": "Διαγραφή", "gui_choose_items": "Επιλογή", - "gui_share_start_server": "Εκκίνηση μοιράσματος", - "gui_share_stop_server": "Τερματισμός μοιράσματος", - "gui_share_stop_server_autostop_timer": "Διακοπή μοιράσματος (απομένουν {}\")", + "gui_share_start_server": "Εκκίνηση διαμοιρασμού", + "gui_share_stop_server": "Τερματισμός διαμοιρασμού", + "gui_share_stop_server_autostop_timer": "Διακοπή διαμοιρασμού (απομένουν {}\")", "gui_share_stop_server_autostop_timer_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", "gui_receive_start_server": "Εκκίνηση κατάστασης λήψης", "gui_receive_stop_server": "Τερματισμός κατάστασης λήψης", - "gui_receive_stop_server_autostop_timer": "Διακοπή Λειτουργίας Λήψης (υπολοίπονται {}\")", + "gui_receive_stop_server_autostop_timer": "Διακοπή λειτουργίας λήψης (απομένουν {})", "gui_receive_stop_server_autostop_timer_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", "gui_copy_url": "Αντιγραφή διεύθυνσης", "gui_copy_hidservauth": "Αντιγραφή HidServAuth", "gui_downloads": "Ιστορικό Λήψεων", "gui_no_downloads": "Καμία λήψη ως τώρα", "gui_canceled": "Ακυρώθηκε", - "gui_copied_url_title": "Αντεγραμμένη διεύθυνση OnionShare", - "gui_copied_url": "Αντεγράφη η διεύθυνση OnionShare στον πίνακα", - "gui_copied_hidservauth_title": "Αντιγραμμένος HidServAuth", - "gui_copied_hidservauth": "Η σειρά HidServAuth αντεγράφη στον πίνακα", + "gui_copied_url_title": "Η διεύθυνση OnionShare αντιγράφτηκε", + "gui_copied_url": "Η διεύθυνση OnionShare αντιγράφτηκε στον πίνακα", + "gui_copied_hidservauth_title": "Το HidServAuth αντιγράφτηκε", + "gui_copied_hidservauth": "Το HidServAuth αντιγράφτηκε στον πίνακα", "gui_please_wait": "Ξεκινάμε... Κάντε κλικ για ακύρωση.", "gui_download_upload_progress_complete": "%p%, {0:s} πέρασαν.", "gui_download_upload_progress_starting": "{0:s}, %p% (υπολογισμός)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Όχι τόσο γρήγορα", - "gui_share_quit_warning": "Είστε στη διαδικασία αποστολής αρχείων. Είστε σίγουρος πως θέλετε να ακυρώσετε το OnionShare?", - "gui_receive_quit_warning": "Είστε στη διαδικασία παραλαβής αρχείων. Είστε σίγουρος πώς θέλετε να ακυρώσετε το OnionShare?", + "gui_share_quit_warning": "Αυτή τη στιγμή αποστέλλονται αρχεία. Είστε σίγουρος/η πως θέλετε να κλείσετε το OnionShare;", + "gui_receive_quit_warning": "Αυτή τη στιγμή παραλαμβάνονται αρχείων. Είστε σίγουρος/η πώς θέλετε να κλείσετε το OnionShare;", "gui_quit_warning_quit": "Έξοδος", "gui_quit_warning_dont_quit": "Ακύρωση", - "error_rate_limit": "Κάποιος προσπάθησε επανειλημμένα να μπει στη διεύθυνσή σας, το οποίο σημαίνει πως μπορεί να προσπαθεί να την μαντέψει, οπότε το OnionShare σταμάτησε τον server. Ξεκινήστε πάλι το μοίρασμα και στείλτε στον παραλήπτη μία νέα διεύθυνση για κοινοποίηση.", + "error_rate_limit": "Κάποιος/α έκανε πολλαπλές αποτυχημένες προσπάθειες να μπει στη διεύθυνσή σας, που ίσως σημαίνει ότι προσπαθεί να την μαντέψει. Γι' αυτό, το OnionShare σταμάτησε τον server. Ξεκινήστε πάλι το διαμοιρασμό και στείλτε στον/ην παραλήπτη/τρια μια νέα διεύθυνση για διαμοιρασμό.", "zip_progress_bar_format": "Συμπίεση: %p%", - "error_stealth_not_supported": "Για τη χρήση άδειας χρήστη, χρειάζεστε τουλάχιστον το Tor 0.2.9.1-alpha (ή τον Tor Browser 6.5) και το python3-stem 1.5.0.", + "error_stealth_not_supported": "Για τη χρήση εξουσιοδότησης πελάτη, χρειάζεστε τουλάχιστον το Tor 0.2.9.1-alpha (ή τον Tor Browser 6.5) και το python3-stem 1.5.0.", "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", "gui_settings_window_title": "Ρυθμίσεις", - "gui_settings_whats_this": " Τί είναι αυτό? ", - "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη", - "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα να επιλέξετε την αντιγραφή του HidServAuth σας.", + "gui_settings_whats_this": "Τί είναι αυτό;", + "gui_settings_stealth_option": "Χρήση εξουσιοδότησης πελάτη", + "gui_settings_stealth_hidservauth_string": "Έχοντας αποθηκεύσει το ιδιωτικό σας κλειδί για επαναχρησιμοποίηση, μπορείτε πλέον να επιλέξετε την αντιγραφή του HidServAuth σας.", "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", "gui_settings_autoupdate_timestamp": "Τελευταίος έλεγχος: {}", @@ -78,9 +78,9 @@ "gui_settings_general_label": "Γενικές ρυθμίσεις", "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης", "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης αρχείων μετά την αποστολή τους", - "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?", - "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare", - "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser", + "gui_settings_connection_type_label": "Πώς να συνδέεται το OnionShare με το Tor;", + "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor που είναι ενσωματωμένη στο OnionShare", + "gui_settings_connection_type_automatic_option": "Προσπάθεια αυτόματης παραμετροποίησης με τον Tor Browser", "gui_settings_connection_type_control_port_option": "Σύνδεση μέσω πύλης ελέγχου", "gui_settings_connection_type_socket_file_option": "Σύνδεση μέσω αρχείου μετάβασης", "gui_settings_connection_type_test_button": "Έλεγχος της σύνδεσης με το Tor", @@ -91,61 +91,61 @@ "gui_settings_authenticate_no_auth_option": "Καμία επαλήθευση ή επαλήθευση cookie", "gui_settings_authenticate_password_option": "Κωδικός", "gui_settings_password_label": "Κωδικός", - "gui_settings_tor_bridges": "Στήριξη Tor bridge", - "gui_settings_tor_bridges_no_bridges_radio_option": "Μην χρησιμοποιείτε bridges", - "gui_settings_tor_bridges_obfs4_radio_option": "Χρησιμοποιήστε ενσωματωμένα obfs4 pluggable transports", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Χρησμοποιήστε ενσωματωμένα obfs4 pluggable transports (απαιτείται obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports (απαιτεί obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Προσοχή: Τα meek_lite bridges επιβαρύνουν πολύ το Tor Project στη λειτουργία.

Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλα κανονικά bridges.", - "gui_settings_tor_bridges_custom_radio_option": "Χρήση κανονικών bridges", + "gui_settings_tor_bridges": "Υποστήριξη Tor bridge", + "gui_settings_tor_bridges_no_bridges_radio_option": "Να μη χρησιμοποιηθούν bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Να χρησιμοποιηθούν τα ενσωματωμένα obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Να χρησιμοποιηθούν τα ενσωματωμένα obfs4 pluggable transports (απαιτείται το obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Να χρησιμοποιηθουν τα ενσωματωμένα meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Να χρησιμοποιηθούν τα ενσωματωμένα meek_lite (Azure) pluggable transports (απαιτείται το obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Προσοχή: Τα meek_lite bridges επιβαρύνουν πολύ το Tor Project στη λειτουργία.

Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλων κανονικών bridges.", + "gui_settings_tor_bridges_custom_radio_option": "Χρήση παραμετροποιημένων bridges", "gui_settings_tor_bridges_custom_label": "Αποκτήστε bridges στο https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Δεν λειτούργησε κάποιο από τα bridges που προσθέσατε.\nΞαναελέγξτε τα ή επιλέξτε άλλα.", + "gui_settings_tor_bridges_invalid": "Δεν λειτούργησε κάποιο από τα bridges που προσθέσατε.\nΞαναελέγξτε τα ή προσθέστε άλλα.", "gui_settings_button_save": "Αποθήκευση", "gui_settings_button_cancel": "Ακύρωση", "gui_settings_button_help": "Βοήθεια", "gui_settings_autostop_timer_checkbox": "Χρήση χρονομέτρου αυτόματης διακοπής", - "gui_settings_autostop_timer": "Διακοπή μοιράσματος σε:", - "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.", - "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?", - "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.", - "settings_error_socket_file": "Ανέφικτη η σύνδεση με τον ελεγκτή Tor, κάνοντας χρήση αρχείου socket {}.", - "settings_error_auth": "Εγινε σύνδεση με {}:{}, αλλα δεν μπορεί να γίνει πιστοποίηση. Ισως δεν ειναι ενας ελεγκτής Tor?", - "settings_error_missing_password": "Εγινε σύνδεση με ελεγκτή Tor, αλλά απαιτείται κωδικός για πιστοποίηση.", - "settings_error_unreadable_cookie_file": "Εγινε σύνδεση με ελεγκτή Tor, αλλα ο κωδικός πιθανόν να είναι λάθος ή ο χρήστης δεν επιτρέπεται να διαβάζει αρχεία cookie.", - "settings_error_bundled_tor_not_supported": "Η έκδοση Tor που συνοδεύει το OnionShare δεν λειτουργεί σε περιβάλλον προγραμματιστή σε Windows ή macOS.", - "settings_error_bundled_tor_timeout": "Η σύνδεση με Tor αργεί αρκετά. Ισως δεν είστε συνδεδεμένοι στο Διαδίκτυο ή το ρολόι σας δεν ειναι συγχρονισμένο?", + "gui_settings_autostop_timer": "Διακοπή διαμοιρασμού σε:", + "settings_error_unknown": "Αποτυχία σύνδεσης στον ελεγκτή Tor, γιατί οι ρυθμίσεις σας δεν βγάζουν κανένα νόημα.", + "settings_error_automatic": "Αδυναμία σύνδεσης στον ελεγκτή Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο;", + "settings_error_socket_port": "Αδυναμία σύνδεσης στον ελεγκτή Tor στις {}:{}.", + "settings_error_socket_file": "Αποτυχία σύνδεσης στον ελεγκτή Tor χρησιμοποιώντας το αρχείο socket {}.", + "settings_error_auth": "Εγινε σύνδεση με {}:{}, αλλα δεν μπορεί να γίνει πιστοποίηση. Ίσως δεν είναι ελεγκτής Tor;", + "settings_error_missing_password": "Έγινε σύνδεση με τον ελεγκτή Tor, αλλά απαιτείται κωδικός για πιστοποίηση.", + "settings_error_unreadable_cookie_file": "Έγινε σύνδεση με τον ελεγκτή Tor, αλλα ο κωδικός πιθανόν να είναι λάθος, ή δεν επιτρέπεται στο χρήστη να διαβάζει αρχεία cookie.", + "settings_error_bundled_tor_not_supported": "Η χρήση της έκδοσης Tor που περιέχεται στο OnionShare δεν είναι συμβατή με το περιβάλλον προγραμματιστή σε Windows ή macOS.", + "settings_error_bundled_tor_timeout": "Η σύνδεση με Tor αργεί αρκετά. Ισως δεν είστε συνδεδεμένοι στο Διαδίκτυο ή το ρολόι του συστήματος δεν ειναι σωστό;", "settings_error_bundled_tor_broken": "Το OnionShare δεν μπορεί να συνδεθεί με το Tor στο παρασκήνιο:\n{}", "settings_test_success": "Εγινε σύνδεση με τον ελεγκτή Tor.\n\nΕκδοση Tor: {}\nΥποστηρίζει εφήμερες υπηρεσίες onion: {}.\nΥποστηρίζει πιστοποίηση πελάτη: {}.\nΥποστηρίζει νέας γενιάς διευθύνσεις .onion: {}.", "error_tor_protocol_error": "Υπήρξε σφάλμα με το Tor: {}", "error_tor_protocol_error_unknown": "Υπήρξε άγνωστο σφάλμα με το Tor", "error_invalid_private_key": "Αυτο το ιδιωτικό κλειδί δεν υποστηρίζεται", - "connecting_to_tor": "Γίνεται σύνδεση με το δίκτυο Tor", + "connecting_to_tor": "Γίνεται σύνδεση στο δίκτυο Tor", "update_available": "Βγήκε ενα νέο OnionShare. Κάντε κλικ εδώ για να το λάβετε.

Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.", - "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση δεν αναγνωρίζεται '{}'…", - "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένοι στο Tor ή ο ιστότοπος OnionShare είναι κάτω?", - "update_not_available": "Εχετε την πιό πρόσφατη έκδοση OnionShare.", - "gui_tor_connection_ask": "Να ανοίξετε τις ρυθμίσεις για να επιλύσετε την σύνδεση με το Tor?", + "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος του OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση είναι η μη κατανοητή: '{}'…", + "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένος/η στο Tor ή ο ιστότοπος OnionShare έχει πέσει;", + "update_not_available": "Έχετε την πιό πρόσφατη έκδοση του OnionShare.", + "gui_tor_connection_ask": "Άνοιγμα των ρυθμίσεων για να επιλύσετε την σύνδεση με το Tor;", "gui_tor_connection_ask_open_settings": "Ναι", "gui_tor_connection_ask_quit": "Εξοδος", - "gui_tor_connection_error_settings": "Προσπαθήστε να αλλάξετε τον τρόπο σύνδεσης του OnionShare, με το δίκτυο Tor, από τις ρυθμίσεις.", - "gui_tor_connection_canceled": "Δεν μπόρεσε να γίνει σύνδεση με Tor.\n\nΕλέγξτε ότι είστε συνδεδεμένοι στο Διαδίκτυο, επανεκινήστε το OnionShare και ρυθμίστε την σύνδεση με το Tor.", - "gui_tor_connection_lost": "Εγινε αποσύνδεση απο το Tor.", - "gui_server_started_after_autostop_timer": "Το χρονόμετρο αυτόματης διακοπής τελείωσε πριν την εκκίνηση του server.\nΠαρακαλείστε να κάνετε εναν νέο διαμοιρασμό.", - "gui_server_autostop_timer_expired": "Το χρονόμετρο αυτόματης διακοπής έχει ήδη τελειώσει.\nΠαρακαλείστε να το ανανεώσετε για να ξεκινήσετε τον διαμοιρασμό.", - "share_via_onionshare": "Κάντε το OnionShare", - "gui_use_legacy_v2_onions_checkbox": "Χρηση \"παραδοσιακών\" διευθύνσεων", + "gui_tor_connection_error_settings": "Προσπαθήστε να αλλάξετε τον τρόπο σύνδεσης του OnionShare με το δίκτυο Tor από τις ρυθμίσεις.", + "gui_tor_connection_canceled": "Δεν μπόρεσε να γίνει σύνδεση στο Tor.\n\nΕλέγξτε ότι είστε συνδεδεμένος/η στο Διαδίκτυο, επανεκκινήστε το OnionShare και ρυθμίστε την σύνδεση με το Tor.", + "gui_tor_connection_lost": "Έγινε αποσύνδεση από το Tor.", + "gui_server_started_after_autostop_timer": "Το χρονόμετρο αυτόματης διακοπής τελείωσε πριν την εκκίνηση του server. Παρακαλώ κάντε ένα νέο διαμοιρασμό.", + "gui_server_autostop_timer_expired": "Το χρονόμετρο αυτόματης διακοπής έχει ήδη τελειώσει. Παρακαλώ ανανεώστε το για να ξεκινήσετε το διαμοιρασμό.", + "share_via_onionshare": "Κάντε OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Χρήση \"παραδοσιακών\" διευθύνσεων", "gui_save_private_key_checkbox": "Χρήση μόνιμης διεύθυνσης", - "gui_share_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να κατεβάσει τα αρχεία σας με χρήση Φυλλομετρητη Tor: ", - "gui_receive_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να ανεβάσει αρχεία στον υπολογιστή σας με χρήση του Φυλλομετρητή Tor: ", - "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

Οποιοσδήποτε μετέπειτα διαμοιρασμός κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", - "gui_url_label_stay_open": "Αυτος ο διαμοιρασμός δεν έχει auto-stop.", - "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει με την πρώτη λήψη.", - "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

Οποιοσδήποτε μετέπειτα διαμοιρασμός θα κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_share_url_description": "Οποιοσδήποτε με αυτή τη διεύθυνση OnionShare μπορεί να κατεβάσει τα αρχεία σας χρησιμοποιώντας το Tor Browser: ", + "gui_receive_url_description": "Οποιοσδήποτε με αυτή τη διεύθυνση OnionShare, μπορεί να ανεβάσει αρχεία στον υπολογιστή σας χρησιμοποιώντας το Tor Browser: ", + "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν θα λήξει αυτόματα.

Οποιοσδήποτε επακόλουθος διαμοιρασμός θα επαναχρησιμοποιήσει αυτή τη διεύθυνση. (Για να χρησιμοποιήσετε διευθύνσεις μιας χρήσης, απενεργοποιήστε τη λειτουργία \"Χρήση μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_url_label_stay_open": "Αυτός ο διαμοιρασμός δε λήγει αυτόματα.", + "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει μετά την πρώτη λήψη.", + "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν θα λήξει αυτόματα.

Οποιοσδήποτε επακόλουθος διαμοιρασμός θα επαναχρησιμοποιήσει αυτή τη διεύθυνση. (Για να χρησιμοποιήσετε διευθύνσεις μιας χρήσης, απενεργοποιήστε τη λειτουργία \"Χρήση μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", "gui_status_indicator_share_stopped": "Ετοιμο για διαμοιρασμό", "gui_status_indicator_share_working": "Ξεκινάει…", "gui_status_indicator_share_started": "Διαμοιράζει", - "gui_status_indicator_receive_stopped": "Ετοιμο για λήψη", + "gui_status_indicator_receive_stopped": "Έτοιμο για λήψη", "gui_status_indicator_receive_working": "Ξεκινάει…", "gui_status_indicator_receive_started": "Γίνεται λήψη", "gui_file_info": "{} αρχεία, {}", @@ -157,10 +157,10 @@ "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", "receive_mode_warning": "Προσοχή: η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας. Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", - "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας.

Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", - "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει", + "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει σε τρίτους/ες να ανεβάζουν αρχεία στον υπολογιστή σας.

Μερικά αρχεία μπορούν δυνητικά να αποκτήσουν έλεγχο του υπολογιστή σας εάν τα ανοίξετε. Να ανοίγετε αρχεία μόνο από άτομα που εμπιστεύεστε ή εάν ξέρετε τι κάνετε.", + "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει τώρα", "receive_mode_received_file": "Ελήφθη: {}", - "gui_mode_share_button": "Διαμοίρασε αρχεία", + "gui_mode_share_button": "Διαμοιρασμός αρχείων", "gui_mode_receive_button": "Λήψη αρχείων", "gui_settings_receiving_label": "Ρυθμίσεις λήψης", "gui_settings_downloads_label": "", @@ -181,50 +181,50 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "Δεν μπορεί να ανοιχτεί ο φάκελος γιατί το nautilus δεν είναι διαθέσιμο. Το αρχείο είναι εδω: {}", "gui_settings_language_label": "Προτιμώμενη γλώσσα", - "gui_settings_language_changed_notice": "Επανεκινήστε το OnionShare για να ενεργοποιηθεί η αλλαγή γλώσσας.", + "gui_settings_language_changed_notice": "Επανεκκινήστε το OnionShare για να γίνει η αλλαγή γλώσσας.", "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος", "gui_add_files": "Προσθέστε αρχεία", "gui_add_folder": "Προσθέστε φάκελο", - "gui_connect_to_tor_for_onion_settings": "Συνδεθείτε με Tor για να δείτε τις ρυθμίσεις της υπηρεσίας onion", - "error_cannot_create_data_dir": "Δεν ήταν δυνατό να δημιουργηθεί φάκελος δεδομένων OnionShare: {}", + "gui_connect_to_tor_for_onion_settings": "Συνδεθείτε στο Tor για να δείτε τις ρυθμίσεις της υπηρεσίας onion", + "error_cannot_create_data_dir": "Δεν μπόρεσε να δημιουργηθεί φάκελος δεδομένων OnionShare: {}", "receive_mode_data_dir": "Τα αρχεία που στάλθηκαν σε εσας εμφανίζοντε στον φάκελο: {}", - "gui_settings_data_dir_label": "Αποθήκευσε αρχεία στο", + "gui_settings_data_dir_label": "Αποθήκευση αρχείων σε", "gui_settings_data_dir_browse_button": "Περιήγηση", "systray_page_loaded_message": "Η διεύθυνση OnionShare φορτώθηκε", "systray_share_started_title": "Ο διαμοιρασμός ξεκίνησε", - "systray_share_started_message": "Ξεκίνησε η αποστολή αρχείων σε κάποιον", + "systray_share_started_message": "Η αποστολή αρχείων σε κάποιον/α ξεκίνησε", "systray_share_completed_title": "Ο διαμοιρασμός ολοκληρώθηκε", - "systray_share_completed_message": "Ολοκληρώθηκε η αποστολή αρχείων", + "systray_share_completed_message": "Η αποστολή αρχείων ολοκληρώθηκε", "systray_share_canceled_title": "Ο διαμοιρασμός ακυρώθηκε", "systray_share_canceled_message": "Κάποιος ακύρωσε την λήψη των αρχείων σας", "systray_receive_started_title": "Η λήψη ξεκίνησε", - "systray_receive_started_message": "Κάποιος σας στέλνει αρχεία", + "systray_receive_started_message": "Κάποιος/α σας στέλνει αρχεία", "gui_all_modes_history": "Ιστορικό", "gui_all_modes_clear_history": "Καθαρισμός όλων", "gui_all_modes_transfer_started": "Ξεκινησε {}", "gui_all_modes_transfer_finished_range": "Μεταφέρθηκαν {} - {}", - "gui_all_modes_transfer_finished": "Μεταφέρθηκαν {} - {}", - "gui_all_modes_progress_complete": "%p%, {0:s} διάρκεια.", + "gui_all_modes_transfer_finished": "Μεταφέρθηκαν {}", + "gui_all_modes_progress_complete": "%p%, πέρασαν {0:s}.", "gui_all_modes_progress_starting": "{0:s}, %p% (γίνεται υπολογισμός)", - "gui_all_modes_progress_eta": "{0:s}, εκτίμηση: {1:s}, %p%", - "gui_share_mode_no_files": "Δεν Στάλθηκαν Αρχεία Ακόμα", - "gui_share_mode_autostop_timer_waiting": "Αναμένοντας την ολοκλήρωση της αποστολής", - "gui_receive_mode_no_files": "Δεν Εγινε Καμμία Λήψη Αρχείων Ακόμα", - "gui_receive_mode_autostop_timer_waiting": "Αναμένοντας την ολοκλήρωση της λήψης", + "gui_all_modes_progress_eta": "{0:s}, Εκτιμώμενος χρόνος: {1:s}, %p%", + "gui_share_mode_no_files": "Δεν στάλθηκαν ακόμα αρχεία", + "gui_share_mode_autostop_timer_waiting": "Αναμένεται η ολοκλήρωση της αποστολής", + "gui_receive_mode_no_files": "Δεν έχει γίνει λήψη αρχείων ακόμα", + "gui_receive_mode_autostop_timer_waiting": "Αναμένεται η ολοκλήρωση της λήψης", "gui_settings_onion_label": "Ρυθμίσεις Onion", "gui_all_modes_transfer_canceled_range": "Ακυρώθηκε {} - {}", "gui_all_modes_transfer_canceled": "Ακυρώθηκε {}", "gui_stop_server_autostop_timer_tooltip": "Το χρονόμετρο αυτόματης διακοπής λήγει σε {}", - "gui_start_server_autostart_timer_tooltip": "Το χρονόμετρο αυτόματης έναρξης λήγει σε {}", + "gui_start_server_autostart_timer_tooltip": "Το χρονόμετρο αυτόματης εκκίνησης λήγει σε {}", "gui_waiting_to_start": "Προγραμματισμένο να ξεκινήσει σε {}. Πατήστε για ακύρωση.", "gui_settings_autostart_timer_checkbox": "Χρήση χρονομέτρου αυτόματης έναρξης", - "gui_settings_autostart_timer": "Εκκίνηση μοιράσματος σε:", - "gui_server_autostart_timer_expired": "Η προγραμματισμένη ώρα έχει ήδη παρέλθει.\nΠαρακαλείστε να την ανανεώσετε για να ξεκινήσετε τον διαμοιρασμό.", + "gui_settings_autostart_timer": "Εκκίνηση διαμοιρασμού σε:", + "gui_server_autostart_timer_expired": "Η προγραμματισμένη ώρα έχει ήδη παρέλθει. Παρακαλώ ανανεώστε την για να ξεκινήσετε το διαμοιρασμό.", "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Η ώρα αυτόματης διακοπής δεν μπορεί να είναι ίδια ή νωρίτερα από την ώρα αυτόματης έναρξης.Παρακαλείστε να την ανανεώσετε για να ξεκινήσετε τον διαμοιρασμό.", "gui_status_indicator_share_scheduled": "Προγραμματισμένο…", "gui_status_indicator_receive_scheduled": "Προγραμματισμένο…", - "days_first_letter": "μ", - "hours_first_letter": "ω", + "days_first_letter": "ημ", + "hours_first_letter": "ώ", "minutes_first_letter": "λ", "seconds_first_letter": "δ" } diff --git a/share/locale/es.json b/share/locale/es.json index b5110f6f..11b3e246 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -95,7 +95,7 @@ "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_autostop_timer": "El temporizador de auto-parada expiró antes de que se iniciara el servidor.\nPor favor crea una nueva conexión compartida.", + "gui_server_started_after_autostop_timer": "El temporizador de auto-parada expiró antes de que se iniciara el servidor.\nPor favor crea un nuevo recurso compartido.", "gui_server_autostop_timer_expired": "El temporizador de auto-parada ya expiró.\nPor favor actualízarlo para comenzar a compartir.", "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", diff --git a/share/locale/fr.json b/share/locale/fr.json index d066ba3d..28cf4ee9 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -43,9 +43,9 @@ "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", "gui_settings_close_after_first_download_option": "Arrêter le partage après envoi des fichiers", "gui_settings_connection_type_label": "Comment OnionShare devrait-il se connecter à Tor ?", - "gui_settings_connection_type_control_port_option": "Se connecter en utilisant le port de contrôle", - "gui_settings_connection_type_socket_file_option": "Se connecter en utilisant un fichier socket", - "gui_settings_socket_file_label": "Fichier socket", + "gui_settings_connection_type_control_port_option": "Se connecter en utilisant un port de contrôle", + "gui_settings_connection_type_socket_file_option": "Se connecter en utilisant un fichier d’interface de connexion", + "gui_settings_socket_file_label": "Fichier d’interface de connexion", "gui_settings_socks_label": "Port SOCKS", "gui_settings_authenticate_no_auth_option": "Pas d’authentification ou authentification par témoin", "gui_settings_authenticate_password_option": "Mot de passe", @@ -55,15 +55,15 @@ "gui_settings_button_cancel": "Annuler", "gui_settings_button_help": "Aide", "gui_settings_autostop_timer": "Arrêter le partage à :", - "connecting_to_tor": "Connexion au réseau Tor", + "connecting_to_tor": "Connexion au réseau Tor", "help_config": "Emplacement du fichier personnalisé de configuration JSON (facultatif)", "large_filesize": "Avertissement : envoyer un gros partage peut prendre des heures", "gui_copied_hidservauth": "La ligne HidServAuth a été copiée dans le presse-papiers", "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p %", - "error_ephemeral_not_supported": "OnionShare exige au moins Tor 0.2.7.1 et python3-stem 1.4.0.", + "error_ephemeral_not_supported": "OnionShare exige au moins Tor 0.2.7.1 et python3-stem 1.4.0.", "help_autostop_timer": "Arrêter le partage après un certain nombre de secondes", - "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", + "gui_tor_connection_error_settings": "Dans les paramètres, essayez de changer la façon dont OnionShare se connecte au réseau Tor.", "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", "gui_share_stop_server_autostop_timer": "Arrêter le partage ({})", "systray_upload_started_title": "Envoi OnionShare démarré", @@ -79,21 +79,21 @@ "gui_settings_general_label": "Paramètres généraux", "gui_settings_sharing_label": "Paramètres de partage", "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor intégrée dans OnionShare", - "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le Navigateur Tor", + "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le Navigateur Tor", "gui_settings_connection_type_test_button": "Tester la connexion à Tor", "gui_settings_control_port_label": "Port de contrôle", - "gui_settings_authenticate_label": "Paramètres d’authentification de Tor", + "gui_settings_authenticate_label": "Paramètres d’authentification à Tor", "gui_settings_tor_bridges": "Prise en charge des ponts de Tor", "gui_settings_tor_bridges_custom_radio_option": "Utiliser des ponts personnalisés", "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des ponts sur https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "Aucun des ponts que vous avez ajoutés ne fonctionne.\nVérifiez-les de nouveau ou ajoutez-en d’autres.", "settings_error_unknown": "Impossible de se connecter au contrôleur Tor, car vos paramètres sont incorrects.", - "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Le Navigateur Tor (proposé sur torproject.org) fonctionne-t-il en arrière-plan ?", - "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", - "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier socket {}.", + "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Le Navigateur Tor (téléchargeable sur torproject.org) fonctionne-t-il en arrière-plan ?", + "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", + "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier d’interface de connexion {}.", "settings_error_auth": "Vous êtes connecté à {}:{}, mais il est impossible de s’authentifier. Est-ce bien un contrôleur Tor ?", - "settings_error_missing_password": "Vous êtes connecté au contrôleur Tor, mais un mot de passe d’authentification est exigé.", - "settings_error_unreadable_cookie_file": "Vous êtes connecté au contrôleur Tor, mais le mot de passe est peut-être erroné ou votre utilisateur n’est pas autorisé à lire le fichier témoin.", + "settings_error_missing_password": "Vous êtes connecté au contrôleur Tor, mais un mot de passe d’authentification est exigé.", + "settings_error_unreadable_cookie_file": "Vous êtes connecté au contrôleur Tor, mais le mot de passe est peut-être erroné ou votre utilisateur n’est pas autorisé à lire le fichier témoin.", "settings_error_bundled_tor_not_supported": "La version de Tor intégrée dans OnionShare ne fonctionne pas en mode développeur sous Windows ou macOS.", "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Êtes-vous connecté à Internet ? Votre horloge système est-elle mal réglée ?", "settings_error_bundled_tor_broken": "OnionShare n’a pas réussi à se connecter à Tor en arrière-plan :\n{}", @@ -107,7 +107,7 @@ "gui_tor_connection_lost": "Vous êtes déconnecté de Tor.", "share_via_onionshare": "Partager avec OnionShare", "gui_save_private_key_checkbox": "Utiliser une adresse persistante", - "gui_share_url_description": "Quiconque possède cette adresse OnionShare peut télécharger vos fichiers en utilisant le Navigateur Tor : ", + "gui_share_url_description": "Quiconque possède cette adresse OnionShare peut télécharger vos fichiers en utilisant le Navigateur Tor : ", "gui_receive_url_description": "Quiconque possède cette adresse OnionShare peut téléverser des fichiers vers votre ordinateur en utilisant le Navigateur Tor : ", "gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.

Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", "gui_url_label_stay_open": "Ce partage ne s’arrêtera pas automatiquement.", @@ -153,14 +153,14 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Quelqu’un a effectué trop de tentatives échouées sur votre adresse, ce qui signifie que cette personne pourrait essayer de la deviner. C’est pourquoi OnionShare a arrêté le serveur. Redémarrez le partage et envoyez au destinataire une nouvelle adresse pour partager.", - "error_stealth_not_supported": "Pour utiliser l’autorisation client, Tor 0.2.9.1-alpha (ou le Navigateur Tor 6.5) et python3-stem 1.5.0 ou versions ultérieures sont exigés.", + "error_stealth_not_supported": "Pour utiliser l’autorisation client, Tor 0.2.9.1-alpha (ou le Navigateur Tor 6.5) et python3-stem 1.5.0 ou versions ultérieures sont exigés.", "gui_settings_stealth_option": "Utiliser l’autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée pour qu’elle puisse être réutilisée,\nvous pouvez maintenant cliquer pour copier votre HidServAuth.", "gui_settings_autoupdate_check_button": "Vérifier s’il existe une nouvelle version", "settings_test_success": "Vous êtes connecté au contrôleur Tor.\n\nVersion de Tor : {}\nPrend en charge les services onion éphémères : {}.\nPrend en charge l’authentification client : {}.\nPrend en charge la nouvelle génération d’adresses .onion : {}.", "update_error_check_error": "Impossible de vérifier l’existence d’une mise à jour : le site Web d’OnionShare indique que la dernière version ne peut pas être reconnue '{}'…", - "update_error_invalid_latest_version": "Impossible de vérifier l’existence d’une mise à jour : êtes-vous bien connecté à Tor, le site Web d’OnionShare est-il hors service ?", + "update_error_invalid_latest_version": "Impossible de vérifier la présence d’une mise à jour : êtes-vous bien connecté à Tor ou le site Web d’OnionShare est-il hors service ?", "gui_tor_connection_ask": "Ouvrir les paramètres pour résoudre le problème de connexion à Tor ?", "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nAssurez-vous d’être connecté à Internet, puis rouvrez OnionShare et configurez sa connexion à Tor.", "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses héritées", @@ -178,7 +178,7 @@ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (exige obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (exige obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor par les transports obfs4 ou autres ponts normaux.", + "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor par les transports obfs4 ou autres ponts normaux.", "gui_settings_autostop_timer_checkbox": "Utiliser une minuterie d’arrêt automatique", "gui_server_started_after_autostop_timer": "La minuterie d’arrêt automatique est arrivée au bout de son délai avant le démarrage du serveur.\nVeuillez mettre en place un nouveau partage.", "gui_server_autostop_timer_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai.\nVeuillez la modifier pour commencer le partage.", diff --git a/share/locale/hu.json b/share/locale/hu.json index e84d0e64..c5ee3299 100644 --- a/share/locale/hu.json +++ b/share/locale/hu.json @@ -1,13 +1,13 @@ { "config_onion_service": "", - "preparing_files": "", + "preparing_files": "Fájlok tömörítése.", "give_this_url": "", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", "ctrlc_to_stop": "", "not_a_file": "", - "not_a_readable_file": "", + "not_a_readable_file": "{0:s} nem egy olvasható fájl.", "no_available_port": "", "other_page_loaded": "", "close_on_autostop_timer": "", diff --git a/share/locale/it.json b/share/locale/it.json index 8779cf2e..1ad1e1b5 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -16,19 +16,19 @@ "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Arresta la condivisione", - "gui_copy_url": "Copia la URL", + "gui_copy_url": "Copia Indirizzo", "gui_downloads": "Cronologia dei Download", "gui_canceled": "Annullato", - "gui_copied_url": "URL Copiato negli appunti", - "gui_please_wait": "Avviato... Cliccare per interrompere.", - "zip_progress_bar_format": "Compressione in corso: %p%", + "gui_copied_url": "Indirizzo OnionShare copiato negli appunti", + "gui_please_wait": "Avviato... Cliccare per annullare.", + "zip_progress_bar_format": "Compressione al: %p%", "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.", "give_this_url_stealth": "Dai questo indirizzo e la linea HidServAuth al destinatario:", "give_this_url_receive": "Dai questo indirizzo al mittente:", "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth con il mittente:", "not_a_readable_file": "{0:s} non è un file leggibile.", "no_available_port": "Non è stato possibile trovare alcuna porta per avviare il servizio onion", - "close_on_autostop_timer": "Fermato perché il timer di arresto automatico è scaduto", + "close_on_autostop_timer": "Arrestato per tempo scaduto", "timeout_download_still_running": "download in corso, attendere", "systray_menu_exit": "Termina", "systray_download_started_title": "Download con OnionShare avviato", @@ -44,25 +44,25 @@ "help_config": "Specifica il percorso del file di configurazione del JSON personalizzato", "gui_share_stop_server_autostop_timer": "Arresta la condivisione ({})", "gui_share_stop_server_autostop_timer_tooltip": "Il timer si arresterà tra {}", - "gui_receive_start_server": "Inizia la ricezione", - "gui_receive_stop_server": "Arresta la ricezione", + "gui_receive_start_server": "Avvia modalità Ricezione", + "gui_receive_stop_server": "Arresta modalità Ricezione", "gui_receive_stop_server_autostop_timer": "Interrompi la ricezione ({} rimanenti)", "gui_receive_stop_server_autostop_timer_tooltip": "Il timer termina tra {}", "gui_copy_hidservauth": "Copia HidServAuth", "gui_no_downloads": "Ancora nessun Download", "gui_copied_url_title": "Indirizzo OnionShare copiato", "gui_copied_hidservauth_title": "HidServAuth copiato", - "gui_copied_hidservauth": "HidServAuth copiato negli appunti", + "gui_copied_hidservauth": "Linea HidServAuth copiata negli appunti", "gui_download_upload_progress_complete": "%p%, {0:s} trascorsi.", "gui_download_upload_progress_starting": "{0:s}, %p% (calcolato)", "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org", "gui_quit_title": "Non così in fretta", - "gui_share_quit_warning": "Stai per inviare dei file. Sei sicuro di voler uscire da OnionShare?", - "gui_receive_quit_warning": "Stai per ricevere dei file, vuoi davvero terminare e chiudere OnionShare?", + "gui_share_quit_warning": "Stai inviando dei file. Sei sicuro di voler uscire da OnionShare?", + "gui_receive_quit_warning": "Stai ricevendo dei file, vuoi davvero terminare OnionShare?", "gui_quit_warning_quit": "Esci", - "gui_quit_warning_dont_quit": "Cancella", - "error_rate_limit": "Qualcuno ha tentato troppe volte di accedere al tuo indirizzo, questo potrebbe comprometterne la sicurezza quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL.", + "gui_quit_warning_dont_quit": "Annulla", + "error_rate_limit": "Qualcuno ha tentato troppe volte di accedere al tuo indirizzo, questo può significare stiano tentando di indovinato. OnionShare ha fermato il server. Riavvia la condivisione e invia al tuo contatto il nuovo indirizzo.", "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno della versione di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Impostazioni", @@ -74,9 +74,9 @@ "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione", "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}", "gui_settings_autoupdate_timestamp_never": "Mai", - "gui_settings_autoupdate_check_button": "Controlla per una nuova versione", + "gui_settings_autoupdate_check_button": "Controlla se esiste una nuova versione", "gui_settings_general_label": "Impostazioni generali", - "gui_settings_sharing_label": "Sto condividendo le impostazioni", + "gui_settings_sharing_label": "Impostazioni di condivisione", "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo che i file sono stati inviati", "gui_settings_connection_type_label": "Come si dovrebbe connettere OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", @@ -85,22 +85,22 @@ "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", "timeout_upload_still_running": "In attesa del completamento dell'upload", - "gui_add_files": "Aggiungi Files", - "gui_add_folder": "Aggiungi una cartella", - "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo", - "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket", - "gui_settings_connection_type_test_button": "Prova la connessione Tor", + "gui_add_files": "Aggiungi File", + "gui_add_folder": "Aggiungi cartella", + "gui_settings_connection_type_control_port_option": "Connetti usando la porta di controllo", + "gui_settings_connection_type_socket_file_option": "Connetti usando il file di socket", + "gui_settings_connection_type_test_button": "Verifica la connessione a Tor", "gui_settings_socket_file_label": "File di socket", "gui_settings_socks_label": "Porta SOCKS", "gui_settings_authenticate_label": "Impostazioni di autenticazione Tor", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", "gui_settings_control_port_label": "Porta di controllo", - "gui_settings_authenticate_no_auth_option": "Nessuna autenticazione o autenticazione tramite cookie", + "gui_settings_authenticate_no_auth_option": "Nessuna autenticazione o cookie di autenticazione", "gui_settings_tor_bridges": "Supporto bridge Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "Non usare i bridge", - "gui_settings_tor_bridges_obfs4_radio_option": "Usare i trasporti obfs4 integrati selezionabili", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usare i trasporti obfs4 integrati selezionabili (richiede obfs4proxy)", + "gui_settings_tor_bridges_obfs4_radio_option": "Usare il trasporto attivabile obfs4 integrato", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usare i trasporti collegabile obfs4 integrati (richiede obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usare i trasporti integrati meek_lite (Azure) selezionabili", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usare i trasporti integrati meek_lite (Azure) selezionabili (richiede obfs4proxy)", "gui_settings_meek_lite_expensive_warning": "Attenzione: i bridge meek_lite sono molto pesanti per l'esecuzione del progetto Tor.

Da usare solo se impossibile connettersi a Tor direttamente, con obfs4, o altri bridge normali.", @@ -213,9 +213,9 @@ "gui_share_mode_autostop_timer_waiting": "In attesa di finire l'invio", "gui_receive_mode_no_files": "Nessun file ricevuto ancora", "gui_receive_mode_autostop_timer_waiting": "In attesa di finire la ricezione", - "gui_stop_server_autostop_timer_tooltip": "Il timer di arresto automatico termina a {}", - "gui_start_server_autostart_timer_tooltip": "Il timer a partenza automatica finisce a {}", - "gui_waiting_to_start": "Programmato per partire in {}. Clicca per cancellare.", + "gui_stop_server_autostop_timer_tooltip": "Il timer Auto-stop terminerà alle {}", + "gui_start_server_autostart_timer_tooltip": "Il timer Auto-start termina alle {}", + "gui_waiting_to_start": "Programmato per avviarsi in {}. Clicca per annullare.", "gui_settings_autostart_timer_checkbox": "Usa il timer a partenza automatica", "gui_settings_autostart_timer": "Inizia la condivisione a:", "gui_server_autostart_timer_expired": "L'ora pianificata è già passata. Si prega di aggiornarlo per iniziare la condivisione.", diff --git a/share/locale/ms.json b/share/locale/ms.json index 77a441e8..8fda843a 100644 --- a/share/locale/ms.json +++ b/share/locale/ms.json @@ -22,10 +22,10 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", + "gui_add": "Tambah", "gui_add_files": "", "gui_add_folder": "", - "gui_delete": "", + "gui_delete": "Padam", "gui_choose_items": "", "gui_share_start_server": "", "gui_share_stop_server": "", @@ -47,22 +47,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Keluar", + "gui_quit_warning_dont_quit": "Batal", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Tetapan", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Tidak pernah", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Tetapan umum", "gui_settings_onion_label": "", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", @@ -77,8 +77,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Kara laluan", + "gui_settings_password_label": "Kara laluan", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -89,8 +89,8 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", + "gui_settings_button_save": "Simpan", + "gui_settings_button_cancel": "Batal", "gui_settings_button_help": "", "gui_settings_autostop_timer_checkbox": "", "gui_settings_autostop_timer": "", @@ -114,8 +114,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Ya", + "gui_tor_connection_ask_quit": "Keluar", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -136,7 +136,7 @@ "gui_status_indicator_share_started": "", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "Penerimaan", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -151,12 +151,12 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_data_dir_label": "", - "gui_settings_data_dir_browse_button": "", + "gui_settings_data_dir_browse_button": "Lungsur", "gui_settings_public_mode_checkbox": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", "gui_settings_language_changed_notice": "", - "systray_menu_exit": "", + "systray_menu_exit": "Keluar", "systray_page_loaded_title": "", "systray_page_loaded_message": "", "systray_share_started_title": "", @@ -167,7 +167,7 @@ "systray_share_canceled_message": "", "systray_receive_started_title": "", "systray_receive_started_message": "", - "gui_all_modes_history": "", + "gui_all_modes_history": "Sejarah", "gui_all_modes_clear_history": "", "gui_all_modes_transfer_started": "", "gui_all_modes_transfer_finished_range": "", diff --git a/share/locale/nl.json b/share/locale/nl.json index 6ca041e5..79e260f2 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -8,8 +8,8 @@ "not_a_readable_file": "{0:s} is geen leesbaar bestand.", "no_available_port": "Er is geen poort beschikbaar om de onion-dienst op te starten", "other_page_loaded": "Adres geladen", - "close_on_autostop_timer": "Gestopt omdat de automatische time-out bereikt is", - "closing_automatically": "Gestopt omdat de download is afgerond", + "close_on_autostop_timer": "Gestopt omdat de automatische stop-timer afgelopen was", + "closing_automatically": "Gestopt omdat de overdracht klaar is", "timeout_download_still_running": "Bezig met wachten op afronden van download", "large_filesize": "Waarschuwing: het versturen van grote bestanden kan uren duren", "systray_menu_exit": "Afsluiten", diff --git a/share/locale/ro.json b/share/locale/ro.json index 36daf7dc..e0b4f8bc 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -1,19 +1,19 @@ { "config_onion_service": "", - "preparing_files": "", + "preparing_files": "Comprima fisierele.", "give_this_url": "", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", "ctrlc_to_stop": "", "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "not_a_readable_file": "Fisierul {0:s} nu poate fi citit.", + "no_available_port": "Nu a putut fi gasit un port liber pentru a porni serviciul \"ONION\".", + "other_page_loaded": "Adresa a fost incarcata.", "close_on_autostop_timer": "", - "closing_automatically": "", + "closing_automatically": "Oprit pentru ca transferul s-a incheiat cu succes.", "timeout_download_still_running": "", - "large_filesize": "", + "large_filesize": "Avertisment: Transferul unui volum mare de date poate dura ore.", "systray_menu_exit": "Închidere", "systray_download_started_title": "", "systray_download_started_message": "", diff --git a/share/locale/sv.json b/share/locale/sv.json index 34a718db..17facc8c 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -84,7 +84,7 @@ "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Anslut med kontrollport", "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", - "gui_settings_connection_type_test_button": "Provningsanslutning till Tor", + "gui_settings_connection_type_test_button": "Testa anslutning till Tor", "gui_settings_control_port_label": "Kontrollport", "gui_settings_socket_file_label": "Socket-fil", "gui_settings_socks_label": "SOCKS-port", @@ -105,7 +105,7 @@ "gui_settings_button_save": "Spara", "gui_settings_button_cancel": "Avbryt", "gui_settings_button_help": "Hjälp", - "gui_settings_autostop_timer_checkbox": "Använd den automatiska stopp-tidtagaren", + "gui_settings_autostop_timer_checkbox": "Använd automatisk stopp-tidtagare", "gui_settings_autostop_timer": "Stoppa delningen vid:", "settings_error_unknown": "Kan inte ansluta till Tor-regulatorn eftersom dina inställningar inte är vettiga.", "settings_error_automatic": "Kunde inte ansluta till Tor-regulatorn. Körs Tor Browser (tillgänglig från torproject.org) i bakgrunden?", @@ -132,7 +132,7 @@ "gui_tor_connection_error_settings": "Försök ändra hur OnionShare ansluter till Tor-nätverket i inställningarna.", "gui_tor_connection_canceled": "Kunde inte ansluta till Tor.\n\nSe till att du är ansluten till Internet, öppna sedan OnionShare och ställ in anslutningen till Tor.", "gui_tor_connection_lost": "Frånkopplad från Tor.", - "gui_server_started_after_autostop_timer": "Tiden för den automatiska stopp-timern löpte ut innan servern startade.\nVänligen gör en ny delning.", + "gui_server_started_after_autostop_timer": "Tiden för den automatiska stopp-tidtagaren löpte ut innan servern startade.\nVänligen gör en ny delning.", "gui_server_autostop_timer_expired": "Tiden för den automatiska stopp-tidtagaren löpte redan ut.\nUppdatera den för att börja dela.", "share_via_onionshare": "Dela den med OnionShare", "gui_use_legacy_v2_onions_checkbox": "Använd äldre adresser", @@ -163,7 +163,7 @@ "receive_mode_received_file": "Mottaget: {}", "gui_mode_share_button": "Dela filer", "gui_mode_receive_button": "Ta emot filer", - "gui_settings_receiving_label": "Mottagning-inställningar", + "gui_settings_receiving_label": "Mottagningsinställningar", "gui_settings_downloads_label": "Spara filer till", "gui_settings_downloads_button": "Bläddra", "gui_settings_public_mode_checkbox": "Offentligt läge", @@ -216,7 +216,7 @@ "gui_stop_server_autostop_timer_tooltip": "Auto-stop timern slutar vid {}", "gui_start_server_autostart_timer_tooltip": "Auto-start timer slutar vid {}", "gui_waiting_to_start": "Planerad för att starta i {}. Klicka för att avbryta.", - "gui_settings_autostart_timer_checkbox": "Använd auto-start timer", + "gui_settings_autostart_timer_checkbox": "Använd automatisk start tidtagare", "gui_settings_autostart_timer": "Börja dela vid:", "gui_server_autostart_timer_expired": "Den schemalagda tiden har redan passerat. Uppdatera den för att starta delning.", "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Auto-stop tiden kan inte vara samma eller tidigare än auto-starttiden. Uppdatera den för att starta delning.", diff --git a/share/locale/sw.json b/share/locale/sw.json new file mode 100644 index 00000000..74707f3c --- /dev/null +++ b/share/locale/sw.json @@ -0,0 +1,175 @@ +{ + "preparing_files": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_autostop_timer": "", + "closing_automatically": "", + "large_filesize": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_autostop_timer": "", + "gui_stop_server_autostop_timer_tooltip": "", + "gui_start_server_autostart_timer_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_autostop_timer": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_waiting_to_start": "", + "gui_please_wait": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Mipangilio ya kawaida", + "gui_settings_onion_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_autostop_timer_checkbox": "", + "gui_settings_autostop_timer": "", + "gui_settings_autostart_timer_checkbox": "", + "gui_settings_autostart_timer": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Ndio", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_autostop_timer": "", + "gui_server_autostop_timer_expired": "", + "gui_server_autostart_timer_expired": "", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_scheduled": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_scheduled": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "error_cannot_create_data_dir": "", + "gui_receive_mode_warning": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_data_dir_label": "", + "gui_settings_data_dir_browse_button": "Vinjari", + "gui_settings_public_mode_checkbox": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "", + "systray_menu_exit": "", + "systray_page_loaded_title": "", + "systray_page_loaded_message": "", + "systray_share_started_title": "", + "systray_share_started_message": "", + "systray_share_completed_title": "", + "systray_share_completed_message": "", + "systray_share_canceled_title": "", + "systray_share_canceled_message": "", + "systray_receive_started_title": "", + "systray_receive_started_message": "", + "gui_all_modes_history": "", + "gui_all_modes_clear_history": "", + "gui_all_modes_transfer_started": "", + "gui_all_modes_transfer_finished_range": "", + "gui_all_modes_transfer_finished": "", + "gui_all_modes_transfer_canceled_range": "", + "gui_all_modes_transfer_canceled": "", + "gui_all_modes_progress_complete": "", + "gui_all_modes_progress_starting": "", + "gui_all_modes_progress_eta": "", + "gui_share_mode_no_files": "", + "gui_share_mode_autostop_timer_waiting": "", + "gui_receive_mode_no_files": "", + "gui_receive_mode_autostop_timer_waiting": "", + "receive_mode_upload_starting": "", + "days_first_letter": "", + "hours_first_letter": "", + "minutes_first_letter": "", + "seconds_first_letter": "" +} diff --git a/share/locale/te.json b/share/locale/te.json index 751a0d62..9f738318 100644 --- a/share/locale/te.json +++ b/share/locale/te.json @@ -19,7 +19,7 @@ "gui_start_server_autostart_timer_tooltip": "స్వీయ నియంత్రణ సమయం అయిపోయినది", "gui_receive_start_server": "స్వీకరించు రీతిని మొదలుపెట్టు", "gui_receive_stop_server": "స్వీకరించు రీతిని ఆపివేయి", - "gui_receive_stop_server_autostop_timer": "స్వీకరించు రీతిని ఆపివేయి ({}s మిగిలినది)", + "gui_receive_stop_server_autostop_timer": "స్వీకరించు రీతిని ఆపివేయి ({} మిగిలినది)", "gui_copy_url": "జాల చిరునామాను నకలు తీయి", "gui_copy_hidservauth": "HidServAuth నకలు తీయి", "gui_canceled": "రద్దు చేయబడినది", diff --git a/share/locale/tr.json b/share/locale/tr.json index 33e6ec9c..793a9d08 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,22 +1,22 @@ { - "preparing_files": "Sıkıştırma dosyaları.", + "preparing_files": "Dosyalar sıkıştırılıyor.", "give_this_url": "Bu adresi alıcıya verin:", "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl+C'ye basın", "not_a_file": "{0:s} dosya değil.", "other_page_loaded": "Adres yüklendi", "closing_automatically": "Aktarım tamamlandığından durduruldu", - "large_filesize": "Büyük bir paylaşımın gönderilmesi saatler sürebilir", + "large_filesize": "Uyarı: Büyük bir paylaşımın gönderilmesi saatler sürebilir", "help_local_only": "Tor kullanmayın (sadece geliştirme için)", "help_stay_open": "Dosyalar gönderildikten sonra paylaşmaya devam et", "help_debug": "OnionShare hatalarını stdout'a ve web hatalarını diske yaz", "help_filename": "Paylaşmak için dosya ve klasörler listesi", - "gui_drag_and_drop": "Dosyaları buraya\n Sürükle ve Bırak", + "gui_drag_and_drop": "Paylaşımı başlatmak için dosya\nve klasörleri sürükleyip buraya bırakın", "gui_add": "Ekle", "gui_delete": "Sil", - "gui_choose_items": "Seç", + "gui_choose_items": "Seçin", "gui_share_start_server": "Paylaşımı başlat", "gui_share_stop_server": "Paylaşımı durdur", - "gui_copy_url": "URL Kopyala", + "gui_copy_url": "Adresi Kopyala", "gui_downloads": "İndirilenler:", "gui_canceled": "İptal edilen", "gui_copied_url": "OnionShare adresi panoya kopyalandı", @@ -25,8 +25,8 @@ "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla.", "give_this_url_receive": "Bu adresi gönderene ver:", "not_a_readable_file": "{0:s} okunabilir bir dosya değil.", - "no_available_port": "Onion hizmetini başlatmak için uygun bir port bulunamadı", - "close_on_autostop_timer": "Otomatik durma zamanlayıcısının bitmesi nedeniyle durdu", + "no_available_port": "Onion hizmetinin başlatılacağı uygun bir kapı numarası bulunamadı", + "close_on_autostop_timer": "Otomatik durdurma sayacı sona erdiğinden durduruldu", "give_this_url_stealth": "Bu adresi ve HidServAuth hattını alıcıya verin:", "give_this_url_receive_stealth": "Bu adresi ve HidServAuth'u gönderene verin:", "help_autostop_timer": "Belirli bir saniye sonra paylaşmayı durdur", @@ -34,165 +34,165 @@ "help_receive": "Paylaşımı göndermek yerine, almak", "help_config": "Özel JSON config dosyası konumu (isteğe bağlı)", "gui_add_files": "Dosya Ekle", - "gui_add_folder": "Dizin Ekle", + "gui_add_folder": "Klasör Ekle", "gui_share_stop_server_autostop_timer": "Paylaşımı Durdur ({} kaldı)", "gui_share_stop_server_autostop_timer_tooltip": "Otomatik durdurma zamanlayıcısı {} sonra biter", "gui_receive_start_server": "Alma Kipini Başlat", "gui_receive_stop_server": "Alma Kipini Durdur", - "gui_receive_stop_server_autostop_timer": "Alma Modunu Durdur ({} kaldı)", + "gui_receive_stop_server_autostop_timer": "Alma Kipini Durdur ({} kaldı)", "gui_receive_stop_server_autostop_timer_tooltip": "Otomatik durdurma zamanlayıcısı {} sonra biter", - "gui_copy_hidservauth": "HidServAuth kopyala", + "gui_copy_hidservauth": "HidServAuth Kopyala", "gui_copied_url_title": "OnionShare Adresi Kopyalandı", "gui_copied_hidservauth_title": "HidServAuth Kopyalandı", "gui_copied_hidservauth": "HidServAuth satırı panoya kopyalandı", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Çok hızlı değil", - "gui_share_quit_warning": "Dosya gönderme sürecindesiniz. OnionShare'dan çıkmak istediğinize emin misiniz?", - "gui_receive_quit_warning": "Dosya alma işlemindesiniz. OnionShare'dan çıkmak istediğinize emin misiniz?", + "gui_share_quit_warning": "Dosya gönderiyorsunuz. OnionShare uygulamasından çıkmak istediğinize emin misiniz?", + "gui_receive_quit_warning": "Dosya alıyorsunuz. OnionShare uygulamasından çıkmak istediğinize emin misiniz?", "gui_quit_warning_quit": "Çık", "gui_quit_warning_dont_quit": "İptal", - "error_rate_limit": "Birisi adresinize çok fazla yanlış girişimde bulundu, bu da tahmin etmeye çalışabilecekleri anlamına geliyor, OnionShare sunucuyu durdurdu. Tekrar paylaşmaya başlayın ve alıcıya paylaşması için yeni bir adres gönderin.", - "error_stealth_not_supported": "İstemci yetkilendirmesini kullanmak için, en azından hem Tor 0.2.9.1-alpha (veya Tor Browser 6.5) hem de python3-stem 1.5.0'a ihtiyacınız vardır.", - "error_ephemeral_not_supported": "OnionShare, en az hem Tor 0.2.7.1 hem de python3-stem 1.4.0 gerektirir.", + "error_rate_limit": "Birisi adresinize çok fazla hatalı girişimde bulundu. Bilgilerinizi tahmin etmeye çalışıyor olabilirler. Bu nedenle OnionShare sunucuyu durdurdu. Paylaşımı yeniden başlatın ve alıcıya yeni bir paylaşım adresi gönderin.", + "error_stealth_not_supported": "İstemci kimlik doğrulamasını kullanmak için, en az Tor 0.2.9.1-alpha (ya da Tor Browser 6.5) ve python3-stem 1.5.0 sürümleri gereklidir.", + "error_ephemeral_not_supported": "OnionShare için en az Tor 0.2.7.1 ve python3-stem 1.4.0 sürümleri gereklidir.", "gui_settings_window_title": "Ayarlar", "gui_settings_whats_this": "Bu nedir?", - "gui_settings_stealth_option": "İstemci yetkilendirmesini kullan", - "gui_settings_stealth_hidservauth_string": "Özel anahtarınızı tekrar kullanmak üzere sakladığınızdan, şimdi HidServAuth'ınızı kopyalamak için tıklayabileceğiniz anlamına gelir.", + "gui_settings_stealth_option": "İstemci kimlik doğrulaması kullanılsın", + "gui_settings_stealth_hidservauth_string": "Özel anahtarınızı yeniden kullanmak üzere kaydettiğinizden, tıklayarak HidServAuth verinizi kopyalabilirsiniz.", "gui_settings_autoupdate_label": "Yeni sürümü denetle", - "gui_settings_autoupdate_option": "Yeni bir sürüm olduğunda bana bildir", + "gui_settings_autoupdate_option": "Yeni yayınlanan sürümler bildirilsin", "gui_settings_autoupdate_timestamp": "Son denetleme: {}", "gui_settings_autoupdate_timestamp_never": "Hiçbir zaman", "gui_settings_autoupdate_check_button": "Yeni Sürümü Denetle", "gui_settings_general_label": "Genel ayarlar", "gui_settings_onion_label": "Onion ayarları", "gui_settings_sharing_label": "Paylaşım ayarları", - "gui_settings_close_after_first_download_option": "Dosyalar gönderildikten sonra paylaşımı durdur", - "gui_settings_connection_type_label": "OnionShare, Tor'a nasıl bağlanmalı?", - "gui_settings_connection_type_bundled_option": "OnionShare'da yerleşik olan Tor sürümünü kullanın", + "gui_settings_close_after_first_download_option": "Dosyalar gönderildikten sonra paylaşım durdurulsun", + "gui_settings_connection_type_label": "OnionShare, Tor ile nasıl bağlanmalı?", + "gui_settings_connection_type_bundled_option": "OnionShare üzerindeki Tor sürümünü kullanın", "gui_settings_connection_type_automatic_option": "Tor Browser ile otomatik yapılandırma girişimi", - "gui_settings_connection_type_control_port_option": "Denetleme bağlantı noktasını kullanarak bağlan", + "gui_settings_connection_type_control_port_option": "Denetim kapı numarası ile bağlan", "gui_settings_connection_type_socket_file_option": "Socket dosyasını kullanarak bağlan", - "gui_settings_connection_type_test_button": "Tor'a Bağlanmayı Dene", - "gui_settings_control_port_label": "Denetleme bağlantı noktası", + "gui_settings_connection_type_test_button": "Tor Bağlantısını Sına", + "gui_settings_control_port_label": "Denetim kapı numarası", "gui_settings_socket_file_label": "Socket dosyası", - "gui_settings_socks_label": "SOCKS bağlantı noktası", + "gui_settings_socks_label": "SOCKS kapı numarası", "gui_settings_authenticate_label": "Tor kimlik doğrulama ayarları", - "gui_settings_authenticate_no_auth_option": "Kimlik doğrulama veya çerez kimlik doğrulaması yok", - "gui_settings_authenticate_password_option": "Şifre", - "gui_settings_password_label": "Şifre", + "gui_settings_authenticate_no_auth_option": "Kimlik doğrulama ya da çerez doğrulaması yok", + "gui_settings_authenticate_password_option": "Parola", + "gui_settings_password_label": "Parola", "gui_settings_tor_bridges": "Tor köprü desteği", - "gui_settings_tor_bridges_no_bridges_radio_option": "Köprü kullanmayın", - "gui_settings_tor_bridges_obfs4_radio_option": "Yerleşik obfs4 takılabilir taşıma araçlarını kullanın", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Yerleşik obfs4 takılabilir aktarımları kullanın (obfs4proxy gerektirir)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Yerleşik meek_lite (Azure) takılabilir aktarımları kullanın", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Yerleşik meek_lite (Azure) takılabilir aktarımları kullanın (obfs4proxy gerektirir)", - "gui_settings_meek_lite_expensive_warning": "Uyarı: meek_lit köprüleri Tor Projesinin çalışması için çok maliyetlidir.

Bunları yalnızca Tor'a doğrudan, obfs4 aktarımları veya diğer normal köprüler üzerinden bağlanamıyorsanız kullanın.", - "gui_settings_tor_bridges_custom_radio_option": "Özel köprüler kullanın", + "gui_settings_tor_bridges_no_bridges_radio_option": "Köprüler kullanılmasın", + "gui_settings_tor_bridges_obfs4_radio_option": "Hazır obfs4 değiştirilebilir taşıyıcıları kullanılsın", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Hazır obfs4 değiştirilebilir taşıyıcıları kullanılsın (obfs4proxy gerektirir)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Hazır meek_lite (Azure) değiştirilebilir taşıyıcıları kullanılsın", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Hazır meek_lite (Azure) değiştirilebilir taşıyıcıları kullanılsın (obfs4proxy gerektirir)", + "gui_settings_meek_lite_expensive_warning": "Uyarı: meek_lit köprülerini çalıştırmak Tor Projesine pahalıya patlıyor.

Bu köprüleri yalnız Tor ile doğrudan ya da obfs4 ve diğer normal köprüler üzerinden bağlantı kuramıyorsanız kullanın.", + "gui_settings_tor_bridges_custom_radio_option": "Özel köprüler kullanılsın", "gui_settings_tor_bridges_custom_label": "Köprüleri https://bridges.torproject.org adresinden alabilirsiniz", - "gui_settings_tor_bridges_invalid": "Eklediğiniz köprülerin hiçbiri çalışmıyor.\nOnları iki kez denetleyin veya başkalarını ekleyin.", + "gui_settings_tor_bridges_invalid": "Eklediğiniz köprülerin hiçbiri çalışmıyor.\nİki kez denetleyin ya da başka köprüler ekleyin.", "gui_settings_button_save": "Kaydet", "gui_settings_button_cancel": "İptal", "gui_settings_button_help": "Yardım", - "gui_settings_autostop_timer_checkbox": "Otomatik durdurma zamanlayıcısını kullan", - "gui_settings_autostop_timer": "Paylaşımı durdur:", - "settings_error_unknown": "Tor denetleyicisine bağlanılamıyor çünkü ayarlarınız mantıklı değil.", - "settings_error_automatic": "Tor denetleyicisine bağlanılamadı. Tor Browser (torproject.org adresinden temin edilebilir) arka planda mı çalışıyor?", - "settings_error_socket_port": "Tor denetleticisine {}:{} adresinden bağlanılamıyor.", - "settings_error_socket_file": "Tor denetleyicisine {} socket dosyası kullanılarak bağlanılamıyor.", - "settings_error_auth": "{}:{} İle bağlandı, ancak kimlik doğrulaması yapamıyor. Belki bu bir Tor denetleyicisi değildir?", - "settings_error_missing_password": "Tor denetleyicisine bağlı, ancak kimlik doğrulaması için bir şifre gerekiyor.", - "settings_error_unreadable_cookie_file": "Tor denetleyicisine bağlı, ancak parola yanlış olabilir veya kullanıcının çerez dosyasını okumasına izin verilmez.", - "settings_error_bundled_tor_not_supported": "OnionShare ile birlikte verilen Tor sürümünü kullanmak, Windows veya macOS'ta geliştirici kipinde çalışmaz.", - "settings_error_bundled_tor_timeout": "Tor'a bağlanmak çok uzun sürüyor. Belki İnternete bağlı değilsiniz veya yanlış bir sistem saatiniz var?", - "settings_error_bundled_tor_broken": "OnionShare, arka planda Tor'a bağlanamadı:\n{}", - "settings_test_success": "Tor denetleyicisine bağlı.\n\nTor sürümü: {}\nGeçici onion hizmetlerini destekler: {}.\nİstemci kimlik doğrulamasını destekler: {}.\nYeni nesil .onion adreslerini destekler: {}.", - "error_tor_protocol_error": "Tor ile bir hata oluştu: {}", - "error_tor_protocol_error_unknown": "Tor ile ilgili bilinmeyen bir hata oluştu", + "gui_settings_autostop_timer_checkbox": "Otomatik durdurma sayacı kullanılsın", + "gui_settings_autostop_timer": "Paylaşımı durdurma zamanı:", + "settings_error_unknown": "Ayarlarınız mantıklı olmadığından Tor denetleyicisine bağlanılamıyor.", + "settings_error_automatic": "Tor denetleyicisi ile bağlantı kurulamadı. Arka planda Tor Browser (torproject.org adresinden temin edilebilir) çalışıyor olabilir mi?", + "settings_error_socket_port": "{}:{} adresinden Tor denetleyicisi ile bağlantı kurulamadı.", + "settings_error_socket_file": "{} socket dosyası kullanılarak Tor denetleyicisi ile bağlantı kurulamadı.", + "settings_error_auth": "{}:{} bağlantısı kuruldu, ancak kimlik doğrulaması yapılamadı. Bu bir Tor denetleyicisi olmayabilir mi?", + "settings_error_missing_password": "Tor denetleyicisi ile bağlantı kuruldu, ancak kimlik doğrulaması için parola gerekiyor.", + "settings_error_unreadable_cookie_file": "Tor denetleyicisi ile bağlantı kuruldu, ancak parola yanlış ya da kullanıcının çerez dosyasını okumasına izin verilmiyor.", + "settings_error_bundled_tor_not_supported": "OnionShare üzerinde gelen Tor sürümü, Windows ya da macOS üzerinde geliştirici kipinde çalışmaz.", + "settings_error_bundled_tor_timeout": "Tor bağlantısının kurulması gecikiyor. İnternet bağlantınız kesik ya da sistem saatiniz hatalı olabilir mi?", + "settings_error_bundled_tor_broken": "OnionShare, Tor ile arka planda bağlantı kuramadı:\n{}", + "settings_test_success": "Tor denetleyicisi ile bağlantı kuruldu.\n\nTor sürümü: {}\nGeçici onion hizmetleri desteği: {}.\nİstemci kimlik doğrulaması desteği: {}.\nYeni nesil .onion adresleri desteği: {}.", + "error_tor_protocol_error": "Tor ile ilgili bir sorun çıktı: {}", + "error_tor_protocol_error_unknown": "Tor ile ilgili bilinmeyen bir sorun çıktı", "error_invalid_private_key": "Bu özel anahtar türü desteklenmiyor", - "connecting_to_tor": "Tor ağına bağlanılıyor", - "update_available": "Yeni OnionShare çıktı. Onu almak için buraya tıklayın.

{} kullanıyorsunuz ve sonuncusu {}.", - "update_error_check_error": "Yeni sürümler denetlenemedi: OnionShare web sitesi en son sürümün tanınmayan '{}' olduğunu söylüyor…", - "update_error_invalid_latest_version": "Yeni sürüm denetlenemedi: Belki de Tor ile bağlantınız yok ya da OnionShare web sitesi kapalı?", - "update_not_available": "En son OnionShare ürününü kullanıyorsunuz.", - "gui_tor_connection_ask": "Tor ile bağlantıyı çözmek için ayarlar açılsın mı?", + "connecting_to_tor": "Tor ağı ile bağlantı kuruluyor", + "update_available": "Yeni bir OnionShare sürümü yayınlanmış. Almak için buraya tıklayın.

Kullandığınız sürüm {}, Son sürüm {}.", + "update_error_check_error": "Yeni sürüm denetimi yapılamadı: OnionShare web sitesi en son sürümün anlaşılamayan '{}' olduğunu bildiriyor…", + "update_error_invalid_latest_version": "Yeni sürüm denetlenemedi: Tor bağlantınız kesik ya da OnionShare web sitesi kapalı olabilir mi?", + "update_not_available": "En son OnionShare sürümünü kullanıyorsunuz.", + "gui_tor_connection_ask": "Tor bağlantı sorunlarını çözmek için ayarlar açılsın mı?", "gui_tor_connection_ask_open_settings": "Evet", "gui_tor_connection_ask_quit": "Çık", - "gui_tor_connection_error_settings": "OnionShare'in ayarlarından Tor ağına bağlanma şeklini değiştirmeyi deneyin.", - "gui_tor_connection_canceled": "Tor'a bağlanılamadı.\n\nİnternete bağlı olduğunuzdan emin olduktan sonra OnionShare'ı tekrar açın ve Tor ile bağlantısını kurun.", + "gui_tor_connection_error_settings": "OnionShare ayarlarından Tor ağı ile bağlantı kurma yöntemini değiştirmeyi deneyin.", + "gui_tor_connection_canceled": "Tor bağlantısı kurulamadı.\n\nİnternet bağlantınızın çalıştığından emin olduktan sonra OnionShare uygulamasını yeniden açın ve Tor bağlantısını kurun.", "gui_tor_connection_lost": "Tor bağlantısı kesildi.", - "gui_server_started_after_autostop_timer": "Otomatik durdurma zamanlayıcısı, sunucu başlamadan önce bitti.\nLütfen yeni bir paylaşım yapın.", - "gui_server_autostop_timer_expired": "Otomatik durma zamanlayıcısı zaten tükendi.\nPaylaşmaya başlamak için lütfen güncelleyin.", + "gui_server_started_after_autostop_timer": "Otomatik durdurma sayacı, sunucu başlamadan önce sona erdi.\nLütfen yeni bir paylaşım yapın.", + "gui_server_autostop_timer_expired": "Otomatik durma sayacı zaten sona ermiş.\nPaylaşmaya başlamak için sayacı güncelleyin.", "share_via_onionshare": "OnionShare ile paylaş", - "gui_connect_to_tor_for_onion_settings": "Onion hizmet ayarlarını görmek için Tor'a bağlanın", - "gui_use_legacy_v2_onions_checkbox": "Eski adresleri kullan", - "gui_save_private_key_checkbox": "Kalıcı bir adres kullanın", + "gui_connect_to_tor_for_onion_settings": "Onion hizmet ayarlarını görmek için Tor bağlantısı kurun", + "gui_use_legacy_v2_onions_checkbox": "Eski adresler kullanılsın", + "gui_save_private_key_checkbox": "Kalıcı bir adres kullanılsın", "gui_share_url_description": "Bu OnionShare adresine sahip olan herkes Tor Tarayıcıyı kullanarak dosyalarınızı indirebilir: ", "gui_receive_url_description": "Bu OnionShare adresine sahip olan herkes Tor Tarayıcıyı kullanarak dosyaları yükleyebilir: ", - "gui_url_label_persistent": "Bu paylaşım otomatik olarak durmayacak.

Sonraki her paylaşım adresi yeniden kullanır. (Bir kerelik adresleri kullanmak için, ayarlardan \"Sürekli adres kullan\" seçeneğini kapatın.)", - "gui_url_label_stay_open": "Bu paylaşım otomatik olarak durmayacak.", - "gui_url_label_onetime": "Bu paylaşım ilki tamamlandıktan sonra durur.", - "gui_url_label_onetime_and_persistent": "Bu paylaşım otomatik olarak durmayacak.

Sonraki her paylaşım adresi yeniden kullanacaktır. (Bir kerelik adresleri kullanmak için, ayarlardan \"Sürekli adres kullan\" seçeneğini kapatın.)", + "gui_url_label_persistent": "Bu paylaşım otomatik olarak durdurulmayacak.

Sonraki her paylaşım adresi yeniden kullanır (Bir kerelik adresleri kullanmak için, ayarlardan \"Kalıcı adres kullanılsın\" seçeneğini devre dışı bırakın).", + "gui_url_label_stay_open": "Bu paylaşım otomatik olarak durdurulmayacak.", + "gui_url_label_onetime": "Bu paylaşım bir kez tamamlandıktan sonra durdurulur.", + "gui_url_label_onetime_and_persistent": "Bu paylaşım otomatik olarak durdurulmayacak.

Sonraki her paylaşım adresi yeniden kullanır (Bir kerelik adresleri kullanmak için, ayarlardan \"Kalıcı adres kullanılsın\" seçeneğini devre dışı bırakın).", "gui_status_indicator_share_stopped": "Paylaşmaya hazır", - "gui_status_indicator_share_working": "Başlıyor…", + "gui_status_indicator_share_working": "Başlatılıyor…", "gui_status_indicator_share_started": "Paylaşılıyor", "gui_status_indicator_receive_stopped": "Almaya hazır", - "gui_status_indicator_receive_working": "Başlıyor…", + "gui_status_indicator_receive_working": "Başlatılıyor…", "gui_status_indicator_receive_started": "Alınıyor", "gui_file_info": "{} dosya, {}", "gui_file_info_single": "{} dosya, {}", - "history_in_progress_tooltip": "{} devam etmekte", + "history_in_progress_tooltip": "{} sürüyor", "history_completed_tooltip": "{} tamamlandı", "error_cannot_create_data_dir": "OnionShare veri klasörü oluşturulamadı: {}", "receive_mode_data_dir": "Size gönderilen dosyalar bu klasörde görünür: {}", "receive_mode_warning": "Uyarı: Alma kipi, insanların bilgisayarınıza dosya yüklemesini sağlar. Bazı dosyalar, onları açarsanız bilgisayarınızın denetimini ele geçirebilir. Yalnızca güvendiğiniz insanlara veya ne yaptığınızı biliyorsanız bunları açın.", - "gui_receive_mode_warning": "Alma kipi insanların bilgisayarınıza dosya yüklemesini sağlar.

Bazı dosyalar, onları açarsanız bilgisayarınızın denetimini ele geçirebilir. Yalnızca güvendiğiniz insanlara veya ne yaptığınızı biliyorsanız bunları açın.", - "receive_mode_upload_starting": "{} toplam boyutunun karşıya yüklenmesi başlıyor", + "gui_receive_mode_warning": "Alma kipi başkalarının bilgisayarınıza dosya yüklemesini sağlar.

Bazı dosyalar, açtığınızda bilgisayarınızın denetimini ele geçirebilir. Yükleme paylaşımını yalnız güvendiğiniz kişilere ya da ne yaptığınızdan eminseniz herkese açın.", + "receive_mode_upload_starting": "Toplam boyutu {} olan karşıya yükleme başlatılıyor", "receive_mode_received_file": "Alınan: {}", "gui_mode_share_button": "Paylaşılan Dosyalar", "gui_mode_receive_button": "Alınan Dosyalar", "gui_settings_receiving_label": "Alma ayarları", "gui_settings_data_dir_label": "Dosyaları şuraya kaydet", "gui_settings_data_dir_browse_button": "Gözat", - "gui_settings_public_mode_checkbox": "Genel kip", - "gui_open_folder_error_nautilus": "Nautilus mevcut olmadığından dizin açılamıyor. Dosya burada: {}", - "gui_settings_language_label": "Tercih edilen dil", - "gui_settings_language_changed_notice": "Dilde yaptığınız değişikliklerin yürürlüğe girmesi için OnionShare'ı yeniden başlatın.", + "gui_settings_public_mode_checkbox": "Herkese açık kip", + "gui_open_folder_error_nautilus": "Nautilus kullanılamadığından klasör açılamıyor. Dosya burada: {}", + "gui_settings_language_label": "Kullanılacak dil", + "gui_settings_language_changed_notice": "Dil değişikliğinin yapılabilmesi için OnionShare uygulamasını yeniden başlatın.", "systray_menu_exit": "Çık", "systray_page_loaded_title": "Sayfa Yüklendi", "systray_page_loaded_message": "OnionShare adresi yüklendi", - "systray_share_started_title": "Paylaşma Başladı", - "systray_share_started_message": "Birine dosya göndermeye başlanılıyor", + "systray_share_started_title": "Paylaşım Başlatıldı", + "systray_share_started_message": "Birine dosya gönderilmeye başlanıyor", "systray_share_completed_title": "Paylaşım Tamamlandı", - "systray_share_completed_message": "Dosya gönderimi tamamlandı", - "systray_share_canceled_title": "Paylaşma İptal Edildi", + "systray_share_completed_message": "Dosyalar gönderildi", + "systray_share_canceled_title": "Paylaşım İptal Edildi", "systray_share_canceled_message": "Birisi dosyalarınızı almayı iptal etti", - "systray_receive_started_title": "Alma Başladı", - "systray_receive_started_message": "Birisi sana dosya gönderiyor", + "systray_receive_started_title": "Alma Başlatıldı", + "systray_receive_started_message": "Birisi size dosyalar gönderiyor", "gui_all_modes_history": "Geçmiş", - "gui_all_modes_clear_history": "Hepsini Temizle", - "gui_all_modes_transfer_started": "Başladı {}", + "gui_all_modes_clear_history": "Tümünü Temizle", + "gui_all_modes_transfer_started": "Başlatıldı {}", "gui_all_modes_transfer_finished_range": "Aktarıldı {} - {}", "gui_all_modes_transfer_finished": "Aktarıldı {}", "gui_all_modes_transfer_canceled_range": "İptal edildi {} - {}", "gui_all_modes_transfer_canceled": "İptal edildi {}", "gui_all_modes_progress_complete": "%p%, {0:s} geçti.", "gui_all_modes_progress_starting": "{0:s}, %p% (hesaplanıyor)", - "gui_all_modes_progress_eta": "{0:s}, Tahmini yükleme zamanı: {1:s}, %p%", - "gui_share_mode_no_files": "Henüz Dosya Gönderilmedi", + "gui_all_modes_progress_eta": "{0:s}, Öngörülen yükleme zamanı: {1:s}, %p%", + "gui_share_mode_no_files": "Henüz Bir Dosya Gönderilmedi", "gui_share_mode_timeout_waiting": "Göndermeyi bitirmek için bekleniyor", "gui_receive_mode_no_files": "Henüz bir dosya alınmadı", "gui_receive_mode_timeout_waiting": "Almayı bitirmek için bekleniyor", - "gui_stop_server_autostop_timer_tooltip": "Otomatik durdurma zamanlayıcısı {} sonra biter", - "gui_start_server_autostart_timer_tooltip": "Otomatik başlatma zamanlayıcısı {} sonra biter", - "gui_waiting_to_start": "{} ile başlaması planlandı. İptal etmek için tıklayın.", - "gui_settings_autostart_timer_checkbox": "Otomatik başlatma zamanlayıcısını kullan", - "gui_settings_autostart_timer": "Paylaşımı başlat:", - "gui_server_autostart_timer_expired": "Planlanan zaman çoktan geçti. Paylaşmaya başlamak için lütfen güncelleyin.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Otomatik durma süresi, otomatik başlama saatinden aynı veya daha erken olamaz. Paylaşmaya başlamak için lütfen güncelleyin.", - "gui_status_indicator_share_scheduled": "Planlanmış…", - "gui_status_indicator_receive_scheduled": "Planlanmış…", - "gui_share_mode_autostop_timer_waiting": "Göndermeyi bitirmesi bekleniyor", - "gui_receive_mode_autostop_timer_waiting": "Almayı bitirmek için bekleniyor", + "gui_stop_server_autostop_timer_tooltip": "Otomatik durdurma sayacı bitişi {}", + "gui_start_server_autostart_timer_tooltip": "Otomatik başlatma sayacı bitişi {}", + "gui_waiting_to_start": "{} içinde başlamaya zamanlanmış. İptal etmek için tıklayın.", + "gui_settings_autostart_timer_checkbox": "Otomatik başlatma sayacı kullanılsın", + "gui_settings_autostart_timer": "Paylaşımı başlatma zamanı:", + "gui_server_autostart_timer_expired": "Zamanlanan süre zaten bitti. Paylaşmaya başlamak için güncelleyin.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Otomatik durdurma zamanı, otomatik başlatma zamanı ile aynı ya da daha önce olamaz. Paylaşmaya başlamak için güncelleyin.", + "gui_status_indicator_share_scheduled": "Zamanlanmış…", + "gui_status_indicator_receive_scheduled": "Zamanlanmış…", + "gui_share_mode_autostop_timer_waiting": "Gönderme işleminin bitmesi bekleniyor", + "gui_receive_mode_autostop_timer_waiting": "Alma işleminin bitmesi bekleniyor", "days_first_letter": "g", "hours_first_letter": "s", "minutes_first_letter": "d", From 4ae26b3623a66174561d150e331e1bb6768e1825 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 12:38:13 -0700 Subject: [PATCH 12/64] Change package in build instructions from python-flask-httpauth to python3-flask-httpauth --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 81eb504e..a72c0342 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,7 +14,7 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python-flask-httpauth +apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth ``` For Fedora-like distros: @@ -400,7 +400,7 @@ To publish the release: - Create a new release on GitHub, put the changelog in the description of the release, and upload all six files (the macOS installer, the Windows installer, the source package, and their signatures) - Upload the six release files to https://onionshare.org/dist/$VERSION/ -- Copy the six release files into the OnionShare team Keybase filesystem +- Copy the six release files into the OnionShare team Keybase filesystem - Update the [onionshare-website](https://github.com/micahflee/onionshare-website) repo: - Edit `latest-version.txt` to match the latest version - Update the version number and download links From 74e961fd68b9cb1ea3c123686e9c5fdf1be5507c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 13:16:00 -0700 Subject: [PATCH 13/64] If ONIONSHARE_HIDE_TOR_SETTINGS is set, hide Tor settings in the settings dialog --- onionshare_gui/settings_dialog.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index ae5f5acf..cb732aa2 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -18,7 +18,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -import sys, platform, datetime, re +import sys +import platform +import datetime +import re +import os from onionshare import strings, common from onionshare.settings import Settings @@ -28,6 +32,7 @@ from .widgets import Alert from .update_checker import * from .tor_connection_dialog import TorConnectionDialog + class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. @@ -52,6 +57,9 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() + # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog + self.hide_tor_settings = os.environ.get('ONIONSHARE_HIDE_TOR_SETTINGS') == "1" + # General settings # Use a password or not ('public mode') @@ -484,7 +492,8 @@ class SettingsDialog(QtWidgets.QDialog): col_layout = QtWidgets.QHBoxLayout() col_layout.addLayout(left_col_layout) - col_layout.addLayout(right_col_layout) + if not self.hide_tor_settings: + col_layout.addLayout(right_col_layout) layout = QtWidgets.QVBoxLayout() layout.addLayout(col_layout) @@ -635,6 +644,8 @@ class SettingsDialog(QtWidgets.QDialog): Connection type bundled was toggled. If checked, hide authentication fields. """ self.common.log('SettingsDialog', 'connection_type_bundled_toggled') + if self.hide_tor_settings: + return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -644,6 +655,8 @@ class SettingsDialog(QtWidgets.QDialog): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -651,6 +664,8 @@ class SettingsDialog(QtWidgets.QDialog): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() @@ -658,6 +673,8 @@ class SettingsDialog(QtWidgets.QDialog): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on @@ -668,6 +685,8 @@ class SettingsDialog(QtWidgets.QDialog): """ Custom bridges option was toggled. If checked, show custom bridge options. """ + if self.hide_tor_settings: + return if checked: self.tor_bridges_use_custom_textbox_options.show() @@ -676,6 +695,8 @@ class SettingsDialog(QtWidgets.QDialog): Connection type automatic was toggled. If checked, hide authentication fields. """ self.common.log('SettingsDialog', 'connection_type_automatic_toggled') + if self.hide_tor_settings: + return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -687,6 +708,8 @@ class SettingsDialog(QtWidgets.QDialog): for Tor control address and port. If unchecked, hide those extra fields. """ self.common.log('SettingsDialog', 'connection_type_control_port_toggled') + if self.hide_tor_settings: + return if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() @@ -702,6 +725,8 @@ class SettingsDialog(QtWidgets.QDialog): for socket file. If unchecked, hide those extra fields. """ self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') + if self.hide_tor_settings: + return if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() From 1cb745b0710cfc295456f5eb52ecd4f8ab5ec6b3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 13:30:49 -0700 Subject: [PATCH 14/64] Add python3-distutils as a dependency, and also remove reduntant build-depends from stdeb.cfg --- BUILD.md | 2 +- stdeb.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index a72c0342..9bfd7fef 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,7 +14,7 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth +apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth python3-distutils ``` For Fedora-like distros: diff --git a/stdeb.cfg b/stdeb.cfg index 451520af..b9321da8 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy -Build-Depends: python3, python3-pytest, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-requests, python-nautilus, tor, obfs4proxy +Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy +Build-Depends: python3-pytest, python3-requests Suite: cosmic X-Python3-Version: >= 3.5.3 From 61421cf8564da87c3249e1f4bb5da71caafd5bd7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 17:35:02 -0400 Subject: [PATCH 15/64] Update pip dependencies --- install/requirements-tests.txt | 10 +++++----- install/requirements.txt | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index 599b9808..e05e007c 100644 --- a/install/requirements-tests.txt +++ b/install/requirements-tests.txt @@ -1,10 +1,10 @@ atomicwrites==1.3.0 attrs==19.1.0 -more-itertools==5.0.0 -pluggy==0.9.0 +more-itertools==7.2.0 +pluggy==0.12.0 py==1.8.0 -pytest==4.4.1 -pytest-faulthandler==1.5.0 +pytest==5.1.2 +pytest-faulthandler==2.0.1 pytest-qt==3.2.2 six==1.12.0 -urllib3==1.24.2 +urllib3==1.25.3 diff --git a/install/requirements.txt b/install/requirements.txt index ce5464cf..486d2a11 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,9 +1,9 @@ altgraph==0.16.1 -certifi==2019.3.9 +certifi==2019.6.16 chardet==3.0.4 Click==7.0 -Flask==1.0.2 -Flask-HTTPAuth==3.2.4 +Flask==1.1.1 +Flask-HTTPAuth==3.3.0 future==0.17.1 idna==2.8 itsdangerous==1.1.0 @@ -11,11 +11,11 @@ Jinja2==2.10.1 macholib==1.11 MarkupSafe==1.1.1 pefile==2019.4.18 -pycryptodome==3.8.1 -PyQt5==5.12.1 -PyQt5-sip==4.19.15 -PySocks==1.6.8 -requests==2.21.0 +pycryptodome==3.9.0 +PyQt5==5.13.0 +PyQt5-sip==4.19.18 +PySocks==1.7.0 +requests==2.22.0 stem==1.7.1 -urllib3==1.24.2 -Werkzeug==0.15.2 +urllib3==1.25.3 +Werkzeug==0.15.5 From 8f35960ab7cd87f2cdac78d6897774515ae6a67c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 17:39:32 -0400 Subject: [PATCH 16/64] Update python and pyqt5 in BUILD.md, and for macOS install pyinstaller from pip instead of building from source --- BUILD.md | 50 ++++------------------------------------ install/requirements.txt | 1 + 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/BUILD.md b/BUILD.md index a72c0342..c08d08f9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,11 +46,11 @@ If you find that these instructions don't work for your Linux distribution or ve Install Xcode from the Mac App Store. Once it's installed, run it for the first time to set it up. Also, run this to make sure command line tools are installed: `xcode-select --install`. And finally, open Xcode, go to Preferences > Locations, and make sure under Command Line Tools you select an installed version from the dropdown. (This is required for installing Qt5.) -Download and install Python 3.7.2 from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2-macosx10.9.pkg`. +Download and install Python 3.7.4 from https://www.python.org/downloads/release/python-374/. I downloaded `python-3.7.4-macosx10.9.pkg`. You may also need to run the command `/Applications/Python\ 3.7/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error. -Install Qt 5.12.1 from https://download.qt.io/archive/qt/5.12/5.12.1/. I downloaded `qt-opensource-mac-x64-5.12.1.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.12.1` > `macOS`. +Install Qt 5.13.0 for macOS from https://www.qt.io/offline-installers. I downloaded `qt-opensource-mac-x64-5.13.0.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.13.0` > `macOS`. Now install pip dependencies. If you want to use a virtualenv, create it and activate it first: @@ -72,48 +72,6 @@ pip3 install -r install/requirements.txt ./dev_scripts/onionshare-gui ``` -#### Building PyInstaller - -If you want to build an app bundle, you'll need to use PyInstaller. Recently there has been issues with installing PyInstaller using pip, so here's how to build it from source. First, make sure you don't have PyInstaller currently installed: - -```sh -pip3 uninstall PyInstaller -``` - -Change to a folder where you keep source code, and clone the PyInstaller git repo: - -```sh -git clone https://github.com/pyinstaller/pyinstaller.git -``` - -Verify the v3.4 git tag: - -```sh -cd pyinstaller -gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65 -git tag -v v3.4 -``` - -It should say `Good signature from "Hartmut Goebel `. If it verified successfully, checkout the tag: - -```sh -git checkout v3.4 -``` - -And compile the bootloader, following [these instructions](https://pyinstaller.readthedocs.io/en/stable/bootloader-building.html#building-for-mac-os-x). To compile, run this: - -```sh -cd bootloader -python3 waf distclean all --target-arch=64bit -``` - -Finally, install the PyInstaller module into your local site-packages. If you're using a virtualenv, make sure to run this last command while your virtualenv is activated: - -```sh -cd .. -python3 setup.py install -``` - #### To build the app bundle ```sh @@ -134,7 +92,7 @@ Now you should have `dist/OnionShare.pkg`. ### Setting up your dev environment -Download Python 3.7.2, 32-bit (x86) from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer. +Download Python 3.7.4, 32-bit (x86) from https://www.python.org/downloads/release/python-374/. I downloaded `python-3.7.4.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer. Open a command prompt, cd to the onionshare folder, and install dependencies with pip: @@ -142,7 +100,7 @@ Open a command prompt, cd to the onionshare folder, and install dependencies wit pip install -r install\requirements.txt ``` -Install the Qt 5.12.1 from https://download.qt.io/archive/qt/5.12/5.12.1/. I downloaded `qt-opensource-windows-x86-5.12.1.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.12.1` > `MSVC 2017 32-bit`. +Install the Qt 5.13.0 from https://www.qt.io/download-open-source/. I downloaded `qt-opensource-windows-x86-5.13.0.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.13.0` > `MSVC 2017 32-bit`. After that you can try both the CLI and the GUI version of OnionShare: diff --git a/install/requirements.txt b/install/requirements.txt index 486d2a11..75c736e0 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -12,6 +12,7 @@ macholib==1.11 MarkupSafe==1.1.1 pefile==2019.4.18 pycryptodome==3.9.0 +PyInstaller==3.5 PyQt5==5.13.0 PyQt5-sip==4.19.18 PySocks==1.7.0 From f2bd2e943d74e50d80a166ca40760330052204f0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 18:05:53 -0400 Subject: [PATCH 17/64] Rename BaseModeWeb to SendBaseModeWeb, because this code is only actually shared by send modes (Share and Website, not Receive) --- onionshare/web/receive_mode.py | 2 +- onionshare/web/{base_mode.py => send_base_mode.py} | 6 +++--- onionshare/web/share_mode.py | 4 ++-- onionshare/web/website_mode.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename onionshare/web/{base_mode.py => send_base_mode.py} (97%) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index b444deb2..d2b03da0 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -8,7 +8,7 @@ from werkzeug.utils import secure_filename from .. import strings -class ReceiveModeWeb(object): +class ReceiveModeWeb: """ All of the web logic for receive mode """ diff --git a/onionshare/web/base_mode.py b/onionshare/web/send_base_mode.py similarity index 97% rename from onionshare/web/base_mode.py rename to onionshare/web/send_base_mode.py index 905414f6..0ca1b306 100644 --- a/onionshare/web/base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -6,12 +6,12 @@ from flask import Response, request, render_template, make_response, send_from_d from .. import strings -class BaseModeWeb(object): +class SendBaseModeWeb: """ - All of the web logic shared between share and website mode + All of the web logic shared between share and website mode (modes where the user sends files) """ def __init__(self, common, web): - super(BaseModeWeb, self).__init__() + super(SendBaseModeWeb, self).__init__() self.common = common self.web = web diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index afcbdcd9..1ed8c29a 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -6,11 +6,11 @@ import mimetypes import gzip from flask import Response, request, render_template, make_response -from .base_mode import BaseModeWeb +from .send_base_mode import SendBaseModeWeb from .. import strings -class ShareModeWeb(BaseModeWeb): +class ShareModeWeb(SendBaseModeWeb): """ All of the web logic for share mode """ diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index b8e4dfdf..f38daa06 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -4,11 +4,11 @@ import tempfile import mimetypes from flask import Response, request, render_template, make_response -from .base_mode import BaseModeWeb +from .send_base_mode import SendBaseModeWeb from .. import strings -class WebsiteModeWeb(BaseModeWeb): +class WebsiteModeWeb(SendBaseModeWeb): """ All of the web logic for website mode """ From bffbc1930d9848b4c84476d09e37ac64204e6204 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 18:44:44 -0400 Subject: [PATCH 18/64] Move all mode-specific code out of SendBaseModeWeb and into inherited methods in WebsiteModeWeb and ShareModeWeb --- onionshare/web/send_base_mode.py | 117 ++++--------------- onionshare/web/share_mode.py | 63 ++++++++-- onionshare/web/website_mode.py | 74 ++++++++++-- onionshare_gui/mode/share_mode/threads.py | 8 +- onionshare_gui/mode/website_mode/__init__.py | 8 +- 5 files changed, 151 insertions(+), 119 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 0ca1b306..80f9e315 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -2,10 +2,11 @@ import os import sys import tempfile import mimetypes -from flask import Response, request, render_template, make_response, send_from_directory +from flask import Response, request, render_template, make_response from .. import strings + class SendBaseModeWeb: """ All of the web logic shared between share and website mode (modes where the user sends files) @@ -40,44 +41,29 @@ class SendBaseModeWeb: self.define_routes() - def init(self): + self.common.log('SendBaseModeWeb', '__init__') + self.define_routes() + + def define_routes(self): """ - Add custom initialization here. + Inherited class will implement this """ pass + def directory_listing_template(self): + """ + Inherited class will implement this. It should call render_template and return + the response. + """ + pass def directory_listing(self, filenames, path='', filesystem_path=None): # If filesystem_path is None, this is the root directory listing - files = [] - dirs = [] - r = '' - files, dirs = self.build_directory_listing(filenames, filesystem_path) - - if self.web.mode == 'website': - r = make_response(render_template('listing.html', - path=path, - files=files, - dirs=dirs, - static_url_path=self.web.static_url_path)) - - elif self.web.mode == 'share': - r = make_response(render_template( - 'send.html', - file_info=self.file_info, - files=files, - dirs=dirs, - filename=os.path.basename(self.download_filename), - filesize=self.filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped, - static_url_path=self.web.static_url_path)) - + r = self.directory_listing_template(path, files, dirs) return self.web.add_security_headers(r) - def build_directory_listing(self, filenames, filesystem_path): files = [] dirs = [] @@ -103,6 +89,11 @@ class SendBaseModeWeb: }) return files, dirs + def set_file_info_custom(self, filenames, processed_size_callback): + """ + Inherited class will implement this. + """ + pass def set_file_info(self, filenames, processed_size_callback=None): """ @@ -114,18 +105,7 @@ class SendBaseModeWeb: filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] self.build_file_list(filenames) - - if self.web.mode == 'share': - self.common.log("ShareModeWeb", "set_file_info") - self.web.cancel_compression = False - self.build_zipfile_list(filenames, processed_size_callback) - - elif self.web.mode == 'website': - self.common.log("WebsiteModeWeb", "set_file_info") - self.web.cancel_compression = True - - return True - + self.set_file_info_custom(filenames, processed_size_callback) def build_file_list(self, filenames): """ @@ -163,56 +143,7 @@ class SendBaseModeWeb: return True def render_logic(self, path=''): - if path in self.files: - filesystem_path = self.files[path] - - # If it's a directory - if os.path.isdir(filesystem_path): - # Is there an index.html? - index_path = os.path.join(path, 'index.html') - if self.web.mode == 'website' and index_path in self.files: - # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - - return send_from_directory(dirname, basename) - - else: - # Otherwise, render directory listing - filenames = [] - for filename in os.listdir(filesystem_path): - if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') - else: - filenames.append(filename) - filenames.sort() - return self.directory_listing(filenames, path, filesystem_path) - - # If it's a file - elif os.path.isfile(filesystem_path): - dirname = os.path.dirname(filesystem_path) - basename = os.path.basename(filesystem_path) - return send_from_directory(dirname, basename) - - # If it's not a directory or file, throw a 404 - else: - return self.web.error404() - else: - # Special case loading / - - if path == '': - index_path = 'index.html' - if self.web.mode == 'website' and index_path in self.files: - # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - return send_from_directory(dirname, basename) - else: - # Root directory listing - filenames = list(self.root_files) - filenames.sort() - return self.directory_listing(filenames, path) - - else: - # If the path isn't found, throw a 404 - return self.web.error404() + """ + Inherited class will implement this. + """ + pass diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 1ed8c29a..8558a996 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -4,7 +4,7 @@ import tempfile import zipfile import mimetypes import gzip -from flask import Response, request, render_template, make_response +from flask import Response, request, render_template, make_response, send_from_directory from .send_base_mode import SendBaseModeWeb from .. import strings @@ -14,11 +14,6 @@ class ShareModeWeb(SendBaseModeWeb): """ All of the web logic for share mode """ - def init(self): - self.common.log('ShareModeWeb', '__init__') - - self.define_routes() - def define_routes(self): """ The web app routes for sharing files @@ -47,7 +42,6 @@ class ShareModeWeb(SendBaseModeWeb): return self.render_logic(path) - @self.web.app.route("/download") def download(): """ @@ -171,6 +165,61 @@ class ShareModeWeb(SendBaseModeWeb): r.headers.set('Content-Type', content_type) return r + def directory_listing_template(self, path, files, dirs): + return make_response(render_template( + 'send.html', + file_info=self.file_info, + files=files, + dirs=dirs, + filename=os.path.basename(self.download_filename), + filesize=self.filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped, + static_url_path=self.web.static_url_path)) + + def set_file_info_custom(self, filenames, processed_size_callback): + self.common.log("ShareModeWeb", "set_file_info_custom") + self.web.cancel_compression = False + self.build_zipfile_list(filenames, processed_size_callback) + + def render_logic(self, path=''): + if path in self.files: + filesystem_path = self.files[path] + + # If it's a directory + if os.path.isdir(filesystem_path): + # Render directory listing + filenames = [] + for filename in os.listdir(filesystem_path): + if os.path.isdir(os.path.join(filesystem_path, filename)): + filenames.append(filename + '/') + else: + filenames.append(filename) + filenames.sort() + return self.directory_listing(filenames, path) + + # If it's a file + elif os.path.isfile(filesystem_path): + dirname = os.path.dirname(filesystem_path) + basename = os.path.basename(filesystem_path) + return send_from_directory(dirname, basename) + + # If it's not a directory or file, throw a 404 + else: + return self.web.error404() + else: + # Special case loading / + + if path == '': + # Root directory listing + filenames = list(self.root_files) + filenames.sort() + return self.directory_listing(filenames, path) + + else: + # If the path isn't found, throw a 404 + return self.web.error404() + def build_zipfile_list(self, filenames, processed_size_callback=None): self.common.log("ShareModeWeb", "build_zipfile_list") for filename in filenames: diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index f38daa06..bb712a59 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -2,7 +2,7 @@ import os import sys import tempfile import mimetypes -from flask import Response, request, render_template, make_response +from flask import Response, request, render_template, make_response, send_from_directory from .send_base_mode import SendBaseModeWeb from .. import strings @@ -12,16 +12,10 @@ class WebsiteModeWeb(SendBaseModeWeb): """ All of the web logic for website mode """ - def init(self): - self.common.log('WebsiteModeWeb', '__init__') - self.define_routes() - - def define_routes(self): """ The web app routes for sharing a website """ - @self.web.app.route('/', defaults={'path': ''}) @self.web.app.route('/') def path_public(path): @@ -43,3 +37,69 @@ class WebsiteModeWeb(SendBaseModeWeb): }) return self.render_logic(path) + + def directory_listing_template(self, path, files, dirs): + return make_response(render_template('listing.html', + path=path, + files=files, + dirs=dirs, + static_url_path=self.web.static_url_path)) + + def set_file_info_custom(self, filenames, processed_size_callback): + self.common.log("WebsiteModeWeb", "set_file_info_custom") + self.web.cancel_compression = True + + def render_logic(self, path=''): + if path in self.files: + filesystem_path = self.files[path] + + # If it's a directory + if os.path.isdir(filesystem_path): + # Is there an index.html? + index_path = os.path.join(path, 'index.html') + if index_path in self.files: + # Render it + dirname = os.path.dirname(self.files[index_path]) + basename = os.path.basename(self.files[index_path]) + + return send_from_directory(dirname, basename) + + else: + # Otherwise, render directory listing + filenames = [] + for filename in os.listdir(filesystem_path): + if os.path.isdir(os.path.join(filesystem_path, filename)): + filenames.append(filename + '/') + else: + filenames.append(filename) + filenames.sort() + return self.directory_listing(filenames, path, filesystem_path) + + # If it's a file + elif os.path.isfile(filesystem_path): + dirname = os.path.dirname(filesystem_path) + basename = os.path.basename(filesystem_path) + return send_from_directory(dirname, basename) + + # If it's not a directory or file, throw a 404 + else: + return self.web.error404() + else: + # Special case loading / + + if path == '': + index_path = 'index.html' + if index_path in self.files: + # Render it + dirname = os.path.dirname(self.files[index_path]) + basename = os.path.basename(self.files[index_path]) + return send_from_directory(dirname, basename) + else: + # Root directory listing + filenames = list(self.root_files) + filenames.sort() + return self.directory_listing(filenames, path, filesystem_path) + + else: + # If the path isn't found, throw a 404 + return self.web.error404() diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index 24e2c242..fed362eb 100644 --- a/onionshare_gui/mode/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -41,12 +41,8 @@ class CompressThread(QtCore.QThread): self.mode.common.log('CompressThread', 'run') try: - if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): - self.success.emit() - else: - # Cancelled - pass - + self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size) + self.success.emit() self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames except OSError as e: self.error.emit(e.strerror) diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 50af4725..9f01cabc 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -165,12 +165,8 @@ class WebsiteMode(Mode): Step 3 in starting the server. Display large filesize warning, if applicable. """ - - if self.web.website_mode.set_file_info(self.filenames): - self.success.emit() - else: - # Cancelled - pass + self.web.website_mode.set_file_info(self.filenames) + self.success.emit() def start_server_error_custom(self): """ From 1e83a1bfd635f0aa56ad5ee85c4c52fed36bcb8d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:02:10 -0700 Subject: [PATCH 19/64] Oops, need to call directory_listing with filesystem_path --- onionshare/web/share_mode.py | 2 +- onionshare/web/website_mode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 8558a996..6f847fe7 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -196,7 +196,7 @@ class ShareModeWeb(SendBaseModeWeb): else: filenames.append(filename) filenames.sort() - return self.directory_listing(filenames, path) + return self.directory_listing(filenames, path, filesystem_path) # If it's a file elif os.path.isfile(filesystem_path): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index bb712a59..9ddbf89b 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -98,7 +98,7 @@ class WebsiteModeWeb(SendBaseModeWeb): # Root directory listing filenames = list(self.root_files) filenames.sort() - return self.directory_listing(filenames, path, filesystem_path) + return self.directory_listing(filenames, path) else: # If the path isn't found, throw a 404 From 2143d7016e80b39646e21a7948608498700f3586 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:03:57 -0700 Subject: [PATCH 20/64] Add Web.generate_static_url_path back, so each share has its own static path --- onionshare/web/web.py | 19 +++++++++++++------ onionshare_gui/threads.py | 3 +++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 17dd8c15..8d5a6af5 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -30,7 +30,7 @@ except: pass -class Web(object): +class Web: """ The Web object is the OnionShare web server, powered by flask """ @@ -51,16 +51,12 @@ class Web(object): self.common = common self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode)) - # The static URL path has a 128-bit random number in it to avoid having name - # collisions with files that might be getting shared - self.static_url_path = '/static_{}'.format(self.common.random_string(16)) - # The flask app self.app = Flask(__name__, - static_url_path=self.static_url_path, static_folder=self.common.get_resource_path('static'), template_folder=self.common.get_resource_path('templates')) self.app.secret_key = self.common.random_string(8) + self.generate_static_url_path() self.auth = HTTPBasicAuth() self.auth.error_handler(self.error401) @@ -127,6 +123,17 @@ class Web(object): elif self.mode == 'share': self.share_mode = ShareModeWeb(self.common, self) + def generate_static_url_path(self): + # The static URL path has a 128-bit random number in it to avoid having name + # collisions with files that might be getting shared + self.static_url_path = '/static_{}'.format(self.common.random_string(16)) + self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path)) + + # Update the flask route to handle the new static URL path + self.app.static_url_path = self.static_url_path + self.app.add_url_rule( + self.static_url_path + '/', + endpoint='static', view_func=self.app.send_static_file) def define_common_routes(self): """ diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index bee1b6bc..57e0f0af 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -42,6 +42,9 @@ class OnionThread(QtCore.QThread): def run(self): self.mode.common.log('OnionThread', 'run') + # Make a new static URL path for each new share + self.mode.web.generate_static_url_path() + # Choose port and password early, because we need them to exist in advance for scheduled shares self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') if not self.mode.app.port: From 15e8ec432112b9101a226063ca3c7e380f852caa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:13:05 -0700 Subject: [PATCH 21/64] Change link style for directory listing --- share/static/css/style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/share/static/css/style.css b/share/static/css/style.css index f2ded524..a904c035 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -222,3 +222,12 @@ li.info { color: #666666; margin: 0 0 20px 0; } + +a { + text-decoration: none; + color: #1c1ca0; +} + +a:visited { + color: #601ca0; +} \ No newline at end of file From 77d5b29c76c6c0dc76c223d416b3204fa12060ff Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 19:59:00 -0700 Subject: [PATCH 22/64] Clear the file list every time a share starts --- onionshare/web/send_base_mode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 80f9e315..68f6aeca 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -29,6 +29,7 @@ class SendBaseModeWeb: self.files = {} # This is only the root files and dirs, as opposed to all of them self.root_files = {} + self.cleanup_filenames = [] self.file_info = {'files': [], 'dirs': []} @@ -114,6 +115,10 @@ class SendBaseModeWeb: """ self.common.log("BaseModeWeb", "build_file_list") + # Clear the list of files + self.files = {} + self.root_files = {} + # Loop through the files for filename in filenames: basename = os.path.basename(filename.rstrip('/')) From 1ceaaaf533c25af1ae4b4e20cc5f43d23783a8cd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:15:30 -0700 Subject: [PATCH 23/64] Add new "Allow downloading of individual files" checkbox to share settings, and only allow it to be enabled when "Stop sharing after files have been sent" is unchecked --- onionshare/settings.py | 1 + onionshare_gui/settings_dialog.py | 25 +++++++++++++++++++++++++ share/locale/en.json | 1 + 3 files changed, 27 insertions(+) diff --git a/onionshare/settings.py b/onionshare/settings.py index 762c6dc2..d76e4855 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -98,6 +98,7 @@ class Settings(object): 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, + 'share_allow_downloading_individual_files': True, 'autostop_timer': False, 'autostart_timer': False, 'use_stealth': False, diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index ae5f5acf..cabddc9b 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -204,10 +204,17 @@ class SettingsDialog(QtWidgets.QDialog): self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) + self.close_after_first_download_checkbox.toggled.connect(self.close_after_first_download_toggled) + + # Close after first download + self.allow_downloading_individual_files_checkbox = QtWidgets.QCheckBox() + self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Checked) + self.allow_downloading_individual_files_checkbox.setText(strings._("gui_settings_allow_downloading_individual_files_option")) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) + sharing_group_layout.addWidget(self.allow_downloading_individual_files_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) @@ -503,8 +510,16 @@ class SettingsDialog(QtWidgets.QDialog): close_after_first_download = self.old_settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) + self.allow_downloading_individual_files_checkbox.setEnabled(False) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.allow_downloading_individual_files_checkbox.setEnabled(True) + + allow_downloading_individual_files = self.old_settings.get('share_allow_downloading_individual_files') + if allow_downloading_individual_files: + self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Unchecked) autostart_timer = self.old_settings.get('autostart_timer') if autostart_timer: @@ -629,6 +644,15 @@ class SettingsDialog(QtWidgets.QDialog): self.connect_to_tor_label.show() self.onion_settings_widget.hide() + def close_after_first_download_toggled(self, checked): + """ + Stop sharing after files have been sent was toggled. If checked, disable allow downloading of individual files. + """ + self.common.log('SettingsDialog', 'close_after_first_download_toggled') + if checked: + self.allow_downloading_individual_files_checkbox.setEnabled(False) + else: + self.allow_downloading_individual_files_checkbox.setEnabled(True) def connection_type_bundled_toggled(self, checked): """ @@ -956,6 +980,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) + settings.set('share_allow_downloading_individual_files', self.allow_downloading_individual_files_checkbox.isChecked()) settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) diff --git a/share/locale/en.json b/share/locale/en.json index 2063a415..23f4dd14 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -52,6 +52,7 @@ "gui_settings_onion_label": "Onion settings", "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent", + "gui_settings_allow_downloading_individual_files_option": "Allow downloading of individual files", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", From fdb94eba0c823099edef1875e9f820e228fe3f93 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:36:30 -0700 Subject: [PATCH 24/64] Only allow downloading of individual files if it is enabled in settings, and stop sharing automatically isn't --- onionshare/web/send_base_mode.py | 11 +++++++++-- onionshare/web/share_mode.py | 20 +++++++++++++++----- onionshare/web/website_mode.py | 3 +++ share/static/css/style.css | 4 ++++ share/templates/send.html | 4 ++++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 68f6aeca..6deb38ac 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -41,10 +41,13 @@ class SendBaseModeWeb: self.download_in_progress = False self.define_routes() + self.init() def init(self): - self.common.log('SendBaseModeWeb', '__init__') - self.define_routes() + """ + Inherited class will implement this + """ + pass def define_routes(self): """ @@ -105,6 +108,10 @@ class SendBaseModeWeb: if len(filenames) == 1 and os.path.isdir(filenames[0]): filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + # Re-initialize + self.init() + + # Build the file list self.build_file_list(filenames) self.set_file_info_custom(filenames, processed_size_callback) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 6f847fe7..c3066a03 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -14,6 +14,12 @@ class ShareModeWeb(SendBaseModeWeb): """ All of the web logic for share mode """ + def init(self): + self.common.log('ShareModeWeb', 'init') + # If "Stop sharing after files have been sent" is unchecked and "Allow downloading of individual files" is checked + self.download_individual_files = not self.common.settings.get('close_after_first_download') \ + and self.common.settings.get('share_allow_downloading_individual_files') + def define_routes(self): """ The web app routes for sharing files @@ -26,7 +32,7 @@ class ShareModeWeb(SendBaseModeWeb): """ self.web.add_request(self.web.REQUEST_LOAD, request.path) - # Deny new downloads if "Stop After First Download" is checked and there is + # Deny new downloads if "Stop sharing after files have been sent" is checked and there is # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: @@ -175,7 +181,8 @@ class ShareModeWeb(SendBaseModeWeb): filesize=self.filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped, - static_url_path=self.web.static_url_path)) + static_url_path=self.web.static_url_path, + download_individual_files=self.download_individual_files)) def set_file_info_custom(self, filenames, processed_size_callback): self.common.log("ShareModeWeb", "set_file_info_custom") @@ -200,9 +207,12 @@ class ShareModeWeb(SendBaseModeWeb): # If it's a file elif os.path.isfile(filesystem_path): - dirname = os.path.dirname(filesystem_path) - basename = os.path.basename(filesystem_path) - return send_from_directory(dirname, basename) + if self.download_individual_files: + dirname = os.path.dirname(filesystem_path) + basename = os.path.basename(filesystem_path) + return send_from_directory(dirname, basename) + else: + return self.web.error404() # If it's not a directory or file, throw a 404 else: diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 9ddbf89b..82cebdb7 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -12,6 +12,9 @@ class WebsiteModeWeb(SendBaseModeWeb): """ All of the web logic for website mode """ + def init(self): + pass + def define_routes(self): """ The web app routes for sharing a website diff --git a/share/static/css/style.css b/share/static/css/style.css index a904c035..bc986e57 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -56,6 +56,10 @@ header .right ul li { cursor: pointer; } +a.button:visited { + color: #ffffff; +} + .close-button { color: #ffffff; background-color: #c90c0c; diff --git a/share/templates/send.html b/share/templates/send.html index 490fddf4..916b3bfe 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -44,9 +44,13 @@ + {% if download_individual_files %} {{ info.basename }} + {% else %} + {{ info.basename }} + {% endif %} {{ info.size_human }} From 9604ee388127d12021e8c96118bf34222467457c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:45:19 -0700 Subject: [PATCH 25/64] Load default settings in CLI mode, of config is not passed in --- onionshare/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 7a1bf170..0003106f 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -109,6 +109,8 @@ def main(cwd=None): # Re-load settings, if a custom config was passed in if config: common.load_settings(config) + else: + common.load_settings() # Verbose mode? common.verbose = verbose From 833fd04ef0874100b42e79f7a490667720726d3c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:46:27 -0700 Subject: [PATCH 26/64] Fix TestSettings.test_init test --- tests/test_onionshare_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 05878899..54c09686 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -51,6 +51,7 @@ class TestSettings: 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, + 'share_allow_downloading_individual_files': True, 'autostop_timer': False, 'autostart_timer': False, 'use_stealth': False, From 1232eb107241c3a3446444fb1bb6e90e45565dbe Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:53:21 -0700 Subject: [PATCH 27/64] Merge SendBaseModeWeb.build_file_list into SendBaseModeWeb.set_file_info function --- onionshare/web/send_base_mode.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 6deb38ac..6468258a 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -111,22 +111,11 @@ class SendBaseModeWeb: # Re-initialize self.init() - # Build the file list - self.build_file_list(filenames) - self.set_file_info_custom(filenames, processed_size_callback) - - def build_file_list(self, filenames): - """ - Build a data structure that describes the list of files that make up - the static website. - """ - self.common.log("BaseModeWeb", "build_file_list") - # Clear the list of files self.files = {} self.root_files = {} - # Loop through the files + # Build the file list for filename in filenames: basename = os.path.basename(filename.rstrip('/')) @@ -152,7 +141,7 @@ class SendBaseModeWeb: for nested_filename in nested_filenames: self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) - return True + self.set_file_info_custom(filenames, processed_size_callback) def render_logic(self, path=''): """ From 113cd7eb4b7cc2623c7f12c8ad8782ec0bca321c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 21:22:59 -0700 Subject: [PATCH 28/64] Remove the "Allow downloading individual files" setting altogether, and make it just automatically enabled if "Stop sharing..." is disabled --- onionshare/settings.py | 1 - onionshare/web/share_mode.py | 5 ++--- onionshare_gui/settings_dialog.py | 26 -------------------------- share/locale/en.json | 1 - tests/GuiShareTest.py | 4 ++-- tests/test_onionshare_settings.py | 1 - 6 files changed, 4 insertions(+), 34 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index d76e4855..762c6dc2 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -98,7 +98,6 @@ class Settings(object): 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, - 'share_allow_downloading_individual_files': True, 'autostop_timer': False, 'autostart_timer': False, 'use_stealth': False, diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index c3066a03..b478fbd4 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -16,9 +16,8 @@ class ShareModeWeb(SendBaseModeWeb): """ def init(self): self.common.log('ShareModeWeb', 'init') - # If "Stop sharing after files have been sent" is unchecked and "Allow downloading of individual files" is checked - self.download_individual_files = not self.common.settings.get('close_after_first_download') \ - and self.common.settings.get('share_allow_downloading_individual_files') + # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked + self.download_individual_files = not self.common.settings.get('close_after_first_download') def define_routes(self): """ diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index cabddc9b..6ffd4523 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -204,17 +204,10 @@ class SettingsDialog(QtWidgets.QDialog): self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) - self.close_after_first_download_checkbox.toggled.connect(self.close_after_first_download_toggled) - - # Close after first download - self.allow_downloading_individual_files_checkbox = QtWidgets.QCheckBox() - self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Checked) - self.allow_downloading_individual_files_checkbox.setText(strings._("gui_settings_allow_downloading_individual_files_option")) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) - sharing_group_layout.addWidget(self.allow_downloading_individual_files_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) @@ -510,16 +503,8 @@ class SettingsDialog(QtWidgets.QDialog): close_after_first_download = self.old_settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) - self.allow_downloading_individual_files_checkbox.setEnabled(False) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.allow_downloading_individual_files_checkbox.setEnabled(True) - - allow_downloading_individual_files = self.old_settings.get('share_allow_downloading_individual_files') - if allow_downloading_individual_files: - self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Checked) - else: - self.allow_downloading_individual_files_checkbox.setCheckState(QtCore.Qt.Unchecked) autostart_timer = self.old_settings.get('autostart_timer') if autostart_timer: @@ -644,16 +629,6 @@ class SettingsDialog(QtWidgets.QDialog): self.connect_to_tor_label.show() self.onion_settings_widget.hide() - def close_after_first_download_toggled(self, checked): - """ - Stop sharing after files have been sent was toggled. If checked, disable allow downloading of individual files. - """ - self.common.log('SettingsDialog', 'close_after_first_download_toggled') - if checked: - self.allow_downloading_individual_files_checkbox.setEnabled(False) - else: - self.allow_downloading_individual_files_checkbox.setEnabled(True) - def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. @@ -980,7 +955,6 @@ class SettingsDialog(QtWidgets.QDialog): settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) - settings.set('share_allow_downloading_individual_files', self.allow_downloading_individual_files_checkbox.isChecked()) settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) diff --git a/share/locale/en.json b/share/locale/en.json index 23f4dd14..2063a415 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -52,7 +52,6 @@ "gui_settings_onion_label": "Onion settings", "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent", - "gui_settings_allow_downloading_individual_files_option": "Allow downloading of individual files", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 64e57b9f..70ae43fd 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -44,7 +44,7 @@ class GuiShareTest(GuiBaseTest): self.file_selection_widget_has_files(0) - def file_selection_widget_readd_files(self): + def file_selection_widget_read_files(self): '''Re-add some files to the list so we can share''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') @@ -117,7 +117,7 @@ class GuiShareTest(GuiBaseTest): self.history_is_visible(self.gui.share_mode) self.deleting_all_files_hides_delete_button() self.add_a_file_and_delete_using_its_delete_widget() - self.file_selection_widget_readd_files() + self.file_selection_widget_read_files() def run_all_share_mode_started_tests(self, public_mode, startup_time=2000): diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 54c09686..05878899 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -51,7 +51,6 @@ class TestSettings: 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, - 'share_allow_downloading_individual_files': True, 'autostop_timer': False, 'autostart_timer': False, 'use_stealth': False, From bd329c487cc838ab643e1fd350099bcd2af32cd8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 2 Sep 2019 18:01:56 +1000 Subject: [PATCH 29/64] Register a history item when an individual file is viewed that does not match a 'reserved' path --- onionshare_gui/mode/history.py | 31 ++++++++++++++++++++++ onionshare_gui/mode/share_mode/__init__.py | 11 +++++++- share/locale/en.json | 3 +++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 51b36f9a..c2c696fc 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -341,6 +341,37 @@ class ReceiveHistoryItem(HistoryItem): self.label.setText(self.get_canceled_label_text(self.started)) +class IndividualFileHistoryItem(HistoryItem): + """ + Individual file history item, for share mode viewing of individual files + """ + def __init__(self, common, path): + super(IndividualFileHistoryItem, self).__init__() + self.status = HistoryItem.STATUS_STARTED + self.common = common + + self.visited = time.time() + self.visited_dt = datetime.fromtimestamp(self.visited) + + # Labels + self.timestamp_label = QtWidgets.QLabel(self.visited_dt.strftime("%b %d, %I:%M%p")) + self.path_viewed_label = QtWidgets.QLabel(strings._('gui_individual_file_download').format(path)) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.timestamp_label) + layout.addWidget(self.path_viewed_label) + self.setLayout(layout) + + + def update(self): + self.label.setText(self.get_finished_label_text(self.started_dt)) + self.status = HistoryItem.STATUS_FINISHED + + def cancel(self): + self.progress_bar.setFormat(strings._('gui_canceled')) + self.status = HistoryItem.STATUS_CANCELED + class VisitHistoryItem(HistoryItem): """ Download history item, for share mode diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 143fd577..dd4ec1ab 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -28,7 +28,7 @@ from onionshare.web import Web from ..file_selection import FileSelection from .threads import CompressThread from .. import Mode -from ..history import History, ToggleHistory, ShareHistoryItem +from ..history import History, ToggleHistory, ShareHistoryItem, IndividualFileHistoryItem from ...widgets import Alert @@ -230,6 +230,15 @@ class ShareMode(Mode): Handle REQUEST_LOAD event. """ self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) + if not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/': + + item = IndividualFileHistoryItem(self.common, event["path"]) + + self.history.add(0, item) + self.toggle_history.update_indicator(True) + self.history.completed_count += 1 + self.history.update_completed() + self.system_tray.showMessage(strings._('systray_individual_file_downloaded_title'), strings._('systray_individual_file_downloaded_message').format(event["path"])) def handle_request_started(self, event): """ diff --git a/share/locale/en.json b/share/locale/en.json index 2063a415..c26577b2 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -160,6 +160,8 @@ "systray_receive_started_message": "Someone is sending files to you", "systray_website_started_title": "Starting sharing website", "systray_website_started_message": "Someone is visiting your website", + "systray_individual_file_downloaded_title": "Individual file loaded", + "systray_individual_file_downloaded_message": "Individual file {} viewed", "gui_all_modes_history": "History", "gui_all_modes_clear_history": "Clear All", "gui_all_modes_transfer_started": "Started {}", @@ -176,6 +178,7 @@ "gui_receive_mode_no_files": "No Files Received Yet", "gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving", "gui_visit_started": "Someone has visited your website {}", + "gui_individual_file_download": "Viewed {}", "receive_mode_upload_starting": "Upload of total size {} is starting", "days_first_letter": "d", "hours_first_letter": "h", From 0abac29b09092fc9f1516573f08c45edbef768c3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:19:42 +1000 Subject: [PATCH 30/64] Add tests to check that hyperlink to a shared file exists when in stay_open mode (and that the file is downloadable individually when so), and not if not --- tests/GuiShareTest.py | 47 +++++++++++++++++++ ...ode_individual_file_view_stay_open_test.py | 26 ++++++++++ ...re_share_mode_individual_file_view_test.py | 26 ++++++++++ 3 files changed, 99 insertions(+) create mode 100644 tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py create mode 100644 tests/local_onionshare_share_mode_individual_file_view_test.py diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 70ae43fd..b6f50a28 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -81,6 +81,35 @@ class GuiShareTest(GuiBaseTest): QtTest.QTest.qWait(2000) self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + def individual_file_is_viewable_or_not(self, public_mode, stay_open): + '''Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)''' + url = "http://127.0.0.1:{}".format(self.gui.app.port) + download_file_url = "http://127.0.0.1:{}/test.txt".format(self.gui.app.port) + if public_mode: + r = requests.get(url) + else: + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + + if stay_open: + self.assertTrue('a href="test.txt"' in r.text) + + if public_mode: + r = requests.get(download_file_url) + else: + r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + + tmp_file = tempfile.NamedTemporaryFile() + with open(tmp_file.name, 'wb') as f: + f.write(r.content) + + with open(tmp_file.name, 'r') as f: + self.assertEqual('onionshare', f.read()) + else: + self.assertFalse('a href="/test.txt"' in r.text) + self.download_share(public_mode) + + QtTest.QTest.qWait(2000) + def hit_401(self, public_mode): '''Test that the server stops after too many 401s, or doesn't when in public_mode''' url = "http://127.0.0.1:{}/".format(self.gui.app.port) @@ -147,6 +176,18 @@ class GuiShareTest(GuiBaseTest): self.server_is_started(self.gui.share_mode) self.history_indicator(self.gui.share_mode, public_mode) + def run_all_share_mode_individual_file_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.individual_file_is_viewable_or_not(public_mode, stay_open) + self.history_widgets_present(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + self.add_button_visible() + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_is_started(self.gui.share_mode) + self.history_indicator(self.gui.share_mode, public_mode) def run_all_share_mode_tests(self, public_mode, stay_open): """End-to-end share tests""" @@ -155,6 +196,12 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_download_tests(public_mode, stay_open) + def run_all_share_mode_individual_file_tests(self, public_mode, stay_open): + """Tests in share mode when viewing an individual file""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + self.run_all_share_mode_individual_file_download_tests(public_mode, stay_open) + def run_all_large_file_tests(self, public_mode, stay_open): """Same as above but with a larger file""" self.run_all_share_mode_setup_tests() diff --git a/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py b/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py new file mode 100644 index 00000000..4e026e16 --- /dev/null +++ b/tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeIndividualFileViewStayOpenTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_individual_file_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_individual_file_view_test.py b/tests/local_onionshare_share_mode_individual_file_view_test.py new file mode 100644 index 00000000..2bdccaec --- /dev/null +++ b/tests/local_onionshare_share_mode_individual_file_view_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeIndividualFileViewTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_individual_file_tests(False, False) + +if __name__ == "__main__": + unittest.main() From 93a63098dea5c1aeb45d174da91e18b75cfd9e37 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:51:59 +1000 Subject: [PATCH 31/64] Add a basic website test --- tests/GuiBaseTest.py | 4 + tests/GuiWebsiteTest.py | 98 +++++++++++++++++++++ tests/local_onionshare_website_mode_test.py | 25 ++++++ 3 files changed, 127 insertions(+) create mode 100644 tests/GuiWebsiteTest.py create mode 100644 tests/local_onionshare_website_mode_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 2f340396..d2a43362 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -14,6 +14,7 @@ from onionshare.web import Web from onionshare_gui import Application, OnionShare, OnionShareGui from onionshare_gui.mode.share_mode import ShareMode from onionshare_gui.mode.receive_mode import ReceiveMode +from onionshare_gui.mode.website_mode import WebsiteMode class GuiBaseTest(object): @@ -103,6 +104,9 @@ class GuiBaseTest(object): if type(mode) == ShareMode: QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + if type(mode) == WebsiteMode: + QtTest.QTest.mouseClick(self.gui.website_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_WEBSITE) def click_toggle_history(self, mode): diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py new file mode 100644 index 00000000..1f0eb310 --- /dev/null +++ b/tests/GuiWebsiteTest.py @@ -0,0 +1,98 @@ +import json +import os +import requests +import socks +import zipfile +import tempfile +from PyQt5 import QtCore, QtTest +from onionshare import strings +from onionshare.common import Common +from onionshare.settings import Settings +from onionshare.onion import Onion +from onionshare.web import Web +from onionshare_gui import Application, OnionShare, OnionShareGui +from onionshare_gui.mode.website_mode import WebsiteMode +from .GuiShareTest import GuiShareTest + +class GuiWebsiteTest(GuiShareTest): + @staticmethod + def set_up(test_settings): + '''Create GUI with given settings''' + # Create our test file + testfile = open('/tmp/index.html', 'w') + testfile.write('This is a test website hosted by OnionShare') + testfile.close() + + common = Common() + common.settings = Settings(common) + common.define_css() + strings.load_strings(common) + + # Get all of the settings in test_settings + test_settings['data_dir'] = '/tmp/OnionShare' + for key, val in common.settings.default_settings.items(): + if key not in test_settings: + test_settings[key] = val + + # Start the Onion + testonion = Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) + + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/index.html'], '/tmp/settings.json', True) + return gui + + @staticmethod + def tear_down(): + '''Clean up after tests''' + try: + os.remove('/tmp/index.html') + os.remove('/tmp/settings.json') + except: + pass + + + def view_website(self, public_mode): + '''Test that we can download the share''' + url = "http://127.0.0.1:{}/".format(self.gui.app.port) + if public_mode: + r = requests.get(url) + else: + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) + + QtTest.QTest.qWait(2000) + self.assertTrue('This is a test website hosted by OnionShare' in r.text) + + def run_all_website_mode_setup_tests(self): + """Tests in website mode prior to starting a share""" + self.click_mode(self.gui.website_mode) + self.file_selection_widget_has_files(1) + self.history_is_not_visible(self.gui.website_mode) + self.click_toggle_history(self.gui.website_mode) + self.history_is_visible(self.gui.website_mode) + + def run_all_website_mode_started_tests(self, public_mode, startup_time=2000): + """Tests in website mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.website_mode) + self.server_status_indicator_says_starting(self.gui.website_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.server_is_started(self.gui.website_mode, startup_time) + self.web_server_is_running() + self.have_a_password(self.gui.website_mode, public_mode) + self.url_description_shown(self.gui.website_mode) + self.have_copy_url_button(self.gui.website_mode, public_mode) + self.server_status_indicator_says_started(self.gui.website_mode) + + + def run_all_website_mode_download_tests(self, public_mode, stay_open): + """Tests in website mode after viewing the site""" + self.run_all_website_mode_setup_tests() + self.run_all_website_mode_started_tests(public_mode, startup_time=2000) + self.view_website(public_mode) + self.history_widgets_present(self.gui.website_mode) + diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py new file mode 100644 index 00000000..5a6dc10f --- /dev/null +++ b/tests/local_onionshare_website_mode_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiWebsiteTest import GuiWebsiteTest + +class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiWebsiteTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiWebsiteTest.tear_down() + + @pytest.mark.gui + @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + def test_gui(self): + #self.run_all_common_setup_tests() + self.run_all_website_mode_download_tests(False, False) + +if __name__ == "__main__": + unittest.main() From 6da58edcda06cb4a612ea009184cdacf8ee84e0c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:53:17 +1000 Subject: [PATCH 32/64] remove unnecessary import from GuiWebSiteTest class --- tests/GuiWebsiteTest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index 1f0eb310..e3d63547 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -11,7 +11,6 @@ from onionshare.settings import Settings from onionshare.onion import Onion from onionshare.web import Web from onionshare_gui import Application, OnionShare, OnionShareGui -from onionshare_gui.mode.website_mode import WebsiteMode from .GuiShareTest import GuiShareTest class GuiWebsiteTest(GuiShareTest): From f4a6c2de010047ab2ffffbeb216e9ded5b6447e0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:00:23 +1000 Subject: [PATCH 33/64] Aww. Adjust the website test html code since my easter egg didn't work --- tests/GuiWebsiteTest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index e3d63547..6697c5b9 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -19,7 +19,7 @@ class GuiWebsiteTest(GuiShareTest): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/index.html', 'w') - testfile.write('This is a test website hosted by OnionShare') + testfile.write('

This is a test website hosted by OnionShare

') testfile.close() common = Common() @@ -64,7 +64,7 @@ class GuiWebsiteTest(GuiShareTest): r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) QtTest.QTest.qWait(2000) - self.assertTrue('This is a test website hosted by OnionShare' in r.text) + self.assertTrue('This is a test website hosted by OnionShare' in r.text) def run_all_website_mode_setup_tests(self): """Tests in website mode prior to starting a share""" From 608e0eccc6082145eae13843bba2943b5369b7a8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:23:27 +1000 Subject: [PATCH 34/64] Extend coverage of website mode tests --- tests/GuiBaseTest.py | 5 ++++- tests/GuiShareTest.py | 9 ++------- tests/GuiWebsiteTest.py | 7 +++++-- tests/TorGuiShareTest.py | 2 +- tests/local_onionshare_website_mode_test.py | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index d2a43362..f478dd94 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -202,6 +202,9 @@ class GuiBaseTest(object): else: self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)') + def add_button_visible(self, mode): + '''Test that the add button should be visible''' + self.assertTrue(mode.server_status.file_selection.add_button.isVisible()) def url_description_shown(self, mode): '''Test that the URL label is showing''' @@ -253,7 +256,7 @@ class GuiBaseTest(object): def server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open) or (type(mode) == WebsiteMode): QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 0) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index b6f50a28..34573a19 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -130,11 +130,6 @@ class GuiShareTest(GuiBaseTest): self.web_server_is_stopped() - def add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - # 'Grouped' tests follow from here def run_all_share_mode_setup_tests(self): @@ -171,7 +166,7 @@ class GuiShareTest(GuiBaseTest): self.server_is_stopped(self.gui.share_mode, stay_open) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - self.add_button_visible() + self.add_button_visible(self.gui.share_mode) self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_is_started(self.gui.share_mode) self.history_indicator(self.gui.share_mode, public_mode) @@ -184,7 +179,7 @@ class GuiShareTest(GuiBaseTest): self.server_is_stopped(self.gui.share_mode, stay_open) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - self.add_button_visible() + self.add_button_visible(self.gui.share_mode) self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_is_started(self.gui.share_mode) self.history_indicator(self.gui.share_mode, public_mode) diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index 6697c5b9..7b88bfdf 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -54,7 +54,6 @@ class GuiWebsiteTest(GuiShareTest): except: pass - def view_website(self, public_mode): '''Test that we can download the share''' url = "http://127.0.0.1:{}/".format(self.gui.app.port) @@ -88,10 +87,14 @@ class GuiWebsiteTest(GuiShareTest): self.server_status_indicator_says_started(self.gui.website_mode) - def run_all_website_mode_download_tests(self, public_mode, stay_open): + def run_all_website_mode_download_tests(self, public_mode): """Tests in website mode after viewing the site""" self.run_all_website_mode_setup_tests() self.run_all_website_mode_started_tests(public_mode, startup_time=2000) self.view_website(public_mode) self.history_widgets_present(self.gui.website_mode) + self.server_is_stopped(self.gui.website_mode, False) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.website_mode, False) + self.add_button_visible(self.gui.website_mode) diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index 352707eb..cfce9d4e 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -67,7 +67,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.server_is_stopped(self.gui.share_mode, stay_open) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - self.add_button_visible() + self.add_button_visible(self.gui.share_mode) self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_is_started(self.gui.share_mode, startup_time=45000) self.history_indicator(self.gui.share_mode, public_mode) diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py index 5a6dc10f..051adb3c 100644 --- a/tests/local_onionshare_website_mode_test.py +++ b/tests/local_onionshare_website_mode_test.py @@ -19,7 +19,7 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") def test_gui(self): #self.run_all_common_setup_tests() - self.run_all_website_mode_download_tests(False, False) + self.run_all_website_mode_download_tests(False) if __name__ == "__main__": unittest.main() From 0bde2e91482cda84539080016b13edc57552ce1c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:35:30 +1000 Subject: [PATCH 35/64] Don't show the IndividualFile History item if we are not in 'stay open' mode, or else 404 requests create History noise --- onionshare_gui/mode/share_mode/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index dd4ec1ab..a9752174 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -230,7 +230,7 @@ class ShareMode(Mode): Handle REQUEST_LOAD event. """ self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) - if not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/': + if not self.common.settings.get('close_after_first_download') and not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/': item = IndividualFileHistoryItem(self.common, event["path"]) From 174dc79a2560e37f8b55c6316e73f31373d96f8a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:36:05 +1000 Subject: [PATCH 36/64] Test to make sure that we *can't* download an individual file when not in stay_open mode, not just that the hyperlink is not present in the page markup --- tests/GuiShareTest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 34573a19..be3f42e3 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -105,6 +105,11 @@ class GuiShareTest(GuiBaseTest): with open(tmp_file.name, 'r') as f: self.assertEqual('onionshare', f.read()) else: + if public_mode: + r = requests.get(download_file_url) + else: + r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + self.assertEqual(r.status_code, 404) self.assertFalse('a href="/test.txt"' in r.text) self.download_share(public_mode) From 04eabbb833a884c86cddd5cfce2d805797150cdb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:38:20 +1000 Subject: [PATCH 37/64] Check for the (absence of) hyperlink in page markup before we move on to trying to download the individual file --- tests/GuiShareTest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index be3f42e3..f8fefe60 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -105,12 +105,12 @@ class GuiShareTest(GuiBaseTest): with open(tmp_file.name, 'r') as f: self.assertEqual('onionshare', f.read()) else: + self.assertFalse('a href="/test.txt"' in r.text) if public_mode: r = requests.get(download_file_url) else: r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) self.assertEqual(r.status_code, 404) - self.assertFalse('a href="/test.txt"' in r.text) self.download_share(public_mode) QtTest.QTest.qWait(2000) From c86ad56a5605daa2082f66830147889b86d5fb0f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 2 Sep 2019 19:45:14 -0700 Subject: [PATCH 38/64] When downloading individual files in either share or website mode, gzip the file if needed, and stream the file in such a way that a progress bar is possible --- onionshare/web/send_base_mode.py | 112 +++++++++++++++++++++++++++++++ onionshare/web/share_mode.py | 36 ++-------- onionshare/web/website_mode.py | 17 ++--- 3 files changed, 121 insertions(+), 44 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 6468258a..88dbd008 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -2,6 +2,7 @@ import os import sys import tempfile import mimetypes +import gzip from flask import Response, request, render_template, make_response from .. import strings @@ -148,3 +149,114 @@ class SendBaseModeWeb: Inherited class will implement this. """ pass + + def stream_individual_file(self, filesystem_path): + """ + Return a flask response that's streaming the download of an individual file, and gzip + compressing it if the browser supports it. + """ + use_gzip = self.should_use_gzip() + + # gzip compress the individual file, if it hasn't already been compressed + if use_gzip: + if filesystem_path not in self.gzip_individual_files: + gzip_filename = tempfile.mkstemp('wb+')[1] + self._gzip_compress(filesystem_path, gzip_filename, 6, None) + self.gzip_individual_files[filesystem_path] = gzip_filename + + # Make sure the gzip file gets cleaned up when onionshare stops + self.cleanup_filenames.append(gzip_filename) + + file_to_download = self.gzip_individual_files[filesystem_path] + filesize = os.path.getsize(self.gzip_individual_files[filesystem_path]) + else: + file_to_download = filesystem_path + filesize = os.path.getsize(filesystem_path) + + # TODO: Tell GUI the download started + #self.web.add_request(self.web.REQUEST_STARTED, path, { + # 'id': download_id, + # 'use_gzip': use_gzip + #}) + + def generate(): + chunk_size = 102400 # 100kb + + fp = open(file_to_download, 'rb') + done = False + canceled = False + while not done: + chunk = fp.read(chunk_size) + if chunk == b'': + done = True + else: + try: + yield chunk + + # TODO: Tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / filesize) * 100 + if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + #self.web.add_request(self.web.REQUEST_PROGRESS, path, { + # 'id': download_id, + # 'bytes': downloaded_bytes + # }) + done = False + except: + # Looks like the download was canceled + done = True + canceled = True + + # TODO: Tell the GUI the download has canceled + #self.web.add_request(self.web.REQUEST_CANCELED, path, { + # 'id': download_id + #}) + + fp.close() + + if self.common.platform != 'Darwin': + sys.stdout.write("\n") + + basename = os.path.basename(filesystem_path) + + r = Response(generate()) + if use_gzip: + r.headers.set('Content-Encoding', 'gzip') + r.headers.set('Content-Length', filesize) + r.headers.set('Content-Disposition', 'inline', filename=basename) + r = self.web.add_security_headers(r) + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r + + def should_use_gzip(self): + """ + Should we use gzip for this browser? + """ + return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + + def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): + """ + Compress a file with gzip, without loading the whole thing into memory + Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror + """ + bytes_processed = 0 + blocksize = 1 << 16 # 64kB + with open(input_filename, 'rb') as input_file: + output_file = gzip.open(output_filename, 'wb', level) + while True: + if processed_size_callback is not None: + processed_size_callback(bytes_processed) + + block = input_file.read(blocksize) + if len(block) == 0: + break + output_file.write(block) + bytes_processed += blocksize + + output_file.close() diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index b478fbd4..07cf0548 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -3,8 +3,7 @@ import sys import tempfile import zipfile import mimetypes -import gzip -from flask import Response, request, render_template, make_response, send_from_directory +from flask import Response, request, render_template, make_response from .send_base_mode import SendBaseModeWeb from .. import strings @@ -16,8 +15,10 @@ class ShareModeWeb(SendBaseModeWeb): """ def init(self): self.common.log('ShareModeWeb', 'init') + # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked self.download_individual_files = not self.common.settings.get('close_after_first_download') + self.gzip_individual_files = {} def define_routes(self): """ @@ -207,9 +208,7 @@ class ShareModeWeb(SendBaseModeWeb): # If it's a file elif os.path.isfile(filesystem_path): if self.download_individual_files: - dirname = os.path.dirname(filesystem_path) - basename = os.path.basename(filesystem_path) - return send_from_directory(dirname, basename) + return self.stream_individual_file(filesystem_path) else: return self.web.error404() @@ -287,33 +286,6 @@ class ShareModeWeb(SendBaseModeWeb): return True - def should_use_gzip(self): - """ - Should we use gzip for this browser? - """ - return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) - - def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): - """ - Compress a file with gzip, without loading the whole thing into memory - Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror - """ - bytes_processed = 0 - blocksize = 1 << 16 # 64kB - with open(input_filename, 'rb') as input_file: - output_file = gzip.open(output_filename, 'wb', level) - while True: - if processed_size_callback is not None: - processed_size_callback(bytes_processed) - - block = input_file.read(blocksize) - if len(block) == 0: - break - output_file.write(block) - bytes_processed += blocksize - - output_file.close() - class ZipWriter(object): """ diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 82cebdb7..e409e7be 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -2,7 +2,7 @@ import os import sys import tempfile import mimetypes -from flask import Response, request, render_template, make_response, send_from_directory +from flask import Response, request, render_template, make_response from .send_base_mode import SendBaseModeWeb from .. import strings @@ -13,7 +13,7 @@ class WebsiteModeWeb(SendBaseModeWeb): All of the web logic for website mode """ def init(self): - pass + self.gzip_individual_files = {} def define_routes(self): """ @@ -62,10 +62,7 @@ class WebsiteModeWeb(SendBaseModeWeb): index_path = os.path.join(path, 'index.html') if index_path in self.files: # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - - return send_from_directory(dirname, basename) + return self.stream_individual_file(filesystem_path) else: # Otherwise, render directory listing @@ -80,9 +77,7 @@ class WebsiteModeWeb(SendBaseModeWeb): # If it's a file elif os.path.isfile(filesystem_path): - dirname = os.path.dirname(filesystem_path) - basename = os.path.basename(filesystem_path) - return send_from_directory(dirname, basename) + return self.stream_individual_file(filesystem_path) # If it's not a directory or file, throw a 404 else: @@ -94,9 +89,7 @@ class WebsiteModeWeb(SendBaseModeWeb): index_path = 'index.html' if index_path in self.files: # Render it - dirname = os.path.dirname(self.files[index_path]) - basename = os.path.basename(self.files[index_path]) - return send_from_directory(dirname, basename) + return self.stream_individual_file(self.files[index_path]) else: # Root directory listing filenames = list(self.root_files) From 7a6d34103d0388399c1a90e5a1aa884497e2024b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 17:02:00 +1000 Subject: [PATCH 39/64] Reset the ToggleHistory indicator count/label when a share starts. Add a test for this --- onionshare_gui/mode/receive_mode/__init__.py | 2 ++ onionshare_gui/mode/share_mode/__init__.py | 2 ++ onionshare_gui/mode/website_mode/__init__.py | 2 ++ tests/GuiBaseTest.py | 3 +++ tests/GuiShareTest.py | 1 + 5 files changed, 10 insertions(+) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index dbc0bc73..0010fbd2 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -212,6 +212,8 @@ class ReceiveMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index a9752174..56aa1364 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -334,6 +334,8 @@ class ShareMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() @staticmethod def _compute_total_size(filenames): diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 9f01cabc..8ac88c8c 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -258,6 +258,8 @@ class WebsiteMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() @staticmethod def _compute_total_size(filenames): diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index f478dd94..9a69619b 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -170,6 +170,9 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 1) + def toggle_indicator_is_reset(self, mode): + self.assertEqual(mode.toggle_history.indicator_count, 0) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) def server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index f8fefe60..038f052b 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -173,6 +173,7 @@ class GuiShareTest(GuiBaseTest): self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) self.add_button_visible(self.gui.share_mode) self.server_working_on_start_button_pressed(self.gui.share_mode) + self.toggle_indicator_is_reset(self.gui.share_mode) self.server_is_started(self.gui.share_mode) self.history_indicator(self.gui.share_mode, public_mode) From 727a3956dbdeabab22c04f5133e0930655753ffb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 20:52:49 -0700 Subject: [PATCH 40/64] Make it so all of the state variables, including self.file_info get reset in SendBaseModeWEeb.set_file_info, which fixes the bug where old files you were sharing would end up in new zip files --- onionshare/web/send_base_mode.py | 154 +++++++++++++++---------------- onionshare/web/share_mode.py | 1 - onionshare/web/website_mode.py | 2 +- 3 files changed, 73 insertions(+), 84 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 88dbd008..a34aedfd 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -18,7 +18,6 @@ class SendBaseModeWeb: self.web = web # Information about the file to be shared - self.file_info = [] self.is_zipped = False self.download_filename = None self.download_filesize = None @@ -26,17 +25,6 @@ class SendBaseModeWeb: self.gzip_filesize = None self.zip_writer = None - # Dictionary mapping file paths to filenames on disk - self.files = {} - # This is only the root files and dirs, as opposed to all of them - self.root_files = {} - - self.cleanup_filenames = [] - self.file_info = {'files': [], 'dirs': []} - - self.visit_count = 0 - self.download_count = 0 - # If "Stop After First Download" is checked (stay_open == False), only allow # one download at a time. self.download_in_progress = False @@ -44,24 +32,51 @@ class SendBaseModeWeb: self.define_routes() self.init() - def init(self): + def set_file_info(self, filenames, processed_size_callback=None): """ - Inherited class will implement this + Build a data structure that describes the list of files """ - pass + # If there's just one folder, replace filenames with a list of files inside that folder + if len(filenames) == 1 and os.path.isdir(filenames[0]): + filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] - def define_routes(self): - """ - Inherited class will implement this - """ - pass + # Re-initialize + self.files = {} # Dictionary mapping file paths to filenames on disk + self.root_files = {} # This is only the root files and dirs, as opposed to all of them + self.cleanup_filenames = [] + self.visit_count = 0 + self.download_count = 0 + self.file_info = {'files': [], 'dirs': []} + self.gzip_individual_files = {} + self.init() - def directory_listing_template(self): - """ - Inherited class will implement this. It should call render_template and return - the response. - """ - pass + # Build the file list + for filename in filenames: + basename = os.path.basename(filename.rstrip('/')) + + # If it's a filename, add it + if os.path.isfile(filename): + self.files[basename] = filename + self.root_files[basename] = filename + + # If it's a directory, add it recursively + elif os.path.isdir(filename): + self.root_files[basename + '/'] = filename + + for root, _, nested_filenames in os.walk(filename): + # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", + # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". + # The normalized_root should be "some_folder/foobar" + normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') + + # Add the dir itself + self.files[normalized_root + '/'] = root + + # Add the files in this dir + for nested_filename in nested_filenames: + self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) + + self.set_file_info_custom(filenames, processed_size_callback) def directory_listing(self, filenames, path='', filesystem_path=None): # If filesystem_path is None, this is the root directory listing @@ -94,62 +109,6 @@ class SendBaseModeWeb: }) return files, dirs - def set_file_info_custom(self, filenames, processed_size_callback): - """ - Inherited class will implement this. - """ - pass - - def set_file_info(self, filenames, processed_size_callback=None): - """ - Build a data structure that describes the list of files - """ - - # If there's just one folder, replace filenames with a list of files inside that folder - if len(filenames) == 1 and os.path.isdir(filenames[0]): - filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] - - # Re-initialize - self.init() - - # Clear the list of files - self.files = {} - self.root_files = {} - - # Build the file list - for filename in filenames: - basename = os.path.basename(filename.rstrip('/')) - - # If it's a filename, add it - if os.path.isfile(filename): - self.files[basename] = filename - self.root_files[basename] = filename - - # If it's a directory, add it recursively - elif os.path.isdir(filename): - self.root_files[basename + '/'] = filename - - for root, _, nested_filenames in os.walk(filename): - # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", - # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". - # The normalized_root should be "some_folder/foobar" - normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') - - # Add the dir itself - self.files[normalized_root + '/'] = root - - # Add the files in this dir - for nested_filename in nested_filenames: - self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) - - self.set_file_info_custom(filenames, processed_size_callback) - - def render_logic(self, path=''): - """ - Inherited class will implement this. - """ - pass - def stream_individual_file(self, filesystem_path): """ Return a flask response that's streaming the download of an individual file, and gzip @@ -260,3 +219,34 @@ class SendBaseModeWeb: bytes_processed += blocksize output_file.close() + + def init(self): + """ + Inherited class will implement this + """ + pass + + def define_routes(self): + """ + Inherited class will implement this + """ + pass + + def directory_listing_template(self): + """ + Inherited class will implement this. It should call render_template and return + the response. + """ + pass + + def set_file_info_custom(self, filenames, processed_size_callback): + """ + Inherited class will implement this. + """ + pass + + def render_logic(self, path=''): + """ + Inherited class will implement this. + """ + pass \ No newline at end of file diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 07cf0548..60620e2a 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -18,7 +18,6 @@ class ShareModeWeb(SendBaseModeWeb): # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked self.download_individual_files = not self.common.settings.get('close_after_first_download') - self.gzip_individual_files = {} def define_routes(self): """ diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index e409e7be..28f2607d 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -13,7 +13,7 @@ class WebsiteModeWeb(SendBaseModeWeb): All of the web logic for website mode """ def init(self): - self.gzip_individual_files = {} + pass def define_routes(self): """ From 37a2f6369c02530a7edcb1a4a11d884d761945cf Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 21:46:32 -0700 Subject: [PATCH 41/64] Start making IndividualFileHistoryItem widgets appear in the history, and make non-GET requests return 405 Method Not Allowed --- onionshare/web/send_base_mode.py | 41 +++++--- onionshare/web/web.py | 32 +++--- onionshare_gui/mode/__init__.py | 47 ++++++++- onionshare_gui/mode/history.py | 103 ++++++++++++------- onionshare_gui/mode/share_mode/__init__.py | 15 --- onionshare_gui/mode/website_mode/__init__.py | 17 +-- onionshare_gui/onionshare_gui.py | 11 +- share/locale/en.json | 1 - share/templates/405.html | 19 ++++ 9 files changed, 185 insertions(+), 101 deletions(-) create mode 100644 share/templates/405.html diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index a34aedfd..402bc32f 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -132,18 +132,28 @@ class SendBaseModeWeb: file_to_download = filesystem_path filesize = os.path.getsize(filesystem_path) - # TODO: Tell GUI the download started - #self.web.add_request(self.web.REQUEST_STARTED, path, { - # 'id': download_id, - # 'use_gzip': use_gzip - #}) + # Each download has a unique id + download_id = self.download_count + self.download_count += 1 + + path = request.path + + # Tell GUI the individual file started + self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { + 'id': download_id, + 'filesize': filesize, + 'method': request.method + }) + + # Only GET requests are allowed, any other method should fail + if request.method != "GET": + return self.web.error405() def generate(): chunk_size = 102400 # 100kb fp = open(file_to_download, 'rb') done = False - canceled = False while not done: chunk = fp.read(chunk_size) if chunk == b'': @@ -152,7 +162,7 @@ class SendBaseModeWeb: try: yield chunk - # TODO: Tell GUI the progress + # Tell GUI the progress downloaded_bytes = fp.tell() percent = (1.0 * downloaded_bytes / filesize) * 100 if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -160,20 +170,19 @@ class SendBaseModeWeb: "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) sys.stdout.flush() - #self.web.add_request(self.web.REQUEST_PROGRESS, path, { - # 'id': download_id, - # 'bytes': downloaded_bytes - # }) + self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) done = False except: # Looks like the download was canceled done = True - canceled = True - # TODO: Tell the GUI the download has canceled - #self.web.add_request(self.web.REQUEST_CANCELED, path, { - # 'id': download_id - #}) + # Tell the GUI the individual file was canceled + self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, { + 'id': download_id + }) fp.close() diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 8d5a6af5..5a96b324 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -37,15 +37,18 @@ class Web: REQUEST_LOAD = 0 REQUEST_STARTED = 1 REQUEST_PROGRESS = 2 - REQUEST_OTHER = 3 - REQUEST_CANCELED = 4 - REQUEST_RATE_LIMIT = 5 - REQUEST_UPLOAD_FILE_RENAMED = 6 - REQUEST_UPLOAD_SET_DIR = 7 - REQUEST_UPLOAD_FINISHED = 8 - REQUEST_UPLOAD_CANCELED = 9 - REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10 - REQUEST_INVALID_PASSWORD = 11 + REQUEST_CANCELED = 3 + REQUEST_RATE_LIMIT = 4 + REQUEST_UPLOAD_FILE_RENAMED = 5 + REQUEST_UPLOAD_SET_DIR = 6 + REQUEST_UPLOAD_FINISHED = 7 + REQUEST_UPLOAD_CANCELED = 8 + REQUEST_INDIVIDUAL_FILE_STARTED = 9 + REQUEST_INDIVIDUAL_FILE_PROGRESS = 10 + REQUEST_INDIVIDUAL_FILE_CANCELED = 11 + REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 12 + REQUEST_OTHER = 13 + REQUEST_INVALID_PASSWORD = 14 def __init__(self, common, is_gui, mode='share'): self.common = common @@ -193,15 +196,18 @@ class Web: r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) return self.add_security_headers(r) + def error403(self): + self.add_request(Web.REQUEST_OTHER, request.path) + r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) + return self.add_security_headers(r) + def error404(self): self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) return self.add_security_headers(r) - def error403(self): - self.add_request(Web.REQUEST_OTHER, request.path) - - r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) + def error405(self): + r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405) return self.add_security_headers(r) def add_security_headers(self, r): diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index e92e36f8..b5a95f41 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -22,6 +22,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.common import AutoStopTimer +from .history import IndividualFileHistoryItem + from ..server_status import ServerStatus from ..threads import OnionThread from ..threads import AutoStartTimer @@ -29,7 +31,7 @@ from ..widgets import Alert class Mode(QtWidgets.QWidget): """ - The class that ShareMode and ReceiveMode inherit from. + The class that all modes inherit from """ start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() @@ -417,3 +419,46 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_CANCELED event. """ pass + + def handle_request_individual_file_started(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_STARTED event. + Used in both Share and Website modes, so implemented here. + """ + item = IndividualFileHistoryItem(self.common, event["data"], event["path"]) + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.in_progress_count += 1 + self.history.update_in_progress() + + def handle_request_individual_file_progress(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_PROGRESS event. + Used in both Share and Website modes, so implemented here. + """ + self.history.update(event["data"]["id"], event["data"]["bytes"]) + + # Is the download complete? + if event["data"]["bytes"] == self.web.share_mode.filesize: + # Update completed and in progress labels + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() + + else: + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.history.cancel(event["data"]["id"]) + self.history.in_progress_count = 0 + self.history.update_in_progress() + + def handle_request_individual_file_canceled(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_CANCELED event. + Used in both Share and Website modes, so implemented here. + """ + self.history.cancel(event["data"]["id"]) + + # Update in progress count + self.history.in_progress_count -= 1 + self.history.update_in_progress() diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index c2c696fc..a9fbbb36 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -345,61 +345,88 @@ class IndividualFileHistoryItem(HistoryItem): """ Individual file history item, for share mode viewing of individual files """ - def __init__(self, common, path): + def __init__(self, common, data, path): super(IndividualFileHistoryItem, self).__init__() self.status = HistoryItem.STATUS_STARTED self.common = common - self.visited = time.time() - self.visited_dt = datetime.fromtimestamp(self.visited) + self.id = id + self.path = path + self.method = data['method'] + self.total_bytes = data['filesize'] + self.downloaded_bytes = 0 + self.started = time.time() + self.started_dt = datetime.fromtimestamp(self.started) + self.status = HistoryItem.STATUS_STARTED # Labels - self.timestamp_label = QtWidgets.QLabel(self.visited_dt.strftime("%b %d, %I:%M%p")) - self.path_viewed_label = QtWidgets.QLabel(strings._('gui_individual_file_download').format(path)) + self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) + self.method_label = QtWidgets.QLabel("{} {}".format(self.method, self.path)) + self.status_label = QtWidgets.QLabel() + + # Progress bar + self.progress_bar = QtWidgets.QProgressBar() + self.progress_bar.setTextVisible(True) + self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) + self.progress_bar.setMinimum(0) + self.progress_bar.setMaximum(data['filesize']) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.total_bytes = data['filesize'] + + # Text layout + labels_layout = QtWidgets.QHBoxLayout() + labels_layout.addWidget(self.timestamp_label) + labels_layout.addWidget(self.method_label) + labels_layout.addWidget(self.status_label) # Layout layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.timestamp_label) - layout.addWidget(self.path_viewed_label) + layout.addLayout(labels_layout) + layout.addWidget(self.progress_bar) self.setLayout(layout) + # All non-GET requests are error 405 Method Not Allowed + if self.method.lower() != 'get': + self.status_label.setText("405") + self.progress_bar.hide() + else: + # Start at 0 + self.update(0) - def update(self): - self.label.setText(self.get_finished_label_text(self.started_dt)) - self.status = HistoryItem.STATUS_FINISHED + def update(self, downloaded_bytes): + self.downloaded_bytes = downloaded_bytes + + self.progress_bar.setValue(downloaded_bytes) + if downloaded_bytes == self.progress_bar.total_bytes: + self.progress_bar.hide() + self.status = HistoryItem.STATUS_FINISHED + + else: + elapsed = time.time() - self.started + if elapsed < 10: + # Wait a couple of seconds for the download rate to stabilize. + # This prevents a "Windows copy dialog"-esque experience at + # the beginning of the download. + pb_fmt = strings._('gui_all_modes_progress_starting').format( + self.common.human_readable_filesize(downloaded_bytes)) + else: + pb_fmt = strings._('gui_all_modes_progress_eta').format( + self.common.human_readable_filesize(downloaded_bytes), + self.estimated_time_remaining) + + self.progress_bar.setFormat(pb_fmt) def cancel(self): self.progress_bar.setFormat(strings._('gui_canceled')) self.status = HistoryItem.STATUS_CANCELED -class VisitHistoryItem(HistoryItem): - """ - Download history item, for share mode - """ - def __init__(self, common, id, total_bytes): - super(VisitHistoryItem, self).__init__() - self.status = HistoryItem.STATUS_STARTED - self.common = common - - self.id = id - self.visited = time.time() - self.visited_dt = datetime.fromtimestamp(self.visited) - - # Label - self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.visited_dt.strftime("%b %d, %I:%M%p"))) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.label) - self.setLayout(layout) - - def update(self): - self.label.setText(self.get_finished_label_text(self.started_dt)) - self.status = HistoryItem.STATUS_FINISHED - - def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) - self.status = HistoryItem.STATUS_CANCELED + @property + def estimated_time_remaining(self): + return self.common.estimated_time_remaining(self.downloaded_bytes, + self.total_bytes, + self.started) class HistoryItemList(QtWidgets.QScrollArea): """ diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 56aa1364..b5da0cd3 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -225,21 +225,6 @@ class ShareMode(Mode): """ self.primary_action.hide() - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) - if not self.common.settings.get('close_after_first_download') and not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/': - - item = IndividualFileHistoryItem(self.common, event["path"]) - - self.history.add(0, item) - self.toggle_history.update_indicator(True) - self.history.completed_count += 1 - self.history.update_completed() - self.system_tray.showMessage(strings._('systray_individual_file_downloaded_title'), strings._('systray_individual_file_downloaded_message').format(event["path"])) - def handle_request_started(self, event): """ Handle REQUEST_STARTED event. diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 8ac88c8c..3d4497f0 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -30,7 +30,7 @@ from onionshare.web import Web from ..file_selection import FileSelection from .. import Mode -from ..history import History, ToggleHistory, VisitHistoryItem +from ..history import History, ToggleHistory from ...widgets import Alert class WebsiteMode(Mode): @@ -204,21 +204,6 @@ class WebsiteMode(Mode): """ self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message')) - def handle_request_started(self, event): - """ - Handle REQUEST_STARTED event. - """ - if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ): - item = VisitHistoryItem(self.common, event["data"]["id"], 0) - - self.history.add(event["data"]["id"], item) - self.toggle_history.update_indicator(True) - self.history.completed_count += 1 - self.history.update_completed() - - self.system_tray.showMessage(strings._('systray_website_started_title'), strings._('systray_website_started_message')) - - def on_reload_settings(self): """ If there were some files listed for sharing, we should be ok to re-enable diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index bed86895..20873bc8 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -383,7 +383,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.autostart_timer_container.hide() self.receive_mode.server_status.autostart_timer_container.hide() self.website_mode.server_status.autostart_timer_container.hide() - + d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) d.settings_saved.connect(reload_settings) d.exec_() @@ -470,6 +470,15 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_CANCELED: mode.handle_request_upload_canceled(event) + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_STARTED: + mode.handle_request_individual_file_started(event) + + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_PROGRESS: + mode.handle_request_individual_file_progress(event) + + elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_CANCELED: + mode.handle_request_individual_file_canceled(event) + if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) diff --git a/share/locale/en.json b/share/locale/en.json index c26577b2..5fbf88f9 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -178,7 +178,6 @@ "gui_receive_mode_no_files": "No Files Received Yet", "gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving", "gui_visit_started": "Someone has visited your website {}", - "gui_individual_file_download": "Viewed {}", "receive_mode_upload_starting": "Upload of total size {} is starting", "days_first_letter": "d", "hours_first_letter": "h", diff --git a/share/templates/405.html b/share/templates/405.html new file mode 100644 index 00000000..55493ae7 --- /dev/null +++ b/share/templates/405.html @@ -0,0 +1,19 @@ + + + + + OnionShare: 405 Method Not Allowed + + + + + +
+
+

+

405 Method Not Allowed

+
+
+ + + From 11860b55f2144120b2af09cb0b1314ae479a1aff Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 21:59:49 -0700 Subject: [PATCH 42/64] Show IndividualFileHistoryItem widgets for directory listings --- onionshare/web/send_base_mode.py | 15 ++++++++++---- onionshare_gui/mode/history.py | 35 +++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 402bc32f..eb6525d1 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -79,6 +79,15 @@ class SendBaseModeWeb: self.set_file_info_custom(filenames, processed_size_callback) def directory_listing(self, filenames, path='', filesystem_path=None): + # Tell the GUI about the directory listing + download_id = self.download_count + self.download_count += 1 + self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '/{}'.format(path), { + 'id': download_id, + 'method': request.method, + 'directory_listing': True + }) + # If filesystem_path is None, this is the root directory listing files, dirs = self.build_directory_listing(filenames, filesystem_path) r = self.directory_listing_template(path, files, dirs) @@ -132,13 +141,11 @@ class SendBaseModeWeb: file_to_download = filesystem_path filesize = os.path.getsize(filesystem_path) - # Each download has a unique id - download_id = self.download_count - self.download_count += 1 - path = request.path # Tell GUI the individual file started + download_id = self.download_count + self.download_count += 1 self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { 'id': download_id, 'filesize': filesize, diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index a9fbbb36..ce783d46 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -352,34 +352,34 @@ class IndividualFileHistoryItem(HistoryItem): self.id = id self.path = path - self.method = data['method'] - self.total_bytes = data['filesize'] + self.total_bytes = 0 self.downloaded_bytes = 0 + self.method = data['method'] self.started = time.time() self.started_dt = datetime.fromtimestamp(self.started) self.status = HistoryItem.STATUS_STARTED + self.directory_listing = 'directory_listing' in data + # Labels self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) self.method_label = QtWidgets.QLabel("{} {}".format(self.method, self.path)) - self.status_label = QtWidgets.QLabel() + self.status_code_label = QtWidgets.QLabel() # Progress bar self.progress_bar = QtWidgets.QProgressBar() self.progress_bar.setTextVisible(True) self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) - self.progress_bar.setMinimum(0) - self.progress_bar.setMaximum(data['filesize']) self.progress_bar.setValue(0) self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) - self.progress_bar.total_bytes = data['filesize'] # Text layout labels_layout = QtWidgets.QHBoxLayout() labels_layout.addWidget(self.timestamp_label) labels_layout.addWidget(self.method_label) - labels_layout.addWidget(self.status_label) + labels_layout.addWidget(self.status_code_label) + labels_layout.addStretch() # Layout layout = QtWidgets.QVBoxLayout() @@ -389,11 +389,26 @@ class IndividualFileHistoryItem(HistoryItem): # All non-GET requests are error 405 Method Not Allowed if self.method.lower() != 'get': - self.status_label.setText("405") + self.status_code_label.setText("405") + self.status = HistoryItem.STATUS_FINISHED self.progress_bar.hide() + return + + # Is this a directory listing? + if self.directory_listing: + self.status_code_label.setText("200") + self.status = HistoryItem.STATUS_FINISHED + self.progress_bar.hide() + return + else: - # Start at 0 - self.update(0) + self.total_bytes = data['filesize'] + self.progress_bar.setMinimum(0) + self.progress_bar.setMaximum(data['filesize']) + self.progress_bar.total_bytes = data['filesize'] + + # Start at 0 + self.update(0) def update(self, downloaded_bytes): self.downloaded_bytes = downloaded_bytes From 54ba711cbf1bd7a6f43944495935a19e7d3941e3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:18:30 -0700 Subject: [PATCH 43/64] Rename download_count/download_id, upload_count/upload_id, and visit_count/visit_id to simply cur_history_id/history_id, and make all errors create IndividualFileHistoryItem widgets --- onionshare/web/receive_mode.py | 24 ++++++++++------------ onionshare/web/send_base_mode.py | 21 +++++++++---------- onionshare/web/share_mode.py | 14 ++++++------- onionshare/web/web.py | 35 ++++++++++++++++++++++++++++++++ onionshare/web/website_mode.py | 11 ---------- onionshare_gui/mode/history.py | 7 ++++--- 6 files changed, 66 insertions(+), 46 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index d2b03da0..5029232f 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -19,7 +19,6 @@ class ReceiveModeWeb: self.web = web self.can_upload = True - self.upload_count = 0 self.uploads_in_progress = [] self.define_routes() @@ -52,7 +51,7 @@ class ReceiveModeWeb: # Tell the GUI the receive mode directory for this file self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, { - 'id': request.upload_id, + 'id': request.history_id, 'filename': basename, 'dir': request.receive_mode_dir }) @@ -272,10 +271,9 @@ class ReceiveModeRequest(Request): # Prevent new uploads if we've said so (timer expired) if self.web.receive_mode.can_upload: - # Create an upload_id, attach it to the request - self.upload_id = self.web.receive_mode.upload_count - - self.web.receive_mode.upload_count += 1 + # Create an history_id, attach it to the request + self.history_id = self.web.receive_mode.cur_history_id + self.web.receive_mode.cur_history_id += 1 # Figure out the content length try: @@ -302,10 +300,10 @@ class ReceiveModeRequest(Request): if not self.told_gui_about_request: # Tell the GUI about the request self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, + 'id': self.history_id, 'content_length': self.content_length }) - self.web.receive_mode.uploads_in_progress.append(self.upload_id) + self.web.receive_mode.uploads_in_progress.append(self.history_id) self.told_gui_about_request = True @@ -337,19 +335,19 @@ class ReceiveModeRequest(Request): try: if self.told_gui_about_request: - upload_id = self.upload_id + history_id = self.history_id if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']: # Inform the GUI that the upload has canceled self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { - 'id': upload_id + 'id': history_id }) else: # Inform the GUI that the upload has finished self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': upload_id + 'id': history_id }) - self.web.receive_mode.uploads_in_progress.remove(upload_id) + self.web.receive_mode.uploads_in_progress.remove(history_id) except AttributeError: pass @@ -375,7 +373,7 @@ class ReceiveModeRequest(Request): # Update the GUI on the upload progress if self.told_gui_about_request: self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { - 'id': self.upload_id, + 'id': self.history_id, 'progress': self.progress }) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index eb6525d1..3a01cb8f 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -44,8 +44,7 @@ class SendBaseModeWeb: self.files = {} # Dictionary mapping file paths to filenames on disk self.root_files = {} # This is only the root files and dirs, as opposed to all of them self.cleanup_filenames = [] - self.visit_count = 0 - self.download_count = 0 + self.cur_history_id = 0 self.file_info = {'files': [], 'dirs': []} self.gzip_individual_files = {} self.init() @@ -80,12 +79,12 @@ class SendBaseModeWeb: def directory_listing(self, filenames, path='', filesystem_path=None): # Tell the GUI about the directory listing - download_id = self.download_count - self.download_count += 1 + history_id = self.cur_history_id + self.cur_history_id += 1 self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '/{}'.format(path), { - 'id': download_id, + 'id': history_id, 'method': request.method, - 'directory_listing': True + 'status_code': 200 }) # If filesystem_path is None, this is the root directory listing @@ -144,10 +143,10 @@ class SendBaseModeWeb: path = request.path # Tell GUI the individual file started - download_id = self.download_count - self.download_count += 1 + history_id = self.cur_history_id + self.cur_history_id += 1 self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { - 'id': download_id, + 'id': history_id, 'filesize': filesize, 'method': request.method }) @@ -178,7 +177,7 @@ class SendBaseModeWeb: sys.stdout.flush() self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, { - 'id': download_id, + 'id': history_id, 'bytes': downloaded_bytes }) done = False @@ -188,7 +187,7 @@ class SendBaseModeWeb: # Tell the GUI the individual file was canceled self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, { - 'id': download_id + 'id': history_id }) fp.close() diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 60620e2a..c9d9b229 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -60,10 +60,6 @@ class ShareModeWeb(SendBaseModeWeb): static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) - # Each download has a unique id - download_id = self.download_count - self.download_count += 1 - # Prepare some variables to use inside generate() function below # which is outside of the request context shutdown_func = request.environ.get('werkzeug.server.shutdown') @@ -81,8 +77,10 @@ class ShareModeWeb(SendBaseModeWeb): self.filesize = self.download_filesize # Tell GUI the download started + history_id = self.cur_history_id + self.cur_history_id += 1 self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': download_id, + 'id': history_id, 'use_gzip': use_gzip }) @@ -102,7 +100,7 @@ class ShareModeWeb(SendBaseModeWeb): # The user has canceled the download, so stop serving the file if not self.web.stop_q.empty(): self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': download_id + 'id': history_id }) break @@ -124,7 +122,7 @@ class ShareModeWeb(SendBaseModeWeb): sys.stdout.flush() self.web.add_request(self.web.REQUEST_PROGRESS, path, { - 'id': download_id, + 'id': history_id, 'bytes': downloaded_bytes }) self.web.done = False @@ -135,7 +133,7 @@ class ShareModeWeb(SendBaseModeWeb): # tell the GUI the download has canceled self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': download_id + 'id': history_id }) fp.close() diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 5a96b324..c4d5385f 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -63,6 +63,9 @@ class Web: self.auth = HTTPBasicAuth() self.auth.error_handler(self.error401) + # This tracks the history id + self.cur_history_id = 0 + # Verbose mode? if self.common.verbose: self.verbose_mode() @@ -193,20 +196,52 @@ class Web: self.force_shutdown() print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") + history_id = self.cur_history_id + self.cur_history_id += 1 + self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { + 'id': history_id, + 'method': request.method, + 'status_code': 401 + }) + r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) return self.add_security_headers(r) def error403(self): + history_id = self.cur_history_id + self.cur_history_id += 1 + self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { + 'id': history_id, + 'method': request.method, + 'status_code': 403 + }) + self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) return self.add_security_headers(r) def error404(self): + history_id = self.cur_history_id + self.cur_history_id += 1 + self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { + 'id': history_id, + 'method': request.method, + 'status_code': 404 + }) + self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) return self.add_security_headers(r) def error405(self): + history_id = self.cur_history_id + self.cur_history_id += 1 + self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { + 'id': history_id, + 'method': request.method, + 'status_code': 405 + }) + r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405) return self.add_security_headers(r) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 28f2607d..55e5c1d4 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -28,17 +28,6 @@ class WebsiteModeWeb(SendBaseModeWeb): """ Render the onionshare website. """ - - # Each download has a unique id - visit_id = self.visit_count - self.visit_count += 1 - - # Tell GUI the page has been visited - self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': visit_id, - 'action': 'visit' - }) - return self.render_logic(path) def directory_listing_template(self, path, files, dirs): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index ce783d46..cd8fe529 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -394,9 +394,9 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.hide() return - # Is this a directory listing? - if self.directory_listing: - self.status_code_label.setText("200") + # Is a status code already sent? + if 'status_code' in data: + self.status_code_label.setText("{}".format(data['status_code'])) self.status = HistoryItem.STATUS_FINISHED self.progress_bar.hide() return @@ -415,6 +415,7 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: + self.status_code_label.setText("200") self.progress_bar.hide() self.status = HistoryItem.STATUS_FINISHED From 4ee6647ee5e638db1f0dc9245edaa0f9b7d80ed7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:20:52 -0700 Subject: [PATCH 44/64] Rename a few more count variables to cur_history_id --- onionshare/__init__.py | 4 ++-- onionshare_gui/mode/receive_mode/__init__.py | 4 ++-- onionshare_gui/mode/share_mode/__init__.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 0003106f..7e7798f8 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -262,12 +262,12 @@ def main(cwd=None): if not app.autostop_timer_thread.is_alive(): if mode == 'share' or (mode == 'website'): # If there were no attempts to download the share, or all downloads are done, we can stop - if web.share_mode.download_count == 0 or web.done: + if web.share_mode.cur_history_id == 0 or web.done: print("Stopped because auto-stop timer ran out") web.stop(app.port) break if mode == 'receive': - if web.receive_mode.upload_count == 0 or not web.receive_mode.uploads_in_progress: + if web.receive_mode.cur_history_id == 0 or not web.receive_mode.uploads_in_progress: print("Stopped because auto-stop timer ran out") web.stop(app.port) break diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 0010fbd2..ecbfa54a 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -97,7 +97,7 @@ class ReceiveMode(Mode): The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress: + if self.web.receive_mode.cur_history_id == 0 or not self.web.receive_mode.uploads_in_progress: self.server_status.stop_server() self.server_status_label.setText(strings._('close_on_autostop_timer')) return True @@ -112,7 +112,7 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters - self.web.receive_mode.upload_count = 0 + self.web.receive_mode.cur_history_id = 0 self.web.reset_invalid_passwords() # Hide and reset the uploads if we have previously shared diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index b5da0cd3..35a2045d 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -132,7 +132,7 @@ class ShareMode(Mode): The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.share_mode.download_count == 0 or self.web.done: + if self.web.share_mode.cur_history_id == 0 or self.web.done: self.server_status.stop_server() self.server_status_label.setText(strings._('close_on_autostop_timer')) return True @@ -146,7 +146,7 @@ class ShareMode(Mode): Starting the server. """ # Reset web counters - self.web.share_mode.download_count = 0 + self.web.share_mode.cur_history_id = 0 self.web.reset_invalid_passwords() # Hide and reset the downloads if we have previously shared From bef116760d42b489290e69bad511f8f0d451cf6a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:31:13 -0700 Subject: [PATCH 45/64] Make the IndividualFileHistoryItem widgets have color --- onionshare/common.py | 20 +++++++++++++++++++- onionshare_gui/mode/history.py | 18 +++++++++--------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 27e8efc2..06563461 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -203,7 +203,7 @@ class Common(object): border: 0px; }""", - # Common styles between ShareMode and ReceiveMode and their child widgets + # Common styles between modes and their child widgets 'mode_info_label': """ QLabel { font-size: 12px; @@ -310,6 +310,24 @@ class Common(object): width: 10px; }""", + 'history_individual_file_timestamp_label': """ + QLabel { + color: #666666; + }""", + + 'history_individual_file_request_label': """ + QLabel { }""", + + 'history_individual_file_status_code_label_2xx': """ + QLabel { + color: #008800; + }""", + + 'history_individual_file_status_code_label_4xx': """ + QLabel { + color: #cc0000; + }""", + # Share mode and child widget styles 'share_zip_progess_bar': """ QProgressBar { diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index cd8fe529..797950ab 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -363,7 +363,9 @@ class IndividualFileHistoryItem(HistoryItem): # Labels self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) - self.method_label = QtWidgets.QLabel("{} {}".format(self.method, self.path)) + self.timestamp_label.setStyleSheet(self.common.css['history_individual_file_timestamp_label']) + self.request_label = QtWidgets.QLabel("{} {}".format(self.method, self.path)) + self.request_label.setStyleSheet(self.common.css['history_individual_file_request_label']) self.status_code_label = QtWidgets.QLabel() # Progress bar @@ -377,7 +379,7 @@ class IndividualFileHistoryItem(HistoryItem): # Text layout labels_layout = QtWidgets.QHBoxLayout() labels_layout.addWidget(self.timestamp_label) - labels_layout.addWidget(self.method_label) + labels_layout.addWidget(self.request_label) labels_layout.addWidget(self.status_code_label) labels_layout.addStretch() @@ -387,16 +389,13 @@ class IndividualFileHistoryItem(HistoryItem): layout.addWidget(self.progress_bar) self.setLayout(layout) - # All non-GET requests are error 405 Method Not Allowed - if self.method.lower() != 'get': - self.status_code_label.setText("405") - self.status = HistoryItem.STATUS_FINISHED - self.progress_bar.hide() - return - # Is a status code already sent? if 'status_code' in data: self.status_code_label.setText("{}".format(data['status_code'])) + if data['status_code'] >= 200 and data['status_code'] < 300: + self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) + if data['status_code'] >= 400 and data['status_code'] < 500: + self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_4xx']) self.status = HistoryItem.STATUS_FINISHED self.progress_bar.hide() return @@ -416,6 +415,7 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: self.status_code_label.setText("200") + self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) self.progress_bar.hide() self.status = HistoryItem.STATUS_FINISHED From bc210c954d7d7be034aa898c8a240c8abdd8b2e8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 09:35:44 -0700 Subject: [PATCH 46/64] Remove method from IndividualFileHistoryItem, and only display these widgets on 200 and 404 requests, not all of the others --- onionshare/common.py | 3 --- onionshare/web/send_base_mode.py | 3 +-- onionshare/web/web.py | 24 ------------------------ onionshare_gui/mode/history.py | 6 ++---- 4 files changed, 3 insertions(+), 33 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 06563461..ab503fdc 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -315,9 +315,6 @@ class Common(object): color: #666666; }""", - 'history_individual_file_request_label': """ - QLabel { }""", - 'history_individual_file_status_code_label_2xx': """ QLabel { color: #008800; diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 3a01cb8f..6a0390ab 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -147,8 +147,7 @@ class SendBaseModeWeb: self.cur_history_id += 1 self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { 'id': history_id, - 'filesize': filesize, - 'method': request.method + 'filesize': filesize }) # Only GET requests are allowed, any other method should fail diff --git a/onionshare/web/web.py b/onionshare/web/web.py index c4d5385f..610c14c2 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -196,26 +196,10 @@ class Web: self.force_shutdown() print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") - history_id = self.cur_history_id - self.cur_history_id += 1 - self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'method': request.method, - 'status_code': 401 - }) - r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) return self.add_security_headers(r) def error403(self): - history_id = self.cur_history_id - self.cur_history_id += 1 - self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'method': request.method, - 'status_code': 403 - }) - self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) return self.add_security_headers(r) @@ -234,14 +218,6 @@ class Web: return self.add_security_headers(r) def error405(self): - history_id = self.cur_history_id - self.cur_history_id += 1 - self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'method': request.method, - 'status_code': 405 - }) - r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405) return self.add_security_headers(r) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 797950ab..2fd7cddb 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -354,7 +354,6 @@ class IndividualFileHistoryItem(HistoryItem): self.path = path self.total_bytes = 0 self.downloaded_bytes = 0 - self.method = data['method'] self.started = time.time() self.started_dt = datetime.fromtimestamp(self.started) self.status = HistoryItem.STATUS_STARTED @@ -364,8 +363,7 @@ class IndividualFileHistoryItem(HistoryItem): # Labels self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) self.timestamp_label.setStyleSheet(self.common.css['history_individual_file_timestamp_label']) - self.request_label = QtWidgets.QLabel("{} {}".format(self.method, self.path)) - self.request_label.setStyleSheet(self.common.css['history_individual_file_request_label']) + self.path_label = QtWidgets.QLabel("{}".format(self.path)) self.status_code_label = QtWidgets.QLabel() # Progress bar @@ -379,7 +377,7 @@ class IndividualFileHistoryItem(HistoryItem): # Text layout labels_layout = QtWidgets.QHBoxLayout() labels_layout.addWidget(self.timestamp_label) - labels_layout.addWidget(self.request_label) + labels_layout.addWidget(self.path_label) labels_layout.addWidget(self.status_code_label) labels_layout.addStretch() From 79f563e443a548ffa8091c931de284de78a94f75 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 09:45:53 -0700 Subject: [PATCH 47/64] Make sure IndividualFileHistoryItem widgets display properly in receive mode too --- onionshare/web/receive_mode.py | 9 +++++++++ onionshare/web/web.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 5029232f..8604a889 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -21,6 +21,8 @@ class ReceiveModeWeb: self.can_upload = True self.uploads_in_progress = [] + self.cur_history_id = 0 + self.define_routes() def define_routes(self): @@ -29,6 +31,13 @@ class ReceiveModeWeb: """ @self.web.app.route("/") def index(): + history_id = self.cur_history_id + self.cur_history_id += 1 + self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { + 'id': history_id, + 'status_code': 200 + }) + self.web.add_request(self.web.REQUEST_LOAD, request.path) r = make_response(render_template('receive.html', static_url_path=self.web.static_url_path)) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 610c14c2..6cd30c93 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -209,7 +209,6 @@ class Web: self.cur_history_id += 1 self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { 'id': history_id, - 'method': request.method, 'status_code': 404 }) From bd3a7fe1f7ccdaeda97110fbb241e23f83062dac Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 11:58:44 -0700 Subject: [PATCH 48/64] Don't consider individual downloads in the in_progress counter --- onionshare/web/send_base_mode.py | 3 ++- onionshare_gui/mode/__init__.py | 22 ++-------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 6a0390ab..a6ad2307 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -177,7 +177,8 @@ class SendBaseModeWeb: self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, { 'id': history_id, - 'bytes': downloaded_bytes + 'bytes': downloaded_bytes, + 'filesize': filesize }) done = False except: diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index b5a95f41..69ad00e6 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -427,9 +427,6 @@ class Mode(QtWidgets.QWidget): """ item = IndividualFileHistoryItem(self.common, event["data"], event["path"]) self.history.add(event["data"]["id"], item) - self.toggle_history.update_indicator(True) - self.history.in_progress_count += 1 - self.history.update_in_progress() def handle_request_individual_file_progress(self, event): """ @@ -438,19 +435,8 @@ class Mode(QtWidgets.QWidget): """ self.history.update(event["data"]["id"], event["data"]["bytes"]) - # Is the download complete? - if event["data"]["bytes"] == self.web.share_mode.filesize: - # Update completed and in progress labels - self.history.completed_count += 1 - self.history.in_progress_count -= 1 - self.history.update_completed() - self.history.update_in_progress() - - else: - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.history.cancel(event["data"]["id"]) - self.history.in_progress_count = 0 - self.history.update_in_progress() + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.history.cancel(event["data"]["id"]) def handle_request_individual_file_canceled(self, event): """ @@ -458,7 +444,3 @@ class Mode(QtWidgets.QWidget): Used in both Share and Website modes, so implemented here. """ self.history.cancel(event["data"]["id"]) - - # Update in progress count - self.history.in_progress_count -= 1 - self.history.update_in_progress() From 04d49dc3bd6f448cefd5e51ea83d1538b9de8f76 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 12:02:17 -0700 Subject: [PATCH 49/64] Add individual downloads label to settings dialog --- onionshare_gui/settings_dialog.py | 2 ++ share/locale/en.json | 1 + 2 files changed, 3 insertions(+) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 6ffd4523..5dbc31d2 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -204,10 +204,12 @@ class SettingsDialog(QtWidgets.QDialog): self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) + individual_downloads_label = QtWidgets.QLabel(strings._("gui_settings_individual_downloads_label")) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) + sharing_group_layout.addWidget(individual_downloads_label) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) diff --git a/share/locale/en.json b/share/locale/en.json index 5fbf88f9..c84c5538 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -52,6 +52,7 @@ "gui_settings_onion_label": "Onion settings", "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent", + "gui_settings_individual_downloads_label": "Uncheck to allow downloading individual files", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", From 8aa871b2772d8e4abfcfade685fe358a5c784b33 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:24:18 -0700 Subject: [PATCH 50/64] Add web requests counter icon to history widget --- onionshare_gui/mode/history.py | 37 +++++++++++++++++++++------ share/images/share_requests.png | Bin 0 -> 738 bytes share/images/share_requests_none.png | Bin 0 -> 754 bytes share/locale/en.json | 1 + 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 share/images/share_requests.png create mode 100644 share/images/share_requests_none.png diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 2fd7cddb..650e57be 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -237,6 +237,7 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): elif self.common.platform == 'Windows': subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) + class ReceiveHistoryItem(HistoryItem): def __init__(self, common, id, content_length): super(ReceiveHistoryItem, self).__init__() @@ -442,6 +443,7 @@ class IndividualFileHistoryItem(HistoryItem): self.total_bytes, self.started) + class HistoryItemList(QtWidgets.QScrollArea): """ List of items @@ -524,12 +526,15 @@ class History(QtWidgets.QWidget): # In progress and completed counters self.in_progress_count = 0 self.completed_count = 0 + self.requests_count = 0 - # In progress and completed labels + # In progress, completed, and requests labels self.in_progress_label = QtWidgets.QLabel() self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) self.completed_label = QtWidgets.QLabel() self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + self.requests_label = QtWidgets.QLabel() + self.requests_label.setStyleSheet(self.common.css['mode_info_label']) # Header self.header_label = QtWidgets.QLabel(header_text) @@ -543,6 +548,7 @@ class History(QtWidgets.QWidget): header_layout.addStretch() header_layout.addWidget(self.in_progress_label) header_layout.addWidget(self.completed_label) + header_layout.addWidget(self.requests_label) header_layout.addWidget(clear_button) # When there are no items @@ -621,6 +627,10 @@ class History(QtWidgets.QWidget): self.completed_count = 0 self.update_completed() + # Reset web requests counter + self.requests_count = 0 + self.update_requests() + def update_completed(self): """ Update the 'completed' widget. @@ -636,14 +646,25 @@ class History(QtWidgets.QWidget): """ Update the 'in progress' widget. """ - if self.mode != 'website': - if self.in_progress_count == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') + if self.in_progress_count == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) - self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) + self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + + def update_requests(self): + """ + Update the 'web requests' widget. + """ + if self.requests_count == 0: + image = self.common.get_resource_path('images/share_requests_none.png') + else: + image = self.common.get_resource_path('images/share_requests.png') + + self.requests_label.setText(' {1:d}'.format(image, self.in_progress_count)) + self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.in_progress_count)) class ToggleHistory(QtWidgets.QPushButton): diff --git a/share/images/share_requests.png b/share/images/share_requests.png new file mode 100644 index 0000000000000000000000000000000000000000..4965744d57bebe31125cca767c4a1bb63dcfbd08 GIT binary patch literal 738 zcmV<80v-K{P)EX>4Tx04R}tkv&MmKp2MKrfO9x4t5Z6$WWauh>AFB6^c+H)C#RSm|Xe?O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgadlF3krKa43N2#1?&BYF{Svtpa+Scy zv49FR$gUs!4}SO7%1=&sN#Quq`QkVqBS2^uXw)3%``B?BCqVESxYAqxN*$Q_B)!(s zqDMggHgIv>(v&^mat9cEGGtSBr64UKp9kL0=$o>@z%9_b=Jl<4j?)JqO}$Fq00)P_ zXo0fVecs*O-nV~in*I9$wKj5FNcgT>00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru;|U1>4>Ds@qc{Kn0N+VOK~yNumC~V315pqK(DOD8 zLEuQk2Y^C!HD(oYBZ_{4t9`M@ro(R5YrQ%rus4qkA9aWuT+ z0mt~l`6yzXhN1EkV_XgDMS&TXU6m8om<|k6tV3I3R-nXV7!I%+G^~OXp0e;y6vz<^ z-Y^;Hli-9Wr@Ak%aku3IHzTw-+wxo6A~x6rF^`zT$n(&euF9MA$7vh|{p&Qo0Z!;Y U2m*6azW@LL07*qoM6N<$f^Tm@n*aa+ literal 0 HcmV?d00001 diff --git a/share/images/share_requests_none.png b/share/images/share_requests_none.png new file mode 100644 index 0000000000000000000000000000000000000000..93a71ef3455a614f95a1f1270585d0b1c121d5dc GIT binary patch literal 754 zcmVEX>4Tx04R}tkv&MmKp2MKrfO9x4t5Z6$WWauh>AFB6^c+H)C#RSm|Xe?O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgadlF3krKa43N2#1?&BYF{Svtpa+Scy zv49FR$gUs!4}SO7%1=&sN#Quq`QkVqBS2^uXw)3%``B?BCqVESxYAqxN*$Q_B)!(s zqDMggHgIv>(v&^mat9cEGGtSBr64UKp9kL0=$o>@z%9_b=Jl<4j?)JqO}$Fq00)P_ zXo0fVecs*O-nV~in*I9$wKj5FNcgT>00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru;|U1>4<`P2LqQOQ;qSHu zK~N+O4}e3oVkHCvn4$oS<%XaMJOBy|u3~ux=##7{0t4m>NC=h?(u9B+adG9io%zq1 z$!sN5*Yz?^af~%oc*ZTpIp+`P2E2taj5XyyFh* zm|?#cYwQHD<*)iUX}wQ7IKXwzxxx!ZEntL~=w0KWgCXt%Sj9uj@DMe@3_Jd*4yLgd zykXGl527ZRX;s(tIj-g$aM6qV@6LJFakrE*ODQ$j#2&s|ANPfX2A4VKr!JIInv~K6 kpOLl_hF9F+Ea&_Lzx&EXpjdPhH~;_u07*qoM6N<$g0PxPp8x;= literal 0 HcmV?d00001 diff --git a/share/locale/en.json b/share/locale/en.json index c84c5538..aab6153d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -134,6 +134,7 @@ "gui_file_info_single": "{} file, {}", "history_in_progress_tooltip": "{} in progress", "history_completed_tooltip": "{} completed", + "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.

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.", "gui_mode_share_button": "Share Files", From 3422cf6ea89ec50895f19a3c4a067372ad3788d4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:27:24 -0700 Subject: [PATCH 51/64] Rename images from share_ to history_, because they are used in all modes --- onionshare_gui/mode/history.py | 12 ++++++------ .../{share_completed.png => history_completed.png} | Bin ...ompleted_none.png => history_completed_none.png} | Bin ...hare_in_progress.png => history_in_progress.png} | Bin ...ogress_none.png => history_in_progress_none.png} | Bin .../{share_requests.png => history_requests.png} | Bin ..._requests_none.png => history_requests_none.png} | Bin 7 files changed, 6 insertions(+), 6 deletions(-) rename share/images/{share_completed.png => history_completed.png} (100%) rename share/images/{share_completed_none.png => history_completed_none.png} (100%) rename share/images/{share_in_progress.png => history_in_progress.png} (100%) rename share/images/{share_in_progress_none.png => history_in_progress_none.png} (100%) rename share/images/{share_requests.png => history_requests.png} (100%) rename share/images/{share_requests_none.png => history_requests_none.png} (100%) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 650e57be..568bda7b 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -636,9 +636,9 @@ class History(QtWidgets.QWidget): Update the 'completed' widget. """ if self.completed_count == 0: - image = self.common.get_resource_path('images/share_completed_none.png') + image = self.common.get_resource_path('images/history_completed_none.png') else: - image = self.common.get_resource_path('images/share_completed.png') + image = self.common.get_resource_path('images/history_completed.png') self.completed_label.setText(' {1:d}'.format(image, self.completed_count)) self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) @@ -647,9 +647,9 @@ class History(QtWidgets.QWidget): Update the 'in progress' widget. """ if self.in_progress_count == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') + image = self.common.get_resource_path('images/history_in_progress_none.png') else: - image = self.common.get_resource_path('images/share_in_progress.png') + image = self.common.get_resource_path('images/history_in_progress.png') self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) @@ -659,9 +659,9 @@ class History(QtWidgets.QWidget): Update the 'web requests' widget. """ if self.requests_count == 0: - image = self.common.get_resource_path('images/share_requests_none.png') + image = self.common.get_resource_path('images/history_requests_none.png') else: - image = self.common.get_resource_path('images/share_requests.png') + image = self.common.get_resource_path('images/history_requests.png') self.requests_label.setText(' {1:d}'.format(image, self.in_progress_count)) self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.in_progress_count)) diff --git a/share/images/share_completed.png b/share/images/history_completed.png similarity index 100% rename from share/images/share_completed.png rename to share/images/history_completed.png diff --git a/share/images/share_completed_none.png b/share/images/history_completed_none.png similarity index 100% rename from share/images/share_completed_none.png rename to share/images/history_completed_none.png diff --git a/share/images/share_in_progress.png b/share/images/history_in_progress.png similarity index 100% rename from share/images/share_in_progress.png rename to share/images/history_in_progress.png diff --git a/share/images/share_in_progress_none.png b/share/images/history_in_progress_none.png similarity index 100% rename from share/images/share_in_progress_none.png rename to share/images/history_in_progress_none.png diff --git a/share/images/share_requests.png b/share/images/history_requests.png similarity index 100% rename from share/images/share_requests.png rename to share/images/history_requests.png diff --git a/share/images/share_requests_none.png b/share/images/history_requests_none.png similarity index 100% rename from share/images/share_requests_none.png rename to share/images/history_requests_none.png From 8cc1aa48bbff38f522207f51bed44f009e66b4ba Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:39:31 -0700 Subject: [PATCH 52/64] Make web requests indicator icon increment on web requests --- onionshare_gui/mode/__init__.py | 4 ++++ onionshare_gui/mode/history.py | 6 +++--- onionshare_gui/mode/website_mode/__init__.py | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 69ad00e6..3ef285c4 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -425,6 +425,10 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_INDVIDIDUAL_FILES_STARTED event. Used in both Share and Website modes, so implemented here. """ + self.toggle_history.update_indicator(True) + self.history.requests_count += 1 + self.history.update_requests() + item = IndividualFileHistoryItem(self.common, event["data"], event["path"]) self.history.add(event["data"]["id"], item) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 568bda7b..5dad9614 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -663,8 +663,8 @@ class History(QtWidgets.QWidget): else: image = self.common.get_resource_path('images/history_requests.png') - self.requests_label.setText(' {1:d}'.format(image, self.in_progress_count)) - self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.in_progress_count)) + self.requests_label.setText(' {1:d}'.format(image, self.requests_count)) + self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.requests_count)) class ToggleHistory(QtWidgets.QPushButton): @@ -697,7 +697,7 @@ class ToggleHistory(QtWidgets.QPushButton): def update_indicator(self, increment=False): """ Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. + only increment the counter if History is hidden. """ if increment and not self.history_widget.isVisible(): self.indicator_count += 1 diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 3d4497f0..b277b6c3 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -80,6 +80,8 @@ class WebsiteMode(Mode): strings._('gui_all_modes_history'), 'website' ) + self.history.in_progress_label.hide() + self.history.completed_label.hide() self.history.hide() # Info label From 4a4437394d05bd9f48d315eeb39c752f3aad645e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 12:19:39 +1000 Subject: [PATCH 53/64] Fix tests in Receive Mode that actually do increment the history item widget count where they didn't previously (due to an additional GET that follows the 302 redirect of a POST request on upload) --- tests/GuiBaseTest.py | 4 ++-- tests/GuiReceiveTest.py | 30 ++---------------------------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 9a69619b..4f087431 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -116,7 +116,7 @@ class GuiBaseTest(object): self.assertEqual(mode.history.isVisible(), not currently_visible) - def history_indicator(self, mode, public_mode): + def history_indicator(self, mode, public_mode, indicator_count="1"): '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' # Make sure history is toggled off if mode.history.isVisible(): @@ -147,7 +147,7 @@ class GuiBaseTest(object): # Indicator should be visible, have a value of "1" self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + self.assertEqual(mode.toggle_history.indicator_label.text(), indicator_count) # Toggle history back on, indicator should be hidden again QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index c4bfa884..ef420ec2 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -66,31 +66,6 @@ class GuiReceiveTest(GuiBaseTest): r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) self.assertEqual(r.status_code, 401) - def uploading_zero_files_shouldnt_change_ui(self, mode, public_mode): - '''If you submit the receive mode form without selecting any files, the UI shouldn't get updated''' - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - - # What were the counts before submitting the form? - before_in_progress_count = mode.history.in_progress_count - before_completed_count = mode.history.completed_count - before_number_of_history_items = len(mode.history.item_list.items) - - # Click submit without including any files a few times - if public_mode: - r = requests.post(url, files={}) - r = requests.post(url, files={}) - r = requests.post(url, files={}) - else: - auth = requests.auth.HTTPBasicAuth('onionshare', mode.web.password) - r = requests.post(url, files={}, auth=auth) - r = requests.post(url, files={}, auth=auth) - r = requests.post(url, files={}, auth=auth) - - # The counts shouldn't change - self.assertEqual(mode.history.in_progress_count, before_in_progress_count) - self.assertEqual(mode.history.completed_count, before_completed_count) - self.assertEqual(len(mode.history.item_list.items), before_number_of_history_items) - # 'Grouped' tests follow from here def run_all_receive_mode_setup_tests(self, public_mode): @@ -127,14 +102,13 @@ class GuiReceiveTest(GuiBaseTest): # Test uploading the same file twice at the same time, and make sure no collisions self.upload_file(public_mode, '/tmp/test.txt', 'test.txt', True) self.counter_incremented(self.gui.receive_mode, 6) - self.uploading_zero_files_shouldnt_change_ui(self.gui.receive_mode, public_mode) - self.history_indicator(self.gui.receive_mode, public_mode) + self.history_indicator(self.gui.receive_mode, public_mode, "2") self.server_is_stopped(self.gui.receive_mode, False) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.receive_mode, False) self.server_working_on_start_button_pressed(self.gui.receive_mode) self.server_is_started(self.gui.receive_mode) - self.history_indicator(self.gui.receive_mode, public_mode) + self.history_indicator(self.gui.receive_mode, public_mode, "2") def run_all_receive_mode_unwritable_dir_tests(self, public_mode): '''Attempt to upload (unwritable) files in receive mode and stop the share''' From 2c87ea55ff3c433a387e7b5a66758ae9fef8ee8c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 16:35:05 +1000 Subject: [PATCH 54/64] Fix the discrepancy between SendBaseModeWeb and Web objects' separate cur_history_id attibutes, ensuring that when we call web.error404() we send a new history_id integer for communicating back to the frontend. Add tests for this --- onionshare/web/send_base_mode.py | 5 +++- onionshare/web/share_mode.py | 12 ++++++--- onionshare/web/web.py | 7 +---- onionshare_gui/mode/history.py | 10 +++---- tests/GuiBaseTest.py | 4 +++ tests/GuiReceiveTest.py | 9 +++++++ tests/GuiShareTest.py | 9 +++++++ ...hare_receive_mode_clear_all_button_test.py | 25 ++++++++++++++++++ ...nshare_share_mode_clear_all_button_test.py | 26 +++++++++++++++++++ 9 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 tests/local_onionshare_receive_mode_clear_all_button_test.py create mode 100644 tests/local_onionshare_share_mode_clear_all_button_test.py diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index a6ad2307..67fb26d0 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -29,6 +29,9 @@ class SendBaseModeWeb: # one download at a time. self.download_in_progress = False + # This tracks the history id + self.cur_history_id = 0 + self.define_routes() self.init() @@ -264,4 +267,4 @@ class SendBaseModeWeb: """ Inherited class will implement this. """ - pass \ No newline at end of file + pass diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index c9d9b229..f52bc2c7 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -207,11 +207,15 @@ class ShareModeWeb(SendBaseModeWeb): if self.download_individual_files: return self.stream_individual_file(filesystem_path) else: - return self.web.error404() + history_id = self.cur_history_id + self.cur_history_id += 1 + return self.web.error404(history_id) # If it's not a directory or file, throw a 404 else: - return self.web.error404() + history_id = self.cur_history_id + self.cur_history_id += 1 + return self.web.error404(history_id) else: # Special case loading / @@ -223,7 +227,9 @@ class ShareModeWeb(SendBaseModeWeb): else: # If the path isn't found, throw a 404 - return self.web.error404() + history_id = self.cur_history_id + self.cur_history_id += 1 + return self.web.error404(history_id) def build_zipfile_list(self, filenames, processed_size_callback=None): self.common.log("ShareModeWeb", "build_zipfile_list") diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 6cd30c93..2b0d2812 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -63,9 +63,6 @@ class Web: self.auth = HTTPBasicAuth() self.auth.error_handler(self.error401) - # This tracks the history id - self.cur_history_id = 0 - # Verbose mode? if self.common.verbose: self.verbose_mode() @@ -204,9 +201,7 @@ class Web: r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) return self.add_security_headers(r) - def error404(self): - history_id = self.cur_history_id - self.cur_history_id += 1 + def error404(self, history_id): self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { 'id': history_id, 'status_code': 404 diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 5dad9614..b8baebd1 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -539,17 +539,17 @@ class History(QtWidgets.QWidget): # Header self.header_label = QtWidgets.QLabel(header_text) self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) + self.clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) + self.clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + self.clear_button.setFlat(True) + self.clear_button.clicked.connect(self.reset) header_layout = QtWidgets.QHBoxLayout() header_layout.addWidget(self.header_label) header_layout.addStretch() header_layout.addWidget(self.in_progress_label) header_layout.addWidget(self.completed_label) header_layout.addWidget(self.requests_label) - header_layout.addWidget(clear_button) + header_layout.addWidget(self.clear_button) # When there are no items self.empty_image = QtWidgets.QLabel() diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 4f087431..3e82769a 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -285,6 +285,10 @@ class GuiBaseTest(object): else: self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + def clear_all_history_items(self, mode, count): + if count == 0: + QtTest.QTest.mouseClick(mode.history.clear_button, QtCore.Qt.LeftButton) + self.assertEquals(len(mode.history.item_list.items.keys()), count) # Auto-stop timer tests def set_timeout(self, mode, timeout): diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index ef420ec2..80e05250 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -127,3 +127,12 @@ class GuiReceiveTest(GuiBaseTest): self.autostop_timer_widget_hidden(self.gui.receive_mode) self.server_timed_out(self.gui.receive_mode, 15000) self.web_server_is_stopped() + + def run_all_clear_all_button_tests(self, public_mode): + """Test the Clear All history button""" + self.run_all_receive_mode_setup_tests(public_mode) + self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.clear_all_history_items(self.gui.receive_mode, 0) + self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.clear_all_history_items(self.gui.receive_mode, 2) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 038f052b..6925defa 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -196,6 +196,15 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_started_tests(public_mode) self.run_all_share_mode_download_tests(public_mode, stay_open) + def run_all_clear_all_button_tests(self, public_mode, stay_open): + """Test the Clear All history button""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + self.individual_file_is_viewable_or_not(public_mode, stay_open) + self.history_widgets_present(self.gui.share_mode) + self.clear_all_history_items(self.gui.share_mode, 0) + self.individual_file_is_viewable_or_not(public_mode, stay_open) + self.clear_all_history_items(self.gui.share_mode, 2) def run_all_share_mode_individual_file_tests(self, public_mode, stay_open): """Tests in share mode when viewing an individual file""" diff --git a/tests/local_onionshare_receive_mode_clear_all_button_test.py b/tests/local_onionshare_receive_mode_clear_all_button_test.py new file mode 100644 index 00000000..f93d4fe1 --- /dev/null +++ b/tests/local_onionshare_receive_mode_clear_all_button_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeClearAllButtonTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiReceiveTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + + @pytest.mark.gui + @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_clear_all_button_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_clear_all_button_test.py b/tests/local_onionshare_share_mode_clear_all_button_test.py new file mode 100644 index 00000000..caed342d --- /dev/null +++ b/tests/local_onionshare_share_mode_clear_all_button_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeClearAllButtonTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.gui + @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_clear_all_button_tests(False, True) + +if __name__ == "__main__": + unittest.main() From f908a1f3839c200bd47a2cef4d2a62cbc3c3c39e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 16:43:09 +1000 Subject: [PATCH 55/64] remove unnecessary import of IndividualFileHistoryItem from share_mode/__init__.py --- onionshare_gui/mode/share_mode/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 35a2045d..28b439af 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -28,7 +28,7 @@ from onionshare.web import Web from ..file_selection import FileSelection from .threads import CompressThread from .. import Mode -from ..history import History, ToggleHistory, ShareHistoryItem, IndividualFileHistoryItem +from ..history import History, ToggleHistory, ShareHistoryItem from ...widgets import Alert From 36fdd3f1d515167e41d82c4805cbfcedb30df8e2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 17:22:18 +1000 Subject: [PATCH 56/64] Ensure we increment and return the history_id when throwing error404() in website mode. Add a route for /favicon.ico unless we are in website mode (website might have its own favicon) --- onionshare/web/web.py | 7 ++++++- onionshare/web/website_mode.py | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 2b0d2812..ca63e520 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -10,7 +10,7 @@ from distutils.version import LooseVersion as Version from urllib.request import urlopen import flask -from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version +from flask import Flask, request, render_template, abort, make_response, send_file, __version__ as flask_version from flask_httpauth import HTTPBasicAuth from .. import strings @@ -178,6 +178,11 @@ class Web: return "" abort(404) + if self.mode != 'website': + @self.app.route("/favicon.ico") + def favicon(): + return send_file('{}/img/favicon.ico'.format(self.common.get_resource_path('static'))) + def error401(self): auth = request.authorization if auth: diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 55e5c1d4..0b7602ea 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -70,7 +70,9 @@ class WebsiteModeWeb(SendBaseModeWeb): # If it's not a directory or file, throw a 404 else: - return self.web.error404() + history_id = self.cur_history_id + self.cur_history_id += 1 + return self.web.error404(history_id) else: # Special case loading / @@ -87,4 +89,6 @@ class WebsiteModeWeb(SendBaseModeWeb): else: # If the path isn't found, throw a 404 - return self.web.error404() + history_id = self.cur_history_id + self.cur_history_id += 1 + return self.web.error404(history_id) From d2b3f0c2edbf7dd4474a772aabef4d6f187892b5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 14:46:29 -0700 Subject: [PATCH 57/64] Allow 404 errors to work in receive mode --- onionshare/web/receive_mode.py | 1 + onionshare/web/web.py | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 8604a889..83040683 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -21,6 +21,7 @@ class ReceiveModeWeb: self.can_upload = True self.uploads_in_progress = [] + # This tracks the history id self.cur_history_id = 0 self.define_routes() diff --git a/onionshare/web/web.py b/onionshare/web/web.py index ca63e520..ecd9edc2 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -119,12 +119,23 @@ class Web: # Create the mode web object, which defines its own routes self.share_mode = None self.receive_mode = None - if self.mode == 'receive': + self.website_mode = None + if self.mode == 'share': + self.share_mode = ShareModeWeb(self.common, self) + elif self.mode == 'receive': self.receive_mode = ReceiveModeWeb(self.common, self) elif self.mode == 'website': self.website_mode = WebsiteModeWeb(self.common, self) - elif self.mode == 'share': - self.share_mode = ShareModeWeb(self.common, self) + + def get_mode(self): + if self.mode == 'share': + return self.share_mode + elif self.mode == 'receive': + return self.receive_mode + elif self.mode == 'website': + return self.website_mode + else: + return None def generate_static_url_path(self): # The static URL path has a 128-bit random number in it to avoid having name @@ -166,7 +177,10 @@ class Web: @self.app.errorhandler(404) def not_found(e): - return self.error404() + mode = self.get_mode() + history_id = mode.cur_history_id + mode.cur_history_id += 1 + return self.error404(history_id) @self.app.route("//shutdown") def shutdown(password_candidate): From eced6fb9cea7202082671029a6f616d58bb75fb3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 15:17:40 -0700 Subject: [PATCH 58/64] Add org.onionshare.OnionShare.svg, remove onionshare80.xpm, and fix sstdeb.cfg so building a .deb works again --- MANIFEST.in | 3 +- install/org.onionshare.OnionShare.svg | 2154 +++++++++++++++++++++++++ setup.py | 1 - stdeb.cfg | 2 +- 4 files changed, 2156 insertions(+), 4 deletions(-) create mode 100644 install/org.onionshare.OnionShare.svg diff --git a/MANIFEST.in b/MANIFEST.in index 6861423d..7dd8a881 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,13 +2,12 @@ include LICENSE include README.md include BUILD.md include share/* -include share/icons/hicolor/scalable/apps/org.onionshare.OnionShare.svg include share/images/* include share/locale/* include share/templates/* include share/static/* include install/org.onionshare.OnionShare.desktop include install/org.onionshare.OnionShare.appdata.xml -include install/onionshare80.xpm +include install/org.onionshare.OnionShare.svg include install/scripts/onionshare-nautilus.py include tests/*.py diff --git a/install/org.onionshare.OnionShare.svg b/install/org.onionshare.OnionShare.svg new file mode 100644 index 00000000..502da0d8 --- /dev/null +++ b/install/org.onionshare.OnionShare.svg @@ -0,0 +1,2154 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + eJzsvfuSHEd25vkE8Q65f8ise226FOGX8IjesTWrq7bX1FJbtzQj2dgYDSKhFkYkQAPB1vQ+/X6/ +73hkRlZlAaDIJns0qCBAVIZnXNyPn+t3zvmL/+M3v/vF9Rdv/unlL/LVeBj+4i9u37588e7N218e +/OnhV19++e03797y0c9++/PD1K5GDbr+1fJZH/hfXr795tWb1788JF3gatLJB779s7sXr1//8XD3 +7ddfvvyfPz/87Oc68Xev3n35Uqe+fPP7N1ff/OH3P99up+/fvXinU+kv619O62H9ZZ0Pv/k1p1+8 +/sOLb7559f/p5DTnJeuzmzffvv7i1evf37z5n788/GJKtR1SG9vhFymnQ245acz/8+q3L795MnC+ +WnLNKbV5rtVfuirLlKa2lnVOvsBuRFm42rQfoivfvfn8269evn73m7dvPn/5zTe3b7588/abXx5u +//ji9eHXL36vMy8O//jyyy/f/Nvh5ssXn//roMmqnz28+vKl5uWrF+8OU2YKr381pc9uvn315Rd/ +8+1X//RSM1aLP86f+ZJ//42upcvybz5un/3qK33yu5fv3umNdEOW4bd/dbN/DH3o42f/7bcvf//K +q6aZ/e8/75d9++brr168/ddvfrC54Kp/9/Krr7/U2nmOUx2vqr7q/+1/6WM1CX0tppTO77+eX/zw +i7ws+/uvh1zPHzGueVqOl3949fLffnn4mzevX8acX79997sgnFLGMf6OM7/99suXb//+9at3mouZ +j9aY9F+/+eLllxp//P7Dly881z6m098x4O9evP39y3citDdffvvOe2DZ7qBF/esXf3wJZUxxg7/9 ++uXrv3vzX/yMv0hzmq7mluuyLlNu66GUMl3lrF+WlEtdDuOVJq6uVTS/lDavIpt1PkzjNB2mxQ+h +QVMaj882Hf/uj8ANud32HE0E8xuR0N++ffX7V69/OXmSawni+qu3r7440daUdO15+5/vcFWnlZ9l +XNNcp/KRn7TqT9axzJmN+hGfxHRp5t+9e/m6T5/2yu2vd7Q/Xv36d3qd+9df3L75iuX/Br4gon+t +/SD2EueO//YZff3br2Nq/PtnopTfvH31mmsOf+Mzy2e/+fJbnfqrt2++/fpXr//5zfCz4IL/5eXn +YnUipi8Of/tP/0O/iKN5Px7+7u2Lz3UB/X4cc/Xi1dc/f+/l9HJvXx7ipL7pX7f/f/jbdy//WZzk +9PX49P71H15++ebr3WVj3Iev95svX7x+8fbgz4+X++tXf9CZF5qe0wUZ+PLdP3zEFUVwX2s6fA0P +eXT19wzYnfrwjf761esnF/BnL96++7c3b//1eMOdGLt68fUHrvq7f3357vN/eXzd/un3u/Ifv/qn +N1+++uar49f3n/xG1371+Zcvf/fHb969/IiF+93n7JG3h5u3337zL4e/e/Pmy9Nlz04dV7B/7E8Z +/+dxj9/4C6//9rX+8S+X7tQHPL6TGHR858/0XsfvXLqPTv6vco/bF19++er3b198/S+vPr90mwvn +j/fbn/tuy6Q3+vLl6y++Od4mfj1dGhYVn330sh/uv3iljfoMU3rvmN/92wtt/79+9U/vvRvP/c+v +Xn+hbfG7b1+9e3maozdffY06evjdv7z4+qU3+zbyd8cL1s8ktfZy6Re/eI/AkoC9eb07/1dvX3zx +SrJQGvbDiy8kmt4cbvXrS1j7y5evfz5c/ljSPR9uvhj+2yABnpYV/WNax7EsSZ9M89ratMx1nEZp +LcOkz6SELBPnS1slsvlePvvRJ3NdpMnUtJZpanPlW3WUZpDXNNV5mfNQDn/x2c3bn+amF9718OS2 +KFOPb3t4ctvDk9sentz2wLXmQ5WGJkVUTyDl5bOf8AluvvnR33+Mu9ef7PVPD/ATvH2f+5967f3u +N3dHVrNxjMtM5DffvpXx/p8Of/v2xevfvxT7ePTBnnGMY05rXcvS9F7Tqk9ynaepjiXLbGdvrqtM +ubK2pZR5nbWj+WzR6dySzLtxWmd28O5VSvO+96mxxdDjDv7Pw/V8XXWU63ydricd43qv4269Wa/D +DFlnHmnVROiRNNHL/XK33MjkWpe2zIumdZnaQ7vVoeca2txKy22aH3Tc6bjRg66aQX7qLOtk1rrM +0zzOY33QcV/vdNzWGx26U11q01Fr0ZFqGmQCPei4L7c69KBlLbpDmTlfso5UxjLmh3yv4y7f6tDL +5DUvOlrW/RikVU76M+VxyGN68HGv4y7d6rjRcZ30hmlJTQePWJOuLPudH5nLaeSYHqZ7HXc6bjRb +N5qRZRB1NR3zVHWUSTdh/DSNDzruxzsdt/pzM17rWH0sOmYfWtx+5LjD8H9rWfRQ0/FIu//7cc9+ +30aN23HzwJ9+3PvPvehV/7vV37c6rm9u9Of6Zr3RQt40HfVmZv5vsg5mg4d90HGv41YHw0UQ14vo +BYopA3MskjHRrA867kQ2tzoYB3WIGkw4kE4QD+/9oONOJHSr48ZkdL3w0wZT07xUlk00JaqCrpZR +tHXf7vTnVn/f6O9rHav+LPq76e8GvUASIrvEQrSxjfPDIPq71xE0eKs/Nzz7kRq1y/V3MUVuNAlV +Tp0qHzpd8kfU6WUZf+Cf919QPEsEsmot7qdRRDWL2m6me1GJLHzR6k26z6OIumrGbvOdaCgNRYxB +c35T7utYs7bIoi11V+/1alkv3DQBt/OD5ihroy6axTuR4qRdPGsRrrU0D1qorIVr4gB368OgFc5a +8EUEcHt9d/0g0kgilCbiuRYp3YviJu25oklqWtT19vb2ThQ/3WVN33yn6/vPcnd9d3N3d/cwaP/k +++pjvm9iNtf3t/d3OuLnQZsmacOzCuYhopj14WZ33D3cH487LcuohYFQ/CIQDgRUREhVL6V3NH1B +Z9eiN+juzhTIdUezNDZ+NqVWiAPKHUTA/FxrFm5M1nfmiw9sYfPJ5A1QzD2hq8b1Rfv83PTJutPu +efCUjcPN5I2Vb7Q23mqzN97C9b0Zb7w12ab37GDPK/s799mtt1o5zzGzfD1oW/INTbeO+9uH2wcY +jSZePE2TX3TUvgQsgF6AB9Ii8IU7JvyOqRzhZ+KEedCyiMt6WVgYTRFvrOXhkU5LxAKND5NZqLju +cam0Tx88qQ9+Z+8XbUqtBDNrzs27w8V5OO7Fd8UAYZuwetErFMuPSNViYDH9rkwyczZ4L/Je996h +D+ahk3cvu5jdXL2v2d8NfuL9zgrdmAvcmh/cQ1ewicH8Ap4vUWEewm35bjNvgces5jg3lnB35kT3 +ftcguD3JiegGU918pLqN7vaUt9Henvqyqe9If+agJsDhjAY7FR7pMB3pcE+JGy3uqbHTo5dFy38r +QrjW9didsya0iGSSiGcUId2LQm5FWte656K3njXpRVSIrBktTxAjN12AtJt50NIUS47JcuNed0Jm +XFtitJAYXcMY9fToF7ddw0BQ7DUMRMT9oHlCPlxrGhYLhqrJzRYKm0C41ZIgCBAAswVAsP/RfP/O +HP/avL7N83BUPSarHfdd5bjuCsdshUNKU0XbQtu4E53eWNtYurZRrGtMoWsMVjVuuqKBmlG7kjFZ +vwjNIrSKxRpF7drE1LWIO21vdIgVOadl2RlVj/TAwyM98PBED7Rv+ZEeeHiiBx6e6IHPmFQ/0f1t +UvwE966TRtfyk77/9gyeA0nwxXxACusazv2FnSIrR2bKuvZgQtnZJ6lhs6S2ewYMnVC7t5920Yj6 +Ue72XUymv3/9+sVXL784/L5/BHHIcLr4MSGcMJ+6dpakNFVx9lUM8lZyapLiVMTkUZ1uJbVQnoq2 +46qteXdUoAiK3mhD30uJmrTTq/b89SAGcGdFKlksIYxuJFFD4hTLmNUKlSWK5AmipFiANIuNG4uM ++00zGY6KSaglm0qyV0c2ZaR2cbCJArNzy4G7oxy4H7oomLpKEsIgxEEIhBAJIRTaUTSchMP2cxPH +0GXFJi9Ox30/HrYDrWZ3TJePwVrP+ZEvHuWZo54fw+MPdsf87NGeP4ZnTy3/vmP4DoPXjzmGjxv2 +8cenC14YcLTvJvGQbNNr4yL34iOjLPosRTU4yY3EN7wk2RRr5ia3EvoP4odZisBsjnI9iLDvOlPJ +Ns2a1WArv13hRdldrOLeWLe9N38JDlPNYxZzGWuvw1F5nayzbszmpKneHLXU0FFDQz3qpzvl1EbR +0CkxdNPOiLp2uumnueunJ6a011K3Yz3pq9ddZ+W4PR53e8616bFxWJsdj7bVtGNoaYj/9SO/5yiP +jvrkMC8cOku8dLSPPpbTMex/+ahjff8xfGjAdz0+9oIf/TN8/ND/bS74Xg6y4x+LSBvbHkfOtNNG +bsw/pHqKf1Qph4t23c2AUiIje5LpUa2WLNZKMJ0nm8vFpvKOf8gqnro9vFnCz3KQjYfsuci1pd9t +t3TvH3GRfOQi4Vxpw9G/suck95ude+QlwU1CwTl5XE5c5Wj9DmYtbcdU1h2NXuYpjznLGYcZnjCZ +czbziOFcOB5xmuE7MKHnjx1jGi7wqvcfz7OwD/K4f9fx6YI/wgV/dB/z1I8IbmQfxQbUbOYV7Ota ++j0BjAcPngZzstJ52eowyK30oeBnMoutES3maLdHrSh1O2sOvoZatClGgznbLM7Gvrjdcbds7oZ+ +BNGH5+/B2lHu7uxmZQQdH7/eg915eRBXg6c1m2GrPXfmZfbZwcM2G6xZd7FP1f7OYFSh8ARLauEO +jp9gOuEWDT6ycYhtU29rufGrblftDCkbT4MtqJOdtDeE9hbNuUHyWGnd/Qz+++7CcX/5OMWLLh5p +6KGmjznKxxzDxw2zl/ujjuHjh/5vc0FzkIeHh7uHG0m89lAf8sN0/3B/pxVftQGqNt1093B3J8rA ++Tvflbtkp+/d7Y3dvbNdvZPdvDh5Vyzk2e5dnLu4dnHs4tYNp264dO8c+Qt3bjhzuyv3zI/bvbjD +0Y37MU7cnRvXXty9H7d7cgfvlnDmbu7ccOiGSzecuuHWDcfuTQ8lL3bv4uCNgHKJkPJgT+/UI8tb +bPmux5dv7Pi9DjbR48ytx5p9T0ecS0cXJI4hOy4cLHYXf8ZPHMe2mzY3ycZaNoVpOR7W24ZjoHru +4erTUXZHPjse/0ynYzhFuc+P6eGZ4/69x93gMPnHHbcfcwwfN+wZT9SFY/j4of+bXPDh4RMH+cRB +PnGQP9f9+ed/wU8c5BMH+cRB/nz355//Bc1BNlx7PyYf3/lDwBRNm28pa2uj9laE5eda8jiDEZ6n +Voz+BTesv0qZciG23bSB2SoL8ZmRkP80nkGEP+qTQCRfQFj8OT3UzTdDJGXOhykDRBjFQbdHvXBm +G68H1Il5upr0c8jlap5OIIr3jDj/ftNVa9JTjVc5re3C9x+POH6/tUdYhscff0/QwXQZdDDtMNv/ +ebg5ungs946/BQDuFDAKWG8Aq3T4VwmF2dDh9sFfj4Dtszsd7xFXv7m9i2+2qV78xw== + + + 2bZorSx1Ej8WuU2mwEXST/IGIF0JCsxNJCNRN6bkRNS1jg0BsoCRSBcALR/zyWXQ0Z/LAxmB0xKo +s2kapRSsazyM1Ic5rdOYpBmsfphCwnGVGtH8MMuoLVYk/9MoLcQP8xQA9MFP4mFS5hmWs1SPP6eH +6rP00y/ZnxkFfV+mM5WLTEcf75nOGdJpsYP+zhHGIpax2CMPPmFyfLFKXev4BL3mZD98k854a2gC +gcUyHP3v9r4bkhu+98VxxX1UsdrnTizxbr5vYw8htmPo8I6A4dDjhQHBXh0YNLhgHY/4ptZxTR1b +3fGs2U701rHUgaO+v34YDDEKnFA9IqcDNx1e7ocjYjow0+d46bWDpTe49P1we2+8dGCmN9R07qhp +cNN75PTavffhU4+f+348BI56uB91dIX6PvejHI96BL0Hvvr8WJ4c69Dh1/vjzIN/8bi7cPSf4f7S +z8Nzx3Mq9+kYnnyUPvoI6PijY3j60fc7Pl3wO3xxF30M/AJhwKXzF1BQgWKI6N9sJFSgKu+cQjB2 +bhP8ZnX0727wWo82QjdYw2LD9c4hwIcj4HLuHOjGPOj+yIUiJaBtGClSAO4N/N8gUvUIxAyIf0Ax +k3NcirH8sCZs/NuOigrMfgkMQ+AXrnsIcMMrpCMygQBgABA2wAFYgqnH/rKzYyIe35E9mw4IhvKI +mLRlE1Z12aWAcOyRgNe7VJDbLYA3HDNCtqyQLS9k43XB7+oxSySO5XicEBOhfMb/bvc/eybycH4c ++eSeX05HrqljuMsXjvLsUT9wzIP578cd7cKxPD6Gpx+951g/fAwfM+i7HP+BL+gkrfGH9Qpen3kF +nXRye+YVTM5LPKWyRprJlmRy3zNMTvklzi4ZztJLPia55OQTzLu01vtj2uBFj2CKRJNjqsklf+DJ +GxiJJ90ZODzyBj72BW6ewPnoCTz3Aj5xAQ47D+C5/++S76/147HLb+fsGy54+k4+vueceo8cd/tj +uOicu+Q7egbWNa3nx/D4g2n56KNdOobLH3/EMV8+hudO/HuP/7AX3McbbiVol4c5eMvDKOX21txl +kcJd4C/3o9ThO6e+kfgWaW8kvUXK23XnMxW0SmS7bbluPdPNKdKnNLfvkOQ2PMlyezgyoeuzRLct +1W1Ldtv4UcQpIlIhvjRYtTklPvfUZ+fMbnnPj7OeFyOmIhO/Ps59HnpCekQ07rssvu2p+Tcdebh2 +dhZp+s3wrGBskbJfesgjk7qfeuRjCm7XOV4cm2G0KQUnCNAp+eOElrQ6NnTE6LI72u6YHx310fHk +Zyj5wpGePaYPHHDnjz3MxT94DB83rB/3Hz6Gjxn0XY7/sBc8cRZnmou7kH1+bSzxom05m9NAy9mC +dHK5Bwbf2+q+NffBBFjNgzDztS8HewGKGRKiF3lHlQjcCPdmT3dO0Q7t/9pZuqu10uZc3dn5urVv +G2kAQ9fCEZVRbCL09ntztTtzttvN79yTu8IY2MwDM5duNGi7D7Yjtu2Yj5bGqfIEx7HaxLHSxHac +sIf9noNZ6Onnenfs8YznuVHn2MezfK6hl6vYH5dyxy5nmj3NSEuRpvb+uNT4MceWHTecEuU+6rj/ +0DF8eMh7j7vHx/D0o+93fLrgxx4fjHASoJGYXcZxnrL+N7mW6SLtQWr1KnW6trTl5Y6k45atWBEO +7HSWh2w/+Hmm7rNhyR/xnudRx1IvBBz14Y8V60uXE4z18YHgR3e7X8zhvN3lzh1zXqzcPTzKf9ln +wVBkJQ/HFJjzNJjzhJgt2e48I2bd5cPcWNEUQx/O0mKepNkdc2CmLQ9mf846672/ubmLfoqqN3+C +C049sWHzZ66P/JnFlh2ezO7F9Gv32bhc0wiTeKtmdEIXzS6I1ZV3q+53diOsobCjMG+lse5dluja +SjlliFIvPITGfX0sgiUtukTtK5Tgpcx2BkxS9e7IDcPkb1JZJ+kfWO/XunHF3JZScSu7F3O1uBAV +5afWsbncVNaEXJ7HKKZz0Wf3PGL/ct72LtFpeKzHPzqWJ0d7cpwp9sNF7f6pfv9hxb4r7qHohSdi +jKNHQe67MnfXfaM3XaXblLqjWqfjpNdZsxveq9ohB06K3V6py70Uy3TU5UKTux26End9VN5ar85S +u6YWGtqmld0fdbDH5b5CewqP/wX+slUa2CrOPK43E9lrW6WZ9Ripc6xu6MUBpp7pX85qHe3rHD3s +ahwVe66P1Y16YaN7nM9D9zRHbG7uMbmoZHS3q2JE0K30qlJLL1x06+jXQy9YFMWK5nMgBISzyoTW +ThvXPAfuZFplKqex8o8p+bO8j/gmgDVrOSsRSF3B5SxuvhVMbPpyLmeFC3/Em0ZJkXFJTb+3aVpy +Q7JLyGvJp3nW5qLKuO+3CfC4pePjZ+ghyoOsusnuQ0v2dkGZ+PFu+T1l/+WQezoPuR8jtVv0dtvz +seOPu/5k0bHx++Z/6JHYuxMLGI48YNnxgPnIAzbn5yMecGbJrXv7bdhxg3q02/LObjtZa4/ttM1S +OxlnLpmwt8M22+uSyXUyqfZ1A7fagben4iLX9pZFIcHliI3acuZqL0VZ7Ec7Hb3KYD92estwVFoe +uqpzt1NfQjOK+51+TgUL2u7oelY46S4fl0TXVhkhH4+juzy0vOGR8jceiyA+7BTF8+O9kalh98vt +e46PrkByWan9HocvGHi01j0KIZrOS4ctrjq5h3TfOngzB6DbcO4br1cNdWt5GHrh0eZoDc7Ru65f +La46mlz48V73vO5KVahVuDKh16WrVNklRbVlB+tVS9cmMpqANas71xCNSEoU9YpYyVbSC/0uCno5 +siGtIfDD14N1LrQuXJ5EWqII6N1Z+c/W9bBitXR6Thv7GM32O/98uuCf6wX/FKbWNIjAwupZncJ9 +79qNUYkCBMetKfe+V7IpDuWRt313rEWxVbNZtSO0jQfXGZ2sPFPRprkixbULPPaijq6XdSrkGMiN +m111ilPNxjLXoRep2DAc171Y691WotHH1KszRq2KLbe7p3AfK9+4RuNwVqjxaanGdCzYmI+p4POj +YzkWyNEx7Mo4Psdu9/z7jLvvijym7f/DTkrkXR2My06Akwtgee4YLn78PQrGDLtfNu/spcD+04Ib +zxzDxw/9Dhfs9qpZbsjb0BHCYRt+6lCjIqq8gdQi/hzx6O0nItam66FvhHqMckfMeyOebfEiPr7p +PpuLfMPEbFBAh9gfOgwqjqkfp6SY7edkrNbjMR+Pth3DiXxPqsdZHZKTu/t2d+zhOY9CJGeBpLNI +1eMg17kF/TR81iNrp5+nhnp9EqqbXRvr0tF9AsMFN8FFnawfHyy/MjxXcO7i8RGFC4aPGPQeTNTT +47Ij5nscumAPanm2F08T77ah7Ta+PZ/wdjuuHfjfC1XJ5rthVwBxq0o270DBx5pkvRTiqY5Q25UP +uj3VQhx6McStMu58VgoxXJ7nJZnPXROPCzLfDo8rMt9sBZnzDo33uCTzzRGH97gscxp6ZeZyVp35 +hDcO22ePOr7bwHWPYMcBPM7DDhq3R7btIWkRPwib7bZXct6jkR82NDLH0Is7R4Hny7jk+giXfI5N +3qGROYbOfJ/HJD9FJT+LRn6MSH4Wh9wrTn/U8RSR/D0PLhhb5sxkvt0ZzOcxzcexzKcRzF0hy6dx +ylNM8kLg8Zl44S4Ydbs79mHW/c9e0O+L0p3Xt3tUr+i8+tQ5J31cAutx9ayj7T6cG/H741Eg4hSO +eHycKSDD8Z+3zx7vK/r4XsXn3G/wvp9nFbPnVLP27z+GCx/O3+cYvt/XL15wH+B0XtrtyZ17ZHxb +rkVwuq06fa9M/8SjGyluZ27nk9P5YcfOz/NDOvM22069vlA74lGflu7+cPHup+W7/WRPwJ+nbPDW +IVI75OcZ7nOH+fSljFN7sLvwmTLtH1Ok/Uf1cl9N+VJrph/xpvZyr9JAZt1rXuh46pRZKRxcZ6or +yWFb/Hq7jJ3KxbWs93ckk649eoTn8/J+1LtG4p10omlpunzNIlCmtczzmmoroyh1nDbf+n5eK0+W +nxQpX56k+ul76Wly3Y92y+/lzU/pkjNfn+7j+D19JcrX3XQz8aEbhVHAbt6VsLsLK64XsSs2xFbb +WLeRwxKum9V5MjfW7e7D10K5vGPGzJbOO5/VzYvsmYedPdpvf7rotX2HXPr+eNHHF95f9MasaV+S +L2/unW4xO3xROwTtfdzmI1tCBNsKuFtA3eb33OD5vhNnt4mLBpru9tHF65OLP37+R5ff3uJRo4W2 +VrHsnKe5ru4MNopQs6h5khEktu9PZtnAy0jmUopq+NrxTyJU52EsPjmRPJ9dzFj9ie5vHpJob5BG +beIpz6uLCdRJvLrJQpvIo+WTUlYxZxlrS3N/NXOos9xXc7snG3zXKaDU6ZmM75/sCb4Xh8kXoULZ +rQjKxmE6pLI9hlbuYvUfis/d7eJxvXz5cgrdd6NhMxRO5sFzNsCm+XdNf3gUc9sXsM2PNPtNi99Q +7Ruu/bqrvi20vqg0EAj4J/j3uYdx9hV5Nsh76VD3+7NoTgBl7oaOkjlhZO7cKG6xQpWkSqFIoUKB +jaE7yxa9oe9bsjYXmpzIQ1trGnbYmOpWbaO46q04LC3Z3oOOeY/3/j8GTOrpz5YGWl0NNqBT67Ee +7P0wPnSBcpZ2tE86Oks52nXd2/fd27KMtvwirfNwTCo6ZRJNR+371ITvlAq0pflsOTqIxNo78pUp +D1tTPsvOsbfmuz+257uNHdQjdLxpRB6iRd8pWucn4ALbql/wh91dcIadu8L2jrCTG8xOsGHzgR0d +YOXYB2Rdr4+Or4djtny9lC9/BOCUoeNvTuibE/bmHHnzCHfzHOhmuIsqI4+SD57PLK7vOebTMex/ +6Q3FLh/Le46d+2B45E94Hn59855jlwk76L9zu/bmEZq+3XY79byq+FY3fLM7j/G1qadFo1J2O7Ie +j51rPHSh+6MzbMu118Tvn+gRZuQcNXLbq870jKWlu3nOJYB49/Epd5pp6nXqW68lEbndXK17iY9+ +4p2n2L7iIJXIksg99/8Is9sgdo9s1aWObZ6ztaAxNJ5RVnSaK6Wql4ViSP70ccMhmjA9NhO73F+X +K+nK8844/VPeJay0BYtP09ZqlgzEZMptJTghfiJ2Nz9vMu10l9HfXE93Q/c73XFO0v1kCJ8MtR/z +rt0anUXBkxjd2kuozGK2egwxNjHxdbvjyfb13PmOu0coq7XW8868pztOV/T73BukP+Jd/Z4LamgT +W5YRvKSoL1MhoSrZV32luON0pnpzxyca67qbaKmqF62DH+l+30sbni8pwzO68NHa7pXlt7ry7ahF +3J4ZyScT+ayo/BkDcmH5oYdeu2F+ZpYv54Z5hyqcykvsAl4uMe+A19B7aAROYasyv3Xhye70tZfw +exl/kvI7OT9cjHptnSIf9+HZWmfcnjWHPGtMOux6Q+567RybYeyj8Q9nTUov9QSzaw== + + + /9TFZmv7stWs2Dp/7Xt8bU27Tg25tr5avXbFsAuZnVeivw8xuleRt5YDG1Gck8VRveyksSeOunPc +XFNJ8NyBkne+m/UkqI7CNu3F7SVSGRy/ro/Co3fHXqXTLky6D5SeQqX7YKmI56QfntPP7b4L6U5T +POmKp7ajd/s2LMOzPZ1aL2Ryaje6b8Fy3mp0119uOAN+nyhqT0vndHSyFE99SE4NkdxsaKOnE03t +KMuk0BtCbBVhVpcwuOndbjbYQepwgtrxAevZFr7bxa2nYRe83pWLOa0KrvmjalL7ssSS7BX3o+o+ +OKs6SsgsZ/r71tVvOm7qWIRTv+HVf7ZNfbsPae+1+r1mf1qA2/Vmh7zfQtxs5n2YO9GGZpv5fbR7 +g+PfXO9bv95uhoFdBMcg+C4YPtGkePtn2vX1S7tP959P+w4WZ/p08I/bCLrfvO/4X8Vwnp5tlVJ6 +XvvWMCWM5BMXu8jHhmek3Il/rU8EXD22Ttn2SOqNKpcAs9y6VeV0bCkXhdso2xYibJmve3WkqRP1 +Vq4t+n9Hnbb7qNCGxFlc8+je+7+4vhHUdG9xUMz8V3P6h2N0jR7gmxW52ZHdihwemZF3uyifY3yn +ZOtItb4+plnXnmI92WAId/C5M/hZR/bwQ7Q33nc3Hn6I9sb7nLHhh2hvvK8/M/wQ7Y333Y2H79/e +2JK72AUzTdpOD/a73NrjstrXMvfMtGTfiknh+5LAo2jG8MMRQdDA8EEi+A5kABUMPyQZQAXDD0sI +4A6/KyH8IKTQV26/dvvV26/ffgX3a7hfxTLsFnK/nfdruVvNs029X8/jig5Wr7ZF3S/rvnv5fmX3 +a7tf3b6+w7GQy7bE+0W+XG3qOcCBi68Mx8XeL/dpwfdLvl/0fXfz/bq7bNS29PvFfw4psSeAPQlM +dsaKBobuhd3IYE8IGyns6xeXtaWpzCiF61ydcK41KJo8TY/ezp+MOjdLpi5rhNdlFpfatFR1yvPU +Y9bPFST+U92h9zlHV5gWzcVSpnA/TdAUGq+WbcL75Dje09jW/vdxuRxZ/JNc/vu5Ci7m2c3l3FkQ +P9OT0MNTy3BfibL1qrc3x1B+j6QPj52WT2zBZgvkuqOj77vLIG8ug8ca6rkOWHYW7Nz7fa7HIMnN +8Xnvd6H7tLXQy94Em9fj5Pk46YW3T6ABJTTE86ea+nPlnWa610qXXsXzus9hPNXpyR7Yg1Maeo22 +3J9qa/HXOrz+2pv4xs91540dzxaaa0q5B4N+MCWuS+/h+6pwe9Xy9FynJzs92+npTs93EXNwPw4X +FI0TquEJ8KAnbu4k1uPqQufPtX+y82e7f1RbaNk/39DzUKc+kefPuH/K8+ectjTTcy6bcBauTYKu +lBmMz9VUxBTXMScxvqmtRmHJ5BW748PSlgjRn3tdXanjsRt2GR9XCLkIEvipngAu3bn35OMCm33u +/Pfik+0Sm2wfyyW38OzesbpxyXPU0QZ4ymS8nKzOp+lfJ3/Zubds7yo7r9ubhqMvZjmVEN+lEFwO +mL4nZDrsYqZb1PTkb9mSs+527tN0dLPUdZ870EOow8lhunOWbo7SekwiuOQlvdl5Se83Z8rJeXIq +9XtympZj0vSWZtDOCv+eiv8evajDWRXg81aeT1yqm2B63MH1seC837q47vj8GXZs2Dkk9o7394vR +0vOTLjhVh+7F21PKeabgpZyTfdbJo7yTYZcnuHejPqadU17gUz/8vo/1ow7WW//qrXLPPgtl74Pf +l8rYF8u4d176RRf8OW3tqWtrCX99RmUnN9mZE/XMmbpzqX5HUriMTtzxiOEMmngihJNvPbLcLvnW +n/Wu1yeEsIvEXEw+Ok8/2gjhg6Tw4dbmF8hh2LqaPxuXGR/HZk6e9O7JPaufMjwTmblEGFtL8Hnn +VT8/9ETDjlr2FHP9v5pL9bmf6YmzdVNq85my3c5JejjS9HvDiRd7VF9yst4NW3vqR70x9p0xbo8O +1q0rRvSgzk5QpvH0vRtO02x6Bdlz7+7S1SXl967V1S2iH9zioh4dq6QtpEhZ2INzDM05ulQfFcOx +SzV/cql+cqlecKl+IoVPpPCJFD6Rwo4U/uO4VH8aY/1ik8A27bIBnrz28OS1hyevPTx57V4ndisb ++6e4aE9y3puXezNjQ48elc0IzoTKcVI4OomdeM2HPvhTk+AT6Oef9C4BifxT3OEp1PNPeZc/4Xs8 +gXL+Ke/S36MYJlraXJ1nJWaeF/dcld4eN5BFOSWxxrS2Eq0TW5UavyYQl/oIfPAe9TvOH/XJxU6F +fxaP810Y33/9l1fvXv6nw82XLz7/V3G8s18/eSY/eSY/eSY/eSY/eSY/eSY/eSY/eSY/eSY/+SA+ +uaM+kcInUvhECp88k38iz6Q++Oxv3rz+zdtXr9+9ev37X/xiZ7fvTwx/8zVncpz5zYt3716+fS2D +/ubb37/4pzdvZMtv/zpMqV5R8FwUlbL+HLRqV2UepRbMos8yH0pdr9ZCKaqsFavloLe+msvICmpe +xuy3ufbf//Bv8ctL/e9b/3Obn3/4o3/9f/XP/6EP/03zdPj14b/99/HwRXzzt/R3evwwj291+Eqj +PvhAh7++NOrJi/31pTt+3KiLd3ytP395/fbd3avP37168/rF2z8efummVX958+bNl4efXf+qr8Rn +91+8evfm7Wc3Lz7/V63WZ3/36suXn/325efvfn74T/rC/6U/j6fR//w/v/Xl7vzJ3wYJp7pKwRO/ +SWj9+H7GddaOW7Rp2wgI+CoVKamrGMqcpUJuhcnaWZZ3uoCV+4cXvt3ZCpa8XGVNp+yR1MAYTzlf +VVkpK5WLZAPFCuV2lcRcNKpmsTGNwrmlZ53nVZyhHsR0rmRuz8s6kresEWm90r7Sielq0uwv6zQu +ZV381Qn7dm5o0u3wuW8wapWq1FgtTfVj6IOc9O6U2R+Xgziankuvu0q1zkmvkXUDLeqK9iyN/aBJ +u8K+k5GU0ZkP4gRXbdXjzOLYs3b/re7EqGkUM1jX4t4qesgr54nOmmRp07pOuxKX1DOuYtdFdyrl +SlM5gbumpbxGzFeL6EYjmpZCL1vylX5jxKwPsu8kRnyVqm29Mel6h7VciUmng1j0VZGZt2gJtajr +Yc1XRWbE4Q/+Vr2ShBhdKmkVma/pKssSPHBCRHBYKb22JP8uE+WwrFd64jWmUZ/VZfKXs6TNYWmw +t9W3XMTAdULEVA7LrKkqXCRrI4j3a8ExhQ4LF2ilXy1d1RVYJjaOvlSu5pTEQIqWVLa4PseZWTmB +G0VrvV7NlcZzy0rLGE6kZZ77Ei+MGjmndeecJAlf0mSOM7Q115wbjzBNpemEXriMzRXcW648tIij +P5vozaSn10My8qbUEdDaiH61ihKAVxQpFHmQ/VAO/+jFT5p5bQZRVIOqGEX5IY2SMNCovOrdRAW6 +7LS2FmQ2axLoXjONeTlkTTh7TSPETsbqEVhojFiz5ifIjNT/xnUkfZL40Lhciep0kyRuVOuBEa0t +iRGLxM40ah6saCSR5jKZGjwIjUODZNvpZmPVG+hxWkaPmH2ZCSNUUqy0hRHaiWIYK4pGYV/ogccM +2nZqyU+SNDMTc7ou2tOezwyl1JFBInu46zjxwWzHNaU1MjSDExdndFlXj9DyMEIqT+JGVTfiQdmh +6xQjSloYwfP2O5UraJ9RMLYYFXfKTWTGACpAMAC21AeI2TBg1AeM0PLpYTVzmvkYMafMCG300m80 +aWMD5s8VXuZRrCGjtGv1Sm29ksqgEaLEcW2eu5T9eW2aQn1OETN93uBZcVmdFKUlTo4QMCvLTGvh +RL/r4seDxejxYF8lKEQzA6vSzWrQ4qJloCCj1qZOKUZVrHcnSWun8wLad36BBOkxQpwMap1rmZgI +isaJ7EQRbQyip8IL5MzDBCnWLux4XG2Pg7jXKFXxoGW8qhSt42EnTZAYlNYk+JAURfGhBvuRPipy +rROXlj41WTs8aKNI3dOJsm4n7BTjxEKtOk9WEdXPfLSKd2usWGviUVKibB5X52F1Qqw6TbNOaFZb +5sRStSviIqJ5XV4nNUYEqy3eRghOXG0WS9GJlbIhvADzr4/FMQsfiwX4hTUfc7+YFkezLSa/aoo8 +mNIjDF649hpsiZvqqggksYECFbpaZr+I9oIEB487Z3jAmLa9MMULtQV1VCui2UiWUpQqm2PpdTLr +RUVCBU+iRqUrmQSSK2VEj9fXeTxN6qhHk2HgEZXaazO6gRg1I6o2vEaI8CEOjViRWnOGY3ZpN7cr +UX+muikeRJEQdxL1ytgYVykYIq2rEbYxiSpMFNNVso0zpRlhN00SSFWTLGEjrWM+rqtEiU6KSmDd +TXMmm8LTlrUHmTYxxxghs0X31gItHqGv6LVXPcpMlUPfWW+vG2j959RvMHFXzTD6hihZPE70OLNd +xRsqqzxZ2KzUHZTwkU2izaE5n1eMj+qVFRfXSe3tiqRPWSOkOhEbamLBGz0s6Ic83rgyCjGH1iAC +lEkl3VDiWe+14s5dmMImVYfdLF2oScXJTRtMtpt29mg2h0qRlqYbEfjT+nopNEpEwCjtN1i5yXr2 +KJqw+Drig82WyBqKibY+ismapjjvcrQ6P1tkaIReNjNCIm6K+6Q1+OukR6pSD7USkmAQhp5Wht8h +pwUa0Apo0mframJv6IizGK4swWAA+hCVbGVb14nrzFImIGVxLT2mrqNNm1kBTdOIrNQHkgSNEfO0 +MEIPuKLraOX0xRixiBa5Ru20RGFcX2ZJyHI/jsR+5XFgqroMm0jcVNcUD9PU6Q10msugbjSPYANb +qaoQpvRHESB0jqLZRU/C/hMDR7cbrYtqNyxU/dVuYLsyokm1ZkSlSR3qcCyCVB9Rgu8k6czTNshI +Iyz1dBsUoqmTb9KFrSfPV4QsVix3aT8HXjbnEe0vI2lNTRmxIGrS4/KyEm5aN9JiZgk5j3BijKgd +pbDfYA7RNWkGEMbo8XGjEReCiEXUT8rNSPlmnlN8uq1wELoA16lzIm0JrZJG1SwD6TBVlyzy+q1S +bw+MQCGU4a//sy2kW2cYJ18Vr+GrInbdoMKACGFhR3etz6MwZhiVkJaSK1Qh0yh9y5yyUDAVuUjN +rsUjRIs8gp5UZO4R0mr9Knphj1gmTZHMdYnyNeheU101iZKYeu0ijjFLGJbw2kCtaIwL5hUlJ6UX +cV58koXOmRwkU31GR4Dtz44NaBS05/IwYkF6K+8vKeLsL6hAIwpzSM0q3QOJKNqjayKiOllDnFHD +teEIPYk5xszoTpVIlO4koaFRTc/bFj/o6LwoymtnTFioVDqYXk/Kl6hUrxqvQsXMbuRGKRLRmYRW +v4G2UZjJdlmRT4ySwOdZNpK+3bS/Y8SCAkvYQcagR1hzwpfSUOQ0oiEwZG+NMif79aUIec8VWk81 +jzJ/EZ8apSKtox1VWi7Yi+ZtJsaiif7Hzg6968U0rT1KsEuvyvTmXTMVsMTtJd1RFA== + + + ZZ1VFtQeAI/Quy4WgdqJBSEpDW/xWkmxQ3fUlcXggy60g6FoinOj2UrzuCLMt+r1RbO8LiZW1Qkp +ykywjDQp9/VIEBIwLGZCqh3S5Ow1rzIigVUW6c46oQVdkSFSzipOBX1VFiYjnKCqEVmELOOiaH/I +QjrSwWQlRuYlPCNh6S4srZgvOb6MEJ1AcdLWEneqVDvTnXQjGC/vqCfX88l4m6kMNmFMiH2a1NeN +IKT64MDUu+Fu1ChN4Cx+zTbJKL7iZXoF9ogUtJkRaCriocVGdjFpjNh32n5SGhePSOPoEcRB+53E +nxe4XMEb1zxKspFRmkq4HKqsbqcRWPzVz0IOHxXuFjAuWboslsOKPZnG6ndKqSvhUna0KTW1YkV6 +pz5pTG1FaZqsw2M9rkT1iswKrZ/IqeBJlTakZwolfNRd0ahQkRBQEvZkJdNEHY4bKrYNowy8Z/WI +TJgWO9nbXWo6/gKNgDdWj6gSmhSxa8vYNTExMfF5OK2Unqrr6AEzOryea7TUXyqLIzJEZjDnEjCN +7Q4KCl1slKIC2UDB2mAxEzOCztqJVN1kIhZ70sqL2jMSR9fHwtAA8Yp1NhVO9otqj8KA52qXkiaA +DZk9lxJPkJc4d6edebkK4pR1vrD32BbMmgSFqyei5zlmrU8T/FZcS5fGr4pVXLxxZA2ZG0sV7tSv +URLT8IpFlvHqUdp87CLWMnkhNaszC2mXmIxdLF6SSfXY0hdk34fxIPainVbNkfsSFfSjWADcF1h/ +2ifSb4tHWc5oVIZ9YL9qezEiqm0zYuZlZfqCu8L0lXa5+kRDYRC/XNHV4ga1T0lCTRU/XMXidKcV +l9BiSuEGY/ENsPCkk+sGqKeiAHTWP/TriGwwo4GSaUVZYZEkhR8zRt+C3jiG50Mqid7wCuEpnogN +EU6M2hCpsqVWBqRwhLFFx7ErvjKWAK6tI+76afUo/a5p1z6qvowUvgLbkxampdXjak+JxSBoki3+ +FUGEsrLaS6QRqISSGRJUsr22O822+vBuSMNhlHRu6u+LeUklyeHEkH2BE4OAuCdYppXDHlqK1L03 +ki8o6VT7RNCw0VbsIcPAuEwwcj6nHjVbdBxbtVNIZpoWcEQkJzMDthPbHBeI/XXTCoRPTFOXHFts +peRiZdIIkKqSc1dMl0ZIp5yTWf6YYWsIqhIOPKl+lHvXIJEcjREmkbiuwVTOKEoFO0QSis0lmp36 +3hmr946YmR7xCs2Pz+0ZQb1mFuz5Qg2vq0/mUG6kyuoqsqnQrbG9tCvLEuJGW5CNvmj+NUIi1n5F +TXYp2Y+GGqURmft31xpivKIDjS5F6Y2LyqaNK40k2adrWaMphDeaIfWZmsUTdSecvI6xTFiDsCz7 +6yAuLWGd+qssNlXQtsQXmkkQDzCEmq19aguFpSy9P2FizFN4OOqasAg6YYjqpIRChg1NDtUO5zOu +XBRUnL3a5IuVDGlIMBKJovAZw1wWK8bWVXQprf3mwJ2xyBfksHVBfVsrjV8Y3c4+WNEABWKkvaOr +a8QiXWDz5LY4icfO5zRBaIEZzVL3d/1WnZeBVeLya4MbwIcXD4CvYTCVZY6rU0jfV4B1h5dWVCqZ +IfZP4f0Y1cAQ1diJh4JJz6RIJ6hYz7KtJOP8+cYtZbjABzGO4qq4BcCwaGfhFPeGwXb2hsn9y9jM +GrAwY7EyBFq0MpPIpmBKzJZL0zgufVUWFGXM8e4Vrka3ZoSXVix7hdbQ2nUj8QbpFfjnfYL4BkZR +JU4nkxl3FU0FdHmP0Gpr+2mEaKir2V6FhI9CqwAvYFR4UnB1QmKrdR5MQmoMYOhIscHQISzQ9QQP +0mZgUMEJwJyK6WInSfmt/TKpj2C59WYiMo8gLBEjWrbt6UYRjGhu/URFwb69PSqhaeCzQW+pLvMr +QtH9pE/5Otrrs23Y2U5sGBfqitQs/AKMyBMGEpsJzbkQjJGOOVOIva2d3828qHaNzE885MQ5rIFL +EZ2ZcpzsaAZwqVFmZXeqTz6B33Gx85pJLfipMRsL+39jJFqPHA7y2REf2sjniBPQTwjdXvsq2UzG +MPhwNIrr/vPluNzPfiFSEs1M5eeHv/zdu7evXv/+8LObm+vPP//2q9++efeCsY8CcneecHwUCB+x +pwQha2llkkAAC5gtR8BgMX3SJWlwE4lREFTSCoggarLwAf9NbIHAlsldBpVZNA5EXOyS0g60cO0w +V3ASixq0tJt7PF1Z5kquLJO3p4R5Coff2tBy9IqzyadhQXVPYLL9LTba1SqZXFZ2tKXtS+Le+BD0 +hqWE55hAmT3HM28tZofUwKSvSCnUEEpkr2hnE6yx8ILZLkiRW6dWvQIeCNEBGiljZDvh7hw1WdkM +3Fb5OIKbXMJnJfmEzyoU7A/O/w+55v/n3/9gxPMkhIvhkLDLJf2TjJjofCKtWSyJ9jAO54p+Mh4b +WBm+gSjg9Lhc0xjBWogBrZ4YwTJ2L7BdjxJcSw+fs5TAGzRInBbCwDuN7MU6L3AMBxIWz6x1dMxP +oqysMUFGVE3SXnCs0OMrCFHiYrI2nOAtOllEmTCiUWKWiKzu2/ATYUK2HHaPcRUL2z34hwOguoYE +mi6BEYT7r4baEnxIe5zApoidYKW+Lm6Z9dicSHNFzahwh0lELVLEip+gDQ1IdMgR0UrvH1vYVHC8 +lYqa6+YYEsVNKw4bPJMoqU1qFRIJ/0qFd9u5fuGE1XGxNuLJRGIQhxe+Lq6lhceW1ZOj+Os1RQj4 +qVcmKBw26NoTjwbDa45tJt61S1xs75G67yBAMfv9uhMvUydiunYMSWND86oNBZXXraO4aS0ROn06 +ghgYrqjqgHM7+gHwATJKOpmmTQaUKBSx7DSTQzzLGs8CG14IGXEjtBw7CnihEi+U8S46/opkJ7yZ +j16A83lZtDzWGVFYctj42Y7dQrBPBLKO3YMzrYuhIF6a3E8s/cTRC4AxSpBAK0sMPEZJcDCKBmdQ +iFYAWMIsDVFPUGVuTsQDJIlS2BXQGa57Bo042v0yIGUyCmBC/knZQEIuBLix7PlwbqZw6SKw6who +TZheWASQKBXtN8wB4SgYrp4IYxnvh/gfdZHtwEGDTc2GFIp+Sj2gqdNo4j0YjF+NjS4ugcYvG+wK +uwRTLhyko/0tOiH5jAEoTR2X5Zq7DwP328IX21X44PFPbpGxNfcNH0gojSIWJltWap2r83OfOmOs +O2Yp+8E+TCxP8GgpTEaxITPzirs3sYcnY1PEXvPJIMTDxCipZpNHZTQ3jdL0Lr5O8rrZumseIcUi +M0KTEyOIT3Ki4hIUgWMfH5WoYl4CKk3UkqBXFoCERc1IkTmC8YmPTMSTPWAR09EA2UZVKyuTBnaB +dURtXd/IGjABWdkDoeLIRKw6lei7QMsG4+is4Gh9CQiIfa9h6K9YpMX6QzjfwVHoZa1DJ/yD2Ndh +TkrQE2oDgdGoTC21Dm0+LPCFicKpgIomnU2aEcAOmT5jsbdA/GIEqYhLrqt63L5xf72XtGyZUiDh +Mn7ShtOdEXiD8BtNmOQiRhkHI427pNZu4JTJ4QyuI90E01A8StyfeOEyG1DCdWxiwt81j5JbeoVE +RoPsCFEVZnbCXbCiIojNJ4Sc7BnNXK3dOYGlDeZJRvSCf0mDRJzadWukh2DL4m3FcsvkSHCjggLO +AniVDhjioVgA1l+LR9i+SrgmN5BLqkhpPXDCJuQ60lhg7TCkhBhK+MsxIhP1yqpHYIuK/fWHJKTg +EwBhMC6WRNzyZE5r8/AGI6anRhkmAm+SnIPtwKu1V8SsLTaYX0iQvYNlR2hEj6fZRryJ3yUM5LUe +kTAihUBeYXtKSyW+r7XFEbWEoRTUKH014QEcJ2uKQYQyr/IoEpOpi73ByQW4STFPO2BfIhz9OU5R +mBQ6P6YbIVp9U3wAbwiQEZnLjFhqKd2IxiuxYJ6K8EUxCWmPn1DcG1SUKTgVW5FiSKI8adYS5xoj +Hrq2iyOWKzNLzZ6IrkPIPKr2UeLiaUZ5ksGIm1kWeMxD1SPIEAYwho2rxRIpkl6Rj5+HXcp+mGzt +Wts+fmkmiXO7umNH+lPr6YQfrjU/XN7gSj5Z+7sBV+Dd2L/VyJLCfUY0A+ZnxswVs9QmxtAubKAZ +r4l22uJpxvvEAhVYoae5VpTmWFpQcXhUZfceOAHzXvDOlCXWd0ZgagTNTj1CG0LTUNiA2SMoQG6P +c1s6mq3hOxqxxcRBqCjaOgSQtS9oe6JOIrsofFNCfjQczfpIWw67IljIMsbENrz8YzAamwzS4DKv +wAhisanVMPhFnatnAkc7prGeqUJvKFFrn2BUmEnf18xXi9jGMkgpSCAG4DsL8Wwezx5LD7B9npDm +4px62ytz7KZ1JqoGc9Mz4isgANVvtNrKKiTygkb1dgMYocfErNeNgNEg1PR4idh9BqKBsiD5beih +3i65KGtDOOJbHJHKcXkpwTNRbOJvnoXpCm2ODTyaU3tEjhHaaKLE5Yq7E4NsoJR8HzLPuA/eTmhX +U5Yd6Z3adieUBqi+wMV0AxrfgtjEkYMJKnmov2YLHTRfg6pr94hogOMOi93XeIhQclYa+Wjz40iR +NLnCRY0iRhjGQtCcFSdUxoMz60G1WjTSXQlsTraygYsa4tY9OBpVeGGNmkKYSn0QZWgU0Z7V12kF +V1uSfiVStOd9wVJAG8SVphkYcUEDT0j2vOuiFQ/+iE8kNlFFmkvXWXBslVBv2mSVuBBbP9Rpcrgc +0uW7obiYm0nmoMlA4uKJOMET8ABuLhUnwr/OLxMd1RSwLfjx6FZF4uFQstQAI53EtsCncP8RBUJy +N0FBTEdN3a04Nws1SafmxXEwZD5wHo0HoAGAWvERQKr6PBm4oJuSxKYBY/jBQOhtWAwGmURnhGiO +URNovuLAK5eZiIkwIlnfkDiXyUtkT6pnnj0gsRgwlxwD8PU4VX4z56WhXOFawFDMtFsm/IfnhABh +xUVkRyp8xcRvvZrgmvRq10TuNLjafIXfrEbqLkv4p8SbQNLgDcth4YjvIsthWng10ZYXXLpAVEne +lCwqrYM3K+4ygCXwSYJS6P3YF6Jro5uWWVva4UJOgKWEhUjyx/qDq5P2r0XB07c8HVVr2INEdb3S +towc/JZOXxdGYDtlAht6TjbhYi8PkRfRQt8a4tFgOpqz1RPwDZw2ekEMNbjBoeKEmoFu0UuzLrb1 +YKDYehIH24hqU3HGztaOEX0Et4uTOb5eetSy4A+QxkG6yOHpI4hrao50gwoOa+6vspIAqzlEiTVe +CjxDrev2IkxI7RNi1ELBaIfk3Pbs8HRSxTwhHemAKFutL87SFwdvxuMRn29rC/9mbQlgxyiipRoF ++4N2gJEvoNMltaRMXxWi7ZWAd+vsD7BGhv1NkmUpbPFCpABbvCFW29j1EbJlILVG7MG2uFTJGaa0 +BidfjDHMnjrxGdwjU5J5EoJXfNrxAO3iZMB6G4Mr4RiVsXCooPNXXOgy5xa7jJdQ4Q== + + + NE8wcY3Ar4pfphA7C7BFActVV1uSMTMpPNgzGrZd8XMUuaCjqmTgoQKER/HCMh2N2Zg6os4gFnEH +exFpUEcTN1B5pYXCIytBpl2/ER72Cc4mGxB8DJBAhIJenr7yet4Zo45kDMRlMozSZuQsrVmzoxEg +R2WFz/jDWngewdPjGpO4DzuwonuDKiO1YDYqPIeKPZNEQkhsNYtvoMY0MGACNp2RW/h14UTg7KFI +WBwUmaceVOak+eUCBm4JmtOqg5TRHGFpzg3ANthHB51bipicnaKrBZP2vC5A4lKAgALjMuPY6Pxo +TIZC4Pgg9OSXLZ53+razx8YSEbkmKZDncMgXEKoggcVpgzGNS+RiNDBcvKwoJ5zYYn/ogh5h7Qyl +F+In1LdU40V1mXbhTgQVsyF9kto9sldtYfDE2oDZ4EFjFzXxSIWKtoUpKVZi7CBL4xG5j7A+JvUR +Rx86/gLf8J3WfqeuxIAFSAZbElJyXop1mATkF7ANSMdmCFbEnlpzT+6QH5OR8cS7s9R1h8hSxPW1 +fbiOuFudCK6BcnQcDL2hEgdLmnrr75D7Qg1qh6lAgxnrpZ0HyuG2K70A7IAMjtMa2QBNzMqYce0i +67Z4gmDNhMIM9Gd/APTHudKVZ9zrM1q5BBfgXCL7xvdJt8goXboOcQOzeBwb0t3Y9NbOkwPQhJ1F +MihjmxcDXbk6CQRY4gxguhLCKNJt8VfA1kZkN3oVunkBqiqa0Akp0zK2iNOu43Y5ZDWaNJh47V5x +sAlkv4kdcsA8Bik+96vNtceDiE2MgdRPQEXEfnTbbuBpZoE8atRcF3jLCMyDm4iQtGFsKpnN4U8x +bh8VNCAepABEtJjIpaPBKE0y3O23DrYrG5hO7NjEKMrYxC0iNRk/vBE9ILIjGQcygQM7R2E2lz4G +1pCYNuvs1dQqWcloVuScy2SDHG1mhWxHsrqwdgmxL6k7W4oBgpHxJD5PZHBh60xAO8Ex4RqcC1Wr +xMU1jxO+MHBgkoVeLnH5ukVaJ1pkVqMm8WqxZOJJEkMkniRsCZuZmRPJkdcxG8WFBoI1AslMm4PG +G3y2pZ+JbpJWtS74zcioydE+DX1wRfcBrw0lVqNJxmx/v2ZlAovTtObjKfopsUT0U1shRzZLaj2k +WfByEDlBQRihUVDjGTd18o2hFm6cDVwf/fbVGTdps+uxbVfieIvR8wfJoatiRz3CxE5fCc+C/Ad5 +Bi4TAdClfl6MIXKuF5kSjsg7IAr8tjGxmFjwiTWQxbUZAursJ/sTsJcdHZS+YASKbnzyFWEhOeUJ +j36zOBhH4OKSnsVOowZFg3XQI4MBb9Ez1a4wsUPnUxFtWryeJeCddkhU6ecgz247ca1rQiedQKVH +PHOZHY6T0d1wMs8rgU5JkggMgloEAW37NtJVHGUSM0hbEEM2tJYL3ysBh2wJY3lIgKKQt0B6l5HR +hggioFYUhQ+Gut4bBUxkOcgc+k6RX/yvRB5EqCQvRPwnhISYBdrKV92BpddLhhm0FmjeZTIKZMYr +KLZDAkO2VTQayftkhARNxkmLi3lG8D6+UzC42bBb4I6LAWTWGFlbzKepHmJE9QjSo2wVyHqbsQrg +3v1Oa9ypIsDtr4BcgOAe0R2zUyPiiYHLa/s6B6PQpgFfBW+9jP2t2U1PRzB7U589IBEX7wQ1FoTB +im9iufDEHlFjxJryhbf+4Dr9kLTxfIT4OxPZ0wgxYS7AvmNy1MrpvYb+ARiTCpodIcb5gnsMhcDZ +uldzb/kbvZ3zhbYXo83FfZE4R5Ar6M8J17rxZ6RJiqNH4BjTraToedmAMxENnjDp0HEWJ4kWkbO2 +YThgpLsseK5lrJaezcc53FILuRYrsn6UjLBRMtvZOtndT6o1qVjSH5PWX5xIohXUSTb2Y4VNy/gE +Xxkab5oBWcvSaCRjzh6FXMMDRT6GrlOwdA2RtRgjD8MwydkDFjtTpOJP9gGT9MoIB1Sd1TKFeVWz +awKAniJ8E8IaM8IqVcWuNJwfY6Q6DdPSWsRMTT9JvhLSmlGrpd0ISJlfS2Je5pHkImRnBlNaHTRo +FAqU7JNuQcRsy7iVDiYzB32p4e9nFgAWLhhqgIxw2Y2SzH55iUe//Cj1cc2nnDve22ElwMf61Oly +pYVnRvsHS0iCIfQyqj7YHxWW86KNp5naa3NVCzjiJQeHJongbDrMeHw4Tgqf8QiHtgEEwSdIxtEJ +KfzZOXVGocTlgLGwsuQf4KvDnV0QwMQmVn978QRJVJINZN9YyF++HL4lmn3zZYBIqw0Y0odlF041 +QJwk2q5jkIVYIWSBRSqyMLp5Bis7GbfPCMyqlX5r0PtmdYe7G8V3Cn0jkkdlm+ATwjyZwzzJVi/Q +2Ls6Cvl0jV1MMZzKeD7ZDyOxWJJZAcDi6QJCg9mNCbMSowbOC/4fgGz4vdfIF2+UJwOXPDo7khgW +3LubQtraBrpZeM9ByICzGUWpAF1njpVsLZE2EXeaiYWReQigAoz0Cvdp1fnhTiYGi6mntT8w7kSk +rYBldbK5RznuhTJMbh4jcK1So2CcMXtR46zggmzPcSeSUFzFYOx3ysCrUeZrzwLEmlnQ+kTOzW5A +a32kZuPvHrvdxKzI3qF6gPU5O32agRAHAgMySHhYcOqLExJH2qNKTSGH8/RKlH5sfBOVfeL5ZhvE +gCeAYDr6MkXWCxMmzW4OQ5JRI4ZNm6zOWGmCn4CHc0ZdJemEZHYxDk19tkdDY1GfkowC7WkIjO1E +6Hd06qgYEXoboTz4TsR5WJDZIWPoX5tFynYlxlENvAWWKX1pifKV5E22iBaRMQt7DWjq6lxEXEaT +E+EmoN7Qysjkd/lvZzpFJ8bV19nKEAD55c7kb61G9e5icHYX6swM4h3uj88Wdrq0Hssb7e/Qgo3O +ThyvXDdittFSXJJAl81G3ZILS2InyfncBi2h30mr2ODb2qREXUjunBo+F30U0b6Mx9gjpKEuTuw0 +JoAMcdxtJao2VBtpeFHITiwd/uRXkV2yeD+xLROMa6YixWxXFjNBBA4zB5eUK15EFiWYusVzWEfz +VU3BGCOsQc160LTRuPPympECaM72YZBRR+64JKXEAK4AVDXp854iMHTkyjlzdOrOcEbZkwTcrJAN +VSRDI/IG5Cb7OsFHqPCzRn0MQvPQc1niRlYWNEBkMNl7l6lYNxPCmToJLthDIo020ynSsFG75vEv +a7Gkg+YIvGvTMtXeMwXcsvYMOdJ2z1g0SwKOW+57BgNAhrxYSddSwd/bPSNrDY6WV+e9oL5SeU9z +IvM8RZb17DxO3Ad1dUzTSP2F7bdESm5kelL8qHRGplEzRQbIAlzsRCTIibXidAXCs3hGwF8UsdYx +IHSNNH/bQti7JOd4RCZgaxTmmGxlA4baQljwHAKjlByxhzZFJF+jcM4fmFQi3mwcuyHBF6P1zfhy +UvLaJLuBtYkbKLYqxWXZHIQ1lCpOdswwaZiY+oSP4tsoepYQJUCWyYDdqcGFY+2B3rL2Sw3frmxD +P4FGdtdGa5Hc4PBxDbPRsWL8IhVUIMl/5DlSg3B0wieeN+Ma8St3sdgiRspJV4jQSbw6+noGvlIx ++drqV2mBWgfUneEgDUcvFo44nvlkXcyqP+9ueWcUSC6B+TYIV2+Fo9vRA7vlIbApSNAZIM6q9Vdx +jeirsiqSXX3UAMHVNwOa2XxbBY8YGKfUwmXoXFRQ9esYzzdTlVGMabQGgo+YyjGaFcpQ8O6TOQtZ +k1N3ItgZL81h7AU5mEcxBHvYCtOPw2AOD+aEr5KlIN2IpZBECI9Ctzon8lwOuPWoMIHLb3VuW1fF +q9nPFji33soz4HHgGVb2LOJoJjemJaC7muXa1XRAcnWTew19lTUawSEC/cNrw+eO3lHorGLtV6xH +iAl0tMgEiWku33Bu4uipW3aE3ncumxtZQmYmkj0Be1ucAppIx0ZKk+lByDZi6iTtEkLGqHG0Zg2y +ZQQxVk7M+LrxW5bcHbNG1ADWXdYIj62YAwRXJmNyPc2ukNNCCGPlSq5nrFwy5jo5gxnCsY6RQ/7S +ai+Hw8sYNvakWu7hBJwjpm4TtrgGVfNiBVclvb/jbSJNdZ2OipexABiHmg7CzyPJ1MTHQF8BGEOP +8e4TF9SO9AjH0QoGF049YL32u9udEWiPQOhRJCmdlMmJOGkj361FEppfhkB9qHgtjELgFDPoJepj +BPIEOEP1iAZKDAFKxRBGZGPsyEFNm+Ylno6DECxYDhgBpV+AEaBQSV9PsAhNDBoObl8SJtGPnUvZ +NstAekVhIUfSkUskcpLMSxyHUviOystMtZIz2dmf2aiGK9uHi3URUScpHoR+7PZtRKRt+gaYs1rB +JO1h2gA3Tnopq0teHzg/YWXJdrfoYQnI0l4du9TU/aFDDQhMp7GG6gygbsQ+BFVtDis2FOsDgxED +LCjltrumUN7IQNRe0YgFfzAjxGXaFmFPgaKB/JEAmqAU6ZU9+RPdpXla7OKHTqee32wzBrV7A4sw +NSDbmZqUcuSEacMfsA4pTIZKCnVE/inMrKDKT5ONyxk0yoyzCcwgsQ/yrcQpRcsb8GC6Am/MbieC +6pQyOOIKikoLYfzCjGKDOkaEhImZSE4pzQBFBtiPDbkl02yJWI1sAKms7Wgdo2FgHQMhc84ZOSg8 +pyUPCgD4QQcPKKRE8ICEh3/sz5mK35eSSj0HsRHwIxmTeDpvYscEVSKW0qkoVVNR6i+CATpbtER0 +IUDai4OXRw+ITSgqrDkzbcnBQqnAhk1VDVvO6NlAUCYnSFsRK5UE18UjIjAMqBaEG2q+YcWkbMzd +Is89akJ1AbYOmI+e9I3JEgFmQiNYCwmbHOCxtQfxKwJqB0K6yZFhEYMzC5mnDtMmbBxQ2CiM7Bsk +HCiFck3Y5GQsjPEGxpRRrgfPSqVGyuJ48gTHd04jMTFgquybfoMaiCvRvCsklNkuFkeJK2CeGdZh +ZPeEdxvfopSpMRktMzlzouVUW+gFUUpByhoBz6M7SheyO4pwHaN6ire43TzbrSXjFLfOUqYWGaYE +DuFS0J9HrFFDgnTMyOuxa596NT1Q6NA4YUCH/mBj5DazvlKy0KgOROmpf0iEHb+7IR8GH7NNRGDB +XjQjJHRrFOC1xcgYo08rUKvElK8RXaUOhx0TaKBmdsl+sGpLEn5JFiheV9wHi30U1Kfv+5akEAKt +VG3IaCHgS2AfemCiaQZuNAKPJNm5HAQ4QYOzChUzkoEbgUySJuA0FGcvMbsoTNsikxbktcSKNtAE +XBhrSWDD5E7tthFCXoAAuqIEaa0VbjN2QZEnV1ai4EUiAu98QJuoI9lzQTOeOupwp1Bx+2aa0Oxn +J0kv1ojyGCV0ZhAHXkBinzBKJ2BMAWQJfYQItRhyJX2EjY2Pm4IvvEcKPaK6RhCoH02NISN4bPAD +VTYEX9YyzijoYej6y8SXKuUORtwvWKApclC0vsBaqAzXkRcLwaIsTZV0JJxrzmUBPw== + + + j2NbO7CCQW4jN3CM34CFYuyS9iPYA0bEM2aSEaLsRym+BrnHXfwY1B3BM5IW+dbUk+4758MI1wkX +8cBBTdnYGmI5GdQNf+45ydWlkWQksY9rinzUwO85EBbLQ1kWsmDJVoh0uNHVM1GWttXv5IrrhXoW +WEoFF8c8RZgpRoxTjLB56+A4NnDFmIw7eQTy0qAS8IiUj8gGmwchWKkH4uWOBiUqCS2jUxmpN6c3 +HyMtXupUy73W0LZ/NacjQZHqVOOo24XXFN1Vc8u8RdWJBb8i0A1GFNt86PElRlgQiXjsB2aEnXm4 +4EAhxMyWUFfwiye2eR5J/7b7YSXHhRFEiQiYsk69XBQ5YZSLmoKkzeB1I2212a6WSh4O9Fi6bVZt +uI0QflpQdabUJRral8g23imugzlpzw8p9Hh+Ym+CmIKHVtsRk0ckHnRe81w3cOHcbekKor3ZxUQe +8mo3zMpl5oC8Ue2JXYWll8LT5HR4MMdrL+VUKSbg4mcNmc3LJaQNni82WDsEyghOiQ24RMi0kpBG +sg5K98Z4HAGewYvlgOgQEgWi44CNQwhwpDLGniHwUMyJltURc9JKatT/K2XzzzvSAJiUSEOJUe6u +PsvomI3IBL1YjW6YCFrjRsmUJyuUA60eIGY0E5OgaIGDs7PdE3p+Zua248MwgFg1uzbJOEA7cLWF +RoiJHeHSes7b5IQzpSoSjHgDIL+162Ga8xW1nmgNMQ2iFATGOSGbkiqblEp8LoD13pDfDJWMZf5O +4WAEtFHEQJ1c2JGE19jXqwXMV30ZY64qCF+7yEbybWZIYOqLiN5Gbade8Gp2bGMi9DI5qhPqEDGK +MncnCDuOTPJeNMNI3OYOMiYs6wtSnMixA8Jcrbnb4TeDZ+gFrVoYx6QqzrYUmr0s81Wk7VI5Lm22 +dQuqaBiqNVLaydV3ha+xg8HKaq92DRcEehYccErB2yQokjM/UVvsHCgkbM5Hm8FZ6PMGeci92onY +qlkWuniP4c2B3uq1hObqaOjhw0vyQ5LB85Hf70xPF3KD2eh2LBUqAkIkicTrCbMOgK8+aXZeGmlG +xTIHh8UMx2SFw0He9UmV52kfGU6RdLwbIUJxIBj/QiQFUEuQwk1hLtXVRVVFuQY7oHCEfw//bLMe +35AWMvYaGfooDFAN9/Q1EF6rW54RFGsp9Fcq+mLeUkyR6iklS9JtzgXqmdkzP1LPk5uQBwBAPirL +R+pACw8yJHVI4K2oLJjtyyoG5llvX3JkI5F2Y6MlFyhmi5YAc7UbmIxJGbfA9ZxWVpyVYPweIlYj +9NykozirfiZ9x2nLdjY3yqEslFFZY4QTaDIm/1bsuUZRHu7U8PAC+qfw/Eq9YVKdPULf9bNQbif7 +Hau9d63LfQYFjqjIvKV4DCnO8IYyORHBDxxVNaRVJsqZodA6uwDpQnUWvHHEspy0ssYIYq2kEYyt +Q2PtQycRZqGL0UKygcSR87ukoMN5nVK0Gk0HSLJ5hCYzR5XRxAj2L1pAJQ+uxIgtHlmtASLNwQ9n +n5worFlQnUB+YGFgnhqAwCoTi3fuE3NI7hk6P+S4gKNpfhMXc9eb0mThWEIk0ihBgzgrVSLDGWxS +bEiJIMRCMJkQSwGb7bSNFZ/KBMjCA6QbkFQBdKV5cZygSrpk2Xy/j2uVsMaLXXRS6KhExYQUEtIX +1xisQSs4KKhSN/Y6RswMVKFR2DOTRwFbg+ZIDfJ7Ozq6uMJ6iqQybGCSyuytRm+FdS7UKWTuKl4V +cnxK5AeYKsnjjopno3MbqdyNl9fVzoA/kwruWj8UgdKapMh2X9zmYtxKGpF8m0nrGysRcL34GmFY +t6giv4Ypp6KcIzRk5q1Bas5+JNIDTHxZYgCR+BybpYOEzMZIpMwF7d6jyhqlCkwIiQ+ST5TF19dS +j1HSqBcr6omiZQysJVhibYtjoaHJpZ0oKVFiVCZbF89uIVbHCMxhjSjmJ4wg+1gjZhczSJheFKCf +sL2TRzglOVNIeHsVhDuBBhnZYJdS6SBHOBUJ5wzQQhI9SKAnNKJE7IpMV4iVJ1ldbDAA6Ik6QRgO +IMhIso77aEImCrQh0DWvpYW7gd5Q1NMj+GjHnJgFyL9DosUBAVIgkvDmyP6cA9tCyhPhEdIayXvR +KMoVNxchcoCdKBT1npO0Oox0TkTyFo0dkz83Z6C9CMlKJkBKaJGeLDKeoMoE7eMMzaJSlEXyrCk8 +mhwRA3eKZCjdr46LjyCEhD0lBzWqJ6cCOQP6SiK3MS3kzdttThYI9cxoo0I23phdrhsQqxaJLZmj +s4AUJUqfdFhohjf5JNZkAQQE/G0lNc03LgDa4SJEf7NHzEQOnNbNfWuU4wJiuxzTw5lNZFYE/Un/ +dJ2bsaNCVqOI1i5TcpcpZPnh2R99edfR8v6mPif7W8QwHx/bWBl6wzU/Jhtz9bQ0l85oQH/0eNRe +Qgul9DI1QcSA8EoCNOSCi0c42z+TgZe2GzRDwTjZSJVk08ykNZSOs1moeI0Mn3tFi0zq3LiRV92C +6KSmrM5EdMCCtIC1RRDdQHI6FpgX2jYrrhmYHdLwCLcuwB2pd5wpD8xMJ8pR9FpdODEXMLCuvld8 +HVfkRnfCKU5m+oyHjgqTVAnkieOFE8yEEeDR2eF4JuC6zjwkExlGsSGIJzt0jL4e7c2c6ZmQfR3X +kCwUfAP/ZPynr9OiRkGmFywxTJzzLlgmI4JYSZp7ZXdxSiqm9TslF9mWuh8eCGlHyZnjeoQ4UaLw +n2zNmewE3jG5xQKPTf3OqO8LVqbZEo3ripM5KkgldrQ0avwuTtQl+BllLVoUfkIS18hCxSVJLoIh +7FbIJoMfGjWkk4OLbGzM9WMLDARk8ijZMJNHAcLxCUvkRpKiP6ccjq2NOG3jEMcIGaecD+oyArEH +o13Y2pwSrGh2xq1RNxnDFBUGJ7rjy0Rap8nJum4+kqlqsxVqo0p1drFLK5MeBa2BMCLH4cAIsp5A +d9P9yCNcEFcEyLyjwIILd17vGHURHN4DFAnKNXg26hKrA18pkaNs+krECMkGIvlbT0HBZKDWFE9w +bgv1e8HGSJ3GkiT0vJCznAyQctUJahr029BgoUVdBbQTfGsOMZI52iybitUrShI4oUb/KLMrGmyN +TbARYFgLcJ1+WTI0Y7bxBvmc6+DgPy7kniCTzKMkSFHBGZHj6cjvri4LCIkh6KvDSC7rwPuIcnCb +hzowRT3eJYfSkyg1u9YOSqoBSkJ5YGc4ak1HEdDrwXRaz/FETc6Rhx4igSgHOfxlCbFCni104m3i +7Ue5kCUyQBo7F+8tfmKc/GXdUE5L1BFopRsCmhbDWvCp417FR4we5yIagAwookGCRlldhxLclZOm +GdB1FTAUGy9LkThNKBLwWFpd+osEMqnJCE3XlABrDYQR/rGuUVVbKzuWDWTBbFdk45TtI82EeoFa +QmGkgDACmPFCdwUIMCNQXH4tO74TBXQmfV5Pin0ukR/uymAkBCClSdhyWWqqlTiHK77VWuy+Ws1X +FqtxeewFkkC01A2Nk3vKq3sfUYUI8OHsbWl3bGRZGLCFxkUNZh6XgiXJRSJjBHDMoGAqi1r865/S +26nr2+8Er+JkiuzthDaCNgHmZV6sd9MHzfs8jSYM6XO5HfV3uhJh2ch2Dn4xE1XKQEoxg6trxUwY +RlH7lkSqZA47ugY/xpX1Tcz7tkYZASPwNIJEsW5VuqIWz1MsJ7JzZuKJ23Yn2BAjSKj0CzsjDEmx +gZM9al48oy4fQ1RotAUHdmv2SunDbD7Zuu3nBOoKRnbyGruZGhA06mFs+KnIgpJO7iwd1IUcGRGh +CBVPp08sJNkSPZ5txi6R6KMvLsBpGeEmSyRA06ZmQ4BRK81W/kyFV0aRGIbyTPUFCmskNzByh5nm +Ea6pwNtMBmiNHY2WYYPFr5LBhCcyc7actUcwrmw0HYwpkdo5GbIWCTyoHSmo0+9qOEJMvOPmiefY +RK0uu3pfTeH6SnCHEma8o1lU0yxjrD+uetYf3HantGjywVdIMAlKQ6JAaaMtRYA1+GMAGNWgWAdi +Mt1eXN5jQeQWeyaIp1i62WMuNp57eWreAqMcRF7CKakni7x+2RDZm88iGOYwmd8dMlCC2dWBJGWW +SK4Pr80EfqJ4hCE31PFftlIohDYoEQWoAXx9Rs2hO47EZSN0ZQwYfAsMmMv3TjmaOaSRrssBMnMI +vQESoRT842t83sFqDi1RBMQ0Nk2dXegdif4G6GYO0A2RgWywEMofMQZkmq/h0pYoH0FfhM5MX3Wr +2kEJRmKGWpgaDG6OkIMRm65nMIfbmDxfV+1YWgRjEm2k52Q6HcHzi5Kpp5r8jp0PzFvZgkwMvfik +u0Zkipujj+Eddq098cU5hWRx/WtRcralBIWvIdGI5yHRxm31W+qJmSN4wObVB35iV7NFZrOqYL9f +WWqMQIYwsQCDjjgo5zk1w1H0nNRUIRBEGbh1CUyaeLrnE/XZI6yFi1vhunCiqi06vwt2tu5k7poA +vBwBSlNYyWTBuBzyAsuJBFhYFica3SNQQqw9U7VgijI1Rgc0dy6I+lMGfSYXvtyodInSp6TEutQZ +lfTALMBqJmNYc7dccOS4HA2Riq0yRQlsBl7GEfuc0JZXGVw5HiJSLXFFtYhBccKh2cUw7RwmQTjP +l5ECoJGWMHUwVKUc0OSSUG4t5Toe9Mlbc3hKmEZKZ1BrqJfeoXwbwrHVKC3LGHtDPbfk+5KMYjou +ZpAe4UradsXgJ1lbT7imGMISOqxz91FLUw9eMMo1dR33GKOUjlEBkjtgKn0dijIxAkh1aMwYjtQN +B1JSSCdxirH4Fj3szLdChk64uvudFqK83AkodtC3r6ON6gQ4PzF+Cj3xuLguzxSorkxbRWxQypHP +US9nsTXi4vbuIoGi07nJmiPdpVHNdIlRjvxkqiTipCL1mxA0JUBdPGVeEA1YhsnYoCCN6g5zjHLZ +XVu8pB+jKbhw8twVeEa69DiqS79BGd0PAaResc9lIbJAvG3ZKm5Rrd5FarTIM1vehWFJHCK7uZXA +7tGSvjGPdr5MkUaT8Wdt+cfg95wxTiJRi1FgchllQF8xqKQ55QTLwCO02xlRQuEDVEIMqKGLdAdO +grPm2X0cjxhAZ5KIFSc7cXDYwqjZQa4WQDjQAGeQ6DiTS4ua5PQ9KsY1OqPeqHQnJOM2m/zWlEbe +agktpCZFPMEtVWBu9iMa8OgbWGDYLYeJstLbbmHygb84x8NhmsUjXBE440HbsqypMUccuLn4QfYo +sUw9KHVf5+qEFGDvVKqiA19ch2iDRox2di7d7qc1lWdhmcq8geKx+EAQkRIDvjoZIoXkMVK4aypu +tCilGMACzhtXV8y09j1WbxqtWGReIRw8KUWm6gppiI6d6W7HkdsIuOVdjLBEdHHeKVxL48gi4Oc9 +qVMGdFEdBnBpNp4+vs0scMLFZElTNSqaCmmpuBbDYpYB1NfTIq2cHNUQhNEXjXobtA== + + + PjIdGFwn9W8BvhBVyGBNGmPXFVap6w8lmgsUj4gSW9rKk7FqSzTe0xzNgEK2eR6j2JgmEbaBL4EY +J73L6LwIcjrpxd0Hys5DStthZBa67YGA/WDM7b3hywZMXNv7uyU10yQn1F0w+rM9uqMJOburwVd9 ++Q3FEMVRFtNFwGgjQCckTFdLN2OpTJNreKhGsnJzsrjxglVrnplGSM38EKsJvkr7n2MdvVgwwolr +N4ntJyIMwILhj41yDGLQKeqmuWgD+sHknCbCQk6mFOl0h5jrUWW6M27eFThCdhjIvbPCZ9Q94cYO +kYYzHh1/0Awwb8xpAjvgXTG3ZxN9irQqqW5t6x6G2Qc6FWlPNospdMZxk0vUx3ExP3uRwaDDXcj2 +Ruf40KL8kHTwfBj7O17ometc6H/8q28++/WLV68/u33z9R8/e/PP7of8V2/ffPt1XO/yN3778uuX +L969/OIz3eLsvuvhZz8//MN/HT7UMvmHr7dNdxvEDHVBXNq5GORevGmiGwcJpE4So0vOMkWbJllw +IFXgItEziaKepFE4MkI1ptL1FaqhUZZrJUufOFR2UUJYrgNp0hh8caoGgR8WC6fP0aG5aGoIeBSG +hkKNJ5RQMrXcpyjzR1o5zvWZ3paUkrfmhG8+E2Gj0ltx712aovpqpNu7DlsVwwYX3yyyA15J20OU +afLS+ud/8HcoXHgaOTmhhTBTg6GSVNGWyEiphi86ATPxoG0r8eqmaNSzp6898OrFfYp6qZ4RYwPs +Gg2B6RQMPOTJCStO6Xg53tAntesO2K8zCZu+j7tqVBI/yFCnSNf2dEs83UzLUZCtcbXHr0QJm7GX +HqCWOaUHqMbmlqNzy326cOsxXeMUJ3o9Ys8xZfKZY8CYPol9pJWh7hkrQ5CIhzMK/x+9znjBi0+S +muEnrL2eIyXKIIAZz67rMueIMxC/gGoIGpGqDVWZavCqIrscqqbySSrhO56hTh4mrQ5N4kF3KWXy +Id2Ti4hTd5ZTb9B5/XSbcd/CRsWZavlIRjw1pWf3r0zr1usRpSl7xALbdi8ZKpPkjpGeZxyo4b5m +lPsul8BzzFTjdGFUQlEzNVGpOB4lAaRMw7GdT205jjKOpJdCOhlPRM1UMhuomtSvvxpayajsaml5 +A3hWTIaoe02aDSMmN3gGGQ2O0cXWYwAgWQpjb1cI3CDL2+FPRiGY37uW7hRJiu79QGEAgtzN2rzM +IBomzx4Q9daKC2EQYKap1eqaVyWaPC7JuELUjM5RSjLakVwqqCE6So4uhABMoVpcosJQtqw5tc/1 +xLLzx0egJ6a25ipNi+uwGrpLpnpxq2liQFGQlZCnwyuRse32Vi1X57HZhCMtyq2fAK2ScIrZLUWY +oKHJkK4wJM9lVtVw8DkKwdTao1FI42KYuENC1ajWsEkwDEh0lLo3Zhe3WZDU1PjLjmHHt6nuKNMh +lV7xPVsiUNGyRKCZpl2ZtvBkbC7dlEAP7Y5H4sA09lnJsKOuFuh4V5egshQJXASEi+1i0u2iEqRz +D6SzAOxyQHhxpxcj4T3AKQX0cp56NVmiujjDQH5QMcf5ZwFqn+ywC8UcyJD2bnGHTipsSPAAWQe1 +bV0inpV5iCr7kfKIN73bJSh4lD2kymtDR+GtQbDy1hOuf4cxCcPoESOzZSHLllKPpDSXqI88TeZH +rbnii+UILBxlp3dnYRTZTIRnMedcP9GoVcq4kbJBVMv0SFdQsnWd1U8ckwLAVLA1PYIzT1vhhakX +XuAxEGsIpmSsd4oRjqRTiKFEVMkd7QgcYb+7t7oLJuBdnboYRRotUZmcTEfXl7PrnJaF1f6RFr3V +E5izsfdiKc7ZmOhteGlE7r06nYLXp8SjVvs2sPIsk5zaT/8Ma7RMSXVlbxyR0dHT2bfIRrgpRcdJ +kHOLIfCyT0b4TjRAQN+RKMjuF//kOuBs4V1A0dxY2c+y9mfBNn46IrvnX9wAVyzhEU4uU5+SqU8J +pgSPYOGx0C8eHDnNFqo9nhTMoCxdpOjhJHWvPPr2LV1MLQFLxfMYbdHH7tBudAV1GffF1UqIp1RQ +TR4BWItXHl2HfXGqGF4twxIZYYRty+79EF0Z6NdJlyEXinAf1NqTeioN1sOrTEcVd/9qrt64hNPW +ulKOMFt2m7Vq37ObC7gLiesTUxmrhv9aTJ9EHyCfyVvC7lVgYsxNBlQw0Sh3jHxwRiwjUU9cOVt1 +NLp6AqycAOavkWzjcLZYYHJsg9Qtqmg5vavEiChHSJyNzuLJvfHcbXlxGiTuTyeKkATVzTYaN+Pm +kiFFy7cpGonQZ7m5Wr4+p614PKfrA1eKixcPWF2ddYkE90rV6q39MjV9q1u4UMUl0lsI8JHeksbo +TL2QgcwInKCoW/brI/hJL2JENPVuaPBLtKYlpkm20ZbYn62LMFF4WFpPfcYVIk1Je4jqOuSwTFG6 +ZF1cdoRS5cF6VrQydjVCw/X5cm/vTsESnBO0RpxYcLHOPEchRvNtwKcus477knp1ZAs2c6kpQgv4 +IadeJ5NRmRdOBFRrVMMhGzG5MR0JF8UBXnefp4GFnZrj6gHu6UNS7ezO82QbbC1vSFJ2BTYSEKLB +Fvkr4Aywid313NaDQShLKEHmfZjIqVM25RByRIddEJvMhGi8TON6qra5EzPFMR04iKpwpltCQujH +IpMr+iDS9pWmT1Fux23PaVrUy0hlUrVo7zyuEcZENaRiFPkurhHJiIm+lLSAoXAl/CVKuZLJQJQI +BBYZfIA1XKx8iUBV3KAEXaYaLlkgd+Qgc4JmvWjDzWkvwAuJABIaZQVQG8gMYkQJQUcj5t7KBJ+o +VFZN0VTcoQMXDz3YXNZodsSPPFfXeWo9gpwNL0MgcD2xQuo3rf6WewsR30V0y4qgwYoDSjRCm32C +MBL5EUhKr/aCM69EA4hG+SaivGvU2qOUKpgo2kX7BK1Los0o+jS3T9G7azuZ0WTcTwJ5RIUqMCD0 +oXWx1Qb8w4Ab7JFy6h7DgmVQtW4GtbobQnHvGQDAMGZJ2xm7DIBEnCBHwic0knJjaLlxNU7OcZK6 +uSBJwFg+vg0FUYtb3Ex2vSxO3+9dYPwixPawipvNUloYP35xQj25LX26orSxfYnbic+3SYYjMsnk +IvlkCcgEDJGVQbGnilomkz0WmuLSPShMwR6XPVriW6ES6EvoypQpwo7Mru0ER8jFbiwU3NItf7gr +wGsr4mxjilkQtgBzC26ZTGI8WrTnTeTVANYi44OmxVNP+KX1NmgmFxqgBPbsyt/RUNsG4FgMdMWl +QS1Cd/Q20GpExSXbzg2ztl5z5A8Xd5ovrmvNxqTRuMFe2hujaz5RvhBzLDoOEw+mfgI1BHCz4HmP +y6GpNufh41XGzxrehJzsoyQ9AvGnl3W/0xTBdRMrmvRo/wMNg2PKRvuAWK7JbeupT2oEFY0BJFGM +uyAGsrrbEXO79Gxc2mC5RCFQWjfg4jnpOo0mDvrA3UlbZ5buBYtpIXbnNq/086LJ1RT6gU8QOEdd +reXp5WhlVNrWFrxcBQgGLHt223mQtC7SQHK3T9AO2M1iV9zheDd8jY4+YMbw4YKNBhByoL13ddAV +GiW4D3K6HChKzfl/7Boe2h1/6B7TvOlliLDbyfdJqERal4bgIRLMVk+ASsiM1bZxHZLSpx+nmf1g +0uxnCsZkF+JH4UEXR2MCm36g7EHZWq5NkXlAw52VjNIGNoGe0hQxIP7GtzCZSTOkvxOa2kRYCUyx +20iLVU8bgU6TextRoMGd66ZoMuFov9vT2Ts/4hiM5H+dWdxAao3OquTZZMfZ4oIGJrlnRVSp/YWr +zhRPBnENlLMSFbUnX3K2vE6WB1It9So2pXqtBvBddBhaXb0JR4/T42erzN7GYHZpq+kGsOtmIBUH +242u08PTnBbqwXBygjCiXbN+cP1aVH/QDsWx4uqo2uwqYAGmTCWKxciyJst5PQBgdLtwIiz2HpDn +I8FIrg5Yu7DHm/Np3YWPDBDXfQNvXGb3OF5Bh1JnAgFn9C54clC4enQiVge6GB8ngopcCfOGEl5a +B3pHsXvB/Tc62VOcBqYA9IQ4ixt3SfA65Y2OrQewc7WrsU7jELFdGRPrtA+qSq7ucABc7MpvmKMP +EJ4aewCpcbT0qtNOr5kPq23uaPWVCPiJe2Qn5kJMraeUUAuauuwu8RQvdnyOYozdiGeWtGMyQCKG +7O5Z4OCQD8AslqNHZnL9MPevIpGrdS2BE+KwQAPQOfQtCkI76N9ZDVEcQh3NvV02K3Q0GJZsPILG +nERvC6QB9hPhO6YQO8ioXxh4wtvCOo7tYPRst9Pc1wkfJE8AlVHGEOckGnr7/9l7k2TbkezKcip/ +BCbQWtH2UbAdbXZ9/KFrbcUzppvRTUjxzIzIjAadYv/qu7gAtDjFLhBwOc9JngD53PWc6836oLis +/qO1RuPM1mjNHk0FHaoFZiAFcxaq5BD4/9P6/D/tn/y3xIFFZkrHA6R0ziZSP/bIf7+lgyKwUUDb +YgNdAOBExTzo2C4OpN///e3RRT5nPV7eP9+W5a9GO8sfOE9NLED9gQ+acjREccRb2G/db6MWwLdV +hbnHuH2w/IQRSIcFcEy1KdBhqb1TfimsPX88Fbh/HOCtd1Gra/d/+HrmRNc5ju5Y+/1H8QF8ID84 ++8PPXfxnT/Jf+dL+b1Xt/X+bu8kBZtJLB7vInKfXaW2FqkiaupxZoHCo3hEoWL5H1oRawfsBdU9Q +9oh8VIWKmvpr/qTYWlMEjlMZuAwWIFVkhoqF8yPyCwYmOK+h8SgZEIlwVDJNfsm/MQLRXu2N1TW+ +gYLvZqKJ54rjgGWlXibW2b3lLerWvABi0GBB7cHSzxvWG2CxCm6VD6amfbQutr4qGLJFoIU9jUJ/ +u74+fK/kp6rrShj4TfFThKRIqotkcy78zBFGMnAkqPKQEXLovXf7rWBaKNyjjwPk5H1EzBAJoTlq +2p18GUtwYA84AaG6jqKcAhxnhHw4pfFvu6haweU9yRCPiK3kCrQD2D6skrHSKEvNq4MLytuaB2ea +Ml/MLezQ3sjoWiCbqmt9F8IcHbAwhhIzWrg6UE6oGezmKMLu/IJU88YS6UNdycm22Yki44xGrBZh +71XpZJSinJDsMKxXDp5irn61lmrJhJfgu1ne6MIbak2IJmCBQMP0S2NYlLTaVXhTTfLy4RhFjqcR ++UqForFYeRCNqt9ZsdP6OVPmGTeE8rmC5yDEynwih1AU9PE5dqHi1kCXkE+WSh5Am3Ib0LWNENR6 +VIHDtea9rVq6ZXqz8T9DrRC0+fggvMATWukkILd+KVBZyO3+fmvpPlwOS0LW2SzmWqynrt2v+NRs +mT2W+cWStftaGAHfbsKrGzfMijYaHQMcmHZGWTKheLhCFgTAzU4eL6BO4jr8wJpR67cxIpLqvm5i +AJRa4WlpihRhJr7XEq9/juU7I86VhiNa0fqn4ipjv0O3YqSnsKNzhN03ZH2/qAyExQ== + + + CMJioL2jDAOIDcpoodrBhOuK7lhrxMZS2Qo7WC0/Zd8fC7IX/QPdiSeAy8v18EE82yMNu5roe6KD +j77nwpQTGBChqWXipiAkCi1ghAtA3oRyb8pEQPUQVp0rigyDKLit4Di1WsWwrUSQ8dEsDwIybY/1 +xtazqsMXaR0tUcEfUEj72+0G2oMDJKhV0FB4HLotUIJlVxGGKiQCSgBRb+SUotV1eegScEGLq+mo +gOPtwpyDA6Myu5dQE0hH3ijpsLSrF9okjkTmeOwSmYOGVKcTGNp4aYN8kfFUjg0sqPVXvaugoZ+J +OYn0IVmjqEv2oAkfQo5DK2i2nxoatsuiQ0+meEjPzJbGqyDNvdKIDcYZNdOE8TRQUAO6VSj7mvQC +dHrqPUTaPUSe3BJvnl87NIVC+wSSH2p89eu1tSsVjtlwueYweGWDx8Z63YdnGR9aQ1UWibKkSoNV +tbdz1UswY/U6Y0b64QPk5z2JJSmbfoluHo5CAwFket8350OJ6tWBK3Yh2voO8fN3cm6WCufeNHPR +chIWH+43phVfNsyeq0CXrRNGIJqeyyTnfBW2c4Tw8PPxAKj37YZVRHKlI5FRkneH0dS0e6somiII +NX5YuW3MFPcUGjon+8OUJpQRHDyDGXxd3uplP0OHBszgKL6VUVW8MbRSEVhnougCpNCVv7jXIUd8 +aHGjemaEriyaI1SNoOD3Cob4bTXLkK4bo/Z+8+alGubqj3ZFyLGCVwjoJDeLVtWrCB576QIpVHW+ +gHV0t7pHLiQNc5AQjlqKw3eQ6NuzRBofnC1Vt5UNYwsChPvmBZ7QjEcPTaHGof7ZGt+IXfzbnQdr +xP1tPzXaZUpCoKE4zAwo3k49UxcAE0Y4vaAa49XLCBCcjJiPnnbXR5pab7/iWGzyCUN6iIG0ka6T +PZCE4ENVnsbEWinqrbgb7nkE4qm50T7fmgaSaXQHKfY/AJJGFKFJfCVwX9fkMPp/58nze2nAP1cQ +BL8s2mEEyEgpsDjvO6B/zJsaBTFWumqZc7ReWNCAv5j/Z0Ak8fcnHtwgv0jwPq+HNj7+HnGkpMmM +RnS9NXR1IDgMqeOr1AQ0gWhm0K96Yi4cs8Yd805M1um7CbRtMcBEaXvgc/qBGSBvv550J4mgjbNf +gVsebvjCc8rDZeGDmh9V6HuILQAlQqRwVuS4OAi0KqHu9e8CWC9vwQUjtjT7RktnT2wzh2R4a1TC +WyT+zi3dKOxJh/dskXyNM9yjxKrMijM2rCiIs3GQ3HclnrcmyJf3utN6FbFEA2Necr8k2XiHn7nA +Sb/emxzgYBWmbNgugPtXXC6jhozc3AzmoCh0jy5rm1fEkAPmXL60u4AE7hdgo89SgPREk3qTYtbL +hURhMKvZnIYKpYUlCn8OhEyCviv5gvNZb1fnVCXU9JerzBNAI3CUKeeiqDoTFZ7jOkx01TwQYyXo +d6tuN+ifKz1QqrhdTViCMizSp74apvL0xBB5lbVGzjWF+D43LCzqqnoBMSIdE8Wv6rZunwhnu0dD +6R3p/I7gQMpUJ/a1fjWlGz1356E+3lZG9GCJUQ4ajhjffEY/aEuWh7Bd1IqNDPJKS1FqMmIeFVlJ +Alx2D5vcoGiuFKRO7+/W8/KcUc1RarCe3aGSaphFPZw0C7Pn1xEwDYlQtdWTKBXOGfzb7FMyaAcF +vvGBGuZvXXMwfmDEbSMabOPW6wwCcoCbFLy4XUrePBAId5kYO9oijHoQ9eTpi8Dq6wHIGlLZUCGs +45LnG4QGwBtsYsq2PrvkhZWIxZePqgSGY31foAF3lUrxmaWyGEr9rdmdnVRTiomslVqw3YZGSBoA +1+8TmeDk6qJha+TPAOK09yt4u8mSx6P3NDKqoKRjl6kIQim4SCEGqxdzuYr5iIOj1F3tmT4iUQbg +Qe4kAR5owttGoVSGtckZRSQyrnDxsgUS1AZOtjtTObcYtJFVNkEE6j5yR8++srUUymb7uYCmbxXv +dM4c0CiswLPi9dE1558s3iknJIcQ6QFpznuFhS1t4G9dNQa6o0C8MBnFxfFAn1cu3fni6YiuVSmQ +TtYZ5YvFRodliDgT6xl6+qIffasgdIGqzZ+msCuYHlrfA42eWu1BnldWVGV8KJzTkOGY7+zTIzzk +FHgaWrvTESrgn6ys/wA+QBA2G3MIC1ddnldBQWpwlC8rUhBAXoXvyPYLZL9HDfphN/rRJ44j+enO +vH/8jrR4sGRiAZWTckCrQ8vDHeUcQVUOaSEyZkQk6sBva0nTTenfX/mO536H25/w1J8LMA+t1Y0n +62ZCjWDdwP6y8/MQPfWTg7ATvJCqKPPo5dOseYE2Z7bAKkzUg+sZ1aHPPbK+X6d/QBJ7HVXJCwZg +NuUAPDUq5V+ZaBxGKAVyGAGCccQELFGBCV9IUkSCz7lYvm7yM26vqmrrhbT9K1k7DoK8FKjexZUG +yohpdyXveS+paJ/HRGiio/okhHuQjgz7F2YmHySEp6R2ngPNOlNHDMRhiXKB7LvFrO4+8Z78HNUh +eJo8z92D/0AoS1jVa+cdDPEKxDIxOIrvJ+riStFaKhYt4qItcrJ6HP3eH7TMjlm4USJoGTyyqzjz +quUV+Q/nWY3HNfJFWtawFLOC6STT6uyRXIaviA6nBFi/hqQ/yHq59CMlr6oVq3+4m4LgJBqeIVhP +vBeuVoFA2MwFMzVjXV6UhiYxPu9NHRPwVSs+poxQj8Ni261egiFADgS5ja2n0o75PNAqGGR0mN+i +KXmPI686s/RLFYBT38N+flGg4yFWhpR/u63okYogFXU9jKhlziKSJVkbXdMW7C6mUuyDGCH4wctz +pm+h0RX7V45bxP4uCg4RODqRvGrN06mM5HuWFvT+ThAsBUC0GS3keoKgYbVHmNqawsgqzhWOSMmA +4+DiNpuy39CG8XEceXPJtHUI981tjRrrb2+ySwiut+YDAuy5UE5ZEMAg+v0rpn5FoHUlhY2jwdn5 +2r4TRZ0WEtI4hFhRGPi9Xee6pvS+DBBwZTVK1sNexTkvTpghGpAFjBwDtgbUcsr45oF+DRVQmXIb +lngw/aTE0wRcPKriMaLCjlcJe2TEQgK7Ib8rc55W4EqZqKCUEjDXfWH1ZNYwA86e1yZYaSTBuTQG +KdBEGTGRljtT7tWdBa4VgfEJCoSOcyWRvCgCeo8lkykXIIBlKwAD5Z9D6ELTm3hzTW/FpiRgC0iA +lAOV+KH88qpCsNUyxBqBtpgj6FNmh6DALw5frWhFoTmiENPHx/kX0EwcsYFmou6SP+cdlJEyADJ9 +ViL6e2/+nFFoY3ybHeDtF/xyf9MwwfiUO6AVmnhlUAYfnAIpCElbGDCg1gfe6IiwcXicpzFSWCrN +0tPJYTyE+m/iEqg41XQmc+pRtxcqeZMYxNnf/XuUw5n8yt3V+BZ7LCb1JqnMoUTASzgm64ARKg2e +EQsEgNDhQpbFeT6jBc4j+mA6OGqfKZ/P0Fhh8zsT4BH4VWoQgmBxCQxQy36F48EKFENbAy+FYjHl +bYjMAkOI3cn3KpFmEWlLRWnqcK7PChKz1O3F81L0wWNV7CWoVECoZ60C1/711/24fz2vk+Ah4e5U +09w682sCiPblm740/tyTfOWcXJ9p0MX7gE/n3+uIS73+UGM+mrjzbrqx5Yhj9UIIinrkaGRHP+Dy +vCMl8EuSYKtIHfH8kmaLkJQVPxpGvADerGFDbDtHxuAI4fchIctR2sbXxNHLmDJVQVs+xSNcsCge +Pbaj7rKobBMeldQHwWzTKttRc3j1jjs5newAqHRdo+X5e1+KaJXgfynr7VQ0jcAcK2iwJ3VpgPTj +joCJzIhtbv1XL+T/cDr/l+B0gmEmYP+DoXSI0L+l24IBiWbW2GVIEqeZVzxvYvcIzQWtewpNNbLe +b/vMayi7DM81Se3nXANntmi3XwKF3465jP02QNsEmhZYVwqs9PXYCgF9AIY32MCvkFiDMwtvWR0/ +Fl17fs9AQaphvBohcd2sgMYAcrpfRxS+2Q21esAJcEr6+YVe8i7tjx8QHaDohST/Hz7UE2emGC78 +GufWSUMUWIiV9isEOahZ+XzOu5kxLuSX48A7bpn0SS0ulV97wvwTXAP9cOJWeL8j+DsJDQOR87gd +8+LnNEjS3pRvOoeJNGwSagIbiSfU6oClXv2Y7zb2m6YfBATE8rlxu8xsAGMZRWw8SCCdPM++H+z7 +wbW44SHbJvfDLjmLBhyvBlWp0NBk0p/Dg9Sc0/qlVr6SlWA9TwbZpXW8iX7MviXSue0hHEN5rJkL +Dfr0zABkGnjTVGFVjilsuqDNOqLW5RdtLLsr+Bto/9PBtL950zKPptoEZH/DXLIq3f1SXej4yvyy +viyJE/2jKYnz+cDfEOFZABAjFfOB38PPUnlCiF0Jw6tj93aSOUQkQMUDm4v72JkO/Ro42Hym2Qym +jn4ImDrUY1WtUEW4RXr3fFCBqCt5QfkA+OtSAD8UVr/Oxl+zeyeHgr7cFMAK9sF/97DDTRcLEu0s +ul9GFZwve96rHYdZy77C3GbU0Teskh0hur4gbjB2Lkjc2E6EDtmi/lKo/KEOuNjPWOWsat5kpxDA +kUnBowyby9ta//tGtA3XGGDaWNyK90eRl6wIY7WBEo1SBFczCuOgJUWtpKLC9kjyygHpUfx1Zmw7 +l8wG6tyWN4SCC02Es1f9q9XDVNM0FNVgyhybzuUITINKY8UFJO8RAiGYg6g8WoCnudQwcTDRrToJ +VwUi9dySHNADO7FwXSn93Foe9S9rTZzeQyLZlrhMrdB6O3CO824w38t1ry/xuW5pF1/+Wnot/igd +kWxcnIn8os+uamW54oRIxS63XeVv3F3l6EEqyC3ybnqJMJvd+Yb+LJEXmmVOh0InJn5+6D07C064 +T1f0fPgsC2PP1TUmaaKYUkZ4AziNqd7MANXZd/Tucey1aLHQ8XodAHeGAbSqsveDtgZLT22soTKH +RAk9ewiUes4wlwhlly6IxU4W9g90sgiQ/2wElR+FWc6+eEt9GVXvqJ7Okc7rTWxuywOBlLkswK3o +HpENlh3kbAy5988Hfi+7UiV15WXM3/9Ia3Tyt31L314PzSao0OQffxjh70Y7D0u5G3JmVM0ode+8 +f3pV3L8GxTVl4ClexkcYkXqSD04KjOMe/YlTbONVaLncsdgraQEwCkFSavp2zJkOsUt8hls6uTTs +k2gaZU4oLwq9w5QNI2SdKmVjZUppvoo3Jvq/Xgis0NQ2mnN12rTRVO2snepZpIXzXLEP3tH/LUtl +Le2Co/HQx33yYJOVUaVnTL/2gadbEzQ1/HsoeqgFhqgl7/yhy1njHwwHDZddti5GUGvy0ZMI+2oo +qstT36nBYFYNMYcYS7Y3HmURSdzkMfafqdtYb1B3HpdOdDoJ38YlVwMlgT0J4GSB5mORYkOGxuLQ +NBrpoiaUhKM+vw/ZcO6AJoIjqIYp7D1aFJVFsPGsypWeKLFaFDdB466MlAln/FfQAQ== + + + k2/fKt4Yr98qRpdvLZHFPiGtaLD381wE54JIDxVRW610DyEc0D2sQqMqeriYEeM7RJ0LGVjES7sw +vLS5JOKOIhG36mHMTkdhGrmzUa9SA8Klk/6cFd+VHRZht3ZNoVjFuhEIe6ckSr0UDbNPXasoS1v8 +MATYhz7ma44tt3ySIBNvk6jqAUe0pK16iZrKRLnB6vm4Bl9PWmLO7RWT4rPQVppAjaVmIHjBQUwE +FaixARMc1KLxSgxBTdi5TaOvGnSiFbeF/sRE4+nQmNKeh1Q/agSngfAzc22RL6qZZ9Zis81PeHRZ +LlPde58EOn36n0Dx4YXA46S68hY1Mk/ghvL3dDtBApvtRG0Ueue65fkD3XCe9PoRsv7UJQgZMy9R +wmU/qZeIqzCS1dulUwtRLyAiw0IVPU9UaJmUERiFnBFr1DtiKJVltn51WkCAQtsm3qx0mYotREaB +fxBZSu0eZCn29t6jPpQDS9NrTw6AVBIivkkyZhWQLAJHBfvSldE8jOq76iAwRVLmRlnz3Kf0KZsk +5wCNZs6gIJc4/gXBW3asvPhvbeuon41+tUsIR3kb3F2dfxxFECy6vSMMtwOVAO9GflANCuFUlUfy +ie09WwqCbWqMN8JoQBN+BjZmwXDDoEvQxybwyYk9kRMzQ8AmdOv02Hf/RtSMaC3NZ4DFzFovnStN +/eoyagfcoOgGhewqIeMffwslFzT8zuJ/JIB4T4DwkKKdsSS8dZcfDYMaG948mRYIkc7CQIhU3P/D +00V3G7WzMQm/1n05f/LB//h5pXp/rkc3KEeJXKiUCUUYc5dT7ZsIS8RaERbjNQJ2OlVq1FhbpTmg +PB755WqhQhupwD8mLgH9ZqLXhY8LddY1ixC4mZyfpPpJVF1mXGJJMbrdLQB17EBIjjKvsftVgERj +rh5AI6EdSFu3cdhjbKQQ5sXA61JEg4zCxqVZPBe5BLdp71udVxzmidgYjWwBrnT0BMIh4kgYPTnn +SzATSFTTlGrbovdKbDPp699Wv3q2IjimrX6wwAp60lOaYDFQyZ6AHrcugv36RU/OVnf7HbR6x6iW +lAaLUhhnutlevyGOFEGy46nIw3ud8LeRQKMFQ/viiRd8sZ8h+Ehf3DihFGlUlnwfcE8x16Rfg24y +4FtfUWvZGmmCTbs/50qV2UzuK8q9aR6Fxv7QKRQCZJtMyKEnqA5COoUP3JF8y2rhIO5EhzJzYWuR +bldmiuGGds8bOK+oE6USeiEPiOS71Qs8Y4oSP56HP2pa1BmpxvILRckXdJPnuCRCR/g9T+QYOC43 +wiwwR9n//nClujN7z6kPzuFHJCuaXitwVejwkPcVbNe4fF788MDcT4fFkSagI57MVnpxzGcc/+6V +3nulG7pMOsYBDzfpA4/Jj6R9UaIrplDKdsXVHvWimx4hFCXalAaLprzIsOv2eVZSD8f/3LZC832h +m4Om3xB9X4UuVtIv6fxq2aatoRDG+YvPbpcIVotjMh4c6h5FRMms8T8trhi2VmDl5ORm+coJLZQS +n35D5cvGmHBjaYMJoCClp9soSt43Sn1xq3QRylcs9R5QbjRU5e7TC75vC+nQjnCAkmDMrrWU3Ncc +hTRXaQRWpEpT2Lqft8sH7VOXZLtTPeOcHA9o0VeZp4hvoW7EHAcQyXXyddiFeZ5jAK4QFF2+M2uW +O/uNcNrVJcISmToDShrqny9V0KZ5kfscrZVU4nuEHXHmRI4gqfAKjmA96VFry+62u28XG6owLKKF +GpckjYa029JQmypTAYvRsFMs6AB5oQ9OARyLbz1vxChiGaapRFduJVYUiLPkKt/SZLiZP6gU+ORY +RVAhoAgXfvnZ2omjP4ElOsyVgt9g6m6Yf1PhOLp0Zz3OF5mOyvsttzziYmwWHigl/pqUEJwaSCf/ +mgr+dH+6VBGkkm9LdQIQ8NXIK/xll081ECbLUujobIGV70Rn25mteumiYX+Ta4CA+xaUhAIvnXGn +XwvdAmkv5RRsQN8D9omCNe9Z1ws+M5ATzKghJC4QaT6VbUOk2o977i6GoJh7DYgZOzPn0LzlGQYh +g63jU59BgD/6ZJ8zCy9A1csKkqNQfluOYI81jERUWuPEolGFbplm3OtCw3CKhpf5t7xldfrtWZ9I +nJzMr+sfmw2w/llnJolK0r23SYCuz61tD/lb4YB5yFDF7jiLy2OJCJ36ZmdVvEU5gz/vR/zrOaxF +t4QT8yFq5MFCou9ui1XeCG+QwsyjhGtBljYdMwQHtS8aKgVSnWKbVLS9/dmIJYP3DxfIVlG1Kman +KTJODLmKOHcqwr8yot4RN3mpAjbQn+aVeoElRgku3sktwNDe6jsmldC4+H1IfBiSsAwwfp6ytJvi ++t6jQIU/jMCHTHHyagL/n1xJx5D+J7/PD9qf3NpfvoP/bWiw4PoQln4ogpYajqvy2VO71mZrrivK +j6sM4cODpsL4jzRXRiEn3n/adXwTUnW/02PnlWPFCg/BT/BKlI8+899/vykW7qH0Jyja7xER1Fqi +/YbFEULegGYRliC5pTix398+1SGVWl+kakjoDHStUOKNhZJL4U8wyUKJCNi52vrwgYp2Mr9yuBLO +AXpsNywE92eQRSBVk8gCNiclWfYJ4YkyQeiCmRCRckwlGxmwoyaII+NQo6Y6ws7ZhFZ2EUAWYUYY +3oTbhhtjpTqTnB1K10gDSQwWUlmfKwyjFtETeJoTZVHoRiSLfz8hyCtxrFuJkaaCawBBI0pG+9bk +eSQoG4doVVURAuHh8lGuc6CyORQRUrZOjiwENhoWPH3T3vttEF8JwZEuxHYCyEhfKVUIoB5Qa/ky +eKeRKKR0/x8DHuBslJMHnENOZMB77aoVUgdDrbB77ArYtaNYPO3h1N8vQeau22VcokSeKzOmo0FA +c9OH0gh3wsDd12Wev05hpS3RsZJvXwN66hxBiauCAXR3t8wAmSuT1mIJppPjl+kl2lHdLApiMK6Y +0F86arGXNFC5y+dyz1RLnGHL9YTri1KsEeyN05gyN4KVKBOhs1c1VVB3uldPDKq2kbzxUgtUbUUw +/QwYGnHAvaIXluNZrUXaafNLCNpvoAGp5IJNc9QsZsgP5s9xn+a1nXnbqzE0WzbdH/jZlGKwPAWT +dOb1VvavR4xvQcVpX9EUF1uKnZQ2WkIZe0BEJXQXGEE/iBENMRxjKEPApk6RI+QXnBEKahLxULMi +sh2XEnRz6ZVc+rkqhNieLc0nkjuwrRHzwyA1zLLssVQcFAkG3gYM2mu6yCmFSiQIKkoeP3eEKDtG +rwbuxMOWcbrm2IJjBPm292IVqJPNm0shGkKYP3hBlyEdImwHyJLkEm9ODiQI7RGqhcPNf6koiz2M +K4d+CvmYqYg9BkgYF2hAfZGCR9kRT18P4TqdnxW0DQA86oFFCDdieY9mJ5mCaKJUEXZIszU7XCmi +PDz1e4hX5t7JjDYrGodxJhPI/207DW/e15+wF29Qzs7vLSrhYCDk9Uun3ClK8MWvgUP8Df9qYUQK +6IfSKy5QU6RvdwQ9FVsx054PrjxfcLBjuLdEAE+TUGlQEwcnsl2IMPkJACijwSzGAxIXfrlcQCFC +AC+qp9b5o5PiHTx+WMR9I7YIrwYnzudLhNC2XchQUFZHyrgp4HryUiom5xmKHqw0X9Q6RaHizmlA +/LDYKNs+BEWN+2eGnjc1ZuREU1QGLTfD8EaENb2kr9YLrdOmCt8+Aq8p6S0BtYh4qbsG6QUtMkbY +VmYC950rRex7yoBLLwlGIA4g7zX4BnEjJWZaBdzy1K09n//bpd4bTZ5KrXFc+QDaTj2kJvsjnLVk +Oj7FBveQRLtvyqI3xtSVQLVsRN8dZZOqlsDcWTVkO2fVPKraAllFnA04HipWocp3Kf71k6pklHEi +eQHCsWcbxyrDqfG4wdJEctvDL2351sfHRBchbBvgtmCqNtBbrGK7o+xag1xUWRRdw+1WfPYtpgSW +LMRoE9OlmmzpscYJOYutDQOG/cnFlxsDnQ/z50+whGznrsYVB6ZXv5ZAdjUTRCEyFery26WkrWWE +Ma5I7WS17HJrM6hWJz/CvN2jyG4nSb9FOdQoUYaYHpyhoKPj9eoV9h1t/fb4JtCXDNISdOKbrfRl +TBl93fviqLcVUGBEKymcOQKn5KUnENSdQNW6ysQgyl6iEMJXA+h7YFelPfTQo7amGyTWv8sR08ah +IF3Jorv+qBeaUFruGbBnUgRTTESezZU3lyMJdNogYiJyhxHc0lOFO1ONCDlbZFJM1a00L+LCO2W6 +XDvLR5xd5uUzNTkEMK3UKps1W+BKCryZ/ZWAdTswTVrk5+LS7eYNnIe7y9fvveEmJBZlyKmfyrJc +8n1CwdYSAF4ia2CMG18/6O98J1q9IScniFC89RsgfgUMkGMr16zslQ0DLV6XPF7Wc07idQvHUeuP +ipFVzW4ghVTJXZsYfNIhpZXgA8aTBmEy7DSL7cZ+23ADAZWVcMuexJupTCslTgKaAXdHaIpHgY36 +zte0l9sFqKkSlL4KWaoDTqLkS0gnry1BBQbYKAhQrX0uK8dSIDROHqCoJIL3mTYqU9VtKtoJ/fw0 +IWtv8lE0UatX+vZUHOZmoAoipFHjKj/vUlL61F2v2fUIBXNf5C9hi4sTDYSVEfaLzqdAKS7G342D +ukPgDMGCKXL4RY5VGiEyjtwDonsJ45rpHB9cHAUd+eEHmiBgGQP9Duyc6C7w+/ULqnrUy8hcJbID +1IM022nB7FCMg7miVd5TGQ9JA6HLrxi2Lg3yTNLUSiOtolMeJKRwSpfklKIkm/XNR9VdOc8hk08b +rFN1QsqbSy0IUNX1brtT1zA6cnCZfFihkEJbKjGgeE2RKIPb/mmxWqcnh8bv328Hfe2Zzh6g/Gck +PO6bZm2oMhLVx7C3Kp/RoAixq9Eu/NRmxIidGH2mR8M/kqMr5WchpM201cAAyK+0gf7cDrj2evax +irSL+aoUGKlpGqr9hCHdASYsnXr0B47Y1zgF+Dr4xPA63xDvEakfhOwl13XPIzATywAZV/AFMHPa +n1T931f28WDxfQ32aYQCDw9BGQtE0rBPsjHDtdejBRmJIaQHuhCN8SWejgHTnyKPMp0/mxQd/Yhr +K8QoOcZQyVT8f6oS0PxOzyXsQeA+Wz8vvJodx5R/u7/zobhGEx/TZaaPmkBDaHvgr6k20PwnQ3OC +VZWKnnpvxFRTY8EU2APA3bbwbrGhJm8aGNL3IHHdVMlyVeoHz0o3BR6JTqNg1AjGOmS/nr0mvVHi +CoCcxPgiUoeK5bcleRsHtPcL+MZ6RUCohz8rPVaORab2iPlITYzREW4EEATO0u4odnH224sK4Kn7 +PEENne8NobcrwSHjEydtaM40df19YqfQOZ4yWKkkE+dMEj/ukYCaA2wWFtK9QDxOGSVGmC/WHQK2 +7GrZbxoTizzr6VGmKFGmoIYZAPJLQwag4LBVViPSpAftu34vNiHqSLHpWVf4TBH7WA== + + + fHpOYLVO0UqMky0+GJNnI+safSj4T7kCTYwe3JIqLCiwfr2yNIohcq8TNbXsWwam1EheVzh7KOcE +xQeruOOyToEQf9JCRWVoivtockynn0DMAXqUrQ0kACUL9tRnZ0RzRzw5BrKoJpRnIXT1fi70GxI7 +8p3v/lQlYw3s263aVbSYUGrIwUoLsmYFWSML8L2wpD7izlkuGAcGybMCCihFjwDIh9/rBlfGyxwi +D2IGpIsHDQcn/kSrGLwTjL1tvGF5YbDxXEECeuRLc6GTOduXHpeuykGw02qXX3NmzcbtV0ZeZhM7 +P+uKLN7A6Zw2BDxwQa88BdVK9kzSa7WsjIKaGH5OSGVWXeNiQ2oOlU9rhTCxKcHKYbCvyoxmCCyO +VzMr5XPJ0qJ4M5RaU2vnfbQoHHuE9xA5hhk/JJ8iyrAA3hsJ3+6XewCcW8WloqSOjiEtkYuKPaB3 +iFx7Ps9PXFT8brra/QY5rfc6+9rYqNOZqFgzD4KagnUMIq0E2Og2bcUJyc5j1zVJShtt/53DnIQV +/SeO2XsB5AuDXwiEbRswuv3prGHZcN0Q6JE+hHb/laV4vnlLA+HWvaQdljSKMmDdAdvUMQZGKhW8 +uU4GDDyMkn+X9LmReb3TocdeiJJCVcbxLOjNqDMzewlgvmlx9gBrfh3xoxAGZDFC/RHcwxsVQQns +hypQ5uexdNtwRij2mZuA9HbPo/Zqv+kIC3mU38qPXNebcEb7I0ER3PWwFvFGwems3u2ImlCLHtGJ +uYFDbpHcZHb8hNrjQnxW8u/aWSR1A9ArKGyOxXrPM6KyJjdh3T+HeC6qsLoMQQu6YdLdXy2pfuV3 +TXxNL3CO52uiDcjHjbc+MYWeEt3DIQjCq7ATpmWukAtwy6ITF9yW4QXiMEWUW+4jQhKc0gYFB9zk +gCpHbu9ldS1xnI/BJz0bMQQPtQ5yPQ6edrededElJ8cyskFWDJSRupi8IToFRo24B1MH6vKy0miw +dvhgOpceqWY/KGf1ryRvQ4GFChridlKlxk8Y0O3iQWkngweFTWR1BT8jBHqm7TgQH4AH6FVyK7w/ +CxsdyvV3SO0El+cca+qXwuX0zYNnea91Fzq1sOa6/fjWLnVFuoMSga9tg/mjAgjMNOutc45pgkOz +qcVbnvfUCdGwqaXD+Z92rv5psw/JbPxG/mtNXsQTbDaUUpKNU1p3sb8ePv9+j5b7+M6hIgh8xb34 +HI3zKfcVV6dKCMyjRbjlxDXn3RcDCZeDBRQW4qNqx0+4bG8KOylJsxiSk/rp5NMS4KQo2MkHuwWT +lWT6zLV9NaFEY5+veAJ5UyTxnCFlfqCY1e5MofFovgcUarI9TgXxZaZtCWhv6hlKa0ZezQ0PDxQL +KBxplhd6NEG+zIJEkrBWlAJafVvh1LP6W9hZN55+LrhpYwSr/Rt90F9//Ur+ldPgP+/5/pfn0/96 +0scqk5KsTSoQff7RQvffb9kjtcKTQCkXBSKAw6Sj+P+6nVRmDdyct8aJd3DmoMtrQw+vQJFeZHB6 +9Z7jEcssBKha/b0op2IajHNpTxU5/G4XGimjYOmpvkyOFk6RM/uQlsOH+rX1FWKdFjTV85yyIhFU +6/ZzLl+kx9rDxzMVkNOrven08bq1KUCLRfZskZjDxYgReJt4HAzkDUisd80IGSWNosG1weJCFSHB +SZuIFIdWM3LgJMlQixxBx4qfosXXOdke7M1A76wbEPiDgf/Q4x+kA+3qmJ17orfvLxZrhqW4Dr1o +64u6LxgM1mueQ68LoyNskMCiIZd4NthnfVBSKu74ikDmk4r3RKaw4wxCpRSuzRZ5hi7gcMTZfcHo +A8Ebtn4WFEgakOocK2F9vx8NIqkDiNY1dn00D9O3Ug17eTbmyuECnU2nyNuamYgnyqNt6g102m/n +BispZi5wfa8WLJyaR9Wkf530XppAAS2PQsFjYCCLwbi7PP3ywzA75nPSCF+JknbnUzrGP/0chUYW +ANsbuNihOoELTJUgHJCtOAtVGTunCAUMpLAg+fzbfSCIQcHQOufm6yiIzEy1Lma/rLRMIRryOOFa +AWiAa2VKeW65Wgo6T5mUkhG04pFuFDbvXEQFgvLl2XlrvNGh1CgjQV2hCWTQH4sQhRp0k4iYUj/s +l+rTetOKJb2mDQbCjjBDWdtp6JnpJBnwib+EpF4+2BeNAJTvRRyBDyFgUKrkLSOhoQQAYcz26wbd +5jglgTXpT9CGIGefKzMEUoJOtz+9Rw1xozXABw+yL3yAzhIiYNDEgUZQaeQD6a5Nw5b761BkUxD1 +wgN1ZK+6xHZV9npPQ6pBzhJL8e6WNAdHe/Is1A7ybWj0s7T7SnHghC0LvTQahtbTkHaHnIxoJG09 +RCPJXu8MqQGcLJTIVuh4jVMZiTG9QCmtVNrHxYBNFUNdfUuJRAQ+8SXqhq7Txt3MlDmwBDdBHTU1 +TXTnMToiRRzqa/IUQFqS4lxLnuU+wWy87ErAyDSKEIOV+DZqrFKBMjada7qSbXiSN1LEE0Ksy+mh +d1I0pqfAplXzQL0HJ9DJHgVWEnQcHs4Ik+EA2qQkQgG/8Qu5pqkkmfvS0SXK8O7N7e7N1a/nobhQ +FHE6C2U/FyVJh+bpV+T65X5QelU8uxCzwZNAxgDur9a44ER48A3S1mVtL10ypx8itoY+qCqUvBKl +vBYiO1AA5uX0YyBNSJV3Xq4pXtnDNB+wqXV2iMMr7WhbRij3um9MOZk6Vzt5HRFs754WXCYZC8+s +QuOvP9uCdhtIDvf4H3tlAgwKzlTQ336N3af6l8S5emdpr8kH7MJQs9+Lfo1u94rx6b6qmWjno2Vc +9IlfIZOfAMAaIiXtHrsUr1LYMu+zpENYY01vLn7CAeIfDcnmZU+iIQV7EslECdUyDtGR0FRXodr8 +NFi6I8RMLZTxRVVYFaY3d421HNs1XXLg6IDRtWJQlm776uC7+Wu1Dmiy4u6ZXy7CA8C0m4hWXTt/ +Tgd12QsufqALL02RfG4ihJBPX/n6zIwSntzfstNd8ekRqAmGwRzMlJCbTsEzvpVn8dkKdPfWSa/6 +odJMKKudUAEPPCxD/SCa2M8Tq+zNy9ddlQYW+c26NiXE6D2EUV88cR2EPNB/RB68/O2k54ziu1Bj +kvu9w+iHeP7RztejdE3N4YnCvK7baBrsaNrYpAZ9P+GfV+KH8M8vYPs8EuK7KJNCnoNPgPgqGy9H +lG5OuXIkkTl9pqR2qmrUJDhzrrYQKHJJ5SqwVe0ZuwLgYmRYKmBkqJkBRPcEPTswMOcs3ZbHgpBi +qKvtboQU/S8USXf2hbyMM63dWX/yZqupcNhZHQtU/oqegOoSlJ37+4Fu+sXNUWNR+7XfGPS58HGq +vTpzPWIroktAmxxdAsE8uIFR8Y7dZgxlcraCBsgegabao9SRtvIFcbUK+4cjnj2D4ABE3DkWqcBl +yg3Y8F0HOQiaOMiBUglmsCxt+lTig5kNmjTU2aJTyk+8aD2uqc5xjoGigKIwZExBl6DB8/D1CqRw +hEwlVgY1IDf7h8BznrMQsBaL+ACs668P/VzOIUU9xKNjQIaKUjPqCspN2BC211q9EDugkQAu580I +pdYBsSFdzwhS+1co65ddc1tKLrRuGNnENRN4cOKSyoK5UirsafJpWZWIlbuW+zW4JGJUBBAiNz6x +jFJ4u0HyJ986kSc4KCJwfqYjdCEiYqqQs5Gfow2AROeqoW8LCDsj2rwyF3xPKyY5J2cv+cUJrk4u +9F0JTBwjkD30zmXnsEH/gGHLdZQ4o7g6j1jVjQpm7aYMWnmQVmjUCD+eQu350StJx5LH+hoTOgvU +kzthSZ1XvFBQUDM14V4ZtdK3rDUOERMh9dn9YPO2cZiaN31K/XIzv5cjll7xXKF+FzjLaZtkdRrb +DoK8UwmmADXjFxhKMAWT4QgZ7VVX4OWIC5liC6jXN2hNfYPGV9S7MCSegxi5s+sUHSdApSkcsK4F +LG3PFpKAhHbtajNHbexijrne9vO1lhpBwUH6092R+gvGk7IYSRLptoDLi5AJv5ceBjodz+2Lkh5/ +E49zgYn31IDy9uODxlsnE9j+AD6LyiWS7062Fno+6hoQb1h7LbS2bt68f1OQ9cyyofEcjVxarJXj +QiIVXDFABmOxZFRELVO1Fexxg+lNsUAoVDRoBYdQrt2fRgWldhz4cBGCIq7SHYJ0NcrTF7EGvezs ++ZQZ0bBboqDwnO1hbdrjpdRFFPmH7/gft9pifwOVIadYKXcb4bihKyk4pF1wSImHorbGlShQqlsL +MprvoBbTbBxiKCksovxcyU7W5NVHCNjaOFNZgXiM9ihYQsH8vBDtElQCD6CHdUSUf2qWEPGGn22h +Sh7nn/ih6Dmcw7U6B89qAB/3KunJYUJ/h8OkBfNJ1LecgKvcyM02E+fa8719sFey58bJ74ha6GKq +Ib/q7Wpc8zxwhgR8GrMp3nGCVuwjPsBOUYDvSYviTDxN2IrNNNl+8OpjfcX5WxERvchLIfEThFWU +yN1hK9n+V6+ipSVm+izIvWOH5KF6PtiSqc8Xd/3lewJXqOMlIiElr7F161j4hANQxKd6ffB/KIoS +dxotrGIAJdEAOyKJ+fW95MTFLDEeAYWRNfoGMECt7PV1AauNoRh+mnxgw3elL8K/92hiURNMeJoj +gXC33nQBJxqCVJxoHq8Jwk9jr86fh5MtbAd38JYRsDNIgq4+KuVpevWayBhBgMinAhdrvWqJ2072 +QK4wA/SPsVJwraakwZ4IokOCIByVW832XL/SOd68W+1BaKfGs7apq3Sk9cExBASRcyfiJQ8DJGcL +HUTHTnf1RdbYHSuHaaFqdy+k2pwih2d5L2e233OWKLbigRggfwDEQEO0VYJFatALSenmcyFxSnnl +lSXJLeyG60e6JFSLc5iaUAdS+mhMKVq+PWmFilUj1cHvKFlYFbYSMj8x/nCU3kQmkPpbMedXiMVw +jRix9SUCS0nZ1qjmXuksk+5cUqmUh6WYHHLKn1LSQCmas70MDQIoDUx0idsMyknAWeVUKCQN06qC +pA40/t5LHRV9Jp/3nPSCi4DvvsVR7KJ+T1Cr58xViBz0a3dET1A4ejoQtAz03wFBy+baUIX7Igmr +C3Q7nzWE6fU3ezVEDbncAx9PIpLHqlssaYiBERjqQvCoGj7Cp6eu9TSKvWtMHT5ZFwoehcjnPEBl +uwQy4761pX/9CkiPK6HKwtPjmNBdByLVExCwNAS6NNMrwVydjsBv+ofrsEZsAk/uVxyFGASQgiHB +EPufsmPjRX7t91DxbtgQcCXa+7MJpEbHxBF5Lhuj5fE7fQMEEUkI2z73pE4Eqgu93TCm200+J8FI +DUU1uvM/7RZGiWhMsp90mAhenCNLVWDrMfR9AOWyEzEgh+PCrfl1BDbXZwTlhJ8QSBQSvgSdogji +ewAjm9DwODsGWqyLpwMUnsQOTeQvslbF5sBDoZ8BeUp90GX5EUoQbjIpptGIwNMVYA== + + + WKO2XMJOj4zNG6h5r5988ojkkdh5mUBnaT6/A313VJ+gqr2f1smI1gn7FdpSAKPo5D6aw7YoJJ1v +bwIr/rLv809baP8tiV0CisS9OGwDZQDy6SxGouKyZeuFDQCqheSBKtOE29Bk6dTIBbPNnhGwAj2k +cBSjzCO+gbfTEoKS2A63xzd2JyfIKJ802fd6cI+pMVJdlnIoVfN6qN4v+/1brTLCHRn28Bjs5JdJ +SZnkmmOse/yqENRw+r3FEpJ9BfnP5WqxJPb+tm/xVz37RWXS85ago+pvMYpkpJE06QQd+tjzzGT/ +NNCsH8aRdBCi7In+SXKcj5OKTOtRM1FlDVNmcGlUupzSLQ5/k6bTX76df+WE+P+P3u5ScYz6e9V6 +ge2IbbxsPYoKHtzwaYeRhsaOkHrjzP0PPd31H9q3NPyiv6uh57KIco6dkzEwr+kbnbMEKGmEeGE7 +gCDawkmXiu16oaHXCSuj03Q00cTh6CE3eaMPpP49JtHRrdgvUqKq0z8JTYCWrisG2gkhSSnAgg9/ +zpM6yBn1cDx0ClvzscG/oGA1QliOb3yzLa4AFscAANGPqeHAjFxlweDlhhSMKup0orzUTvrXrnsA +vOATUTS0PrBWeOF1npCnQRNlcZ6NZCNh3IANcpxT5YbHy4hHxjl0vpJ4t6OkA0PhnBnyitDBfEzJ +16ubYEcTUJX+cy5V658nSYM3RMkSPYq/+z1D+xjJKSgKEfLziPjAGiA2XFPHeazRth+sJ9KYY6WH +sOrtUfDhQOV3U81biX5F0AMSf5a/akPHoB381XfpKp08/mVz4caWPvYLWczq/qb72HmebVwWd0dI +gYCFFvCP37S1TLjS7XxN0Xl8g5lc/pBbawUrSAjWYU4OraLoWN6cb1quQhlh36lDG1EreYqpDnIn +xNuUx0SeTllux942aoJAEIDDUnbuzT4KDQ8KcePWusetdd84vQvM08zgRFm3wk33k4yAQuSZFyjU +nv/PlKfYWliZV2K22QA8C/B5qLbEb0PGG5LpxIENgTaaBOoSrMxwJi5BFzDrX436uQKWhsPDERQE +X8kkV2qyUYJn8ziRNhDCX1heaJxCX4kUiREi5Sj+Yt7z3vYO8H50//5+v0boFS26h/zmvR2ZSoSG +YwwUfaI+0KLI0WuBwzQ8Z84m9+IHm4g+JU0ENGGd4EP8gQ+27YvewB+Dggwa7gWx0wpL5az9hloA +ViwPakhvHKHFiZwHR4P1jMA3ih/K5qAOPArhVVPp+l5ty4Z+Lcn5Ayqu3VG5EgvwbBnQ1ElAzwi2 +xjuCgiMjsEk5I9THfGgJaC9N1yb2TvUrwzUSblhVT0NQYjlqAxUA7wK5tpGWUjwH0/lcYyvLWeQU +YLWay/dMYbxdLWWBipsaz0N8uk+PHLkYyBYAxL5tUXUYXqK3yy+2ZiCZ8unOGjPiM2tAfmR+KiXM +3ML7uGbUwsakkNa/vEyUzLr3VOmMa+jyJQXkOufEJ2BaXH5R7wKIcYkKJ6nCiQxZK9xxmjUPKpeE +nnJqG2kPenf8WAwd7MqAWjxPGumuTMthiSp9H2S3UDbGA/FkIqnsFGCzbAT7QtBQvkD6qPX3G3G2 +w3YdBoeawnhI5Cg68bg1KjSXybkqsTcV1b6iTlnoT77RVtZl9twJ8pRnBGxZ7Gsg3UKuArRGB7FC +XLxtg2b0jn/LmdF6K9NKsqZN8eE8NRACEygqkad6zCPOrxaGpgO0/ShUGmYeZSbpggb13ut0NRep +QHGW8N+aep2/2l72zT5Zv5o0gDxK5cBeTXB1llD+cbSPo8VNKnCH6ASFalbrXYsPSKQzb7dZSANA +jPVWQSi2hCbIh/Y+zjvG7M5RFl+KBjfnr1dk19CQAmTDAMgafKBHKxqwmrugF6foyQg50GmGehLq +ehKy35jYkHY3eLmsqod2eLNqrg1AkwhLyxH5wdsOEo1L6Rxjzm9WANegOm4gXZuFNB6aMskF+7+R +AYrG0uI5CcIZ0SKQSIe/t9uHMkdUk/pazUARHNwMcRY/C9qGctqVrIH3V0LMO4scxSJKxeA2cNHC +/d0XrCR0iTnUL726XsvNE7u6n2lhyl9CqXHULTl3tJt+NdyTUUo5UQHFyaaeLdvFioZS87zHsR5X +t7YMoeCNoG012ncMUfcFQPZAYDnrpWkZPR0lLocRKh4/3Jafy6E5gRjWnH7e3nzD5JwioNNttnA2 +X64N+5Sb+tmFBqBebYEWj1L81NZC6+yf6CxIvXBEg7MLg/m96rM6cKnjsuBs5XuUkMWXZrkjEgnx +Bujzc0AzAv4QrhdknXp9aYK19K7JCFkOL+C/66FGqO33bMrz+T16yRUk6XfRV0lPl3JiBQiM+CCd +GInvOaeKVlk4vr/b6HFcLyUBO9hCfbJdRtG4dBPNPgbfyMv5D6hGxIxKkaYiUaWYB+QtnLfPyxbj +7o9FyHB5jzamAJb2Sw7je4v5Abo+hCzgUGnV2vDhlVF4ZhZUbdV8zHDbfuGgJnAX1Y5zsDoCqzYk +l7FEuBeYOS6L1tG+03uhOFA0kBzvOUpwI6LP3Vq0Z7PrtKiYPqhFT/a6J1XCQo2XTXdJ7zwfXPez +89Q5rv3AItoDpw1LKnjXVfrDeSu3v+cokrVHhs50VLd1xzZQ8/OskSCBXLcDlHo4ccgC5OAIWHjc +gF1uii3IarOeEch0tmPfjhYPeEc2p4YKgD/nbNmoOpCMKOl7YkSw0G5wAOFf3faurkdTopnNHgET +3jP748McPEcXNXvXFU0KBA7oPXCCqx+NsAStaR3jYK8V21ccwTt+6ZqEfAeGJhN0gdE/Pf/YLFYv +TRKRmTJ7otxZ2O4aG1ANsxnJekQXuOncEpapOyP0zzo7Ouaj90I0kfKZX72HbwK+CMWWopZqBjwE +zc9Ob4kRBmtF0Z5zY1hdgJF9WCV3g46My3lgHc+w5SBlRaHLslVRIqI4putVJNbv5BtZ7me71FSa +s0REWWHxsrrphpJPYjM27ksV43q29lGMFleKdzD4yPFyaiXase2RyXHdDQp9i3Fz2U55ElUA/Lo4 +bbnipZefD7DSqz9zoiGGX7B7Pf/YnzA8AZWC/Y0VIKo5hc2DFA0BJhppr8jy19xH4uvJfXQm6U8E +ZTd0kHFLW8mQCHEmXuMn6XzQKoCMAmuJUxMpVeHr0/zojJhS/LbaRDXHP3ru3IDdog651E1yQW/9 +Zh/NFL4HUNDie7Yu7LT8iBA8dvQbLprNZASwLUp8KSwwsUhCC9rA1DSQAd5Pi9DpFyG0a4lKx+VM +J0dh5ipCe7LTsSNhhFBYB+cH8osH/AWAyRh+sS1xg2xLGwAxdw2cm7wU7N09wuHDDvcou3d5ejzj +BRZvmzrYwns6sXSiH7FIMDcRNUrI/0TN+GmB2BFb6ytYwDezB+znpmZIZoGeAIJJ4o9DRqlJClQR +xMMQSAkjFh1LiDboDCUW6BGhfjCbPwFqA9mGWRK1Ha1rp8GiS47XZZFlKMZFds3BgWy/xgzgEFom +N3q+VD1wo8+zATU5DV426A6mML+L1L5A7nZzoW3/gPJ9pxPUKsQLmI9JjjMNgnMPqvit+XibfKpl +X+leaP+WmTtEYWw9ZXE3tfX2KLK8p/OAztarayrmkWcE+5uii3bM/JEb2gHVdwKdHCmwECtbygZW +n1vBv5g3Vo2I3yAZ6LRRJeTSogofNnViRMjAZCQPxRD8dvHCyvuDO5uXo5Q8Pzg1AgflaHoEQ5td +q3LxgFLYGaGNiHZUhFiw93EeRm8P7LwjwEieEcj73iuN+2jqjL0j+utgGh6V2uu90tO9kjUGKFrn +2VNpgoidjWpTdH7I9vHotRAhOBllmHwvQCd8LImvZkozdlooJtLaoNginuCxEfSL8oy1QkrhXzuq +YdhLseN5hOE5anENZDc5yjSXZauIMpQFD4oYlESgOzpiYrKBpTQsRZ4UASSvUM2/XGiaWzrZCUit +ZxCuUs9gY6DUsh7ukNA8b+AculUmES2r1JhKWlQP3I6WlBopMgTIoUt4A+2uUhn0eMEui08tsgZI +x6A8xYizhljH1C8Ta3fgYQBliTSp7nOOIGtveHnm/68uPIyt8SwK+rpd+RB2wQ27MRDwExn9pj4l +XQIqex15ukVV9pyQ5C4nllG82aomNbSsIvZ7Wh4n1u4gpp58QA1nw1zq17mz24p7/VCnIczW8GDq +9Ab3MiIXnuL55IFHkHs+38pDEX1iW+Yvo8/NL1vrlqwoap9gi/DpYUW4NkHSbL0WesreHk26mBHV +vut7UpMC3y9K3hTaN4EkvVdmmwBKLG+oxOZGttkNgRo6wb4obLffUNuYueNm1E+RxkloML8tHv4g +KxQQLk0/IouU53FDuSECwS31cCGvRiegKgsVOXZMyofNunq1bk0JAIuzEyLgKNB+yt0Y+2jyWAwo +KQOQJVJ9flOvhqhEwtIM9MEjIB0lZOLdP+XulVHAux1UQK5bKKJaC1GCftMmTyO0ZgTKv48Yr+UI +dj32nw5H0hE0A6nVfKDKjs4fHtX7bMUuAeAn0GfYePpZhJ0Yi0e20r5Tn46NWO2v1Kvm/TJVvxYu +LxsUtKuH1JvVA6fBvyH+Bre6WDm+JUvqJ5Fixo9YEVGyAoqcVwUjC653uT07Rj2UP87pVoSC4mib ++F9AZOf19bxGFTBKxVeBrsBSI2Nbfr4jnpu/+NQrx+B56ujhEMBadUFtm3rRa9/XKsT574c6BUJl +d5a9Kgk4iFVmNQMh/KImQL1fU++IlhFyIqCG13JHrOZOMswC+hNhDfy05lUAdVQ1KkDItjtK61Fq +ddt1xo7EAsfVyTo7XVM6S93IzBHw54l9lUxhBB5AhPZovtytbgrkos/12Bdq/Ubt+IoQm7VunMD+ +9ICVSFT7jES12wGUQz2XcL0wfI6Zsjzuu5cErWC1377YxPC42f5oGL/w948ZGeTnjT02NdYz0f+y +ifdPu6/YW6No+V9qxzdNazmKaDZZ3yoBuxeplOnHN42IfAskWamNCvnDtGNQywGOj1Ma2l/0bZju +G40KjF0eD/EVfcsHSEW5Oc/1C6b2Oj0a0BVN9Veb7MLRZzq9ApV/8MZuqd9qxAEjFI/bFBSrKb+Q +Xl3bv3weQREila76rCki8UOB7vOm1C2IjFL3NGGBt9OtRpbR0pLAbdLYhl4Hudf+iswaDhECIiRc +kpdtIEwQSPhRNFvI888WAesjtS94fAUmF9n3X76If+XL/89b7//lWfT/gLtskxRC4IZd360hW8JE +M2Zmip43OAhJkBtGz6ipHThSZUA0Mw2P7YMlqKLyOoDp8GoFwKHM0FOQL+Tc4EQ+IhOBnoL4ED+4 +B+v6rOYiZOhEMHjF4NLK+xN6Te7kL3iA12SLsYVM8RBV5IYjMgk5uV2dCas71irMOKwK6ORSwAFv +zAdAprfaaGRdK+Rp9k6oQB0yMEjk86OpeJuXnTDQ1vyJBm+CALAMryMIIpMYFufcbQ== + + + 6QNAdJYQtIg/fGC0HlIoBRhEDMaf/DlUfSC2WJTDruE2O7p/xObVzXcmEi+Kp9Tc612rJ4EHYU5/ +PpXyefV5IJMK2m+XKg271rBVLUVaWRCX3j8bAfaH0x6b9325ohlFiRghTXYBoJqNeaZ4wf6V3/Lm +t7AhQ8OAX6lTwq1kwTDiPqhgN5vIFA4hunzG9n94HkBl6cI9+GSQZ/BMLRAj/87EgDpFPQhSc2d/ +9ZW0+8HKB21/TRyggurFr9ciiKPazp+fYJSZcaYmMw3Bw9T8oady6H9lVeYXPQAGPUBXLVNVM1Is +yp2AyNZQ2erLkv4bP1IKE6iT2VWnMKnDJmm+UOXGxqrO1A9og34XOy5aJS4dCpU72g2mWUv7Syye +z4VLmrKgLMmQbkebIh0rHssVcgpZtJxSYyI4YAieGuxjEeeMoPXGghvF0h10XQsiEOQ3X6EaTdcH +s+xvd2936dPZmIwi/Jma3D5FCAnrmOdgu/UsVaQRWhfp8GFM0Cpjd7eMSULULn2LD4u88hdNnOKH +ps9IuvjWMHc33TkvAC890B2GIK9e8H4AMmRH0egnuOrqHr/kfK8pFp2UDTUCxXQqIed9WwkhKDed +OikpaVpRBpuSFpsE5bVNxbHXNKFI5AD4JOZ5zvP4RRY4ONygcmJy3C2QFYox9F2aeXwhTcVJDWMY +Zx3nHGbuWC1RYiTprk4ZGtrkbAhh1xPjnoR7rFsgAI8AtqWetJ8QfMEF4wN5KCd3IrW+od8SB4mX +3oPwawdSPpDPOGv9ZPO+9bPx89bnOZMYgU0acmDQ6ddF7hSdxvmeE6OQJWLQ8ZwjexOfkRJJ0F9+ +D8KuvzqOVnwNAgGUq7pmB5wAUGu1QEcPCcUjVt5PlsgWqw4FgqeMot1ACpTKivNraMWCLNa7HEEe +wktIGHHyifOUzwfDdGqDUppXWbQjqNHJz2ug7N2i9mAUAJDMhGYeCQKY8Bi1tSEsijSZ1/3eH0ln +whFUBQe3QmRTf0+t+cHwoBtnH9AFlnQjYDSaZoc+k1EZMV4m84tNgeOOjI4mC+WZ7mlG1sVvH7/j +eJAzErFG2bDPABHowXPKmillGtIZ5+QGvEGEeNYuU+xOQ/JkjK9P2msqgugfW0J/cxieCFdcljKz +7IYkdMPvrU0GF0AX5OYeLWuTRlOfgBWCKtYW9vYI9upKQjhzX+d4BEBJcmtQX38YEXMhTJLA3t1E +r4uXzygmGJnqedigsMjnvPlxvm6EvrV3x//sPMxRxdAj0EpJ7Q8Dkp6yMLjFG3X/8WumQoP3uqDI +uC7Nhu8DfzZac/zsWwzNh+PeNak5Cwfvu45zeXUrX7oTIfz88vu7zL3z/exUOy/xesSd0+m3x7RU +e4Pmi+VpjyFGAmKCxyADQNKYgXXz+KHpDtQR9R8dgZIK5wyFOL8f4vl27kMD2Lm58zuhADAZz+QV +XTOZ4AWGdodvgk0cAma3znnypTxNnFvmk22GNOEMokh1bhu7JDoAU+c1ugbEEecf9FJFmXyDQHKS +P1pAAYradxveoRAwF1Q27IgkIIQKtAnT0I7Dh6Q8i5iuKNJ2VhTgjP5G7W1jHkJnzi3uxNM73WOv +omWP2+KJQmu2wbBZ0LHklMI5hGNMcy02dupZ9dzvNTlBcYcVmqdEUQW89bo1f0ZxuG6beD6NQpUT +ZFjnkf7KiJYR29nxnscBxouYlGoiV6LlzZWofzLDHtSE6Mjtq+PejQ+akwucOZOLb/CKEG30ktzG +Y28kMbDNxZ/VLQOpceG0Erpzvukr8GA5ww9VTJYaES1yDihkYtHXs0O6fEQntmQGdI2LSDOIcai8 +t3lBYT5I7pcHmYMU0S5Sg4rR4fR74D+DCu7NCv6IYDwHBoGYM4aaNDJJ9Sa0snxO1rNgAzDTByd5 +RVYHM+jEMZChhvoh4F8fda4Tolw7+6FOztJ0zGVHKXd7urTz6EcNjIzt98G+idPHyXsmQaFhes59 +MVRQ8emjKfRnOXmoe80zGPUWEqGxdY9a6V2Ex9zO0KzQjWNQpWKPLl5d/crzxDa7gqHmuhK1fJbO +yS6am/Ah1LShq8Dmg0pcwb+jFqpbB/SDoQESvxjDRuKkVwLw1oDstkVHCQGOfh+WJGlWbg7Vme4V +C3RbE9LS2miZiJj2GxPrTrPX9JSd5RXBfDYbC1MYn7s5Ik3C2bQBcrzJUzatmwdxcR65zpXVQES1 +MFKdDiPznJ3Ph/EZFB9BjKIdUC8CsFCiglPEqIGWC9vOSaNmEJZPIKCOCPRmXL8IaHKPadQ/DBoj +6R8dYd84d6TC50mIkLccKBtRv6TRhwCaeRXOFXpd32UxYOzQiGFcikQQq9avASQBy0Rcf3B6EowB +MvBEEg+o9YwY5oX0fMl45pWgyWctf91vd3MmLaJD8/7644VJUVG1fFbAW7kBlJNpcLR0IZHf5wBB +8/vewJTnl+cg2AEXRpH3ag3/+uOjJNl+wM4iJd6+N7LvG6F68Y8j/sf3Ztn5eLMUMzOqN0dhT+wc +Am6PWZY9NqYMCaWtxna3PER9GlteOfOi3tQbLvWDAu7OXExEYmGOETuKaY+U64Rx7t5bZGLz6Wl1 +QCxdZoD97M30a88xc/ZK+sOaxJ/NAhIDOkw0trY141gjDsun8Dc7DbMAMxTXVqTqe7sC2Umyca+w +4j5DC6L9gS3ewGiB5BOZjUeAR73Vf3l35+32ps3uxnRI8B5F3xvbDEjghcZ8g5U3LCUOGg5nauhT +OgD90BqfhOAXHUEyOKRjYw/J5kXvx4wcKMvYH+1zEFGDNCP9muLTW6orTL2zFSWCIT1EarIHBoB4 +Pwk2tVp2HcUpIFXRun1kB+Sxz8unQGZg7kynTnb8yAdsHvd4XUEEqIHf0HCjAf/aH59TQTzEmLCW +pNEv+IW4pF5tnEGD8BUbAi0jFVOtAdjQyO3GE9MH9AOGPdN2LZUKJMqd9tI50ENHASPSuFnyFgvT +BPNnI3aEQRcBLPOaZiW5nk4qZ5r88Urs28WeYsfLIo8dhQSEu+F2NPGE4zeKS+d9VYlinVwcd1jh +hLwYR1RHwFU9IyhQnNCGRhfeSrnSVph/fA0EeAOlKpzTepCAqWfDldmc9RG0hfu9UC6jo2VnSU+l +KydHCANWnxC1YTBy4kaIyWcLxa6JIEddKzpmgv+aiLDZFOynSLAMml457Z1LBTMmIgxoBRP+bzes +BYcHoPBRZATIOoC7yjyisbEVsQJy2LYc2Tc+gomKK9ImZ3ZQYIDuSO+efZbcFJ1x/py2gMbtVSA2 +3igoKpwzb1BNWVGe3NJmnugYoIrC8PpJ3hPG4z0gPJL6h0Br9RDQYaMZifgJidrS1G+I1dYbBeng +4gjgRdM0eGgkAMRhqu7wSVOTyZKrk8kuSvnKDqFGDAIZ8QTXCLNoU+i4ggG5zHhM485xkWYRcMMn +hING+ipu47YdCC9BUZI/DLTAJR1s9BH0sK5Gtu6J1FYgHdDlrAGDgDgKiptupt1iNNhHD54gKwDU +S0vyCYWN7HileYNxUvN7QH6FvqRsfiI7R6RBg+DlVXSghvKo9aPP+fRlG4dU4GLkizrUM//oFYHc +R5MB6SI0Gdpls0gfMzyAPgYvGy4N/kpgzanym2m/qoJHjo4R+iQga1uo0REqLkpZ2I75sumMd5UH +dv8y7AIQd2h1QhksU+JF2AZx1h5QF7hJUMV1qkExlGIRrbBp/u5H/+z6GZqe06X+XjxjY2FDwmwY +BopC/E51WhdBCgHEppvN0/WR6CeCnuk2a30V/aTU1ySRAQxR3mAiDDh/CoIwreiUClR0ulAAPtOl +jMAIYYJu+zIQWYG7iG3H7OUJMAJ/an4Kuo8hI/nMOoi89nNLA3sK1shSpeca7VTEvG1l4GJBtEE/ +uAZrqmkbXF3wsylIb6FMjHot6I4v8pXmkI7pxJBO96/mAAHRZ0BZt6eaCgYswhJ4ARmbuMgvPKJp +i6Ti1g3ZcO2JyZWIIlLEJmqgagCA56rhCLs+hznQYV9/7wGSIkhP3M+i/Nudry/qLQAt+gVdnLNm +2kAFpkg1u4CIeGh72sN4bgcSSE+1xlzTV05TSYre92afcJQfCD08B9Dvjxj5KoWEbC+I7h3NmgKX +WUzZP++t/dOu43/LMJmCL42OHUndtJlyfu2KnUK4quIbpCU+wEA95c7JpYfFRPKGWhQUKfK0RyTy +H0ZQOKCiuOU39z9eKbtoLLO2AFrbVVtn83ww/ADkllmI8CxAWt1Ypwi59AJoe/g0ied5mt/byQ8t +94facNyhjDz04WibcLMovnmzkFb+OKIoVp+HBibjT6/URFnuaD2++09+sSNGRpBb/9zsX76Vf+VM ++P+WhfIAhCp1bVnsg0r6RhKeVBEfpy1cuYddi0U5Sm6LqwyKlBC41jVnhF5xVvH5oKXqgy+JKhxy +AyeATLxWblw6gpvGZQjY4lVdnFd1cQ7TjVeRMmQZOzsjZJSTbsAEwj/wxCSPnRe9HUiFFhDbG6pX +k2osOmCzTnErEsLL2dHhsQ7U0HDVmgtjiB3eDF0km/slBZ6F9oYgU5K5OtLTVeFy3SvBL2QdTuL8 +muCAm6BYCOn615CiwM465LGG0/YS9cGlem79AVzO9MA/9+mB3yHeDYWNeFTn6xqsUiEpnJILWdmT +LNjTWCCUUOveYTWy0QJ++bSETgj5m7KQPWjXCkyc9K0G948o9lChGKrmOf58QDxSHpBKHLt/TMVz +Iu6vGtXSCJvoq60wCRWww4aZJ29ZCn1B1RluYKkaZN1mytbaUg3YMA36n4Wn56lTql9qVeGPahgM +z/bMTcChv4aGPN3Czxo3MtLDqS6bto6ArwR6CYUaH3ZifuLDOe+VliLsjOo6UdFTqcQBE1jo8nu2 +D9xin/MCM53XaH8lleFrUmxDTWhnfhW0cwrh/wlLqEAKZFXs8MkIpXHO/Jqwu0fpamdDwJLqwCwu +lD/PHD3bavkpOqTQT/hfEgCF50tZj+IFGqNJ4c4CJtN4xxdyI9WY6FQOpchIAqsdJrWMTq0rm0k6 +k8ok7x2hUhMCx8XUiv8bNYGFOAtaPu/HPlXq8p5d5DkUyowcZpaM8iVnyUDrPt8z87YX1qxGbHTi +4pcKquyM6JqwbUpxQ6Jov5MeB6ef5kLXd4pR6A04inbfK+4Z/QMm44s7A90DqKqEl0buA/NlB2hH +g8bFcy/EgzZdqeW7UFG6CXt4k7IEoWRpQ9hF0kkeygmSrxTok5IXi2lwymFcBw2LTQnJXWicZb+/ +3wnkXLQUBFvQBkfFsiJmsFIz0BgP3qzNJsB8QoYmBd9IAAxtUFDsQjHuDYFqLUlYeJ+caflQjEDd +sCi6FRpMFRQILfHsDDiJs9LORFRFjfBLA3JC1rW//hZ9immHnOmPlZ6rsT7FJK/8RtsUMOq55RZV +y3deAO4rb5N6WZEziFKg1AD4W/sGH3YNlgGvI8aSU4sI6DC3E/T8oLGjUOF5Gf+hBQ== + + + abn0zMFzbA5HaVZ0InDaP7YyHytC54U9UjrRE5sKMOLkpwYF4qaijTtpKhLIqoeWakaaKzV9Z0is +EaNTgBHyOiWzbrMTPYLmgJOop0YTMYwH9HF1hK6QPFv+pOE9B9arEnd/eHBuqdqMxZeqWqexQl90 +Io8mB+V3CV8A/BVIlYIK3m/5UDGMZYccos8w7ubzMHLeH9DykklAJ6ZHJ4XkkzH0r88EGelELDUb +o28ypb/g9T3vTBwpuQGx6zW4pKpk3lkys/gt2Uxqk2pCSAAyxJ/ccxkjmDMA5IsVzYGWXEHQ8isO +bmirHXm7UUeofrYrCp6VK/28NwUJ2KTLhaNuU4EFPbrFKyOBc7w+ny7BwK/+jBgf/5LSVO0Wr15s +GB0FOUjP0pl2diHv2x2PoBDVtkzYmQSLzq7MhY1b1w6rOVxZwOWf2gglL/qGEFS3FddrL14w1rVx +vvVUgNDT6P0CH0T3DvhghbtLErAc0RAZsVxcH4meA33Rm2QQptEeRqWmhJmqGwXO4en9YjTsVj4t +21JHpmOAjE1zrxgIezfe0TnqSuhl5pAgvS62ylGaQuPMma8prHzybGIPBxCPem70AE27MOZGQaNl +MoBDZjJsC+klrpxkW0hFpCm+QvpQZWIkpbV1jl4CytzUynCSA3pCRys0ysFsqEqTZINa6RbzoRIf +oGEab3DRYqRYhPzFLfSL3Fccio1lQTMh/aKLxfKhTMVOXoqmULcc94RzsaE3sX8O6imQbsEr0t5b +1lOk1UH0V9uoP2lmWPeBSzZkQaDeAwz/cfv8qfd1qobbUmJKrVJ9z8wvTiLJX5sRNIm25Q7iC26+ +uNMAeSmpcMhSV6ni24+WrVqLjlqMMGqn4gsEmwLokorFy0C2wRE3Fz6v4JxYFD8RBNka5EwHmA08 +UszvyWjgLHOiUbF/qVo2T6kJeXdV8Mu+6eQJyPCVG6WpcOxr1H+9VPWhPQTtca4CBnX5kpSqpZEO +VYKD1N1/sV2Bne+XJAIJ4jawHtYltXJM/FyGSp4qfodu4S9a1wEXsBxopatiiFJDyawdquiGY7Ht +EcCWfm9lkqZqBa+83/QMgYTTjab1SISNnBs7DXpNsBFAdqOY//f7hgZerwsFjxGOHSJHwnpJpykt +ewguFMyDKEh6XajIdV9QNlcEEvrltIFBoQK1bsuQ2KdIXadWDsASH4AaXa3yhXPDVYd6SM+INBVB +7Awjwm2fwspKMC8fFpGAEhcIMoNqMYR2BJo6sJwS5q2kpWBI1C8hBwvqJkrfBqUg5Dg96xUo4pYo +XHfwnLnSqwDnBgfXAp5YZARo5wEV06RuuiiNjAvwofO8kxJQfuZtaZZzm1zUaeC67mVGQD+CjADy +XsDYFLhRkQX7MvQ3JZdEgGekYA2vjqI1ytfZTkEEACUGcxWEEWfVqzIHrUCQq6RkaOh6zJAgCGsj +zShfm4rV+IbcpQsvYk7dvx7uoRhPXOAr8sUAX5UfHyJfaU49MEGm9C3yBtKw/eH5eGiWjcK6mWHd +0Kk+z6mmgnxJsTyT7uOx0QHprFxueFdn95zd+2s1liVRf0cfPeVfaLCclcRFQ3W4ZWyKB6MDHgV0 +QaLOZUKKlCjgD6op/n4Ax8l4i4bsLOxhrxD2muoRgpZe0Ru4kegPGlWtiQAFBCaWwHKE1XcVWJyw +PV0sWJ7zSiiP2E6SgYvPFKuHGpW5HSbE/FBPm6m7ajolql+d6WB74N/uL67dG0YH61IyQeMDIKkW +AcothkBAvORaJF8yrfa9J1JRPU8l/7eA0+mWjMsCGCpk4P42Iy+HJoXbJvt5td9NcxgFKfyB5VRO +wzHY5RzJjkirfKTWBJy3iKM+2+i6IL/Rbo9onhOuXrTLZcxLObPlvqf8Zw52mjTVkIFIuiCeStO7 +2jMH309QpG8NM2U+2smkNd6TF5/ssI6L+27CYc/f0cocyBE9uRUBdhTVYRsOlWNtuSOZG54ottM0 +V3QczwVGkGewPC3nWjkZdtYHmKbJpiK0HYGeKWyoyBlZDa/hbCRoUQhHH/+hBgaCjvIUuAw2pUuL +h7g4rKVBkaeWppuP25aOZgWT5+qIV5ATT/IyBmkxQHH6eqmCBx59euCE9uiVEG0qdSI6AaGsBR7g +rJQarQpg15waWEVmw+nYeTygHOorO5Dep2cLmAN2AM5YChlRc8oIxdHRLqAHOUwr2UIhxVIRhv1w +e6kAU2xCn6OwgfKKZMpakUxhbwOxOfV1phY1hIoEkgYRioUwPmjWuaTEm7mihPrgffi90wG3ildH +Ii3YBiWi8+rg551dduuUnbhCyWlQYhTj/+2iJWwT0x5rbMEwISUE1/AHR5AHjOhItRndTPS5ClYm +Tv8zJcCLTl2BIq3l8gUmxRPx1dH1ZbOUclIC5gmAZtlI/TXw0WJJD96UGggAJGl6LSXs8+oUWgL7 +1IdILfHMcMfPr0bHdqDwiDOAFn47DwRXSIp1kpMHtAzcO2DQvDUEHE0+H9Rh3/sKcU06ed6WlEXx +TSKPSiqqjp9ZeY6ahwuJjhDroaDlI7NvOSK/tcHEiIrKtFh+9opx9WIHrYqWHl8TlYb33VkC/Hm5 +CgVThcUnHlBDfRQ9gJ6mJCe1cLU/nxMX3ErRUHAK1OZZ04NeBnLi1vSUIskGiOgNnOAz8YdZF1YI +FKFR0bpz5M5l3JrQVyax0rkJY0pnmn3SkgEjDCetywp45T1zIUdwmgrKAaGpCg6wwPDtx1TLDzMm +fIa6BM9X9a6N9eJ5IqCwQGkONNiuflN5NWFEmuECLhF6p4kzwM7MqKKJH8BKDLsNRqh6agxC95BD +xzQQLLSfe0qB1qbWzOfW/Ey4+veAe4IbxOGRqqY8cYKRTgBEi+3XUAbUEcriXS2uFS0uQEVMevd8 +bF9wMUSgTAHSaATd8N5M7mFp1E2AQmknfhZPTKK8o3zPNKpFL0351bOdA60fbC+s/THloFpDYoXj +e1Q/x2tegVn2AOW/LEVtBRFRCyIs1D1t+IGmB2wT9B8HUN2a0hQKAkS2JN/3a1uQGECw9oyc29nU +CJnP8cSTErbFbjrADaa/u+CkogSAzfy3XdmuntRC2kU9VVsXBpC4cFYV4+AglHREurvXfmUNokPQ +ouLI5niPeDofQHDpfKg3phardRmVLuh8vIsW/iSyTcGF5nEh0mFmpk6D9g3Msm4nWb8/ellrf+dX +lWOPpPXyHSNOd2YBzwcOznk+SlrbeimUnAhXn4x4PVAAG9rbQJaX58zr/SJSYZTnIDsLBUuF9Ei6 +Vl9nq8pbojeDjQTlzGpvh9f6py26f9rT/G85RXPMi8Q+G9pW64mGenYCWntRYuY952kOcNOW24rG +ajTlPZV44YR/aG1dBTK8aghFEkORf/R56yasSTo0V5VSZDMxKDJC0w20xPZSaBnhSLk1Q6yhbPxD +2E5C3RCRN/VYFmZmhDGL9sQfHG5lviy0arkAIMmp/AUx9GtWr33fyepTtEBWkHRLRvfspnpuh+eM +qdL+WgmY5OTlms1/SYj8/vkhOOrVkCmrGMsPd1H7jzOIueg5RfOjtF9//U7+lfPgX2YV/X9kzZHs +p8cBmU25naoxADr8VbSuiwnjGrCvZ9SJiOkrwRUgOmda0X/G0YRlD4VhCher19CitxYDRwh2TbAF +1TG+ARL3K9nyWkRVOT2aEHTtrKtdxqqlQx+YKYIPpVenlsTUu7lS3NChmEjv7O18o567rjbATSdL +d/eM0z0H9DkTEN7Ak9ZQS93MWCHHNTpHrOa47Ap4RwCMbEmu/TLA8ENHpfPPZWqp/iqYvWJDiJCy +Tup7J4OAmYqw+N/98x1/Lh5hx5B9pc40aLV3dcYXYWml6oVzveJPcYOUwYK6zIuNxb6gRNLUD1Kk +Z+QC8fj6dUWnzoJ55lpVg8IViUwUsFDxK8aaETDHxRR+Jqfg+eAJ6wbjrlXu19uVZdCJ83CrlqCF +8piQ3har0KbKeS7ICPj/ZwQ2IHEZtf7G7gPWrUjbnoyg9LXvlUqqRg211DdXsqlJRaPNOFMoMQ3+ +YftzbQ+c7+bLNOWJInvFPTJ3apUIkadPGFqfVCSD8UlFFocnpuccZsXEdBqArKkBCNH6eZnppZ8Y +GuVtAwwnNgR1JjatIdyqkYHFiLxTDGdpaJbSUW8HgIlSysYdL+o6uoRMLbsbIrnLEcQwkFrXZ3le +ddaQpq756ae0CyeC/JfPr/U7sNHztU+JdRcG1mT0f7/fYi6sKRtTnELVy6+5xelqaYeCf8Mctv0q +iCH6gWADNd0buMwTkpJJ0q+g3X2fqxYITdMqUM1nFLqTdJqfqjBxxbSQWgIQL5wwi8aV5lpt4kVc +wTkglM17cEUhAEaueEaMDw3BKEDPfpgLYN/y+ucLoMrZKn2zDqh3QMkAtZ3PADyiMoKaEiO+GtcJ +VDFT4mvWwi2NUSbEA1IcWIVz4Mbg/GzGFDuK2jnqhE8bpoxQkr8Dlued1Hhsq7V2ofAMQvuSQQB1 +fbcwMWqZsduloqTRDCn7tfGpmJi4R/UonKL7CNGkqli8/N1N+xRUF1CaPr+5uZ8Xvh+DemoJ3Agi +j/Btzu0+7UpWsY0p6zdsW52VZun6bEID6VmcmNjD8D9hD+NFWoMXrYOtdsAbBcgPplmjRnqhWM4+ +PwfTHxSU2dBUaxjofuCPdf5kkV7jPk87hrV5R7zoizoCuhpw5R+XDNf5yM869+GHHb8Jtgg7cmeL +0N2PERp9nx+OQ6ibzCxuESBptiOMg2nM1s/cBnVuQONDZ6zuTi//9TwiXJyQzMGfYyABzo1IrIcv +WOg0NUcoQcdsA4NWbErYvZsUMe51VuTtyN4ofhWwKqg6Iz1Pp44ReicOpEywmP+gH4S4OkGd3wrW +5PyYMCQHhaQbonO7elEPJF23a/SuQPT+zi5mmZwZd/bc7qY146SL1fh7HXkZddYSGQCOnmcQfiju +fQim8DVPhJpZVCjuMULJ6BM7o4jtCERDQcLQlOXEhZmPgh6xZbY+AdsY4AI8wymuarxCU9IIxymk +/CQ8XVTXC22d5i+LNfC+iKFzO1gI3gs/ufC+0tPMIMTzCbUJkOlHIXtQrPb57/oC49I7/fcZWRGo +u1QBvS4U+84y+JyKVpSwED7c6BpUnYzdFR5Iyb8YMSB4ovZGzawqnU1UB2q/Da+kxNgYyh27iz5U +QknJx+0BMS3I7t6RqzlKuZDegfo09zCAyfgqhZHRauz0Ou11jjm6h2DCISPzRtBSoUVr5jjmfSNY +aPkkzn6HiVN/wg0DB0PM4zMEFgjewWO7PxEISx4xHIE7ASO0/yWGg0iuWhvxgLvQjCwUh+TCZbrj +9kJ8BCFnIlB+AgEYHVRczixBxoWl9Lsfov487LjvPcTo4+JUQJsQ1z9M1wi419CzC+BudcSZ7DO+ +izoQnimJOqsjEMfE3KTtzz1wJm2k+Nru7ylQ9GhBnqWnb12ssR5ZWTmx8ZOno1Hxuw== + + + hKd8420ENYi38WC5FzgH/gS3iCYCkQaLgKIAi8DdBtVKWM9U5Lsn9lVS7iCbXi+EzClhr9YAiF5+ +2nEY753QTlW4Sv/OGHs0PRNpGXCHZgmc0W4JlGS47EqH7kSpS59ylLzea1kHgYD64aCJwr3Brsu3 +PpXG7XmZ1pnern9pjfWdyJ/z3rkvtxpJIQRfJ4jFWauKGNrWShYc1yr0i7eyQHzEFhAj0DNCLy6/ +AqU7pHbIIYA/OH3QMLtOvg4iQAZFu9jCzqimAMtCcqjl9yJk1Ylz34zgKHm1eCTUZgRQgL6MxxyB +VBojarkKegWNX2YThof+jIG2As6ZK6CLk4IpGlDBu6NcjJ4xtbvMbBzcUN7ZXRN4MBKccU076Nhi +bqPrHY05XfsIY3Tm5NQGsrW1JBNd0USK3xNipobL149xTdSG0qZVd47zNTMI1YE+o9/DfOzK1YD5 +/MUd3hspgW2ibICTElrN6xJIGaXss2V+SFeMYrbCfOWBcyGiYZJNGtZ8YO1Vg/meEZ/CcSHLoCRD +QdKjYM7IF1J2gwRaKbWwQXaAAzih0FqqlL9AKT/vnXA767nj+MehoPpY4z29Yk4q3H7mKwunYf1+ +9k9X/gfH43At2mmGR53XDkCOg2hpHaf1tz6D1e8+t0fJjTYmfulImcI0+7HqpPOA/eNZ8o+SxaKY +bNZC+I2r6kPyuUlSdkyKOWOm6Gf/8mRBgT2K+iW+gen6vXfTL/ouU6fsN2kFB72J6+yxuxwc2BDT +zpn3WATTZ6d4A/v6MNvCQtZ01M84ljnQLOgSXGHlzAZz/mpH8xpWr8KDnJKaB9+k35mi2rWFjRhG +2xSgzDm/M3xd02h0qSh6WfqWqAaQkQWw0uKmA65hh0E6qG/kfrFxXqilpOJNwY2K9/lF3/cLlkZB +foUE4xPiOaKVxQQDPYCXD7HkdupwLJM20VxZX/BkTZdRaGedPxtQhSvi3VRKWsJ0oGoo2+6REap5 +tq74n6E+qrlnxAam6QiFi88GPRAqyGEN3hvUPrYb8OYQK3hiDh2b+BM90t9/EU/0kTzlegs1aWWO +0KCUREXWAo71oE2ZfM/3bABrnHQUiKZCY8zOJ9DXF7cxotFapIPOLSqIIACTZHzngE90t6KgXlGM +hV2xyqXLkNJ6RvHKlUMrELWcYiCAOQVAvwkX7iJsd1yaOAC1kRLOZwJGx375C2NTVvXGvNcZEciG +pCdZ7Bm6BJmqQXQnVXsuywS0LFWhigRhDQCWV1ItXXkA+s668gF/u6mragEU87RrIgrxDEGPnhIA +5pOEyScqVHBFO1Xu9EysZaEBrAg27J2Xf/1U0fhRPfEzxOB7agpFdBDj2R07pYlG8bhX4kDukicC +effJ00m7gCxGEcOjuTCIuKUcixU+OVQxJodRS6BFIoovE6+UOK92xM8pKJiCatRlrQ5o8k89pupf +RH5Hhv86KhZlvCp6EIDGG8l1QdwmiHnQG+cnINbcHAGggxzxwdFQWzFcnyqJbPmu9JINhmIhHWTe +W6LygQAeYHg69r47kFitq/RwKz494oBnS8IH2lHxL4WyxrujJkRuM3b46tQUoDtRUbL52OmbUEeV +Ycfs6bYafGMIScGDN9Omf46+PkXLiv0pRQROIiS3m1pZ8aFXEgA/2xkN8ToiucapBFNSFLpd7QrX +hbk8ZgzAqPsItHWxQNg6i4XVWpV8HjFbosJSjTxvSRDMJF7vOK1rHPJEHYlR52fGSFhRRApfpca0 +qSCQcOaKdtWzaLaoeCbCoEHK47RXJcPcSl0PwJ4iOL4QLEZVElmyDzODIwnHh3YW5Qtt8KnfI5Mp +QaWZ2hJxxtDQ/T1rZWvpdx7Z7cHwc0w1qGth13GeU9lULiUokedWIIrNR2e9BBXLfXVlmFcQj5mf +W3P7Ma5/MRAONidalLjBdiipBJdE7KoacsuvITdYRE5EesuaaAtMBWndb+TIngMikdfbYl/Ltqvd +OPlWtWbsdsWZr8JLCwv4q0RPXHQoKEtutljP9zXALTGWR7XkjZnizggytK9MAmNOCh0fJPfUEY4I +4Px+Cv/Xsly5cDS26qVvdVlaaIv6/jVlOu+qAp1mBPgHKGAUqmOoC3JPj+grHStRrNqjfGy2Mk/N +hVlGjZh6GxzL46Jaf18YqSX1Ep4M1g/EUfDSxrgva/1+K1n//WYIZ3/osczTswtZNnyneH8VgilY +4a6JKchnGzgVbLmxJRbKrObn8+T0DRQRoQAtRnzbLYvg284xRW4lp7hAAJsmM9YIKecTjjrjGIVi +IKNeM2hQEoOU5yy8key+26NlBDtOj5YUaRMrJCOm/SOSyWpipdB2zEnvYmcURl9QXCQRYezmy0UK +k6AHieaKolJJOt5jukIWB0feAZV9poMVeT4T9LvpgT4mFG/vDbI51lZSBuFjzV7CvFX4Z1mFt9NE +FZjdAc6N1xkh8nZ8bH66CvTIUkkVr6IxijO60ll6wCIDW80SnFmCjy6xf9Uy+6e9zv+WiDiX2FE9 +LFq/gg9ftYkLQITr3+92kweOpiPHDvrf2KQ1HIH8pWSWlYMXTbLlHFf/XAM+ndCfcCJ6d+dMprI9 +NTDZ/QrhI6aBZIIoP4wZ8+Gz+2kBbJkP7MaZSLRBnM+75gxj8qfaRmZKtS1J/lUMbyBubnSnKpgR +MUgRcnyl8QyEDYgRHwAZcxZSU08BuuOmA2GvM/XFZSr3qhl6D3z62bxShDPs5KzIvXVAxSWFEnNx +KlBIL8MXWMRUf/Um/pUv/38vEXFmxUnuyU66cYq13Z5nf1Z8Jik5HKEAjBdoYU01754mhX3AkzNj +F1Blu7DQMXhkbuLph9sENpGvXWHiLHjUgvfWnTMUOP1Mpwyssw0tMJijqdxSgUtm5C/ogBays+jc +R0vE7IqCDDUocC0/Zy2aBRZ+Khggzsbn8s0LejXd+oJuiic6oXeQPickOfqcSHdSOoGedUaglhgP +ZkDS9ErB06YwoNH0tHZbDKhNiB/dX5eFAZqwOE8iK1Zup/P/MsIKAcqw5c/+ukiNeZHMLj19VOpr +3C+909RrCMGHVM+aezWGhKxX108Cr3MApZ1pg5r21bCvO6xVz2B3X6SEhx3yHqkpPafn+rMRMwI5 +7cE56/cr8dQcZcqz3ojkVBPV/Su/hayU39Jj9i2TRiOc8d3Szi3FtlYRzErX9HJA/vhkTvhQt8a0 ++Fa2X3mmZDcgrXnyyH/wyHwlFNl4JU/5PtDFHGn5n76Rdga8WougjurODATrmBnAzYwfws1VJP32 +mKN156hiQZC7UMtxQ21NAQyxOWbprC3ewKsb3uAa1p3tQCud7XWEiW/T2fTows04/DRj7bxwYP0Q +Pm/uNc/+zEt8xWOW2zEl06pXuJIcTmWEArFsSe8U+kTi84JOINrWzLQ+n/FnAd7OgJQ6KFOg+lI/ +uDeygbe2xyiNv7t4+hcN1bpTCce7m9yxvStkdA8O7IFwVDvb91yaZ2oJgibU+YL5e0Ko1iTkahXw +6gdHPbFIeguvLCFGaBaMvaY2mIu4czvAJieN7fsNghphtZSLvDJEomiGi3uX4FzS0wA3DvKKLfRs +GTQedLZyRMT0EC28CBblI4mJZg+w0NhXQ58LNOXgBJUJB4wJEf2arYLECWjwNLcGRsqkit0MHV5x +tRG+ljMPwOqyUBRSmMR7Z81AwHwov3Fie83tVxjo0XAYJzC9LvBIYAKO2eYq74j4Izbp7cr5VLFy +CJTSjhXbPiOr86xQJhmhoKPOfVi4DZDUmXZYFXESNWSyexzkVpR6dheexF+baE4DeTD++1Z+MQSh +8kuT4toro1PYY2eaTLfpzGSU33SW29dQrV8LeX3jyIqGBV+t50xy+gPP1chr69wzmvzb/ZhQ5eu3 +WvZkL8gN+SGAfPLnSrrYCGRnKHTnN2hWgNejpfv8MtK3GCHIxHwQCL5UP/NnDmnyZ9NwskN9c86u +j06VtX0Q1NRkZOGc7dijgZz/HnHFDQg+/lL/U5NbOGWfuHVAYbsEFFZj9SdeAPUCACjsbk463swa +kSIwCqxwu/stjJJl1U8rolytiPiJc/pV0efVz98r62F7tSJzQGsXxInKDidhAP7uFEMKeovSQjgz +qoRquhAf2xfQ1GaL43o1/4WwUcMoYY3/8fMWI1Yhyfcwyajr2dVUAVxRIOCUQlnXp4AvK0/hoYA+ +Lx34EYYZSTQ4fDpCgcr9wwivBLiAeK1Oj8A/+Z6zpinag9rZumb7W978FkFDfxzR9Gv0ns7N3iuR +LOPqzahd7pMpeTLGxfwWjwWkMJFbgBm7IgmDvgewYYmFvlWN6dEh7eO+nKBfz6g6yn33uqMxMUYE +U8Q7VJyrCY4YoBgNjHwSCtAqb8TmAiZ8b0P2jDiHTowyCraGrA0sNTS7vcjGE06crX7ZCyBn0aRN +V+u5A+R/FG67Ztil3UePVgLreb5XQFrMf5oXs9vewP+oKv9qqeCZfelwRu2elLqiSArUn2iLbB6q +WLmhA+fseVzEl+rGyQsiyIUwELEOJGoQHGOdvZ+XsjbD5+bx8YDzQGEE26std5P9UfoKdc8L8VPi +RRk3pBnfLN9IyE38TaNmBI6AeFIv0xV1Zx2p1dLdoeA//L7PiooMagyLGVXnFfg3ZJLnYYDcD/B9 +LM/KClIA3syLFDWpmC2yMyLW7TjW0BqbV/eGvs+nPdAMMuaF0K5LyaZF/PiMWyRWqCAjsYIxiP+g +NCDNkq8xwwlFE6PSbu1ZRtBH0Oj4n+y9a3Mcx5Xm/wn4HfqNI+yJBVyZlVlZNXolypf1Lm3rL9kz +dmxsKCAQlLACAQ4IStZ8+v/5PSeru3EjqikQ6AZrLiK6Oruq8nbyXJ+nbeV4JvQZXTLpfBGGe+ee +w6SopO0CySjy0ilb4Ewd872sVUuHI5hZ2WF8VDxporrtYjeiABZHAaxOe1Y+TvsBF7AgQXt9IZU9 +Chiq8ogmnDUCrqPoxpnReuFTAiDSONdSL4NGAHw+B8pGlcbT11VOYJvsWt6/T14L4ZTbgFdXRiY5 +X8C1VDzGWbF7Z8VWFjwoqcDd4vcWXCwWNMdtUSDHpy5Xl2SjcnsHlRU8qelFrXBFCI71Cr2a6ORw +IWwhlF5SYtoRSiBLZZUvLTYQP1TKGbzhgwOY2FTocMwKUAEI3ftuhHiDKpqUO6cvpLpahajChCQd +MOA/p/q0G188iEbBBWHr8mNgA0TZkQqkgV8L6XwB40MIWFmyvVO9sRgCm+i2LpAYlCkK0EpguGzQ +zgF3lE5D6axsIVKVVPZDgTHvR0Ioo24txGiItUQ1K15clILKMQu7d3J2j1IBrcTEEEFgzc7eFVvn +mIVU3FuQ5kmLNK4NUv8Iopt1oHSca60oyHceEdat4i1Apcq5a4McvE4+BMH7Y4H2OuKVnEphT6i7 +RUx4RAgglS1u4gnDWfw+qg+ngon68CJnPcoZqYFR2X29t6DqQy2KZzwXkCBbgCLSig== + + + lMi6762k9GC0gthiRquiI1ffw4SoKGZbucJqd9D1igMd6K4aBMpEcREU2dyR3F+yRFsx+l4dR+x2 +CHBVY9WkOh8Ond2TznytxeE4r61QFKqbUq3oJK2o42YFcTBHEHXwhZbsmRKgEZSRqxK6Z3nfKRoi +oswwgHWG7S4kG6CfpODYbaTRF1XoK+giOS5mdnzEHDNRDpUAuIMvwKjyGneKIJNacXV3Ok3ELwGY +dKwJF9L/KIARerdcsXidydmMHj5JmJgkJYUKT9IqVR0A7RxER64YCpj0AD0K17IUN2ZNVDcyu1Kp +8QNsDgQdoILiT4S2vbgnUgoPyGrtyHcGQU9DnSh5pew4lTESz+AcwtyUSymKx7vzbLvO7U/qMFXE +QxUc9YAN3ORyX0PEQKb9YGuzTi4afJFiRyZ5pwe564bEACENEvBQat1Qs1V6N7Bb5eJ4RL/TVsTU +aNz9Qwk0S1Fc0pqdpvGM4653/CVbo0G8BKRgDjrZCNNHd8wTy8Mx37O0ocdS+k5X02GiAmZ+3zYr +o4LeZWUtNkTYnDxeobuAhRPdfe+hcajUavk1gDkqJwmcJcXjI/J1pyCXx8Jb5NpCMTu0ATzOuBwI +kl57Egn1UWl8EFBVaQOlIC5tPOPMD4Z95pgRRmBfvxBPBuE3P9AavMVB5Fp+10rHqtR/ariEmdV6 +IY3KZOKgKq/oABaAxwm0RAZdhelD7VC+fYTuJyhaJvwAstXkxgbruRGGdWhjNRhzwhNn614g19hb +HutvJTqEYoNQMJOHZIcvqjLrPlbYAlC2ST+nKNIsbyGcBda5kNn9yCYfV9CayuVqR504CyCao3Ug +1ERSsUptspBDvVKV4kPdB+9GECeBzkz7Hp07JE8GQQkXZwOmb+UxJLU2CeQMEnNPR1dhDLaU0Dqa +YV9skKR5QXZL+rXApzJ47NQZq4auUToIXD9qUX0mSrfqRBvjaM5kswsoJQOc1fkCh3s2smDaqBeo +tycpV0qZYkmUKnVed0ChBbm2FMtV5c+JwAbBi+kVBmE2oGoDKumo1og5kB/IzmTWgieHAKJXXTMk +zRfhs4eu8dwgF7Y2Ro1UCEdREsYygIy0GMi8hG+CJ6vkiIVAyREuYoX0FbZJxJurbZYc6IjkRchv +NK/SK3BmNthiqVRfEukbjWf5APdIlk/OY+WJirqiF3UBU6SUIkCsiWsqeYmETcQRYpgcbVooeygD +/N57FBQpSXwTnGAlxqumyJ6CS6h6v1qhmuL7R9/QLEN3zDoouJcUZgTHXmyQqVMLATRJgkjAdJ5F +lhpIdZLWJDmqFJclyjjdo4e00FsQEuYtCFYryRWMV4ryGjK+GZFGqZCmI0l7aJ0uDF+g0vIZVUGC +Zsq/6wmDP3bofIng95E/I/qvTY5L/MmhHiXdyTtH91QgrBWxDGm2GXw01QslJUZlsTiP75+Uewtp +LisA5Duli/bYqZ3CnxzpNi2twBIV1feiPY+3up8wCJ1PJHlxrP9TEgtI2PI3wkEkmEH0ZE8pUPay +tWh7j7NKgLdspuK3SMqNaQEiGsvcFLDkdTpKcaRGiEvEbBP8TjrWlAuJGePxmVyPbiUm9B7nAdZe +K01pKJ37PKgdGCpGpBY11mXL6/aeigGgq8Kr8rFjlRaKG6J8V5xcik6qZmiQVzok1f6Cb+qFOkSg +Eq6VtdgkUGnyDXPgEj63Dwp/Qa5KDZRJ914z2sFwnFjJaC13BeDeG5v8IKjxWGppkYA9WR54R0oN +SJP7oLwxoRsVTyxulJZCknIvHw2UPvholEh/rQFIqdQWgZRKiG+8v0vOweuxbH83iqJI8URBbOAa +SZLQIs1WC3cZmrQh2tPJR6TbOz4qzmWntWhl/KCj1k2ntxTQh7DEZP+rqIN6RFjGvKdt8Z4iT661 +0ECRso3NkW57Eisx4cwndYlshKsvTAN4AdQAAXity7fNyH1O/tNCF2+Vg4pyb9MLCBnVZU5DyZli +c/O6mp0tuhN8E8R8KCITxmdOoLgmAazBV6mL2LOqqFWNURKbk2jl5OghmcxrboHKJcYL5MPo4hNd +dlfpslVVlh2sHqWNampFPfSkzskliZ6rEA2qD2DNxK+Q3QAMfevFGSJcRF1vKgaWwBBRblvB7PMl +FFaO9E+CIwegRAeZasrupLYAzE38OB35jo2g0Aa1UNQ3kciUqioJ4o0ohIXT6KeaAHrRAVPjjAFy +mAApYltZJ2PfqCAJBOuqS+qQEccRxSiuCSgjjeKGWHm7xJrdQN2UO53PYhTM8LunWImU4FbugWtu +vXxRdc2qP6qDD5tSU9mUSCpF61N+rPVeDuE8VCriwblmgnD2GClWRx4HT0RAefDa634sTgSXu9oC +FWF1CIJeD2qFE41Wg2JCuKIIYAbMn6qAsphVTokjEnebXAGBBMthuKS+Zry1JFIKrMe10wxMMomK +QQBs1MSB8s3ir8qTEivBiw16g+CFSAF7ovGqQlkCcHm07dgTOSOBe7Qxzl49CysSdbEiw8YpFuQ3 +ZE1mRUcH8J2SmKEd+o3buIONRZi9GL8RDQpuADB0hD4qACqqvjx/EnegyvWL4IcK+Tp43FtVOLGy +G2URw06V49LlIK8/GeKoHk3wOl62XFMR+LObcDX3uGlGnbwlPOCLMubqRkfQRi+MJsfMWpUgInUS +jXoFpkkO6T2rXTVAw+AM2hGUWvyFSspzZVp1psS4cgXVaQXyp2wYMx+jbyPBGJPnTEKCgleabXKJ +VQRNTG/QfZCBAg4SWR4wKICMqoyb5W9KZA/rthtVxIV7PL+FkJBaKYKYkAMjAJFw6YdeiUNSRltV +8HfJ30UEcvrCE8nhNavg4I5OhDcOFx2HdlOtNlwesAjIuoSjITZe/6Lke/xbZvTiUVSuQdsp2tKI +upfaHG3BREZ6yauuRGFB24Akr8/x3EelCzp0L/jGwO4mCviVDagAYKPYva/LBGgroZ6WjHkvn4Zq +k4RCzEHBWZFsCL1HgvEMY4HCPOoFyftf2MR4CAGQzhw840xkDa0kR1UxKWIGpRyWKhVAEjRiQ6YK +Nq881l4Jp03vtfPRo2ewwxNAlmUFgipp3qD5ZS9SaAQN0BEwTa6vEEuMcuslL0tX4ijnFdlXVDZj +2QdS5ahMzV55P4Y0s2DKYSlSvh+vhW8F+KHBqyBUHdyyvVX4m51aA6L7pnOfq+qcW6U3DF4nASqc +jZcJz1G9B/MCXb2JVOVKC8bVp/IFBU+BfeVd2EXgd5MApnwszAJPruocXgGXhVIH4PYmbwxVq4ww +BUV4STjHObxTTf6nFUj4PBrrWFQYA4QPrWOceIEpg91rWFs8D6RnAJBHi+JgELgXV8nEIpsj1NM1 +Xt0JTuAgPWJw9LOszFvb9qb7CAtrcAArIaPhVQN2mhAKURWRy4mdEUmS4GfNjoAS/G5SbyCybVQH +Gb1IiDpU/CQuE0Hd6mUJKxwBOvegkFjnmQqKugfHFxChQnYFIOYKUoDtSKoEIBXD2Fms5BZ/VqfE +ILVyUHoowETVAWkWbomOgvPkm0dlrM7sKytB5Q0k1LCbGBc3FVk9IyIJICsRU4H8GhZCah2IsBVg +pEfVzKLiSaaRFM+OFjQFoCMxOKFCpxZ4/jWGBO9R21E88xiuo9JUZBOCG1YrZT9YKyCPFgwmqEug +YoNgoScRtmYu8DiTLS3wFYC3QwWU0ZfobBT8aGGQm23SYpACEx08XZDKtFBlYVL1tuhNzabtfLIJ +nzDZKtkAbLHpqjMXUxhmCgohg2cIKyTeIhjIYFApJOE9gEqVb4//0Y+7poZ7I1VBcilHB+EQTnvv +ZSdJYVAKzhgYJbvjG+goZAteXCWDi8oNRhyPfS/K2aGr2Fqt0C7keLPdVVPxRcfWchDV0EVDBMdW +U/I05eKI2kCh5MpHoSiz1KMu1dwZUT5C+h6XLr5WDkKynpAVxJFVmEs8UzWXeJ+jvBz0G8sUrbUp +osZiLJLyJgTMj/O8pe6unkAqig/ikci90vvINo9epYaDF2JPoDX5ohq+pEI5CJ7APLqq2LeSIOMh +XbXh2NR64k70P6r3gFlV540NAwsFi6HTTEv9b4E/6PPyZJOuCy2VzpsMESsJRgCPYAdQo48Hk4Qt +YgRZdq1OzszBm8dKEQoiahTEvoOihlO1IyhMI5EPw3PQ1F8rqCAFN4qZphH8Ieh2JNdSXtmpYora +UhKRqD3tRv90BPwiC5lQdVpmHzk3IV0WOCDIm+SuwUWQ3YXK90LDI576Y52ehKOW+iTAkHGP20rC +PQ5UoVcwDZX2MZZYoQk93u8luiKdLEvNhm2gBJAa+5cctS3YjhpYYnNBRZIbL4hT8M9aJN1+sN4q +hI3DxFNWFB3CEOwqlGNNlWw9VZJJGDoH1IlgiIi+p3EzMwxKbZSsrtkz0Mq2C1coQ4U1Lx53wLOO +NO9GPK5W2AgkgLGpUD8o56QaIFKlJcYAHLsENOyUdZ9ycQW3BXykGTWoThSPYgDuCFQQjFIJaOqb +xokHAGhVIXmtfO2GXrnZCXB9bAPFzRDGrQrlMKlUQZsFX+l5DuTlidHBxkRZEdTBMQUK80SB/DUy +qVoKiDQDTlAFpk8/Bpcw/8V3aMJBu73yGUAGP2idChOeJW5jRlomFgTofxHsAGHwJ1QwEioGqmQv +EeQwasoFIizPUYUckvoVxSlZMf7bLIx/l8tZVbGC4EVghTDKueyGSRzxlhiqrISdkFovZ1UFGYmx +wAAINxlXN+TRZDzzcImuKFvZ4bKzsmvI20meSSAABoTpmLyKddpL32ha3VcpCzl7ysLg+Re5FzQv +0DVJgySOEWLXletBTndrgN0rd53CVRGQ7VGcwDOC0zw0Qt9RNl/g0MTQaqOn3enYMRmOzeDxkabq +S6RtJLGkEtys1ZmwgdosZOHWJ3d1QF6RxpUV68rqajcwKRvBdXoRpFLTTfsnLjR6UmQVNQIk7WvF +XVd5yUk9LbKiJEM83yPgbO6V/+RxboXwHdpUgOYY4f2YzNTVIFAD70jnmdi1Gr51ViFKmvIgPvFW +GSzFdQOQYlFCWnFhEOYGAL5y83g5O92r1bBiNOyV8doJBYMHOdVpo+2opFLiJCSVgrjgZEJZ9fIx +SZAJFykKDFv4PAhScSOTGTwyv3raqe4j/Byv9hTAgTwgFb+5E6k7Jbmu7ZPZhLbfiKEISaICo74i +SSRHiFq6t3IjstKhk7ZRxmp4cup5QOMiC+9ai9FF6BjmUaUoUZTc1+pOxRQA3xmjpKUGQQn2t53n +mUibxAlE4glJAwVdDGEvB33feNJ1VJJ7dW0JKFUMkuC1BqWvKhuXLSBsZlI58EYIzix6C5FOYho2 +4qpMLj8biMtrTkzQOQLXQTWmSC1RbNn2fYITVm9cLZoEqBMtAO7CDdI51mDyPLMWG6vSnHoad3Tu +P4ITI1NVC1aspqv1YH4U5z3GmalvIuaUmgQpCgRS0ksqiB2ZXFz4Z01vEEpUIz5rLw== + + + H5N92WJDij42i1BZC4MRQL8RDV8LsCNWD/uOin3bd8nPFBlnzkVCaBeaZVL/QvE0HNdEkNjQUAP1 +D8uf2QleW67cRlRZE2ZhZCWRggdGPr4sQrXKcC4KWkl/FrsxcX0gSogWArEKoBwI/wDM9oK3YHGQ +CQRISEtFRXXYi0JZqAcmPBKJ5ariEfYJZWdBNMyhEzAZR4/XPwpsEo8dSA2ictZLwsuk8qPO3QOt +9JyRhhlhTNEBztHgpcJRqIm2L8DjEOFzwGixAW1al6RA0lCLG8mRoLNFGYSww9Z6cXHIgE/RjfRw +jUArKPyCOGrQBlTOIipi33iyvwwMkHfZObbXxJnxzyoIfRVDlkOoE/I08hBMLnXKFqUBFMXkaqvE +taWIBShQ4dL5/b0FbApuTHMzN6Zz5V8iUw8AeGKOJXt8T1DWSUDA0SkSiFJFZD1+Azv+KRv0tQGx +gLAWB/cFFE8OSNSSwdTFoU/SH85omC2EeJBVwJkhQ1AL0QeYzYN/ySOIctlFDKpQx7NIp+nldMSZ +E22PUo+RgN4UaUghD5z7NIqkChmr1yJH1AsOUnydYRAEqN8WpZqIcRFkp1wvnGh6b/9VL4ZzoRLa +poQUArnpzGrF4QpbTogkIL/KtdS4bdzjGFXBrphkGoUthbkqkhVxjw8jahzQEnBHZ8bPOVp8KeIt +avUgwQSoSGN0S6M8D85kggIuDLVWpTb4bAZH7Q8Smz3ZXsU9e6gQpDEQVx+lkQLRDWHywVuR64Nm +xkkuqSaFsYEqQ99r59j3OIM8xoF/kBhHHDwyrOM1kbk2JgcpgtHXCAZOAxwtnQcupFng8gvZAUNF +3yQ2RlFik/iD3DYrs1dHEKuqGyIGUXOuAKpRymxGAXSkD9Xfw1ihNGCGDJ5DhgxcGgVRIOxCMQT4 +lhYCgBkGoDqSogoiEswhj1zZxDGYLs9W9sAGhlLW0a7Zw+xk9toKxQcHGeEtVuni7oDce4OZHwQr +zqGvDOtG4HBeelVcJhAgciRk1oIPNH5nVp7ZCiIyaYFQ8nCWOAm1FLBxE3zgQihBWeo9CV36FXSB +ysGq3lq5UUJaar2gpqCVNsogIW8asC/T36l1kykgRyBatFvXbY9yjunRKS0fhSV5vqPALalLrFSP +Tm03iLWu92JaJcs3yrWByRYjHbYVsdZpTYH7h7ZI9oezzWUJSMwYVCB57jylhKTJmt8kMjjqWsKY +x9FWOJlk/ZK+Be6IBxt7jFitXsE4JXJ4QG+/c3Luc0HcG774jWXXptZ04roT2hChbBBLo8pmml5V +13ZuibaWvJ+ueAC878k8hR1FgezhGr53WI9+R6/nXmthgtaD3YBzYdzZE202k8wE6gAoHyQBzdd5 +04+eQoptB5kKPaUd6EWyciIRyE4/Fzh0GMD6G9wiVXJgJCTXuldD/GLAvIxODHCgshudQLLIIxHJ +wcaWGVKo1VxYi0kERvJFCYcRhkZHHIrS/d3+E7tJEfakYimUUMhpDKfS4Awnyh0LDSSBXioBIBve +nujRzuiQgqERm5Zc0/CADzLT2ROwEqH1EsykPHd8knCOIqRtvTNKYPGazttoW9BATAkwomYn20QJ +JIeUEfpxfF/yoSKu6NZB9il1Uo2Ll0A1FaGkA0Hbo5RefSGUpFgBjLAvOqBbi1pQZ0Acsx9B1vG3 +yyQSv4FTOSsKSs2Vat2LYDJEFS10v+ITKcIIIKDI2dSQwu0kpZJ88FQTmHDGEUoFYElMHaAfATnK +chYqrIJY/gCvDWP9qEi5rasOVqmaSpsB7rX3b7oR2Rz7qIiTOwGvo3IjQUpinws6gHLoXhUBGB/F +q1m8nICSQPHe4O4VS4cYDGGn6i9hvai2JXttCyEEdV94QhEYRgaI1dCr3I+E/goTSHb+4OUT0i3F +eQnvLvx2Jddi8qFW1mV3W7SARdOxFjlIjWcn7jMOEwg/GyKkNf2SEne8lq1gLbIz4YhSCgDvUhOx +BYCCzTVUXojOeSHg+dR6I8dN+dbJw69kqgmRGrTBouvOu5a1dFQ0KsumrXNDA5S9IJKKFYK/STgZ +DWYs9d5KrspMtCNViB8Vn5IQ4fcBOG9wIA/3wcuIaFOtrhWxrPNaqF7DnxQcSVmsBwgQaWWImUwR +RtZ9HD1WMNfZW4B/HojbqPhCU0yKLNn23sLLuE2rHPkxBDlERQYewy54iroyM+ESEewChpBKK5IA +rfG+O091ghJv8Ffp68sqiYTDFVPChlQYEWOXUBEVfBDUIsqFigwJfKjOv3HfncoRW52chWioyX4i +3zX3cvCMGoIG1GeYeSV6EcGAEi3Af9MS0QP0PXgtFbRzRKIbufmFq0DpEBm7+EnwpIDCjOeFgItn +REYv5W4BSE1OTSpvKnTDBNgFAtyBfIGcbzw/A+QvHJs9fmctxRaJRBALqqbO6U9V6BEEZJm8SgC3 +bQu1qhzGbPkoEtREoYdUD1k7mELBU0Uq2bEQgoLIItoiDliAQ1QsCXFi4xp7R/4eEX0CV7QoEpyB +4O91PHQ5pJF5OKS7scoexb/oJXLnpb4VOyjgr5Vmk2uxAHG9JtYTpa0nCn4GXlbEDhSkNe6N7USN +TdXnWMJNjFTY/4TVBi9b69COTD1xiDJyP3MF1o7iAMsOkQ3TbhRAE5qpikTgfA7u6RSMAt7WEWIB +rU9Z2iSWaymYMabsJ9tcAmAqvePTRfLVg5Q1gYTAUh2qS0eNSH6IhPmSCjYVHIGaE7VBfBXKxMeD +JK664BiZdhgHufxpofz1QdyOsnoEDG76atOPwFECnRWkLLnZzpgmKHMYLDXZOMhV3NNR8iwnoPdb +CGK4gCG27ZLAY3CNeQvOIXKYqIr2kSni5iTqG+TwRkst5DeLgbFzz0RUdrq9APJF/G564xFZSwBx +rDxnd6vA/I0KTGpmAmo2Ylx+DfYRfg0OEiCTxNNQEpUaSgsUwCKZVI0KnGCCGPRFM+6G3IjhCWiF +xt2dvaNXsyVc1I+Y7FHUlpUhDzFDVCz5ZEFwKVqaRsR1hCn7ILuoVA2MRqriGODZc74+1SijfsTK +cAKkB9i3QJC0QpIlL4g8DcKixU1JpVw0iN6ughgSsKNEwKe8qe5Zjs1ah6wEHSrUQOt03MbecRtV +s0b2eZGvnMKPqoLh2FcwHRjXpEaavsY+eri8J03S4VapcqeFg1GAN9equCpGKlhKXKUpY97kJDoN +uCUo2PbFRI1xdIwt5di3FE6x5UslwSZIHzr3kBF6wcwDDEt4DFTREatG/aoPAvmsEW6EfibXMipM +I6y8VNFgs/zzAtvPYrXBoS//VUMd4BL8EHh2nVkIQW+lagJqNVUHynlFPTZHOWBbauFvBYBw9Hzu +4lHzroRcgTIAv6Sup/IkKvM/KrEfu6h4+CALLQNyh1jzlWR4mm3YOpGlAt6m8TcsDFe9OG06ude5 +n9efD4IWJKJdS0WJEZPvKDfyUPcbeL3MMBm7ve8kodkQym3jqHiGinAKC3Xwn8scaEDHYr3iqQfn +hkwDnSMk6orpGx4yB+pR1o74jtuucnaqVJ8fxLqgqT3G644DzymPvRQ0eQJL66QGrcrXSUWF57tm +JWrMHeIRz3Il9MU9CPSyIBpt/Q3UsZI5md0fI3y1QYkhvaMQEWxAlhHygnVYypeo5cdZK147T6VZ +EgcyGUktjUykDZ6sTqYFv+5FHAKiBAJGMFLogEGkNvL4Ub7YAzXWLimGajVwC/Yl2s4g0B2YmcOy +FqUWz7Kmivek9S8UzW4hzxVFNzAUQV3EU18fYJPRy+MOc2kQOzJF7z1KhuraYFbqlKoV3LbjiQJK +6KgmT0uVH94ULJ4ctJ+Cn6zYmXICYTnhPDQDgFig5IOiog3oa9lznBTjwfjSYVxqzh6q/5K/qifB +RqgXndg2VSPkL5xbYaKgBkU1iG10NmgWjJxxSI4f622klilojwk4DELpYX6zoP7FmCLQBpNoJjGY +3CaoRSc1BWNR2WrUmMWkBmXE7evHOjLiV20rz7Ao1YGfiPjsSaqOMkJL3zZOKEzOEr5jlfNj7Q6d ++5RHyxV7X3YVXJkwpRNyBszNfiRPo8ouWscsIjykFiBQ9MIHVIZXW7PZBETvqzZRTwlsa9uO+CZX +8sCcl7urvNzFMyoc8APGlN6XsHeRnIucKvKRci4YwqSpcC5mslliu3ySXMdAFuMyY7viHcPB2nUK +5Q0eVIvkpNTVBYc3K0OwG14bKtoU4T5E0VSyBsl3orSyrzjPaG2CjIDGghYCiYXXPnSOTQGXBo6M +QbXobKNBJWTo+aO7AxspKu+vFWsxVjtICXgqoXtQJkAifo0Q7xnjLLoWs4DIDw9q4b6dlnSOTi3I +81GwoRsxGhRBUYUy2WhRcQyq07HhbPqSJ5wlKnNgvCAC1fQiyiAgFzw/ovfoPXcAqfjaPTx1xwNX +eM+ICSkyIlJI5yLtnYs01+CHkN8gOcieaqTcdv1U0i6KxURLbuRYppXilbjnSCDTdqE+Bb6lQHiI +ZGmOIkx5mW0cFZgrJoiz4Lxt+YO3yGoZQsWHKcSPCPi3ozI0jKgPtpzlNqNCrvVSqiAPPLEzyTIQ +tHpBZwVZWax64gMR6LgKYuPM16R+VwUbz51Xvdo6I8qNOwY/A7BvHLJMihAko2CfFJymdJejRJ6D +MffKC7vIGWiKAsuEnkQLWwPNrpRHuUSUFSEbMAYVwenBLmVbxWFWSVgK68I+jNKpI7n34HnASCWj +WbAH1GcVx+ZyvRp+iMYhO6JPqfwQ0qOUSWp6lD1wfFJwgHrKjwcpg50XrjTkqUeHJHKThvNLKDWg +uVGBIqbzqrXg51S1dXIicfggoBW0hej5sIPASWUcKArWlFxGWCuCw1QOt+K5x3zQmVFYnWNib6op +Wn11ppdKHmZSCvgN+eE8PcuGk2g4LURr1GQlrnmxA2cj6arFAX9RUuVPbfCmJIfSUyIDxyzQM62Q +IsgCMdUmDc616+XvDn6AJix4BJNj3TL/hJoOJZ0hp6SsRs9SAIeD4A8tCD1JyW4FyjBgy1Q4GoxX +W67K30OD92M2pDH+DIQeeAMUxWSSA4XVpPt0civ6e9I+gJUna7XzbDMYmIocjp7hWJxsi+/lDSCJ +NC2z57paXgOJX3agI8WKgH5sW59A4twEjYIbjhHXbl0XAh13O1j05PB34N0cpOYDxMYJ4dap6tJx +ITT1vpHIc5S7a5CDR/EzlD5+4mmU4AULxJTyjejM7Z4bKPwc1dIAuRNXCEDkDebOdaScnLvW7D40 +bgB4HX1KGcRoTeQeiu29VQvy5IsnRysgRDltcXcPJhfO/SaP5VExOn+lSbq2qxBFEsmQIypLuk+y +kZWuhxaMj7kVtiFZze5ki2POJOoL2x6gm5rYKm6aoHJL77WKd0kUBOhYJRuBBQ9CEsuNEhvVgeBs +j+7mE7sdIAXgS41VJ3JlBdEjd2pFWhS3NSGfFUoxJU8IXj3ZLbpPTLpPkVkSshv2GA== + + + P/IstIP6viygyZJwIDd7XESALAEg5a7qJkTKo5Jo3bHj8JVNHPPipJ5IYVDKuvuHFN4TQPrgxbF9 +xVFSckRovJt84UceCdbFU+RwxdFiGOmjBKVFllhkvWCNKmGQuEiDX2LQfTyL2w6ZlB1iSaC9FLwp +qwucOQDQI2d450T2PlQO8LPEKFG6EMSdNErRM/hsQXRak+TaCOCeF4wOGKq0flvXkZpwWni6OPnD +AvRsnUSRTA9qMMe868ER2kDx7LTSFPAMYNvxM3DgcOdgdOa+ggqi14DxLyDQO+Nv7w1l4tonW2Gz +Ku0hVqVXdF7uEsbQAtSaUIiXaw+e8KEkYUHRwEGCsUqUOEZfWMreggJReTTYo0HZXEF2+gAo1aD8 +4NK7md7g9WtILUqjcj1OFyAknYPi9HIeAZDburnkUBamP6e+QgEBeEF+lExLO/szJwlv2rTuHMut +OwQyRf2H1SHQwZWkCEpyDxTw0biPiBXWip2+94odHaH4AonO93KPy2JX+AZUoiCPbfGarAY83xGX +AW0FNyAmYKkLVQmfoXGMIQEi4oQGnDHXFpycXt2ZnGbl/bN0nyvj9iD3hje6NxLtTcmx7x3AnCR0 +FXeQoIA+ALJk6rQ5VL+j+hpgJonKKjvB7AkSEUTiHpzcIrSe6khpjbJAE7Imk0eB78yFOPmMUr2U +q9HLq0KK1AKc1a7CbvpziHvCUEoFKlqmnKhddaLWOm5gDlReA9QS0EKmYVButaAgFccU20OkXVTL +29pd4A8RuT2Y36BugVOZK0cQKrjn4YFz2GRVb1Pl0pNQHNrrX6AboNwIU+fql0mlMp5uJYQ+514z +8Q7YgRzn3UgaIt4Ayvi0oUFfocSFHdXoXatbtyNHgEtAUjayYZs0Zox7kkXr+c7CG6GeeOiZebFw +SlVXNQ/lmoS+sZ05KFD0qddr0H4rkePy9cVOTb8SZLXtQtocehNROQJVTeuEtr3qV/v6RfEv2pqf +z+Aq+q0v8S6LiykxJTn17v1VPhx4V+RccVzLjVPcFOllwVFIGZngpBSzMrgSRCEIYLlaLwP0SMkh +YlrwAVGRGmESu7WI55VwFpmbQbqpHd+mA3aRaI2Zu6J9xYgnRpycubwTXC1fuuUHCrqKmUELAlqM +XNKgRZrZowt5mQtPKeL3JpEPphnfCoNvBTsl9kFtIzYmrL9OCDz2mBiEkaWEe65D4UQ+qh3RJPRy +E+eNybpG2K8D7MDGks+dKOZNJ+5t+yhkhKMAVC4KZEVp48e7At+8JuiYhZpGDFrrqHgHzZwy9UzH +mdwBhLzx5NvMJWC9BrndvCqDu9ks4uS1L6OMaCGtD9Bj4utA0ULj56gCNwJbBHGEd08THsjqxKlX +EGeNwycqjcJOEQGLBVwblEERO5FXX+TCcRGgzQDS0UShai75wlYSoIO4aNyP06s60l6e8cRl0hOM +sEXKgYePPdVYS49Rwtgx971I3vhVkTLXE2AC0KiXp99+VRwVo8dEAbgfGEi7ayqtOxApfSo1ha9n +C+FeE1c8gOWNuJMXomMmQ7V4wRwORTAcUvGMk74I0cYVChxbciIVZzaWd55IKd5A/Od8kbJALR26 +Y8ir50IE30OQyEjpdoBF4/QG459Uo55Qmi3rAUhUUxoSbrFEuRCTbtIVKUslR5IwxWdAWUOutwOg +PPAlMocYLcYD6wASE60DW0yyJ0ROBnxg0Bz7OiB7O6Dwh0Z0i7QC0ClQlgEwo02C0MUAKFFUmzxJ +ZINpme6hKBKYagBVrNOXuKSnwBwgQbxftk1qkvfgawg1lTWkZYyhRwmOfQBG9aYWvYrierAvmloS +4K1ibWXnWCINC4JfU7dhGtcokJBb5GGzySKnAf9Zb7udmLkdWI5cVciByf31FnqQbTO236B04+6G +20ABQFAXnCi0A38TUTdBwxBvaKEegU5Gj6orz1tFb8UR6iNjE8Ib4PDtlP4i8cgmJ19eRfE29o4q +AVWk/GtMeE2m4EslZJLgjP+L76CaYzGwWrCaVcsqhhxfB6qBQvUGtJIWqrfqBM7r66nAKoLoTiOr +SEqe9da1np2WEOfE/Qi0EvYMSZ43WkRknbVolcbdi5G+0o7CtqvhBgeMOgRFgFn7GInQgkJQBcUa +8CzAXSXkK+MhZz1gJQTQQ1th7pJvEVxKbBFlD2m8ScXQfJBzw6btnFauB5ywBKc5Ftw/qfIQFrIP +AyUDgFt3nYiKAXOW0OrtiCHroxJkZzE7EOpg7rwNqcgkRNuXUJM0jsYvQlpKOqg94eYw9AHiSAsp +i1kFyp0PGBkdoucaeWg7JUpL3ioeJ/+xeFhJdLARJMemRGjDG1LbdF+n+BatnDUoIn3vIY3pq6UI +n3JfGuH0AI5orRwmDUYy7mTroqBo2MoBXRsTtB8cGaeBYr76A4IYLMiGLyQY+6mkxM4G3DTeE7pF +0xEBI23kGBCEFWlD1IrbSOF+w50CKAAgdMAdKbeMYqCu8jqGlvq3oC8dIZ2qSgB7nOuOyk07hYMD +D9MpWjh5SxucI6iDu0M+cjAIeEdgMftceXNIQCUB0lYHNfsoEBR7iQw9aPfZhoitawuVqgSsJjPH +WrxaSZIwOjGaCE1JQSfjBrQnub05193/SbZlWMJDFqH82MBHfy7FaqpvHqoninx5vDboQuTdgEIR +qJXii4Y0jEwaY+/qFUgOqv0RgE8P9gWogtHxgFRvU8OxtsPqIjSLiBoUvgSMnS8SUPqZsE7nJUmw +YwIxJXcnLSgcoz4IMKqFicB9ISINMrer3CNrE+B+zGxWDbEWANmVUcdJH0glhB0PaVek2MAe0ypb +vEuVQiQIPQVSEdgPlKVANqz06iga9EBCo4ihsoAj3LMeVbjdE+tQi+SebACvUPCJgFeCU9SWXkRS +jZI/SR6An42VKYY7yh4UGoCbDxQtvqiglDg/sRagW8ixu9aI2hlBtCVgtHpPi1B1uxKvkjA/Gipn +U5u8Rpplp+wZReVHihmqSjmrlN7vfXTWVJWWc0Ljc+gdvZ+kOjv7HJJBLaK3AINJAUHwjBsSeqpD +Qo1QKGnUe66CaFbI+RFv1bU3wOIKqpgLWdkb6gl5c9B513LM6j7JOY1gMBqQWAekrblAMHYJrzU7 +dO7lMcVCxJVuFqIocH1GbvjicDmhRHRtQts0tvJyGrGrsBzEUtSJ8yh4AhB5vQ2opW2VcCwrk0ui +YSru9jcbhWHrVDYflCcIcWLuALsifaTIruu8LoGlW+TIL14BjAIcKioB0tokbhbhrSJYOhYcPVdc +MwlVzIvdhcgpOm8gcUzKKI+dnMWezCuy9saJbGrOEeCjwNComF4MQGIbzKLSU/Zpwqua3f8pCEyk +n3CBFRXtRAXa9vJtl/0lkyOwHijMdlQqWG9HBZTGZP4WNq/9POLnJrWe5CDiJ9hdIEwpkdxkRIcr +hB8KiJ3dNpKDoU0ri66BkdkbOUy//GSB2j8sp64R2K8cH9iOg2iIUKsESZrllzUZ4FliHUDT1gB3 +tAa/bV36EcASB3eHJ1IVI2gOiAy7rzaxHURtLBUOzmumiK9bC3IFhHeXi2LHSkSuUHCxFlHBSk7W +vikDqo6Rt0ixTWSXzC8Wn/jAOykAXhAAqga5pKmCDMAghi+QxDZ81Z7FnkieLWBVhoW30H0a59+I +0SvXA4uEE/Pak0ItTQcPqql4+XCiOUtcKVJYCS94wqYNPGpt6GoiL1JOIX21iGqBWrVATSLAO5Dn +AuWwP6lXMCRX/hnULJIblMU74lDIoPFj014Y3lwlcALo2zj/DBSnuDgjEMT/rBq2WHVQ6WLJlTNC +gHqqcbb7SJWkgrQ1g3NQBKJrlSmP3W6TiX1viwufeurGGAXJi4QAWPuuDCM4GlTdRmyjYtEgAk5F +HBhpADEAzi0GFhEUOVGU68C1YIKoizCCidBHub8HKuj8553qYfsogDHyXoBWI+9FzPKJSkBsAmJj +ol+olU2xtIQZqg4MtIhTtMkzXVihaNAqpQIFmGJeR0JRxl+Es0UtglOM2VmPwqkWKpRG5653N7nH +IjO5B16ZEzO0QjbpK8QhW6PT5smeWw9SmT+HQF6RaqYFRHsx4aoqRljk2DBV+WkrgZWKtpOnHMAL +RJCo6WSDNS4UISx3dzskEUTuxFeSq1FcPHvADoSSlrwjLPzGKYwxm4XsU+BnSw6mKKrAQCU7mNS4 +tkBSIMGhEn/wJR57ERPJSVJReCiDRNEgzErEFu4odGKl3URPuyEHw30kHHfZk5wclsYWMuQs+Oj6 +mv0UOpYZCNQ2NkXpb8k5i1rnLOpHmATgy6KSC4m+qLCGJNm8KICiJ590zDu8D0JXAS9cxN5mA43u +KAkBUuMAKRXDTRFk3wKbK3m+lTCWKIAiTVpWDgsS3FPBP4BVXKOcFOjnSv+GZ4X5I+QPeYEgleFa +Ej8RaT847ShEavJIZBO9NLRiEpNJqNQHsqwAAAcZtAVuskmO40PIhNoZAtlOQTQ4f7W7ypLDrhW4 +cgaH6HSNksqtpNuEDDIgtmXyyBaA5kLrwBkVmiprCwXcyUE+ewV5QjcqIjQCEUY2feo8cVzkuICs +wnYVyFajXgga8q6tOgASliOUmFeByLt4VD+JBMxUsL6y3rGqgNlRQBz6XALisElhCOp27Vgk1ymv +WaZpcVICjxbANlUDxRnwsN5pmnQUOgHesMiqevGEDXHqAZbHO9wWAHlv3OmDcKPxbgoaQ4eMBqqt +BwF487Y9X1fvlJCOIOEsNfYmqJpGxJtZPo+eYkNSyZrc3tSiCE/72gNcHlF6izwCTVIRD1S+sPAv +Yv0iO0WIIDea1ClV3+/r4BxKGm8YsDr0vFYhVbATsphDA6uSR9nSuOfVteJdUxLEtRYq2u99iKh/ +uPlJIrZPN7yfvmhXPbpzxO9zlp8WQLTjO6A/RxD+nPlv6CvrNtWNgIraRAvoI1c6YHvLXrgPwdkI +zc5vhBboAj2okiqJC3uQR4SqK2FkqiS2ur/EeuyB6ui1EtRxZynl8psmimGFVAe1DPE00PFFloB/ +s1UQplUEgSWL7MVdYSvIVSDcydIO0QCxirEcOzF/keWZhClMrweqE2SlkS+WxB0qkjF5PrL1F89H +VG06tqWoE4odz2V8UufUz2hJWDjEuaMgBLrqWQAyLFeaEuHS2O7jzX+sPy8oe5ROqFpycAQaXjII +E7cTz3Ffi9CiDo0gQGlVw0C1IFYUPE8ISsA3RtgsRmsYdPthULJDPzKXkj4qeyc6qSxYCNQjWQPc +oxofKndFDlW8vlUWuyl9ALNU90mFuYQrAFwpL3YFEoliV0gdcNSQ3A1IyZh7rjJXCp6zPFAM/00a +HAgKrcOIN0oNRFMkvgRBQYP5o3eCkg23r6sBqmGMQFAD1qssXtdNXAXuSHGrUkapliwSk68leEmz ++PbAs4b+SN4hDXILykCtHgYchurhvjLYcB/3NLVFkTVVEAtGBHW46QWZLkIPO6By3w== + + + egsV4cBCSJWs9QGKZ3D17cm9L1hVpmL5NZW4GGtcbu4sZNw4wphW8lCMeZEByqQB/SQ5BJ7ropCy ++qJTxY+nPIW2Js9mhgtbB7McW6f3zHPl4FEcLud2FmY7xf7i9HA+XZGQQk4T6sQJag0mSS+Z1OZA +HWVzCPE3iKzKptYmI4kFiycRBmDpYzEHShEB4+5g8CkOVqjlTVbl6EUH/ajHvWtmiMB7Bg99ifoQ +ygJagB5FCzOWPP/LdVs7SeOgBiqXEEKKP4cdK909htFbXzzlpiP3vylVE1SymILtsomQsthEGOfS ++uT0iYIzoQXykNNT6HfdJew8dcSGjbdQDBb4OuXyRzEeYDeTvUq6UO49zCLHdcVrh4f2n9X+VjE7 +7PBjvbcX7Xaej0MLgSTnTjX5cnwIu95kFaeotYjuM8dfpBRgUxtE7WrrRglQHmPDQ+9gkkVbDlgi +wnPFc4bIOuwFjloUDtLCA91NKNxmCKjyralV3RS8UtwkDQK8WxsEqJUdPa0IkxQc4SwLR5CnmCpS +H5vBsZLFx7wWoUuo1GT+C/AO7y+pkVHskASsB68rK4RlOs/RLo2Yl4TkqxappssLDIak7pzGhIfe +gY+KMpw7JcmpvIuIIO67pir1oLErPQPmKDFk4SQw2cH9FYsguUdMwoBFk1QTgYipmcPqieh0omoD +8VEEVc5qNfZu8pEmSTienHilwAahDsGBVzSoyphkzkRGPghAvN6/cuEU0qPQ/WLjKOzBIY8WEMe7 +r70Ddc2L0oVgFZjnGtG1RvIzAYUJk7kK+AGQILKOs4XCJ0QHNg8biwYildCrJn+OFBLyZ4n94dDT +8QOZTh6dYmSYUebDmZWSF+PLH2+j0ooxC7vB7XHQCJ2us6MSEEAESrUUFuJox/xqskMmQA9Ai9zX +mlL8MST0DWZtYVU6SxpQj7CkdR69lE+ULYThBQpaCq7A46JxeRZyLfanBcASFfyQVik4fLKWUiBQ +A7QQLurkbG9Uy5F9FcnIRj3PtYQeVmGV0IfxrMfwGQhckBxTW4moFk4jBS9Bjewll8nC6fUA9GoK +9kEO0xQ0cvdSeUZWuhB1lOfFOdPXWFSoOpi7kXUfpUxaq0jemFrgtaYytnjSclJqc4uV33oDij6Z +a1UapcrqbH01dbY+iCRoKkRVuNu7Dangb4jOwBR4P0rAOkA4ulpDn1XRR9JF1bJSjXZ2SWjqNOqi +oErt8GbCWiWMcZu+Ea2GWY2N3MVkDSIuWopMS2VryAJ1RXFe+ppMdMjXhHKnQnphqwaSxcxUwskO +1wZe+KxaXQA0wKAiqYDwru6RhkrwCsxtI6FYH4BrCJdYdiR7nIdCSWkGcZDo/QjzDWSRS8XI4jZU +18RG1EHXltyPAPvsoJLPUcok1RbKo1aE7Kq0J/kwSYLGp1fJvkSK6w2qTWonlEkv3HmmGpksbRVH +dkwodPdGafP1OVXfpSaIImMl8auWlHAw3hEIRyn05QQXmW3qqoIfsywjFzexKrZ2o6KExOKoX4DL +Kv4Hh1ArgUQ0PmvmglZYZISSjlRg8AU8KPiDNDgCWRB+gt7X2tiECfyyaFjT4OB/dtDZv1l38RBl +BhmmqIXDrwRfyYRuQ1T+CoAVSS16OcUFe56WKQyqZOtQPnqP0VHixsB7lBPAQAUg8c2LBNV6XUEb +5RJNuBhxQ4mVB85FRZFt0eCt8VxOIZKXrFy9odrKVJZw4KVRtIpO09M1lAmOI2jkSWAKVWrfCYC3 +VSFvqqDLYdTdWm1KYByKt/DgWSPqhkCmKK75Ti4QT+oYs974cvA0kgBnK+a9q3Kth9ixozyHJHad +HqfIGcpkBO+eBkJlANGy9GNKRHKiM6xpleWTfWVnYzGTgTw4kv4zUSSQhAXsRHJMm+pyq5WdBAhx +BCug0zvMTRFdDpTrLZGqIDwwpbpClhfoMoqXcHAUae44peS6zUKuID+85rEQL6W6NkMoqErXoFrY +oSYlaTENsqcoMub0GMRLi3RGsIxxGLItOSHFNi/sOenFpliQo8pzVHqviKcmAJKOQQh4uFOUVpAU +p4FNsHihJylq2FX9mKTG6CkdJgsRsqIcKn8ApxwpV31TEwDMrCdfhgxOW309VSLEBxk5efshfVQN +YSODhQaCxxxNNdYTgwd3TS1d7WvpanQyKVt7oO3Zmh7UACmHmsI5L0MzKe8Emds43CKG7Zhn0Ek5 +ocAQZ6WKmLLQ/EjiV5pC6gkGmNmW81Ar0B2IUUyGtOj0CqoQjSpkV/jG9Namqwg/gg2kaDrDPh08 +wU2s6FhxIXtOhs6iDNJddB++MG9tpcCLXY1OMnrgXTFjWHQteYQ2ahzHihbu4YhyutYVF+uK62uf +ZHSSEZideUmJx0QMck3DxkMgE8r2VBRwNiYEghS5H0naI483ibixkMnhOLOgXQ/a9Nk5hBQtRqsg +hVW0C6TggrhRs93gI/LY8QBqcFRSB+WShJoGYWk3wMa0lIGZUCYBE4qXAMArSUCAYojDQLwuRV5k +kn0J9GPUAlNa/U+N506FplYvkxaGHUqVJCmQ5BcLH7bpPJEMLT1SjI79UXCbdejfg+OlFkF/wnhY +488UxLS6D/UvgwpoO6iFBjlGsgatZbElQVQ47EabnaRzKJQW2i7OjYeBHG2KTIe85uGCIhIPV1Mq +iluOnqHFiAZVXjC7QYSPLtnwtplkI0VQvrSi3J7Q2EHdC9lHSPgUoo/hRA+lw8JWuqwMKUK4rUot +2Y5ZkXg8EkTzG5U8wbmtEltSpSu7MWOufZ06cEY6AWKw8ADEsE2WRLijeGxSLre3yMjIRkhgRWCn +ErNJgEU1530s0AdsjzgssI3QqelF8X3Yizp0K7mKoiNNVO60FaiCZCycWGKbrNlIMPE0xbMiVMXa +gGa4nF3AT5i7rNQL5ZB0IlilcLDAJEYNopN7iYQrqC6w5mzAc4VCDc+VovapluKaPppS51kIqhWy +xdFDaq96Q3iEAkAK7eB7DvOKY6uLjlsJgNCgvNvOzxMQlQg6wjrj4FukGSiHRBD2nTPjsLs5cyKn +uUJkBKMKkeqKq4Z62DqUMM7ZCp5D/SaAqdHhiPsMtxXYEMGHRDhySlojSkwGpqoksFKyF3047AS2 +xgibAvct26RlQCr2qOxTCi94YQo4M0iSrsQIfIhMpp4nO7yyv2oRrZmCUqKDaSC6GuFriApF0fRW +3njUZWVjgWfdV+hQWOaEZcYRCRhVJ0DGJMlCl7vGcfTIIHDIRrRnkprzWK5FvebgmFjK6hNxXesy +sYiQC/ciWpLi3tgcYLvlCsPRjIsaGsNWBhmuSAx+olj+fanf9zImvSoyoRM5q27xBmY6KlelcxxS +DHNyQX2hpH3lkuFqiEKuBPVcBamhU7oN6UikKcLpLtYGOF1JXQB91+RjRT2DJK9VVofjDGI7906J +3kZgisEkrgWvMhlJanPoUw4rwPnlrwHNuRPZENnOIxTZ4MoPGFtB6SUNJjRlhg2RMcdm69VCCenO +F0CdAvaj6jwx9YrTdBcqVaKGYkQgI1gIOguZ6ZyZoZ51UDfDyKF+lEpnAPo3dC25dTA1CVOivJxV +uI3YzJTS45qqD6gmOLlRzjreOPx2BO2FKEwbayJcQEo6FoOyrJANRVCzja/MFCj8ySOeQ+t4Dktp +OHhWgu0m4WXzxsIxgspBsTiymKSzEnMKXiEu/LVgGwWp888aofCEHYg7u5r4EyXqXMUEiKDzOnjt +INjVcWcpAiJXo8n0gs3bwoVI1k/XUTFfQxQEOMilsXGLNewrqACvemsV6hBEaCbnAQOIrom2As9n +cXbNCKpTJrBKTwA1l+sDOdCPB1nvqqidda0gXFsn3LIvOnmPuW/BT4rRnzznAPoCEewovIFrSbtR +riwPSYj9HRDMUVuFnrHuyCTGK6IQSThsQjjSBCLywHdFw4vy/gsmLoqQ67bY3HujmR+EEC1qcQUv +AtziNUzu4oFYniNEi7bbBxgAkl6uOSgXhRbZVRb4Ii3RloPqv3UgOKO4Nox8LqLLIaM0LPVuUmbR +UYMqi0F1hnImAHalNN7RrwgDLL4beDndABfptCyVrFR2u0XjqYRChgwCI66rrLR1DRFR7b2+XkR4 +pBMCooAdH3vCfYROmhFg1AE6O3qEsSMZqcK7xl3nSvsInQub0VRpwE3KNelCCPmCkw3yahIzq3p5 +UxPK4BNg28EEB8PunXNyn+vg3oCh761m9uZffHX05ujg4ujlN/aIW2prb3mDtPjtn04v1m/HA8/O +dcuLY5p+87ef3xz5XW9tzT8H354c+Wu/ODv85n8f/ey/yc3it18dHZzc8KvfHb/+5suj88Oj04u1 +9rc948vzox+Pj36yR5y8ndj0q7Of3t46bmsvYW99fPR29Q6kOEOaBukssd9be/C3Y+vzfx6/vPi+ +PoakKtX7BIzE8v4f/s+j4+++v7jrBV8cvbr4xlbEH87PTu9s/LezN1faXlkJv3/2q8//1H7z+9OX +9Zd8znz+5i9np1/aUr6w1by355efH31nd1v74tlf3vBN7988P3/39vvxPr/+y9FPi/phMTS/edYs +Prf//8dPz5TT8e5ZWHz+5pkKsvW///jZPvwv++P/2aWfFmnx58X/+b/N4iW/+erZHkHUhf7zevkB +TlFg315whQ9U9dx62X7yYv0+L56d8iq8w1fPAIyE5ybZQU38j2JhcuNJMU7CEiJfhOATuXMgEqlw +fF+kVLFLKo3LtGnXUdjNWFUy4HriyaLZr5XnNfmkXfzj85t74F1VGXu+1LGvr7QfPIHuavt6ubZf +fRG7fRksry/fZrx87f5QoZLAeOX+4+Vr9xcVeLx2//Ey7f/u68YW2qVVM2kxhSmLKbx3MVGhTIKv +3hqU5uW71suEIAlXv6g9y7dcG3/+4ua7bt8iS/vYlGu9UcejaLKHq51sVXqd1i57z+GL9AVQTK1Z +rIajVdotJvWlIcprawrORm4oc7+/9vh6WY9Hh8R0W13mnVilmXeK2bNQL10ETjwJDO3S5dWEXHkP +yrMp3r3yHuNlfw98nsN6a+5NWcdAVi96C7Rgly5efo/l5dvfA+V+3DNr71Ev+3tk9P31yz5I2uX6 +C6qdyxcvvcfq8pX3iCsxub4fVpfH27y4chmGyKBlEWXjp/oeQWO01g+/9sWVTvvVDxAIfz89PXh9 +9HKhq4iGRXtdKtx6uoTm/ccLZmJRxSm7ae3wuPbFeAEkiH7tgLnt+urg8Re0f/5rO4SDCUkt1Z4s +c4AYMinYywuZIGpX9wll430IHTytpbveqK6q6/fKAiZzWNR0270uNxp3ytVm7DpKMZ1+8vWNz7vc +5ta38sLpDpLe2N32Vpcb3fpWUEoVnghq0G2vdbkR92JFvm+BkaVUzF5/fXWFjV+c3Lxum0vzcnOj +a3f6/tl/2hr9/56NWuIWLlMBF1AjAstF/55leqlRnS6KvUlmGCdR9QfFmhEmvu1eVw== + + + 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 + + + diff --git a/setup.py b/setup.py index caaf5a65..608d4088 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ data_files=[ (os.path.join(sys.prefix, 'share/applications'), ['install/org.onionshare.OnionShare.desktop']), (os.path.join(sys.prefix, 'share/icons/hicolor/scalable/apps'), ['install/org.onionshare.OnionShare.svg']), (os.path.join(sys.prefix, 'share/metainfo'), ['install/org.onionshare.OnionShare.appdata.xml']), - (os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']), (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')), diff --git a/stdeb.cfg b/stdeb.cfg index b9321da8..8c7ef6f5 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-requests +Build-Depends: python3, python3-all, python3-pytest, python3-requests Suite: cosmic X-Python3-Version: >= 3.5.3 From 52d72642d668e8084b3174a7aaccd37dd20be932 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:23:19 -0700 Subject: [PATCH 59/64] Fix bug with rendering index.html files in subdirs --- onionshare/web/website_mode.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 0b7602ea..9dac8627 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -30,11 +30,13 @@ class WebsiteModeWeb(SendBaseModeWeb): """ return self.render_logic(path) - def directory_listing_template(self, path, files, dirs): + def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): return make_response(render_template('listing.html', path=path, files=files, dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, static_url_path=self.web.static_url_path)) def set_file_info_custom(self, filenames, processed_size_callback): @@ -51,7 +53,7 @@ class WebsiteModeWeb(SendBaseModeWeb): index_path = os.path.join(path, 'index.html') if index_path in self.files: # Render it - return self.stream_individual_file(filesystem_path) + return self.stream_individual_file(self.files[index_path]) else: # Otherwise, render directory listing From cccbe35276dac210201d7a4a57ea916ed048e39c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:44:24 -0700 Subject: [PATCH 60/64] Add breadcrumbs for website mode --- onionshare/web/send_base_mode.py | 8 +++++++- onionshare/web/share_mode.py | 2 +- share/static/css/style.css | 24 ++++++++++++++++++++++++ share/templates/listing.html | 6 ++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 67fb26d0..24ad55d7 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -90,9 +90,15 @@ class SendBaseModeWeb: 'status_code': 200 }) + breadcrumbs = [('☗', '/')] + parts = path.split('/')[:-1] + for i in range(len(parts)): + breadcrumbs.append(('{}'.format(parts[i]), '/{}/'.format('/'.join(parts[0:i+1])))) + breadcrumbs_leaf = breadcrumbs.pop()[0] + # If filesystem_path is None, this is the root directory listing files, dirs = self.build_directory_listing(filenames, filesystem_path) - r = self.directory_listing_template(path, files, dirs) + r = self.directory_listing_template(path, files, dirs, breadcrumbs, breadcrumbs_leaf) return self.web.add_security_headers(r) def build_directory_listing(self, filenames, filesystem_path): diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index f52bc2c7..3d1af447 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -168,7 +168,7 @@ class ShareModeWeb(SendBaseModeWeb): r.headers.set('Content-Type', content_type) return r - def directory_listing_template(self, path, files, dirs): + def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): return make_response(render_template( 'send.html', file_info=self.file_info, diff --git a/share/static/css/style.css b/share/static/css/style.css index bc986e57..af41b155 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -74,6 +74,30 @@ a.button:visited { bottom: 10px; } +ul.breadcrumbs { + display: block; + list-style: none; + margin: 10px 0; + padding: 0; +} + +ul.breadcrumbs li { + display: inline-block; + list-style: none; + margin: 0; + padding: 5px; + color: #999999; +} + +ul.breadcrumbs li span.sep { + padding-left: 5px; +} + +ul.breadcrumbs li a:link, ul.breadcrumbs li a:visited { + color: #666666; + border-bottom: 1px solid #666666; +} + table.file-list { width: 100%; margin: 0 auto; diff --git a/share/templates/listing.html b/share/templates/listing.html index e394f842..2f70dbf0 100644 --- a/share/templates/listing.html +++ b/share/templates/listing.html @@ -12,6 +12,12 @@

OnionShare

+ {% if breadcrumbs %} + + {% endif %} + From b844dfdccfa1f4ce612fae1d923df542dee8fe9c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:46:28 -0700 Subject: [PATCH 61/64] Add breadcrumbs to share mode --- onionshare/web/share_mode.py | 2 ++ share/templates/send.html | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 3d1af447..8a3f5969 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -174,6 +174,8 @@ class ShareModeWeb(SendBaseModeWeb): file_info=self.file_info, files=files, dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, filename=os.path.basename(self.download_filename), filesize=self.filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), diff --git a/share/templates/send.html b/share/templates/send.html index 916b3bfe..941c4130 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -22,6 +22,12 @@

OnionShare

+ {% if breadcrumbs %} + + {% endif %} +
Filename
From cc965a875c9ae10364226973b0d9dcf623a6f21f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 17:06:03 -0700 Subject: [PATCH 62/64] Validate filenames, and require filenames be passed in, in website mode as well as share mode --- onionshare/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 7e7798f8..65e605f6 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -88,13 +88,14 @@ def main(cwd=None): else: mode = 'share' - # Make sure filenames given if not using receiver mode - if mode == 'share' and len(filenames) == 0: - parser.print_help() - sys.exit() + # In share an website mode, you must supply a list of filenames + if mode == 'share' or mode == 'website': + # Make sure filenames given if not using receiver mode + if len(filenames) == 0: + parser.print_help() + sys.exit() - # Validate filenames - if mode == 'share': + # Validate filenames valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): From bd493d20a7da44839c10b4af66e9d89f678a3b0a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 14:51:16 +1000 Subject: [PATCH 63/64] Ensure the backend receives the latest settings object before starting the onion service, and likewise for the GUI, so that we absolutely always save the private key for persistence back to the json settings file when we need to --- onionshare/onion.py | 4 ++++ onionshare_gui/server_status.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/onionshare/onion.py b/onionshare/onion.py index 2f4ddffd..b0499449 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -438,6 +438,10 @@ class Onion(object): return the onion hostname. """ self.common.log('Onion', 'start_onion_service') + # Settings may have changed in the frontend but not updated in our settings object, + # such as persistence. Reload the settings now just to be sure. + self.settings.load() + self.auth_string = None if not self.supports_ephemeral: diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 3a6e31cc..dbcc6ca8 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -240,6 +240,9 @@ class ServerStatus(QtWidgets.QWidget): """ # Set the URL fields if self.status == self.STATUS_STARTED: + # The backend Onion may have saved new settings, such as the private key. + # Reload the settings before saving new ones. + self.common.settings.load() self.show_url() if self.common.settings.get('save_private_key'): From da355ed8f923bddf49d96451ddf9f5ee26b14ccc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 17:58:15 +1000 Subject: [PATCH 64/64] Source string suggestions from the translators on Weblate --- onionshare_gui/onionshare_gui.py | 2 +- share/locale/en.json | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 20873bc8..8e77efe4 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -487,7 +487,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"])) if event["type"] == Web.REQUEST_INVALID_PASSWORD: - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('invalid_password_guess'), event["data"])) + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('incorrect_password'), event["data"])) mode.timer_callback() diff --git a/share/locale/en.json b/share/locale/en.json index aab6153d..bb5b9094 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -3,7 +3,7 @@ "not_a_readable_file": "{0:s} is not a readable file.", "no_available_port": "Could not find an available port to start the onion service", "other_page_loaded": "Address loaded", - "invalid_password_guess": "Invalid password guess", + "incorrect_password": "Incorrect password", "close_on_autostop_timer": "Stopped because auto-stop timer ran out", "closing_automatically": "Stopped because transfer is complete", "large_filesize": "Warning: Sending a large share could take hours", @@ -98,20 +98,20 @@ "error_tor_protocol_error_unknown": "There was an unknown error with Tor", "connecting_to_tor": "Connecting to the Tor network", "update_available": "New OnionShare out. Click here to get it.

You are using {} and the latest is {}.", - "update_error_check_error": "Could not check for new versions: The OnionShare website is saying the latest version is the unrecognizable '{}'…", + "update_error_check_error": "Could not check for new version: The OnionShare website is saying the latest version is the unrecognizable '{}'…", "update_error_invalid_latest_version": "Could not check for new version: Maybe you're not connected to Tor, or the OnionShare website is down?", "update_not_available": "You are running the latest OnionShare.", "gui_tor_connection_ask": "Open the settings to sort out connection to Tor?", "gui_tor_connection_ask_open_settings": "Yes", "gui_tor_connection_ask_quit": "Quit", "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.", - "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", + "gui_tor_connection_canceled": "Could not connect to Tor.\n\nMake sure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", "gui_tor_connection_lost": "Disconnected from Tor.", "gui_server_started_after_autostop_timer": "The auto-stop timer ran out before the server started. Please make a new share.", - "gui_server_autostop_timer_expired": "The auto-stop timer already ran out. Please update it to start sharing.", - "gui_server_autostart_timer_expired": "The scheduled time has already passed. Please update it to start sharing.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing.", - "share_via_onionshare": "OnionShare it", + "gui_server_autostop_timer_expired": "The auto-stop timer already ran out. Please adjust it to start sharing.", + "gui_server_autostart_timer_expired": "The scheduled time has already passed. Please adjust it to start sharing.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "The auto-stop time can't be the same or earlier than the auto-start time. Please adjust it to start sharing.", + "share_via_onionshare": "Share via OnionShare", "gui_connect_to_tor_for_onion_settings": "Connect to Tor to see onion service settings", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address", @@ -146,12 +146,12 @@ "gui_settings_public_mode_checkbox": "Public mode", "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}", "gui_settings_language_label": "Preferred language", - "gui_settings_language_changed_notice": "Restart OnionShare for your change in language to take effect.", + "gui_settings_language_changed_notice": "Restart OnionShare for the new language to be applied.", "systray_menu_exit": "Quit", "systray_page_loaded_title": "Page Loaded", "systray_page_loaded_message": "OnionShare address loaded", - "systray_site_loaded_title": "Site Loaded", - "systray_site_loaded_message": "OnionShare site loaded", + "systray_site_loaded_title": "Website Loaded", + "systray_site_loaded_message": "OnionShare website loaded", "systray_share_started_title": "Sharing Started", "systray_share_started_message": "Starting to send files to someone", "systray_share_completed_title": "Sharing Complete",
Filename