From 2d43588a3bfd6afee8bcc4239a3259870bfc504b Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 19 Apr 2019 14:25:42 +0200 Subject: Add website sharing and directory listing cli-only --- install/check_lacked_trans.py | 1 + install/requirements.txt | 1 + onionshare/__init__.py | 15 ++++ onionshare/web/web.py | 6 +- onionshare/web/website_mode.py | 154 +++++++++++++++++++++++++++++++++++++++++ share/templates/listing.html | 40 +++++++++++ 6 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 onionshare/web/website_mode.py create mode 100644 share/templates/listing.html diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 010cdb7a..5ccce923 100755 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -59,6 +59,7 @@ def main(): files_in(dir, 'onionshare_gui/mode') + \ files_in(dir, 'onionshare_gui/mode/share_mode') + \ files_in(dir, 'onionshare_gui/mode/receive_mode') + \ + files_in(dir, 'onionshare_gui/mode/website_mode') + \ files_in(dir, 'install/scripts') + \ files_in(dir, 'tests') pysrc = [p for p in src if p.endswith('.py')] diff --git a/install/requirements.txt b/install/requirements.txt index 0abd773f..fff0b009 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -3,6 +3,7 @@ certifi==2019.3.9 chardet==3.0.4 Click==7.0 Flask==1.0.2 +Flask-HTTPAuth future==0.17.1 idna==2.8 itsdangerous==1.1.0 diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 620ada98..dad092ed 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -51,6 +51,7 @@ def main(cwd=None): parser.add_argument('--connect-timeout', metavar='', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)") parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)") parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them") + parser.add_argument('--website', action='store_true', dest='website', help=strings._("help_website")) parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share") @@ -68,10 +69,13 @@ def main(cwd=None): connect_timeout = int(args.connect_timeout) stealth = bool(args.stealth) receive = bool(args.receive) + website = bool(args.website) config = args.config if receive: mode = 'receive' + elif website: + mode = 'website' else: mode = 'share' @@ -168,6 +172,15 @@ def main(cwd=None): print(e.args[0]) sys.exit() + if mode == 'website': + # Prepare files to share + print(strings._("preparing_website")) + try: + web.website_mode.set_file_info(filenames) + except OSError as e: + print(e.strerror) + sys.exit(1) + if mode == 'share': # Prepare files to share print("Compressing files.") @@ -206,6 +219,8 @@ def main(cwd=None): # Build the URL if common.settings.get('public_mode'): url = 'http://{0:s}'.format(app.onion_host) + elif mode == 'website': + url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) else: url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index edaf75f1..0ba8c6b3 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -15,7 +15,7 @@ from .. import strings from .share_mode import ShareModeWeb from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest - +from .website_mode import WebsiteModeWeb # Stub out flask's show_server_banner function, to avoiding showing warnings that # are not applicable to OnionShare @@ -111,13 +111,15 @@ class Web(object): self.receive_mode = None if 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 define_common_routes(self): """ - Common web app routes between sending and receiving + Common web app routes between sending, receiving and website modes. """ @self.app.errorhandler(404) def page_not_found(e): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py new file mode 100644 index 00000000..7b8429ae --- /dev/null +++ b/onionshare/web/website_mode.py @@ -0,0 +1,154 @@ +import os +import sys +import tempfile +import mimetypes +from flask import Response, request, render_template, make_response, send_from_directory +from flask_httpauth import HTTPBasicAuth + +from .. import strings + + +class WebsiteModeWeb(object): + """ + All of the web logic for share mode + """ + def __init__(self, common, web): + self.common = common + self.common.log('WebsiteModeWeb', '__init__') + + self.web = web + self.auth = HTTPBasicAuth() + + # Information about the file to be shared + self.file_info = [] + self.website_folder = '' + self.download_filesize = 0 + self.visit_count = 0 + + self.users = { } + + self.define_routes() + + def define_routes(self): + """ + The web app routes for sharing a website + """ + + @self.auth.get_password + def get_pw(username): + self.users['onionshare'] = self.web.slug + + if self.common.settings.get('public_mode'): + return True # let the request through, no questions asked! + elif username in self.users: + return self.users.get(username) + else: + return None + + @self.web.app.route('/download/') + @self.auth.login_required + def path_download(page_path): + return path_download(page_path) + + @self.web.app.route('/') + @self.auth.login_required + def path_public(page_path): + return path_logic(page_path) + + @self.web.app.route("/") + @self.auth.login_required + def index_public(): + return path_logic('') + + def path_download(file_path=''): + """ + Render the download links. + """ + self.web.add_request(self.web.REQUEST_LOAD, request.path) + if not os.path.isfile(os.path.join(self.website_folder, file_path)): + return self.web.error404() + + return send_from_directory(self.website_folder, file_path) + + def path_logic(page_path=''): + """ + Render the onionshare website. + """ + + self.web.add_request(self.web.REQUEST_LOAD, request.path) + + if self.file_info['files']: + self.website_folder = os.path.dirname(self.file_info['files'][0]['filename']) + elif self.file_info['dirs']: + self.website_folder = self.file_info['dirs'][0]['filename'] + else: + return self.web.error404() + + if any((fname == 'index.html') for fname in os.listdir(self.website_folder)): + self.web.app.static_url_path = self.website_folder + self.web.app.static_folder = self.website_folder + if not os.path.isfile(os.path.join(self.website_folder, page_path)): + page_path = os.path.join(page_path, 'index.html') + + return send_from_directory(self.website_folder, page_path) + + elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in os.listdir(self.website_folder)): + filenames = [] + for i in os.listdir(self.website_folder): + filenames.append(os.path.join(self.website_folder, i)) + + self.set_file_info(filenames) + + r = make_response(render_template( + 'listing.html', + file_info=self.file_info, + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize))) + + return self.web.add_security_headers(r) + + else: + return self.web.error404() + + + 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("WebsiteModeWeb", "set_file_info") + self.web.cancel_compression = True + + self.cleanup_filenames = [] + + # build file info list + self.file_info = {'files': [], 'dirs': []} + for filename in filenames: + info = { + 'filename': filename, + 'basename': os.path.basename(filename.rstrip('/')) + } + if os.path.isfile(filename): + info['size'] = os.path.getsize(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['files'].append(info) + if os.path.isdir(filename): + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['dirs'].append(info) + + self.download_filesize += info['size'] + + self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) + self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + + # Check if there's only 1 file and no folders + if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: + self.download_filename = self.file_info['files'][0]['filename'] + self.download_filesize = self.file_info['files'][0]['size'] + + self.download_filesize = os.path.getsize(self.download_filename) + + + return True diff --git a/share/templates/listing.html b/share/templates/listing.html new file mode 100644 index 00000000..a514e5d2 --- /dev/null +++ b/share/templates/listing.html @@ -0,0 +1,40 @@ + + + + OnionShare + + + + + +
+
+
    +
  • Total size: {{ filesize_human }}
  • +
+
+ +

OnionShare

+
+ + + + + + + + + {% for info in file_info.files %} + + + + + + {% endfor %} +
FilenameSize
+ + {{ info.basename }} + {{ info.size_human }}download
+ + + -- cgit v1.2.3-54-g00ecf From 391c82f2a6ac7e0260f06d6018df57bc52da95da Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 19 Apr 2019 16:52:43 +0200 Subject: Add gui for website sharing and listing --- onionshare/__init__.py | 2 +- onionshare/web/website_mode.py | 16 +- onionshare_gui/mode/file_selection.py | 430 +++++++++++++++++++++++ onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/mode/share_mode/file_selection.py | 430 ----------------------- onionshare_gui/mode/website_mode/__init__.py | 323 +++++++++++++++++ onionshare_gui/onionshare_gui.py | 64 ++++ onionshare_gui/server_status.py | 25 +- setup.py | 1 + share/locale/en.json | 1 + 10 files changed, 855 insertions(+), 439 deletions(-) create mode 100644 onionshare_gui/mode/file_selection.py delete mode 100644 onionshare_gui/mode/share_mode/file_selection.py create mode 100644 onionshare_gui/mode/website_mode/__init__.py diff --git a/onionshare/__init__.py b/onionshare/__init__.py index dad092ed..a96f2fca 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -257,7 +257,7 @@ def main(cwd=None): if app.autostop_timer > 0: # if the auto-stop timer was set and has run out, stop the server if not app.autostop_timer_thread.is_alive(): - if mode == 'share': + 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: print("Stopped because auto-stop timer ran out") diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 7b8429ae..51137183 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -38,25 +38,29 @@ class WebsiteModeWeb(object): def get_pw(username): self.users['onionshare'] = self.web.slug - if self.common.settings.get('public_mode'): - return True # let the request through, no questions asked! - elif username in self.users: + if username in self.users: return self.users.get(username) else: return None + @self.web.app.before_request + def conditional_auth_check(): + if not self.common.settings.get('public_mode'): + @self.auth.login_required + def _check_login(): + return None + + return _check_login() + @self.web.app.route('/download/') - @self.auth.login_required def path_download(page_path): return path_download(page_path) @self.web.app.route('/') - @self.auth.login_required def path_public(page_path): return path_logic(page_path) @self.web.app.route("/") - @self.auth.login_required def index_public(): return path_logic('') diff --git a/onionshare_gui/mode/file_selection.py b/onionshare_gui/mode/file_selection.py new file mode 100644 index 00000000..a7af61f8 --- /dev/null +++ b/onionshare_gui/mode/file_selection.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +import os +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from ..widgets import Alert, AddFileDialog + +class DropHereLabel(QtWidgets.QLabel): + """ + When there are no files or folders in the FileList yet, display the + 'drop files here' message and graphic. + """ + def __init__(self, common, parent, image=False): + self.parent = parent + super(DropHereLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + + if image: + self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + else: + self.setText(strings._('gui_drag_and_drop')) + self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + + self.hide() + + def dragEnterEvent(self, event): + self.parent.drop_here_image.hide() + self.parent.drop_here_text.hide() + event.accept() + + +class DropCountLabel(QtWidgets.QLabel): + """ + While dragging files over the FileList, this counter displays the + number of files you're dragging. + """ + def __init__(self, common, parent): + self.parent = parent + super(DropCountLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + self.setText(strings._('gui_drag_and_drop')) + self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.hide() + + def dragEnterEvent(self, event): + self.hide() + event.accept() + + +class FileList(QtWidgets.QListWidget): + """ + The list of files and folders in the GUI. + """ + files_dropped = QtCore.pyqtSignal() + files_updated = QtCore.pyqtSignal() + + def __init__(self, common, parent=None): + super(FileList, self).__init__(parent) + + self.common = common + + self.setAcceptDrops(True) + self.setIconSize(QtCore.QSize(32, 32)) + self.setSortingEnabled(True) + self.setMinimumHeight(160) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.drop_here_image = DropHereLabel(self.common, self, True) + self.drop_here_text = DropHereLabel(self.common, self, False) + self.drop_count = DropCountLabel(self.common, self) + self.resizeEvent(None) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # file list should have a background image if empty + if self.count() == 0: + self.drop_here_image.show() + self.drop_here_text.show() + else: + self.drop_here_image.hide() + self.drop_here_text.hide() + + def server_started(self): + """ + Update the GUI when the server starts, by hiding delete buttons. + """ + self.setAcceptDrops(False) + self.setCurrentItem(None) + self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + for index in range(self.count()): + self.item(index).item_button.hide() + + def server_stopped(self): + """ + Update the GUI when the server stops, by showing delete buttons. + """ + self.setAcceptDrops(True) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + for index in range(self.count()): + self.item(index).item_button.show() + + def resizeEvent(self, event): + """ + When the widget is resized, resize the drop files image and text. + """ + offset = 70 + self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) + self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) + + if self.count() > 0: + # Add and delete an empty item, to force all items to get redrawn + # This is ugly, but the only way I could figure out how to proceed + item = QtWidgets.QListWidgetItem('fake item') + self.addItem(item) + self.takeItem(self.row(item)) + self.update() + + # Extend any filenames that were truncated to fit the window + # We use 200 as a rough guess at how wide the 'file size + delete button' widget is + # and extend based on the overall width minus that amount. + for index in range(self.count()): + metrics = QtGui.QFontMetrics(self.item(index).font()) + elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + self.item(index).setText(elided) + + + def dragEnterEvent(self, event): + """ + dragEnterEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + count = len(event.mimeData().urls()) + self.drop_count.setText('+{}'.format(count)) + + size_hint = self.drop_count.sizeHint() + self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.show() + event.accept() + else: + event.ignore() + + def dragLeaveEvent(self, event): + """ + dragLeaveEvent for dragging files and directories into the widget. + """ + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.drop_count.hide() + event.accept() + self.update() + + def dragMoveEvent(self, event): + """ + dragMoveEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + dropEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + for url in event.mimeData().urls(): + filename = str(url.toLocalFile()) + self.add_file(filename) + else: + event.ignore() + + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.drop_count.hide() + + self.files_dropped.emit() + + def add_file(self, filename): + """ + Add a file or directory to this widget. + """ + filenames = [] + for index in range(self.count()): + filenames.append(self.item(index).filename) + + if filename not in filenames: + if not os.access(filename, os.R_OK): + Alert(self.common, strings._("not_a_readable_file").format(filename)) + return + + fileinfo = QtCore.QFileInfo(filename) + ip = QtWidgets.QFileIconProvider() + icon = ip.icon(fileinfo) + + if os.path.isfile(filename): + size_bytes = fileinfo.size() + size_readable = self.common.human_readable_filesize(size_bytes) + else: + size_bytes = self.common.dir_size(filename) + size_readable = self.common.human_readable_filesize(size_bytes) + + # Create a new item + item = QtWidgets.QListWidgetItem() + item.setIcon(icon) + item.size_bytes = size_bytes + + # Item's filename attribute and size labels + item.filename = filename + item_size = QtWidgets.QLabel(size_readable) + item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + + item.basename = os.path.basename(filename.rstrip('/')) + # Use the basename as the method with which to sort the list + metrics = QtGui.QFontMetrics(item.font()) + elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + item.setData(QtCore.Qt.DisplayRole, elided) + + # Item's delete button + def delete_item(): + itemrow = self.row(item) + self.takeItem(itemrow) + self.files_updated.emit() + + item.item_button = QtWidgets.QPushButton() + item.item_button.setDefault(False) + item.item_button.setFlat(True) + item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.clicked.connect(delete_item) + item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + + # Item info widget, with a white background + item_info_layout = QtWidgets.QHBoxLayout() + item_info_layout.setContentsMargins(0, 0, 0, 0) + item_info_layout.addWidget(item_size) + item_info_layout.addWidget(item.item_button) + item_info = QtWidgets.QWidget() + item_info.setObjectName('item-info') + item_info.setLayout(item_info_layout) + + # Create the item's widget and layouts + item_hlayout = QtWidgets.QHBoxLayout() + item_hlayout.addStretch() + item_hlayout.addWidget(item_info) + widget = QtWidgets.QWidget() + widget.setLayout(item_hlayout) + + item.setSizeHint(widget.sizeHint()) + + self.addItem(item) + self.setItemWidget(item, widget) + + self.files_updated.emit() + + +class FileSelection(QtWidgets.QVBoxLayout): + """ + The list of files and folders in the GUI, as well as buttons to add and + delete the files and folders. + """ + def __init__(self, common, parent): + super(FileSelection, self).__init__() + + self.common = common + self.parent = parent + + self.server_on = False + + # File list + self.file_list = FileList(self.common) + self.file_list.itemSelectionChanged.connect(self.update) + self.file_list.files_dropped.connect(self.update) + self.file_list.files_updated.connect(self.update) + + # Buttons + if self.common.platform == 'Darwin': + # The macOS sandbox makes it so the Mac version needs separate add files + # and folders buttons, in order to use native file selection dialogs + self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button.clicked.connect(self.add_files) + self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button.clicked.connect(self.add_folder) + else: + self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button.clicked.connect(self.add) + self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) + self.delete_button.clicked.connect(self.delete) + button_layout = QtWidgets.QHBoxLayout() + button_layout.addStretch() + if self.common.platform == 'Darwin': + button_layout.addWidget(self.add_files_button) + button_layout.addWidget(self.add_folder_button) + else: + button_layout.addWidget(self.add_button) + button_layout.addWidget(self.delete_button) + + # Add the widgets + self.addWidget(self.file_list) + self.addLayout(button_layout) + + self.update() + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # All buttons should be hidden if the server is on + if self.server_on: + if self.common.platform == 'Darwin': + self.add_files_button.hide() + self.add_folder_button.hide() + else: + self.add_button.hide() + self.delete_button.hide() + else: + if self.common.platform == 'Darwin': + self.add_files_button.show() + self.add_folder_button.show() + else: + self.add_button.show() + + # Delete button should be hidden if item isn't selected + if len(self.file_list.selectedItems()) == 0: + self.delete_button.hide() + else: + self.delete_button.show() + + # Update the file list + self.file_list.update() + + def add(self): + """ + Add button clicked. + """ + file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) + if file_dialog.exec_() == QtWidgets.QDialog.Accepted: + for filename in file_dialog.selectedFiles(): + self.file_list.add_file(filename) + + self.file_list.setCurrentItem(None) + self.update() + + def add_files(self): + """ + Add files button clicked. + """ + files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + filenames = files[0] + for filename in filenames: + self.file_list.add_file(filename) + + def add_folder(self): + """ + Add folder button clicked. + """ + filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, + caption=strings._('gui_choose_items'), + options=QtWidgets.QFileDialog.ShowDirsOnly) + self.file_list.add_file(filename) + + def delete(self): + """ + Delete button clicked + """ + selected = self.file_list.selectedItems() + for item in selected: + itemrow = self.file_list.row(item) + self.file_list.takeItem(itemrow) + self.file_list.files_updated.emit() + + self.file_list.setCurrentItem(None) + self.update() + + def server_started(self): + """ + Gets called when the server starts. + """ + self.server_on = True + self.file_list.server_started() + self.update() + + def server_stopped(self): + """ + Gets called when the server stops. + """ + self.server_on = False + self.file_list.server_stopped() + self.update() + + def get_num_files(self): + """ + Returns the total number of files and folders in the list. + """ + return len(range(self.file_list.count())) + + def setFocus(self): + """ + Set the Qt app focus on the file selection box. + """ + self.file_list.setFocus() diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 6cb50b2b..1ee40ca3 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -25,7 +25,7 @@ from onionshare.onion import * from onionshare.common import Common from onionshare.web import Web -from .file_selection import FileSelection +from ..file_selection import FileSelection from .threads import CompressThread from .. import Mode from ..history import History, ToggleHistory, ShareHistoryItem diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py deleted file mode 100644 index 0d4229fe..00000000 --- a/onionshare_gui/mode/share_mode/file_selection.py +++ /dev/null @@ -1,430 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" -import os -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - -from ...widgets import Alert, AddFileDialog - -class DropHereLabel(QtWidgets.QLabel): - """ - When there are no files or folders in the FileList yet, display the - 'drop files here' message and graphic. - """ - def __init__(self, common, parent, image=False): - self.parent = parent - super(DropHereLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - - if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) - else: - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) - - self.hide() - - def dragEnterEvent(self, event): - self.parent.drop_here_image.hide() - self.parent.drop_here_text.hide() - event.accept() - - -class DropCountLabel(QtWidgets.QLabel): - """ - While dragging files over the FileList, this counter displays the - number of files you're dragging. - """ - def __init__(self, common, parent): - self.parent = parent - super(DropCountLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) - self.hide() - - def dragEnterEvent(self, event): - self.hide() - event.accept() - - -class FileList(QtWidgets.QListWidget): - """ - The list of files and folders in the GUI. - """ - files_dropped = QtCore.pyqtSignal() - files_updated = QtCore.pyqtSignal() - - def __init__(self, common, parent=None): - super(FileList, self).__init__(parent) - - self.common = common - - self.setAcceptDrops(True) - self.setIconSize(QtCore.QSize(32, 32)) - self.setSortingEnabled(True) - self.setMinimumHeight(160) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.drop_here_image = DropHereLabel(self.common, self, True) - self.drop_here_text = DropHereLabel(self.common, self, False) - self.drop_count = DropCountLabel(self.common, self) - self.resizeEvent(None) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # file list should have a background image if empty - if self.count() == 0: - self.drop_here_image.show() - self.drop_here_text.show() - else: - self.drop_here_image.hide() - self.drop_here_text.hide() - - def server_started(self): - """ - Update the GUI when the server starts, by hiding delete buttons. - """ - self.setAcceptDrops(False) - self.setCurrentItem(None) - self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - for index in range(self.count()): - self.item(index).item_button.hide() - - def server_stopped(self): - """ - Update the GUI when the server stops, by showing delete buttons. - """ - self.setAcceptDrops(True) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - for index in range(self.count()): - self.item(index).item_button.show() - - def resizeEvent(self, event): - """ - When the widget is resized, resize the drop files image and text. - """ - offset = 70 - self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) - self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) - - if self.count() > 0: - # Add and delete an empty item, to force all items to get redrawn - # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') - self.addItem(item) - self.takeItem(self.row(item)) - self.update() - - # Extend any filenames that were truncated to fit the window - # We use 200 as a rough guess at how wide the 'file size + delete button' widget is - # and extend based on the overall width minus that amount. - for index in range(self.count()): - metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) - self.item(index).setText(elided) - - - def dragEnterEvent(self, event): - """ - dragEnterEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) - count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) - - size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) - self.drop_count.show() - event.accept() - else: - event.ignore() - - def dragLeaveEvent(self, event): - """ - dragLeaveEvent for dragging files and directories into the widget. - """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) - self.drop_count.hide() - event.accept() - self.update() - - def dragMoveEvent(self, event): - """ - dragMoveEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - """ - dropEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - for url in event.mimeData().urls(): - filename = str(url.toLocalFile()) - self.add_file(filename) - else: - event.ignore() - - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) - self.drop_count.hide() - - self.files_dropped.emit() - - def add_file(self, filename): - """ - Add a file or directory to this widget. - """ - filenames = [] - for index in range(self.count()): - filenames.append(self.item(index).filename) - - if filename not in filenames: - if not os.access(filename, os.R_OK): - Alert(self.common, strings._("not_a_readable_file").format(filename)) - return - - fileinfo = QtCore.QFileInfo(filename) - ip = QtWidgets.QFileIconProvider() - icon = ip.icon(fileinfo) - - if os.path.isfile(filename): - size_bytes = fileinfo.size() - size_readable = self.common.human_readable_filesize(size_bytes) - else: - size_bytes = self.common.dir_size(filename) - size_readable = self.common.human_readable_filesize(size_bytes) - - # Create a new item - item = QtWidgets.QListWidgetItem() - item.setIcon(icon) - item.size_bytes = size_bytes - - # Item's filename attribute and size labels - item.filename = filename - item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) - - item.basename = os.path.basename(filename.rstrip('/')) - # Use the basename as the method with which to sort the list - metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) - item.setData(QtCore.Qt.DisplayRole, elided) - - # Item's delete button - def delete_item(): - itemrow = self.row(item) - self.takeItem(itemrow) - self.files_updated.emit() - - item.item_button = QtWidgets.QPushButton() - item.item_button.setDefault(False) - item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) - item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - - # Item info widget, with a white background - item_info_layout = QtWidgets.QHBoxLayout() - item_info_layout.setContentsMargins(0, 0, 0, 0) - item_info_layout.addWidget(item_size) - item_info_layout.addWidget(item.item_button) - item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') - item_info.setLayout(item_info_layout) - - # Create the item's widget and layouts - item_hlayout = QtWidgets.QHBoxLayout() - item_hlayout.addStretch() - item_hlayout.addWidget(item_info) - widget = QtWidgets.QWidget() - widget.setLayout(item_hlayout) - - item.setSizeHint(widget.sizeHint()) - - self.addItem(item) - self.setItemWidget(item, widget) - - self.files_updated.emit() - - -class FileSelection(QtWidgets.QVBoxLayout): - """ - The list of files and folders in the GUI, as well as buttons to add and - delete the files and folders. - """ - def __init__(self, common, parent): - super(FileSelection, self).__init__() - - self.common = common - self.parent = parent - - self.server_on = False - - # File list - self.file_list = FileList(self.common) - self.file_list.itemSelectionChanged.connect(self.update) - self.file_list.files_dropped.connect(self.update) - self.file_list.files_updated.connect(self.update) - - # Buttons - if self.common.platform == 'Darwin': - # The macOS sandbox makes it so the Mac version needs separate add files - # and folders buttons, in order to use native file selection dialogs - self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) - self.add_files_button.clicked.connect(self.add_files) - self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) - self.add_folder_button.clicked.connect(self.add_folder) - else: - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) - self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) - self.delete_button.clicked.connect(self.delete) - button_layout = QtWidgets.QHBoxLayout() - button_layout.addStretch() - if self.common.platform == 'Darwin': - button_layout.addWidget(self.add_files_button) - button_layout.addWidget(self.add_folder_button) - else: - button_layout.addWidget(self.add_button) - button_layout.addWidget(self.delete_button) - - # Add the widgets - self.addWidget(self.file_list) - self.addLayout(button_layout) - - self.update() - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # All buttons should be hidden if the server is on - if self.server_on: - if self.common.platform == 'Darwin': - self.add_files_button.hide() - self.add_folder_button.hide() - else: - self.add_button.hide() - self.delete_button.hide() - else: - if self.common.platform == 'Darwin': - self.add_files_button.show() - self.add_folder_button.show() - else: - self.add_button.show() - - # Delete button should be hidden if item isn't selected - if len(self.file_list.selectedItems()) == 0: - self.delete_button.hide() - else: - self.delete_button.show() - - # Update the file list - self.file_list.update() - - def add(self): - """ - Add button clicked. - """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) - if file_dialog.exec_() == QtWidgets.QDialog.Accepted: - for filename in file_dialog.selectedFiles(): - self.file_list.add_file(filename) - - self.file_list.setCurrentItem(None) - self.update() - - def add_files(self): - """ - Add files button clicked. - """ - files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) - filenames = files[0] - for filename in filenames: - self.file_list.add_file(filename) - - def add_folder(self): - """ - Add folder button clicked. - """ - filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, - caption=strings._('gui_choose_items'), - options=QtWidgets.QFileDialog.ShowDirsOnly) - self.file_list.add_file(filename) - - def delete(self): - """ - Delete button clicked - """ - selected = self.file_list.selectedItems() - for item in selected: - itemrow = self.file_list.row(item) - self.file_list.takeItem(itemrow) - self.file_list.files_updated.emit() - - self.file_list.setCurrentItem(None) - self.update() - - def server_started(self): - """ - Gets called when the server starts. - """ - self.server_on = True - self.file_list.server_started() - self.update() - - def server_stopped(self): - """ - Gets called when the server stops. - """ - self.server_on = False - self.file_list.server_stopped() - self.update() - - def get_num_files(self): - """ - Returns the total number of files and folders in the list. - """ - return len(range(self.file_list.count())) - - def setFocus(self): - """ - Set the Qt app focus on the file selection box. - """ - self.file_list.setFocus() diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py new file mode 100644 index 00000000..e10da8b9 --- /dev/null +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -0,0 +1,323 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +import os +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.onion import * +from onionshare.common import Common +from onionshare.web import Web + +from ..file_selection import FileSelection +from .. import Mode +from ..history import History, ToggleHistory, DownloadHistoryItem +from ...widgets import Alert + +class WebsiteMode(Mode): + """ + Parts of the main window UI for sharing files. + """ + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Threads start out as None + self.compress_thread = None + + # Create the Web object + self.web = Web(self.common, True, 'website') + + # File selection + self.file_selection = FileSelection(self.common, self) + if self.filenames: + for filename in self.filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status.set_mode('website', self.file_selection) + self.server_status.server_started.connect(self.file_selection.server_started) + self.server_status.server_stopped.connect(self.file_selection.server_stopped) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.hide() + + # Download history + self.history = History( + self.common, + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), + strings._('gui_no_downloads'), + strings._('gui_downloads') + ) + self.history.hide() + + # Info label + self.info_label = QtWidgets.QLabel() + self.info_label.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, self, self.history, + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) + ) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.info_label) + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) + + # Primary action layout + self.primary_action_layout.addWidget(self.filesize_warning) + self.primary_action.hide() + self.update_primary_action() + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.history) + self.setLayout(self.wrapper_layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def get_stop_server_shutdown_timeout_text(self): + """ + Return the string to put on the stop server button, if there's a shutdown timeout + """ + return strings._('gui_share_stop_server_shutdown_timeout') + + def timeout_finished_should_stop_server(self): + """ + The shutdown 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.website_mode.download_count == 0 or self.web.done: + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_timeout')) + return True + # A download is probably still running - hold off on stopping the share + else: + self.server_status_label.setText(strings._('timeout_download_still_running')) + return False + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.website_mode.download_count = 0 + self.web.error404_count = 0 + + # Hide and reset the downloads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + + def start_server_step3_custom(self): + """ + Step 3 in starting the server. Display large filesize + warning, if applicable. + """ + + # Warn about sending large files over Tor + if self.web.website_mode.download_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize")) + self.filesize_warning.show() + + if self.web.website_mode.set_file_info(self.filenames): + self.success.emit() + else: + # Cancelled + pass + + def start_server_error_custom(self): + """ + Start server error. + """ + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + def stop_server_custom(self): + """ + Stop server. + """ + + self.filesize_warning.hide() + self.history.in_progress_count = 0 + self.history.completed_count = 0 + self.history.update_in_progress() + self.file_selection.file_list.adjustSize() + + def cancel_server_custom(self): + """ + Stop the compression thread on cancel + """ + if self.compress_thread: + self.common.log('WebsiteMode', 'cancel_server: quitting compress thread') + self.compress_thread.quit() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + 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_download_page_loaded_message')) + + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + + filesize = self.web.website_mode.download_filesize + + item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.in_progress_count += 1 + self.history.update_in_progress() + + self.system_tray.showMessage(strings._('systray_download_started_title'), strings._('systray_download_started_message')) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.history.update(event["data"]["id"], event["data"]["bytes"]) + + # Is the download complete? + if event["data"]["bytes"] == self.web.website_mode.filesize: + self.system_tray.showMessage(strings._('systray_download_completed_title'), strings._('systray_download_completed_message')) + + # 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() + + # Close on finish? + if self.common.settings.get('close_after_first_download'): + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_status_label.setText(strings._('closing_automatically')) + 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_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + self.history.cancel(event["data"]["id"]) + + # Update in progress count + self.history.in_progress_count -= 1 + self.history.update_in_progress() + self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) + + def on_reload_settings(self): + """ + If there were some files listed for sharing, we should be ok to re-enable + the 'Start Sharing' button now. + """ + if self.server_status.file_selection.get_num_files() > 0: + self.primary_action.show() + self.info_label.show() + + def update_primary_action(self): + self.common.log('WebsiteMode', 'update_primary_action') + + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info_label.show() + + # Update the file count in the info label + total_size_bytes = 0 + for index in range(self.file_selection.file_list.count()): + item = self.file_selection.file_list.item(index) + total_size_bytes += item.size_bytes + total_size_readable = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + else: + self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + + else: + self.primary_action.hide() + self.info_label.hide() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.history.reset() + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 17839669..9fdf9395 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -25,6 +25,7 @@ from onionshare.web import Web from .mode.share_mode import ShareMode from .mode.receive_mode import ReceiveMode +from .mode.website_mode import WebsiteMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog @@ -39,6 +40,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ MODE_SHARE = 'share' MODE_RECEIVE = 'receive' + MODE_WEBSITE = 'website' def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False): super(OnionShareGui, self).__init__() @@ -92,6 +94,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button')); self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.clicked.connect(self.receive_mode_clicked) + self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button')); + self.website_mode_button.setFixedHeight(50) + self.website_mode_button.clicked.connect(self.website_mode_clicked) self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFixedWidth(40) @@ -103,6 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow): mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) mode_switcher_layout.addWidget(self.receive_mode_button) + mode_switcher_layout.addWidget(self.website_mode_button) mode_switcher_layout.addWidget(self.settings_button) # Server status indicator on the status bar @@ -154,6 +160,20 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.receive_mode.set_server_active.connect(self.set_server_active) + # Website mode + self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) + self.website_mode.init() + self.website_mode.server_status.server_started.connect(self.update_server_status_indicator) + self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator) + self.website_mode.start_server_finished.connect(self.update_server_status_indicator) + self.website_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.website_mode.stop_server_finished.connect(self.stop_server_finished) + self.website_mode.start_server_finished.connect(self.clear_message) + self.website_mode.server_status.button_clicked.connect(self.clear_message) + self.website_mode.server_status.url_copied.connect(self.copy_url) + self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.website_mode.set_server_active.connect(self.set_server_active) + self.update_mode_switcher() self.update_server_status_indicator() @@ -162,6 +182,7 @@ class OnionShareGui(QtWidgets.QMainWindow): contents_layout.setContentsMargins(10, 0, 10, 0) contents_layout.addWidget(self.receive_mode) contents_layout.addWidget(self.share_mode) + contents_layout.addWidget(self.website_mode) layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -199,15 +220,27 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode.hide() self.share_mode.show() + self.website_mode.hide() + elif self.mode == self.MODE_WEBSITE: + self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + + self.receive_mode.hide() + self.share_mode.hide() + self.website_mode.show() else: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.share_mode.hide() self.receive_mode.show() + self.website_mode.hide() self.update_server_status_indicator() @@ -223,6 +256,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_RECEIVE self.update_mode_switcher() + def website_mode_clicked(self): + if self.mode != self.MODE_WEBSITE: + self.common.log('OnionShareGui', 'website_mode_clicked') + self.mode = self.MODE_WEBSITE + self.update_mode_switcher() + def update_server_status_indicator(self): # Set the status image if self.mode == self.MODE_SHARE: @@ -239,6 +278,17 @@ class OnionShareGui(QtWidgets.QMainWindow): elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) self.server_status_label.setText(strings._('gui_status_indicator_share_started')) + elif self.mode == self.MODE_WEBSITE: + # Website mode + if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) + self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) + elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) + self.server_status_label.setText(strings._('gui_status_indicator_share_started')) else: # Receive mode if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED: @@ -317,6 +367,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.start(500) self.share_mode.on_reload_settings() self.receive_mode.on_reload_settings() + self.website_mode.on_reload_settings() self.status_bar.clearMessage() # If we switched off the auto-stop timer setting, ensure the widget is hidden. @@ -337,6 +388,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # When settings close, refresh the server status UI self.share_mode.server_status.update() self.receive_mode.server_status.update() + self.website_mode.server_status.update() def check_for_updates(self): """ @@ -367,10 +419,13 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() + self.website_mode.handle_tor_broke() # Process events from the web object if self.mode == self.MODE_SHARE: mode = self.share_mode + elif self.mode == self.MODE_WEBSITE: + mode = self.website_mode else: mode = self.receive_mode @@ -450,13 +505,20 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: self.share_mode_button.show() self.receive_mode_button.hide() + self.website_mode_button.hide() + elif self.mode == self.MODE_WEBSITE: + self.share_mode_button.hide() + self.receive_mode_button.hide() + self.website_mode_button.show() else: self.share_mode_button.hide() self.receive_mode_button.show() + self.website_mode_button.hide() else: self.settings_button.show() self.share_mode_button.show() self.receive_mode_button.show() + self.website_mode_button.show() # Disable settings menu action when server is active self.settings_action.setEnabled(not active) @@ -466,6 +528,8 @@ class OnionShareGui(QtWidgets.QMainWindow): try: if self.mode == OnionShareGui.MODE_SHARE: server_status = self.share_mode.server_status + if self.mode == OnionShareGui.MODE_WEBSITE: + server_status = self.website_mode.server_status else: server_status = self.receive_mode.server_status if server_status.status != server_status.STATUS_STOPPED: diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 0c51119e..755904ea 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -39,6 +39,7 @@ class ServerStatus(QtWidgets.QWidget): MODE_SHARE = 'share' MODE_RECEIVE = 'receive' + MODE_WEBSITE = 'website' STATUS_STOPPED = 0 STATUS_WORKING = 1 @@ -159,7 +160,7 @@ class ServerStatus(QtWidgets.QWidget): """ self.mode = share_mode - if self.mode == ServerStatus.MODE_SHARE: + if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE): self.file_selection = file_selection self.update() @@ -207,6 +208,8 @@ class ServerStatus(QtWidgets.QWidget): if self.mode == ServerStatus.MODE_SHARE: self.url_description.setText(strings._('gui_share_url_description').format(info_image)) + elif self.mode == ServerStatus.MODE_WEBSITE: + self.url_description.setText(strings._('gui_share_url_description').format(info_image)) else: self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) @@ -258,6 +261,8 @@ class ServerStatus(QtWidgets.QWidget): # Button if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: self.server_button.hide() + elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0: + self.server_button.hide() else: self.server_button.show() @@ -266,6 +271,8 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_start_server')) + elif self.mode == ServerStatus.MODE_WEBSITE: + self.server_button.setText(strings._('gui_share_start_server')) else: self.server_button.setText(strings._('gui_receive_start_server')) self.server_button.setToolTip('') @@ -278,13 +285,27 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_stop_server')) + if self.mode == ServerStatus.MODE_WEBSITE: + self.server_button.setText(strings._('gui_share_stop_server')) else: self.server_button.setText(strings._('gui_receive_stop_server')) +<<<<<<< HEAD if self.common.settings.get('autostart_timer'): self.autostart_timer_container.hide() if self.common.settings.get('autostop_timer'): self.autostop_timer_container.hide() self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) +======= + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + if self.mode == ServerStatus.MODE_SHARE: + self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout)) + if self.mode == ServerStatus.MODE_WEBSITE: + self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout)) + else: + self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout)) + +>>>>>>> Add gui for website sharing and listing elif self.status == self.STATUS_WORKING: self.server_button.setStyleSheet(self.common.css['server_status_button_working']) self.server_button.setEnabled(True) @@ -411,6 +432,8 @@ class ServerStatus(QtWidgets.QWidget): """ if self.common.settings.get('public_mode'): url = 'http://{0:s}'.format(self.app.onion_host) + elif self.mode == ServerStatus.MODE_WEBSITE: + url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.slug, self.app.onion_host) else: url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) return url diff --git a/setup.py b/setup.py index f482abb6..7d5d6620 100644 --- a/setup.py +++ b/setup.py @@ -89,6 +89,7 @@ setup( 'onionshare_gui.mode', 'onionshare_gui.mode.share_mode', 'onionshare_gui.mode.receive_mode' + 'onionshare_gui.mode.website_mode' ], include_package_data=True, scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'], diff --git a/share/locale/en.json b/share/locale/en.json index 03e7ec1a..926f6f43 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -135,6 +135,7 @@ "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", "gui_mode_receive_button": "Receive Files", + "gui_mode_website_button": "Publish Website", "gui_settings_receiving_label": "Receiving settings", "gui_settings_data_dir_label": "Save files to", "gui_settings_data_dir_browse_button": "Browse", -- cgit v1.2.3-54-g00ecf From 0c6dbe4c8a08c3372fa1707a4afc2611e2bdd535 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 23 Apr 2019 16:03:50 +0200 Subject: Clean ui, add strings, fix web listing logic --- onionshare/web/website_mode.py | 13 ++++--- onionshare_gui/mode/history.py | 21 ++++++++++ onionshare_gui/mode/website_mode/__init__.py | 57 ++++------------------------ share/locale/en.json | 2 + 4 files changed, 39 insertions(+), 54 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 51137183..65e486e6 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -81,24 +81,27 @@ class WebsiteModeWeb(object): self.web.add_request(self.web.REQUEST_LOAD, request.path) + print(self.file_info) + + filelist = [] if self.file_info['files']: self.website_folder = os.path.dirname(self.file_info['files'][0]['filename']) + filelist = [v['basename'] for v in self.file_info['files']] elif self.file_info['dirs']: self.website_folder = self.file_info['dirs'][0]['filename'] + filelist = os.listdir(self.website_folder) else: return self.web.error404() - if any((fname == 'index.html') for fname in os.listdir(self.website_folder)): + if any((fname == 'index.html') for fname in filelist): self.web.app.static_url_path = self.website_folder self.web.app.static_folder = self.website_folder if not os.path.isfile(os.path.join(self.website_folder, page_path)): page_path = os.path.join(page_path, 'index.html') - return send_from_directory(self.website_folder, page_path) - - elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in os.listdir(self.website_folder)): + elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in filelist): filenames = [] - for i in os.listdir(self.website_folder): + for i in filelist: filenames.append(os.path.join(self.website_folder, i)) self.set_file_info(filenames) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 1546cb68..34cd8306 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -341,6 +341,27 @@ class ReceiveHistoryItem(HistoryItem): self.label.setText(self.get_canceled_label_text(self.started)) +class VisitHistoryItem(HistoryItem): + """ + Download history item, for share mode + """ + def __init__(self, common, id, total_bytes): + super(VisitHistoryItem, self).__init__() + 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.started_dt.strftime("%b %d, %I:%M%p"))) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + self.setLayout(layout) + + class HistoryItemList(QtWidgets.QScrollArea): """ List of items diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index e10da8b9..e2c5bd72 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -27,7 +27,7 @@ from onionshare.web import Web from ..file_selection import FileSelection from .. import Mode -from ..history import History, ToggleHistory, DownloadHistoryItem +from ..history import History, ToggleHistory, VisitHistoryItem from ...widgets import Alert class WebsiteMode(Mode): @@ -130,7 +130,7 @@ class WebsiteMode(Mode): The shutdown 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.website_mode.download_count == 0 or self.web.done: + if self.web.website_mode.visit_count == 0 or self.web.done: self.server_status.stop_server() self.server_status_label.setText(strings._('close_on_timeout')) return True @@ -144,7 +144,7 @@ class WebsiteMode(Mode): Starting the server. """ # Reset web counters - self.web.website_mode.download_count = 0 + self.web.website_mode.visit_count = 0 self.web.error404_count = 0 # Hide and reset the downloads if we have previously shared @@ -201,11 +201,10 @@ class WebsiteMode(Mode): def cancel_server_custom(self): """ - Stop the compression thread on cancel + Log that the server has been cancelled """ - if self.compress_thread: - self.common.log('WebsiteMode', 'cancel_server: quitting compress thread') - self.compress_thread.quit() + self.common.log('WebsiteMode', 'cancel_server') + def handle_tor_broke_custom(self): """ @@ -217,7 +216,7 @@ class WebsiteMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_download_page_loaded_message')) + self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_page_loaded_message')) def handle_request_started(self, event): """ @@ -226,52 +225,12 @@ class WebsiteMode(Mode): filesize = self.web.website_mode.download_filesize - item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) + item = VisitHistoryItem(self.common, event["data"]["id"], filesize) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_download_started_title'), strings._('systray_download_started_message')) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - self.history.update(event["data"]["id"], event["data"]["bytes"]) - - # Is the download complete? - if event["data"]["bytes"] == self.web.website_mode.filesize: - self.system_tray.showMessage(strings._('systray_download_completed_title'), strings._('systray_download_completed_message')) - - # 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() - - # Close on finish? - if self.common.settings.get('close_after_first_download'): - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically')) - 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_canceled(self, event): - """ - Handle REQUEST_CANCELED event. - """ - self.history.cancel(event["data"]["id"]) - - # Update in progress count - self.history.in_progress_count -= 1 - self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) - def on_reload_settings(self): """ If there were some files listed for sharing, we should be ok to re-enable diff --git a/share/locale/en.json b/share/locale/en.json index 926f6f43..dafd20d1 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -114,6 +114,7 @@ "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address", "gui_share_url_description": "Anyone with this OnionShare address can download your files using the Tor Browser: ", + "gui_website_url_description": "Anyone with this OnionShare address can visit your website using the Tor Browser: ", "gui_receive_url_description": "Anyone with this OnionShare address can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not auto-stop.

Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", "gui_url_label_stay_open": "This share will not auto-stop.", @@ -168,6 +169,7 @@ "gui_share_mode_autostop_timer_waiting": "Waiting to finish sending", "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 {}", "receive_mode_upload_starting": "Upload of total size {} is starting", "days_first_letter": "d", "hours_first_letter": "h", -- cgit v1.2.3-54-g00ecf From 357374c147071b217f1480dbda67d3dfa6a091c2 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 23 Apr 2019 16:20:33 +0200 Subject: Fix merge conflicts with upstream --- onionshare/web/website_mode.py | 2 -- onionshare_gui/mode/website_mode/__init__.py | 8 ++++---- onionshare_gui/server_status.py | 12 ------------ share/locale/en.json | 2 ++ 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 65e486e6..dd7be1d5 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -81,8 +81,6 @@ class WebsiteModeWeb(object): self.web.add_request(self.web.REQUEST_LOAD, request.path) - print(self.file_info) - filelist = [] if self.file_info['files']: self.website_folder = os.path.dirname(self.file_info['files'][0]['filename']) diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index e2c5bd72..156f578e 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -75,9 +75,9 @@ class WebsiteMode(Mode): # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), - strings._('gui_no_downloads'), - strings._('gui_downloads') + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), + strings._('gui_share_mode_no_files'), + strings._('gui_all_modes_history') ) self.history.hide() @@ -216,7 +216,7 @@ class WebsiteMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_page_loaded_message')) + self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message')) def handle_request_started(self, event): """ diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 755904ea..e8385e64 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -289,23 +289,11 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setText(strings._('gui_share_stop_server')) else: self.server_button.setText(strings._('gui_receive_stop_server')) -<<<<<<< HEAD if self.common.settings.get('autostart_timer'): self.autostart_timer_container.hide() if self.common.settings.get('autostop_timer'): self.autostop_timer_container.hide() self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) -======= - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout)) - if self.mode == ServerStatus.MODE_WEBSITE: - self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip').format(self.timeout)) - else: - self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip').format(self.timeout)) - ->>>>>>> Add gui for website sharing and listing elif self.status == self.STATUS_WORKING: self.server_button.setStyleSheet(self.common.css['server_status_button_working']) self.server_button.setEnabled(True) diff --git a/share/locale/en.json b/share/locale/en.json index dafd20d1..de6639a6 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -147,6 +147,8 @@ "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_share_started_title": "Sharing Started", "systray_share_started_message": "Starting to send files to someone", "systray_share_completed_title": "Sharing Complete", -- cgit v1.2.3-54-g00ecf From 8f7e52e4eee84d31b1b568ba43db23f6f5f49f1d Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 3 May 2019 19:29:58 +0200 Subject: Add version for Flask-HTTPAuth --- install/requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/requirements.txt b/install/requirements.txt index fff0b009..ce5464cf 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -3,7 +3,7 @@ certifi==2019.3.9 chardet==3.0.4 Click==7.0 Flask==1.0.2 -Flask-HTTPAuth +Flask-HTTPAuth==3.2.4 future==0.17.1 idna==2.8 itsdangerous==1.1.0 diff --git a/setup.py b/setup.py index 7d5d6620..347ff366 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ setup( 'onionshare_gui', 'onionshare_gui.mode', 'onionshare_gui.mode.share_mode', - 'onionshare_gui.mode.receive_mode' + 'onionshare_gui.mode.receive_mode', 'onionshare_gui.mode.website_mode' ], include_package_data=True, -- cgit v1.2.3-54-g00ecf From 849a084266588d881411c167db902891a3659886 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 7 May 2019 12:12:49 -0700 Subject: Update build instructions, and fix typo in changelog --- BUILD.md | 6 ++++-- CHANGELOG.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index c4a5c5e3..e3f3fec7 100644 --- a/BUILD.md +++ b/BUILD.md @@ -181,7 +181,7 @@ Download and install [Microsoft Build Tools for Visual Studio 2019](https://www. Then, enable the 32-bit Visual C++ Toolset on the Command Line like this: ``` -cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build" +cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build" vcvars32.bat ``` @@ -346,7 +346,7 @@ To make a macOS release, go to macOS build machine: Then move back to the developer machine: -- PGP-sign the macOS installer, `gpg --detach-sign OnionShare-$VERSION.pkg` +- PGP-sign the macOS installer, `gpg -a --detach-sign OnionShare-$VERSION.pkg` Note that once we support notarizing the macOS installer (see [this issue](https://github.com/micahflee/onionshare/issues/953)), these will be the steps instead: @@ -400,8 +400,10 @@ 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 - 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 - Deploy to https://onionshare.org/ - Email the [onionshare-dev](https://lists.riseup.net/www/subscribe/onionshare-dev) mailing list announcing the release +- Make a PR to [homebrew-cask](https://github.com/homebrew/homebrew-cask) to update the macOS version diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c309fb0..b579c6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ * Ukrainian (Українська) * Removed translations because less than 90% of the strings were translated: * Bengali (বাংলা) - * Persian (فارسی), + * Persian (فارسی) ## 2.0 -- cgit v1.2.3-54-g00ecf From 4648d1801a3e4ad31e5e14e11059f29ffe83b332 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Tue, 7 May 2019 23:07:18 +0200 Subject: Fix typos in changelog --- CHANGELOG.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b579c6a9..b70da866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ * New feature: Auto-start timer, which allows scheduling when the server starts * Renamed CLI argument --debug to --verbose * Make Tor connection timeout configurable as a CLI argument -* Updated various dependencies, including to fix third-party security issues in urllib3, jinja2, and jQuery -* Update Tor to 0.3.5.8 +* Updated various dependencies, including fixing third-party security issues in urllib3, Jinja2, and jQuery +* Updated Tor to 0.3.5.8 * New translations: * Traditional Chinese (正體中文 (繁體)), * Simplified Chinese (中文 (简体)) @@ -14,13 +14,13 @@ * German (Deutsch) * Icelandic (Íslenska) * Irish (Gaeilge) - * Norwegian Bokmål (Norsk Bokmål) + * Norwegian Bokmål (Norsk bokmål) * Polish (Polski) * Portuguese Portugal (Português (Portugal)) * Telugu (తెలుగు) * Turkish (Türkçe) * Ukrainian (Українська) -* Removed translations because less than 90% of the strings were translated: +* Removed translations with fewer than 90% of strings translated: * Bengali (বাংলা) * Persian (فارسی) @@ -35,31 +35,31 @@ * New feature: Allow selecting your language from a dropdown * New translations: Bengali (বাংলা), Catalan (Català), Danish (Dansk), French (Français), Greek (Ελληνικά), Italian (Italiano), Japanese (日本語), Persian (فارسی), Portuguese Brazil (Português Brasil), Russian (Русский), Spanish (Español), Swedish (Svenska) * Several bugfixes -* Invisible to users, but this version includes some major refactoring of the codebase, and a robust set of unit tests which makes OnionShare easier to maintain going forward +* Invisible to users, this version includes some major refactoring of the codebase, and a robust set of unit tests which makes OnionShare easier to maintain going forward ## 1.3.2 -* Bug fix: In debug mode, stop saving flask debug log in /tmp, where all users can access it +* Bugfix: In debug mode, stop saving flask debug log in /tmp, where all users can access it ## 1.3.1 * Updated Tor to 0.2.3.10 -* Windows and Mac binaries are now distributed with licenses for tor and obfs4 +* Windows and Mac binaries are now distributed with licenses for Tor and obfs4 ## 1.3 * Major UI redesign, introducing many UX improvements * Client-side web interfact redesigned -* New feature: Support for meek_lite pluggable transports (Amazon and Azure) - not yet ready for Windows or Mac, sorry -* New feature: Support for custom obfs4 and meek_lite bridges (again, meek_lite not available on Windows/Mac yet) -* New feature: ability to cancel share before it starts -* Bug fix: the UpdateChecker no longer blocks the UI when checking -* Bug fix: simultaneous downloads (broken in 1.2) -* Update Tor to 0.2.3.9 +* New feature: Support for meek_lite pluggable transports (Amazon and Azure) - not yet ready for Windows or macOS, sorry +* New feature: Support for custom obfs4 and meek_lite bridges (again, meek_lite not available on Windows/macOS yet) +* New feature: Ability to cancel share before it starts +* Bugfix: The UpdateChecker no longer blocks the UI when checking +* Bugfix: Simultaneous downloads (broken in 1.2) +* Updated Tor to 0.2.3.9 * Improved support for BSD * Updated French and Danish translations * Minor build script and build documentation fixes -* Add flake8 tests +* Flake8 tests added ## 1.2 @@ -67,18 +67,18 @@ * New feature: Ability to use a persistent URL * New feature: Auto-stop timer, to stop OnionShare at a specified time * New feature: Get notification when Tor connection dies -* Updated versions of python, Qt, tor, and other dependencies that are bundled +* Updated versions of Python, Qt, Tor, and other dependencies that are bundled * Added ability to supply a custom settings file as a command line arg * Added support for FreeBSD * Fixed small user interface issues * Fixed minor bugs -* New translations for Dutch +* New Dutch translations ## 1.1 * OnionShare connects to Tor itself now, so opening Tor Browser in the background isn't required * In Windows and macOS, OnionShare alerts users about updates -* Removed the menu bar, and adding a Settings button +* Removed the menu bar, and adding a "Settings" button * Added desktop notifications, and a system tray icon * Ability to add multiple files and folders with a single "Add" button * Ability to delete multiple files and folders at once with the "Delete" button @@ -145,7 +145,7 @@ * Fixed critical bug in OS X binaries that caused crashes on some computers * Added Security Design document -* Minor bug fix with Windows code signing timestamp server +* Minor bugfix with Windows code signing timestamp server * Linux version uses HS dir that is allowed by Tor Browser Launcher's AppArmor profiles ## 0.7 @@ -160,7 +160,7 @@ * Brand new drag-and-drop GUI with ability to start and stop server * Much cleaner code split into several files -* Support for sharing multiple files and folders at once, and automatically zips files before sharing +* Support for sharing multiple files and folders at once, and automatically compresses files before sharing * Redesigned receiver HTML interface * Waits for hidden service to be available before displaying URL * Cleans up hidden service directory on exit @@ -198,4 +198,4 @@ * Shows download progress * Limited suite of tests * If a localized string doesn't exist, falls back to English -* New translations: Dutch, Portuguese, German, Russian, and updated translations: Norwegian, Spanish, French, Italian +* New translations: Dutch, Portuguese, German, Russian, and updated translations: Norwegian Bokmål, Spanish, French, Italian -- cgit v1.2.3-54-g00ecf From a352dbe9ff3bd54904cbb4cf715ab5341a113acb Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 7 May 2019 23:16:35 +0200 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/de.json b/share/locale/de.json index 44839231..ef1f1b09 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -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", -- cgit v1.2.3-54-g00ecf From abc30b315ce77a6a2dd4b8a8d24f7c478a33c7c5 Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 8 May 2019 00:04:09 +0200 Subject: Clean code and fix UI bugs --- install/build_rpm.sh | 2 +- onionshare/web/receive_mode.py | 3 ++ onionshare/web/share_mode.py | 4 ++ onionshare/web/website_mode.py | 14 ++++++- onionshare_gui/mode/history.py | 34 ++++++++++------ onionshare_gui/mode/website_mode/__init__.py | 59 ++++++++++++++-------------- share/locale/en.json | 3 ++ stdeb.cfg | 4 +- 8 files changed, 77 insertions(+), 46 deletions(-) diff --git a/install/build_rpm.sh b/install/build_rpm.sh index 0872a447..22153c6d 100755 --- a/install/build_rpm.sh +++ b/install/build_rpm.sh @@ -9,7 +9,7 @@ VERSION=`cat share/version.txt` rm -r build dist >/dev/null 2>&1 # build binary package -python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4" +python3 setup.py bdist_rpm --requires="python3-flask, python3-flask-httpauth, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4" # install it echo "" diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index bc805445..e7f3b3ae 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -18,6 +18,9 @@ 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 = [] diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 560a8ba4..a0c8dc90 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -34,6 +34,10 @@ class ShareModeWeb(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 define_routes(self): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index dd7be1d5..b9fe74e0 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -25,6 +25,9 @@ class WebsiteModeWeb(object): self.download_filesize = 0 self.visit_count = 0 + # Reset assets path + self.web.app.static_folder=self.common.get_resource_path('static') + self.users = { } self.define_routes() @@ -79,7 +82,15 @@ class WebsiteModeWeb(object): Render the onionshare website. """ - self.web.add_request(self.web.REQUEST_LOAD, request.path) + # 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, page_path, { + 'id': visit_id, + 'action': 'visit' + }) filelist = [] if self.file_info['files']: @@ -102,6 +113,7 @@ class WebsiteModeWeb(object): for i in filelist: filenames.append(os.path.join(self.website_folder, i)) + self.web.app.static_folder=self.common.get_resource_path('static') self.set_file_info(filenames) r = make_response(render_template( diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 34cd8306..51b36f9a 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -347,6 +347,7 @@ class VisitHistoryItem(HistoryItem): """ def __init__(self, common, id, total_bytes): super(VisitHistoryItem, self).__init__() + self.status = HistoryItem.STATUS_STARTED self.common = common self.id = id @@ -354,13 +355,20 @@ class VisitHistoryItem(HistoryItem): self.visited_dt = datetime.fromtimestamp(self.visited) # Label - self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + 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 class HistoryItemList(QtWidgets.QScrollArea): """ @@ -425,19 +433,19 @@ class HistoryItemList(QtWidgets.QScrollArea): Reset all items, emptying the list. Override this method. """ for key, item in self.items.copy().items(): - if item.status != HistoryItem.STATUS_STARTED: - self.items_layout.removeWidget(item) - item.close() - del self.items[key] + self.items_layout.removeWidget(item) + item.close() + del self.items[key] class History(QtWidgets.QWidget): """ A history of what's happened so far in this mode. This contains an internal object full of a scrollable list of items. """ - def __init__(self, common, empty_image, empty_text, header_text): + def __init__(self, common, empty_image, empty_text, header_text, mode=''): super(History, self).__init__() self.common = common + self.mode = mode self.setMinimumWidth(350) @@ -556,12 +564,14 @@ 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') - 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)) + 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') + + 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)) class ToggleHistory(QtWidgets.QPushButton): diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 156f578e..06212b02 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -18,6 +18,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import os +import secrets +import random +import string + from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -41,9 +45,6 @@ class WebsiteMode(Mode): """ Custom initialization for ReceiveMode. """ - # Threads start out as None - self.compress_thread = None - # Create the Web object self.web = Web(self.common, True, 'website') @@ -76,8 +77,9 @@ class WebsiteMode(Mode): self.history = History( self.common, QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_share_mode_no_files'), - strings._('gui_all_modes_history') + strings._('gui_website_mode_no_files'), + strings._('gui_all_modes_history'), + 'website' ) self.history.hide() @@ -88,8 +90,8 @@ class WebsiteMode(Mode): # Toggle history self.toggle_history = ToggleHistory( self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) + QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) ) # Top bar @@ -113,31 +115,27 @@ class WebsiteMode(Mode): # Wrapper layout self.wrapper_layout = QtWidgets.QHBoxLayout() self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.history) + self.wrapper_layout.addWidget(self.history, stretch=1) self.setLayout(self.wrapper_layout) # Always start with focus on file selection self.file_selection.setFocus() - def get_stop_server_shutdown_timeout_text(self): + def get_stop_server_autostop_timer_text(self): """ - Return the string to put on the stop server button, if there's a shutdown timeout + Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_shutdown_timeout') + return strings._('gui_share_stop_server_autostop_timer') - def timeout_finished_should_stop_server(self): + def autostop_timer_finished_should_stop_server(self): """ - The shutdown timer expired, should we stop the server? Returns a bool + 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.website_mode.visit_count == 0 or self.web.done: - self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_timeout')) - return True - # A download is probably still running - hold off on stopping the share - else: - self.server_status_label.setText(strings._('timeout_download_still_running')) - return False + + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_autostop_timer')) + return True + def start_server_custom(self): """ @@ -194,9 +192,7 @@ class WebsiteMode(Mode): """ self.filesize_warning.hide() - self.history.in_progress_count = 0 self.history.completed_count = 0 - self.history.update_in_progress() self.file_selection.file_list.adjustSize() def cancel_server_custom(self): @@ -222,14 +218,17 @@ class WebsiteMode(Mode): """ Handle REQUEST_STARTED event. """ + if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ): + filesize = self.web.website_mode.download_filesize + item = VisitHistoryItem(self.common, event["data"]["id"], filesize) + + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.completed_count += 1 + self.history.update_completed() - filesize = self.web.website_mode.download_filesize + self.system_tray.showMessage(strings._('systray_website_started_title'), strings._('systray_website_started_message')) - item = VisitHistoryItem(self.common, event["data"]["id"], filesize) - 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 on_reload_settings(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index de6639a6..0c26a9d5 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -157,6 +157,8 @@ "systray_share_canceled_message": "Someone canceled receiving your files", "systray_receive_started_title": "Receiving Started", "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", "gui_all_modes_history": "History", "gui_all_modes_clear_history": "Clear All", "gui_all_modes_transfer_started": "Started {}", @@ -169,6 +171,7 @@ "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", "gui_share_mode_no_files": "No Files Sent Yet", "gui_share_mode_autostop_timer_waiting": "Waiting to finish sending", + "gui_website_mode_no_files": "No Website Shared Yet", "gui_receive_mode_no_files": "No Files Received Yet", "gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving", "gui_visit_started": "Someone has visited your website {}", diff --git a/stdeb.cfg b/stdeb.cfg index 0adbac43..451520af 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy -Build-Depends: python3, python3-pytest, python3-flask, 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, 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 Suite: cosmic X-Python3-Version: >= 3.5.3 -- cgit v1.2.3-54-g00ecf From 574a0c395318d7f3aeca3c7b9ef8556d22178cfe Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 8 May 2019 09:42:39 +0200 Subject: Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/ar.json | 280 +++++++++++++++++++++++++++------------------------ share/locale/ca.json | 4 +- share/locale/de.json | 10 +- share/locale/el.json | 154 ++++++++++++++-------------- share/locale/fr.json | 34 +++---- share/locale/it.json | 50 ++++----- share/locale/nl.json | 4 +- share/locale/ro.json | 12 +-- share/locale/tr.json | 194 +++++++++++++++++------------------ 9 files changed, 382 insertions(+), 360 deletions(-) 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..64779f7d 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -138,10 +138,10 @@ "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", diff --git a/share/locale/de.json b/share/locale/de.json index 44839231..2f6675f1 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,8 +49,8 @@ "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", - "close_on_autostop_timer": "Angehalten da der Stoptimer abgelaufen ist", + "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onion-dienst zu starten", + "close_on_autostop_timer": "Angehalten da der automatische Stoptimer abgelaufen ist", "systray_upload_started_title": "OnionShare Upload wurde gestartet", "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", "help_autostop_timer": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", @@ -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..8b95deb2 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Παρακαλείστε να το ανανεώσετε για να ξεκινήσετε τον διαμοιρασμό.", + "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_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/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/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/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/tr.json b/share/locale/tr.json index 33e6ec9c..54432d35 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_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 durma zamanlayıcısının bitmesi nedeniyle 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 zamanlayıcısı başlangıcı {}", + "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", -- cgit v1.2.3-54-g00ecf From 543744d81dca1b0f75724083d073fe66fb0e6c0e Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 7 May 2019 23:16:35 +0200 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 2f6675f1..0a0eb9c1 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -49,8 +49,8 @@ "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 Onion-dienst zu starten", - "close_on_autostop_timer": "Angehalten da der automatische Stoptimer abgelaufen ist", + "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", "help_autostop_timer": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", -- cgit v1.2.3-54-g00ecf From 814a3fd403f9fb56019f3dbd769f4f67ad6d6b8d Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 8 May 2019 12:06:06 +0200 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ Added translation using Weblate (Swahili) --- share/locale/es.json | 2 +- share/locale/sw.json | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 share/locale/sw.json 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/sw.json b/share/locale/sw.json new file mode 100644 index 00000000..44dfde5a --- /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": "", + "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": "", + "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": "", + "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": "" +} -- cgit v1.2.3-54-g00ecf From 65a7a1790c54f3986d7d6683e06dc1092fff2e6c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 10 May 2019 13:43:46 -0700 Subject: Starting to refactor website sharing so set_file_info builds a dict of all files, and path_logic will display any arbitrary file, or directory listing if no index.html is present --- onionshare/web/website_mode.py | 173 +++++++++++++++++++++-------------------- share/templates/listing.html | 25 +++--- 2 files changed, 105 insertions(+), 93 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index b9fe74e0..e337c368 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -19,10 +19,8 @@ class WebsiteModeWeb(object): self.web = web self.auth = HTTPBasicAuth() - # Information about the file to be shared - self.file_info = [] - self.website_folder = '' - self.download_filesize = 0 + # Dictionary mapping file paths to filenames on disk + self.files = {} self.visit_count = 0 # Reset assets path @@ -55,10 +53,6 @@ class WebsiteModeWeb(object): return _check_login() - @self.web.app.route('/download/') - def path_download(page_path): - return path_download(page_path) - @self.web.app.route('/') def path_public(page_path): return path_logic(page_path) @@ -67,17 +61,7 @@ class WebsiteModeWeb(object): def index_public(): return path_logic('') - def path_download(file_path=''): - """ - Render the download links. - """ - self.web.add_request(self.web.REQUEST_LOAD, request.path) - if not os.path.isfile(os.path.join(self.website_folder, file_path)): - return self.web.error404() - - return send_from_directory(self.website_folder, file_path) - - def path_logic(page_path=''): + def path_logic(path=''): """ Render the onionshare website. """ @@ -87,85 +71,106 @@ class WebsiteModeWeb(object): self.visit_count += 1 # Tell GUI the page has been visited - self.web.add_request(self.web.REQUEST_STARTED, page_path, { + self.web.add_request(self.web.REQUEST_STARTED, path, { 'id': visit_id, 'action': 'visit' }) - filelist = [] - if self.file_info['files']: - self.website_folder = os.path.dirname(self.file_info['files'][0]['filename']) - filelist = [v['basename'] for v in self.file_info['files']] - elif self.file_info['dirs']: - self.website_folder = self.file_info['dirs'][0]['filename'] - filelist = os.listdir(self.website_folder) - else: - return self.web.error404() - - if any((fname == 'index.html') for fname in filelist): - self.web.app.static_url_path = self.website_folder - self.web.app.static_folder = self.website_folder - if not os.path.isfile(os.path.join(self.website_folder, page_path)): - page_path = os.path.join(page_path, 'index.html') - return send_from_directory(self.website_folder, page_path) - elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in filelist): - filenames = [] - for i in filelist: - filenames.append(os.path.join(self.website_folder, i)) - - self.web.app.static_folder=self.common.get_resource_path('static') - self.set_file_info(filenames) - - r = make_response(render_template( - 'listing.html', - file_info=self.file_info, - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize))) - - return self.web.add_security_headers(r) - + # Removing trailing slashes, because self.files doesn't have them + path = path.rstrip('/') + + 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 = os.listdir(filesystem_path) + filenames.sort() + + files = [] + dirs = [] + + for filename in filenames: + this_filesystem_path = os.path.join(filesystem_path, 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)) + return self.web.add_security_headers(r) + + # 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: + # If the path isn't found, throw a 404 return self.web.error404() - def set_file_info(self, filenames, processed_size_callback=None): + def set_file_info(self, filenames): """ - 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. + Build a data structure that describes the list of files that make up + the static website. """ self.common.log("WebsiteModeWeb", "set_file_info") - self.web.cancel_compression = True - - self.cleanup_filenames = [] - - # build file info list - self.file_info = {'files': [], 'dirs': []} - for filename in filenames: - info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) - } - if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) - if os.path.isdir(filename): - info['size'] = self.common.dir_size(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['dirs'].append(info) - - self.download_filesize += info['size'] - self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) - self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + # This is a dictionary that maps HTTP routes to filenames on disk + self.files = {} - # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] + # If 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.download_filesize = os.path.getsize(self.download_filename) + # 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 + + # If it's a directory, add it recursively + elif os.path.isdir(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.lstrip(filename)).rstrip('/') + + # Add the dir itself + self.files[normalized_root] = filename + + # 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 diff --git a/share/templates/listing.html b/share/templates/listing.html index a514e5d2..8bb4062d 100644 --- a/share/templates/listing.html +++ b/share/templates/listing.html @@ -8,11 +8,6 @@
-
-
    -
  • Total size: {{ filesize_human }}
  • -
-

OnionShare

@@ -24,17 +19,29 @@ - {% for info in file_info.files %} + {% for info in dirs %} + + + + + {{ info.basename }} + + + + + {% endfor %} + + {% for info in files %} - {{ info.basename }} + + {{ info.basename }} + {{ info.size_human }} - download {% endfor %} - -- cgit v1.2.3-54-g00ecf From 818f2bac2c792f5c99c6442c1a5dc4126be64915 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 10 May 2019 14:04:13 -0700 Subject: Make it so directory listings work, including root directory listing --- onionshare/web/website_mode.py | 86 +++++++++++++++++++++++++++--------------- share/templates/listing.html | 6 +-- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index e337c368..99b3f0f2 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -76,9 +76,6 @@ class WebsiteModeWeb(object): 'action': 'visit' }) - # Removing trailing slashes, because self.files doesn't have them - path = path.rstrip('/') - if path in self.files: filesystem_path = self.files[path] @@ -96,31 +93,7 @@ class WebsiteModeWeb(object): # Otherwise, render directory listing filenames = os.listdir(filesystem_path) filenames.sort() - - files = [] - dirs = [] - - for filename in filenames: - this_filesystem_path = os.path.join(filesystem_path, 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)) - return self.web.add_security_headers(r) + return self.directory_listing(path, filenames, filesystem_path) # If it's a file elif os.path.isfile(filesystem_path): @@ -132,9 +105,54 @@ class WebsiteModeWeb(object): else: return self.web.error404() else: - # If the path isn't found, throw a 404 - return self.web.error404() + # 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 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)) + return self.web.add_security_headers(r) def set_file_info(self, filenames): """ @@ -146,6 +164,9 @@ class WebsiteModeWeb(object): # 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])] @@ -157,9 +178,12 @@ class WebsiteModeWeb(object): # 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". @@ -167,7 +191,7 @@ class WebsiteModeWeb(object): normalized_root = os.path.join(basename, root.lstrip(filename)).rstrip('/') # Add the dir itself - self.files[normalized_root] = filename + self.files[normalized_root + '/'] = filename # Add the files in this dir for nested_filename in nested_filenames: diff --git a/share/templates/listing.html b/share/templates/listing.html index 8bb4062d..8883eea9 100644 --- a/share/templates/listing.html +++ b/share/templates/listing.html @@ -23,11 +23,11 @@ - + {{ info.basename }} - + — {% endfor %} @@ -35,7 +35,7 @@ - + {{ info.basename }} -- cgit v1.2.3-54-g00ecf From ab930011ad42e0eed074f1a23be6bb3b506d719e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 10 May 2019 14:52:07 -0700 Subject: Fix bugs in how self.file was building the dictionary, so now browsing works --- onionshare/web/website_mode.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 99b3f0f2..39f41b3e 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -53,13 +53,10 @@ class WebsiteModeWeb(object): return _check_login() - @self.web.app.route('/') - def path_public(page_path): - return path_logic(page_path) - - @self.web.app.route("/") - def index_public(): - return path_logic('') + @self.web.app.route('/', defaults={'path': ''}) + @self.web.app.route('/') + def path_public(path): + return path_logic(path) def path_logic(path=''): """ @@ -91,7 +88,12 @@ class WebsiteModeWeb(object): else: # Otherwise, render directory listing - filenames = os.listdir(filesystem_path) + 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) @@ -188,10 +190,10 @@ class WebsiteModeWeb(object): # 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.lstrip(filename)).rstrip('/') + normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') # Add the dir itself - self.files[normalized_root + '/'] = filename + self.files[normalized_root + '/'] = root # Add the files in this dir for nested_filename in nested_filenames: -- cgit v1.2.3-54-g00ecf From 915ff0f4f3725695eae230165a3670fdc827fb22 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 10 May 2019 14:57:41 -0700 Subject: Remove references to self.web.website_mode.download_filesize because that variable no longer exists --- onionshare_gui/mode/website_mode/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 06212b02..9018f5cb 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -167,11 +167,6 @@ class WebsiteMode(Mode): warning, if applicable. """ - # Warn about sending large files over Tor - if self.web.website_mode.download_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize")) - self.filesize_warning.show() - if self.web.website_mode.set_file_info(self.filenames): self.success.emit() else: @@ -219,8 +214,7 @@ class WebsiteMode(Mode): Handle REQUEST_STARTED event. """ if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ): - filesize = self.web.website_mode.download_filesize - item = VisitHistoryItem(self.common, event["data"]["id"], filesize) + item = VisitHistoryItem(self.common, event["data"]["id"], 0) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) -- cgit v1.2.3-54-g00ecf From 8083fd49298768d96d33684f65b05a2aa3dddd1c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 16 May 2019 17:49:23 +0200 Subject: Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Translated using Weblate (Telugu) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/te/ Translated using Weblate (Swahili) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sw/ Translated using Weblate (Malay) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ms/ Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ Added translation using Weblate (Swahili) --- share/locale/el.json | 2 +- share/locale/es.json | 2 +- share/locale/hu.json | 4 +- share/locale/ms.json | 34 +++++----- share/locale/sw.json | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++ share/locale/te.json | 2 +- share/locale/tr.json | 6 +- 7 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 share/locale/sw.json diff --git a/share/locale/el.json b/share/locale/el.json index 8b95deb2..90655c18 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -133,7 +133,7 @@ "gui_tor_connection_lost": "Έγινε αποσύνδεση από το Tor.", "gui_server_started_after_autostop_timer": "Το χρονόμετρο αυτόματης διακοπής τελείωσε πριν την εκκίνηση του server. Παρακαλώ κάντε ένα νέο διαμοιρασμό.", "gui_server_autostop_timer_expired": "Το χρονόμετρο αυτόματης διακοπής έχει ήδη τελειώσει. Παρακαλώ ανανεώστε το για να ξεκινήσετε το διαμοιρασμό.", - "share_via_onionshare": "Κάντε το OnionShare", + "share_via_onionshare": "Κάντε OnionShare", "gui_use_legacy_v2_onions_checkbox": "Χρήση \"παραδοσιακών\" διευθύνσεων", "gui_save_private_key_checkbox": "Χρήση μόνιμης διεύθυνσης", "gui_share_url_description": "Οποιοσδήποτε με αυτή τη διεύθυνση OnionShare μπορεί να κατεβάσει τα αρχεία σας χρησιμοποιώντας το Tor Browser: ", 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/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/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/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 54432d35..793a9d08 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -13,7 +13,7 @@ "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": "Adresi Kopyala", @@ -26,7 +26,7 @@ "give_this_url_receive": "Bu adresi gönderene ver:", "not_a_readable_file": "{0:s} okunabilir bir dosya değil.", "no_available_port": "Onion hizmetinin başlatılacağı uygun bir kapı numarası bulunamadı", - "close_on_autostop_timer": "Otomatik durma zamanlayıcısının bitmesi nedeniyle durduruldu", + "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", @@ -183,7 +183,7 @@ "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 sayacı bitişi {}", - "gui_start_server_autostart_timer_tooltip": "Otomatik başlatma zamanlayıcısı başlangıcı {}", + "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ı:", -- cgit v1.2.3-54-g00ecf From 2a50bbc3bc3324d9aca3b1ac3e57ae8fd9eee08c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 17:59:20 -0700 Subject: Move HTTP basic auth logic from WebsiteMode to Web, so it applies to all modes --- onionshare/__init__.py | 9 +++------ onionshare/web/base_share_mode.py | 19 +++++++++++++++++++ onionshare/web/web.py | 21 ++++++++++++++++++++- onionshare/web/website_mode.py | 22 ---------------------- 4 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 onionshare/web/base_share_mode.py diff --git a/onionshare/__init__.py b/onionshare/__init__.py index a96f2fca..5df59975 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -50,8 +50,8 @@ def main(cwd=None): parser.add_argument('--auto-stop-timer', metavar='', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds") parser.add_argument('--connect-timeout', metavar='', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)") parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)") - parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them") - parser.add_argument('--website', action='store_true', dest='website', help=strings._("help_website")) + parser.add_argument('--receive', action='store_true', dest='receive', help="Receive files instead of sending them") + parser.add_argument('--website', action='store_true', dest='website', help="Host a static website as an onion service") parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share") @@ -174,7 +174,6 @@ def main(cwd=None): if mode == 'website': # Prepare files to share - print(strings._("preparing_website")) try: web.website_mode.set_file_info(filenames) except OSError as e: @@ -219,10 +218,8 @@ def main(cwd=None): # Build the URL if common.settings.get('public_mode'): url = 'http://{0:s}'.format(app.onion_host) - elif mode == 'website': - url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) else: - url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) + url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) print('') if autostart_timer > 0: diff --git a/onionshare/web/base_share_mode.py b/onionshare/web/base_share_mode.py new file mode 100644 index 00000000..64cf3dce --- /dev/null +++ b/onionshare/web/base_share_mode.py @@ -0,0 +1,19 @@ +import os +import sys +import tempfile +import zipfile +import mimetypes +import gzip +from flask import Response, request, render_template, make_response + +from .. import strings + + +class ShareModeWeb(object): + """ + This is the base class that includes shared functionality between share mode + and website mode + """ + def __init__(self, common, web): + self.common = common + self.web = web diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 0ba8c6b3..83c441d7 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -10,6 +10,7 @@ from urllib.request import urlopen import flask from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version +from flask_httpauth import HTTPBasicAuth from .. import strings @@ -53,6 +54,7 @@ class Web(object): 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.auth = HTTPBasicAuth() # Verbose mode? if self.common.verbose: @@ -119,8 +121,25 @@ class Web(object): def define_common_routes(self): """ - Common web app routes between sending, receiving and website modes. + Common web app routes between all modes. """ + + @self.auth.get_password + def get_pw(username): + if username == 'onionshare': + return self.slug + else: + return None + + @self.app.before_request + def conditional_auth_check(): + if not self.common.settings.get('public_mode'): + @self.auth.login_required + def _check_login(): + return None + + return _check_login() + @self.app.errorhandler(404) def page_not_found(e): """ diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 39f41b3e..354c5aa7 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -3,7 +3,6 @@ import sys import tempfile import mimetypes from flask import Response, request, render_template, make_response, send_from_directory -from flask_httpauth import HTTPBasicAuth from .. import strings @@ -17,7 +16,6 @@ class WebsiteModeWeb(object): self.common.log('WebsiteModeWeb', '__init__') self.web = web - self.auth = HTTPBasicAuth() # Dictionary mapping file paths to filenames on disk self.files = {} @@ -26,8 +24,6 @@ class WebsiteModeWeb(object): # Reset assets path self.web.app.static_folder=self.common.get_resource_path('static') - self.users = { } - self.define_routes() def define_routes(self): @@ -35,24 +31,6 @@ class WebsiteModeWeb(object): The web app routes for sharing a website """ - @self.auth.get_password - def get_pw(username): - self.users['onionshare'] = self.web.slug - - if username in self.users: - return self.users.get(username) - else: - return None - - @self.web.app.before_request - def conditional_auth_check(): - if not self.common.settings.get('public_mode'): - @self.auth.login_required - def _check_login(): - return None - - return _check_login() - @self.web.app.route('/', defaults={'path': ''}) @self.web.app.route('/') def path_public(path): -- cgit v1.2.3-54-g00ecf From 79b87c3e30480708af6d824a19430d24d2693dd4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 19:04:50 -0700 Subject: Add an error 401 handler, and make it start counting invalid password guesses instead of 404 errors for rate limiting --- onionshare/web/web.py | 49 +++++++++++++++------------- onionshare_gui/mode/receive_mode/__init__.py | 2 +- onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/mode/website_mode/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 5 ++- share/locale/en.json | 3 +- share/templates/401.html | 19 +++++++++++ 7 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 share/templates/401.html diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 83c441d7..14e2f9b3 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -44,6 +44,7 @@ class Web(object): REQUEST_UPLOAD_FINISHED = 8 REQUEST_UPLOAD_CANCELED = 9 REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10 + REQUEST_INVALID_SLUG = 11 def __init__(self, common, is_gui, mode='share'): self.common = common @@ -55,6 +56,7 @@ class Web(object): template_folder=self.common.get_resource_path('templates')) self.app.secret_key = self.common.random_string(8) self.auth = HTTPBasicAuth() + self.auth.error_handler(self.error401) # Verbose mode? if self.common.verbose: @@ -95,7 +97,8 @@ class Web(object): self.q = queue.Queue() self.slug = None - self.error404_count = 0 + + self.reset_invalid_slugs() self.done = False @@ -141,10 +144,7 @@ class Web(object): return _check_login() @self.app.errorhandler(404) - def page_not_found(e): - """ - 404 error page. - """ + def not_found(e): return self.error404() @self.app.route("//shutdown") @@ -164,18 +164,26 @@ class Web(object): r = make_response(render_template('receive_noscript_xss.html')) return self.add_security_headers(r) - def error404(self): - self.add_request(Web.REQUEST_OTHER, request.path) - if request.path != '/favicon.ico': - self.error404_count += 1 + def error401(self): + auth = request.authorization + if auth: + if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_slugs: + print('Invalid password guess: {}'.format(auth['password'])) + self.add_request(Web.REQUEST_INVALID_SLUG, data=auth['password']) - # In receive mode, with public mode enabled, skip rate limiting 404s - if not self.common.settings.get('public_mode'): - if self.error404_count == 20: - self.add_request(Web.REQUEST_RATE_LIMIT, request.path) + self.invalid_slugs.append(auth['password']) + self.invalid_slugs_count += 1 + + if self.invalid_slugs_count == 20: + self.add_request(Web.REQUEST_RATE_LIMIT) self.force_shutdown() - print("Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") + print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") + r = make_response(render_template('401.html'), 401) + return self.add_security_headers(r) + + def error404(self): + self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('404.html'), 404) return self.add_security_headers(r) @@ -198,7 +206,7 @@ class Web(object): return True return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) - def add_request(self, request_type, path, data=None): + def add_request(self, request_type, path=None, data=None): """ Add a request to the queue, to communicate with the GUI. """ @@ -226,18 +234,15 @@ class Web(object): log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) - def check_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if self.common.settings.get('public_mode'): - abort(404) - if not hmac.compare_digest(self.slug, slug_candidate): - abort(404) - def check_shutdown_slug_candidate(self, slug_candidate): self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) if not hmac.compare_digest(self.shutdown_slug, slug_candidate): abort(404) + def reset_invalid_slugs(self): + self.invalid_slugs_count = 0 + self.invalid_slugs = [] + def force_shutdown(self): """ Stop the flask web server, from the context of the flask app. diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 4c0b49ba..d6b1c0f3 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -113,7 +113,7 @@ class ReceiveMode(Mode): """ # Reset web counters self.web.receive_mode.upload_count = 0 - self.web.error404_count = 0 + self.web.reset_invalid_slugs() # Hide and reset the uploads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 1ee40ca3..f51fd0bb 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -147,7 +147,7 @@ class ShareMode(Mode): """ # Reset web counters self.web.share_mode.download_count = 0 - self.web.error404_count = 0 + self.web.reset_invalid_slugs() # Hide and reset the downloads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 9018f5cb..c6009ebe 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -143,7 +143,7 @@ class WebsiteMode(Mode): """ # Reset web counters self.web.website_mode.visit_count = 0 - self.web.error404_count = 0 + self.web.reset_invalid_slugs() # Hide and reset the downloads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 9fdf9395..4945ca7e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -472,7 +472,10 @@ class OnionShareGui(QtWidgets.QMainWindow): if event["type"] == Web.REQUEST_OTHER: if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded'), event["path"])) + self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"])) + + if event["type"] == Web.REQUEST_INVALID_SLUG: + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_slugs_count, strings._('invalid_slug_guess'), event["data"])) mode.timer_callback() diff --git a/share/locale/en.json b/share/locale/en.json index 7183e734..6dea9860 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -3,6 +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_slug_guess": "Invalid password guess", "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", @@ -34,7 +35,7 @@ "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?", "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", - "error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.", + "error_rate_limit": "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.", "zip_progress_bar_format": "Compressing: %p%", "error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.", diff --git a/share/templates/401.html b/share/templates/401.html new file mode 100644 index 00000000..9d3989a3 --- /dev/null +++ b/share/templates/401.html @@ -0,0 +1,19 @@ + + + + + OnionShare: 401 Unauthorized Access + + + + + +
+
+

+

401 Unauthorized Access

+
+
+ + + -- cgit v1.2.3-54-g00ecf From 7fe733e9fc8aca764f2a20c970620bcf0354f54f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 19:11:24 -0700 Subject: Simplify share and receive mode so they no longer need to worry about slug_candidates --- onionshare/web/receive_mode.py | 48 +++++------------------------------------- onionshare/web/share_mode.py | 24 ++------------------- 2 files changed, 7 insertions(+), 65 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index e7f3b3ae..99451fc3 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -31,7 +31,8 @@ class ReceiveModeWeb(object): """ The web app routes for receiving files """ - def index_logic(): + @self.web.app.route("/") + def index(): self.web.add_request(self.web.REQUEST_LOAD, request.path) if self.common.settings.get('public_mode'): @@ -44,23 +45,8 @@ class ReceiveModeWeb(object): upload_action=upload_action)) return self.web.add_security_headers(r) - @self.web.app.route("/") - def index(slug_candidate): - if not self.can_upload: - return self.web.error403() - self.web.check_slug_candidate(slug_candidate) - return index_logic() - - @self.web.app.route("/") - def index_public(): - if not self.can_upload: - return self.web.error403() - if not self.common.settings.get('public_mode'): - return self.web.error404() - return index_logic() - - - def upload_logic(slug_candidate='', ajax=False): + @self.web.app.route("/upload", methods=['POST']) + def upload(ajax=False): """ Handle the upload files POST request, though at this point, the files have already been uploaded and saved to their correct locations. @@ -141,35 +127,11 @@ class ReceiveModeWeb(object): r = make_response(render_template('thankyou.html')) return self.web.add_security_headers(r) - @self.web.app.route("//upload", methods=['POST']) - def upload(slug_candidate): - if not self.can_upload: - return self.web.error403() - self.web.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) - - @self.web.app.route("/upload", methods=['POST']) - def upload_public(): - if not self.can_upload: - return self.web.error403() - if not self.common.settings.get('public_mode'): - return self.web.error404() - return upload_logic() - - @self.web.app.route("//upload-ajax", methods=['POST']) - def upload_ajax(slug_candidate): - if not self.can_upload: - return self.web.error403() - self.web.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate, ajax=True) - @self.web.app.route("/upload-ajax", methods=['POST']) def upload_ajax_public(): if not self.can_upload: return self.web.error403() - if not self.common.settings.get('public_mode'): - return self.web.error404() - return upload_logic(ajax=True) + return upload(ajax=True) class ReceiveModeWSGIMiddleware(object): diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index a0c8dc90..d5d3280f 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -44,18 +44,8 @@ class ShareModeWeb(object): """ The web app routes for sharing files """ - @self.web.app.route("/") - def index(slug_candidate): - self.web.check_slug_candidate(slug_candidate) - return index_logic() - @self.web.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.web.error404() - return index_logic() - - def index_logic(slug_candidate=''): + def index(): """ Render the template for the onionshare landing page. """ @@ -94,18 +84,8 @@ class ShareModeWeb(object): is_zipped=self.is_zipped)) return self.web.add_security_headers(r) - @self.web.app.route("//download") - def download(slug_candidate): - self.web.check_slug_candidate(slug_candidate) - return download_logic() - @self.web.app.route("/download") - def download_public(): - if not self.common.settings.get('public_mode'): - return self.web.error404() - return download_logic() - - def download_logic(slug_candidate=''): + def download(): """ Download the zip file. """ -- cgit v1.2.3-54-g00ecf From 29abfd8f876e2474a119aa13d957a6a84ebbd3c3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 19:14:04 -0700 Subject: This should be an elif, not an if, because otherwise the share mode stop button says "Stop Receive Mode" --- onionshare_gui/server_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index e8385e64..3b3a7794 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -285,7 +285,7 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_stop_server')) - if self.mode == ServerStatus.MODE_WEBSITE: + elif self.mode == ServerStatus.MODE_WEBSITE: self.server_button.setText(strings._('gui_share_stop_server')) else: self.server_button.setText(strings._('gui_receive_stop_server')) -- cgit v1.2.3-54-g00ecf From b667fcc4f8ca6d78ffda0e3b2faa9b606b733825 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 19:22:03 -0700 Subject: Fix onionshare URLs non-public mode is always http basic auth --- onionshare/__init__.py | 17 +++++++---------- onionshare_gui/server_status.py | 4 +--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 5df59975..765a083e 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -127,6 +127,13 @@ def main(cwd=None): app = OnionShare(common, onion, local_only, autostop_timer) app.set_stealth(stealth) app.choose_port() + + # Build the URL + if common.settings.get('public_mode'): + url = 'http://{0:s}'.format(app.onion_host) + else: + url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) + # Delay the startup if a startup timer was set if autostart_timer > 0: # Can't set a schedule that is later than the auto-stop timer @@ -135,10 +142,6 @@ def main(cwd=None): sys.exit() app.start_onion_service(False, True) - if common.settings.get('public_mode'): - url = 'http://{0:s}'.format(app.onion_host) - else: - url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) schedule = datetime.now() + timedelta(seconds=autostart_timer) if mode == 'receive': print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) @@ -215,12 +218,6 @@ def main(cwd=None): common.settings.set('slug', web.slug) common.settings.save() - # Build the URL - if common.settings.get('public_mode'): - url = 'http://{0:s}'.format(app.onion_host) - else: - url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) - print('') if autostart_timer > 0: print("Server started") diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 3b3a7794..b23a89a8 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -420,8 +420,6 @@ class ServerStatus(QtWidgets.QWidget): """ if self.common.settings.get('public_mode'): url = 'http://{0:s}'.format(self.app.onion_host) - elif self.mode == ServerStatus.MODE_WEBSITE: - url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.slug, self.app.onion_host) else: - url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) + url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.slug, self.app.onion_host) return url -- cgit v1.2.3-54-g00ecf From 322921142299604a6e5ad41a1252c2eb5493d4d3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 20:01:14 -0700 Subject: Make shutdown slug use http basic auth also, so the shutdown command doesnt fail with a 401 --- onionshare/web/web.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 14e2f9b3..f8f8f6ca 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -5,6 +5,7 @@ import queue import socket import sys import tempfile +import requests from distutils.version import LooseVersion as Version from urllib.request import urlopen @@ -131,6 +132,8 @@ class Web(object): def get_pw(username): if username == 'onionshare': return self.slug + elif username == 'shutdown': + return self.shutdown_slug else: return None @@ -293,14 +296,8 @@ class Web(object): # Reset any slug that was in use self.slug = None - # To stop flask, load http://127.0.0.1://shutdown + # To stop flask, load http://shutdown:[shutdown_slug]@127.0.0.1/[shutdown_slug]/shutdown + # (We're putting the shutdown_slug in the path as well to make routing simpler) if self.running: - try: - s = socket.socket() - s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) - except: - try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() - except: - pass + requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_slug), + auth=requests.auth.HTTPBasicAuth('shutdown', self.shutdown_slug)) -- cgit v1.2.3-54-g00ecf From fe64a5a059642aae13a505bdbba8808e199d8d43 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 22:02:43 -0700 Subject: Make the shutdown get request use the onionshare user for basic auth --- onionshare/web/web.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index f8f8f6ca..43316b43 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -132,8 +132,6 @@ class Web(object): def get_pw(username): if username == 'onionshare': return self.slug - elif username == 'shutdown': - return self.shutdown_slug else: return None @@ -155,9 +153,10 @@ class Web(object): """ Stop the flask web server, from the context of an http request. """ - self.check_shutdown_slug_candidate(slug_candidate) - self.force_shutdown() - return "" + if slug_candidate == self.shutdown_slug: + self.force_shutdown() + return "" + abort(404) @self.app.route("/noscript-xss-instructions") def noscript_xss_instructions(): @@ -237,11 +236,6 @@ class Web(object): log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) - def check_shutdown_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if not hmac.compare_digest(self.shutdown_slug, slug_candidate): - abort(404) - def reset_invalid_slugs(self): self.invalid_slugs_count = 0 self.invalid_slugs = [] @@ -293,11 +287,11 @@ class Web(object): # Let the mode know that the user stopped the server self.stop_q.put(True) - # Reset any slug that was in use - self.slug = None - # To stop flask, load http://shutdown:[shutdown_slug]@127.0.0.1/[shutdown_slug]/shutdown # (We're putting the shutdown_slug in the path as well to make routing simpler) if self.running: requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_slug), - auth=requests.auth.HTTPBasicAuth('shutdown', self.shutdown_slug)) + auth=requests.auth.HTTPBasicAuth('onionshare', self.slug)) + + # Reset any slug that was in use + self.slug = None -- cgit v1.2.3-54-g00ecf From 7d89f80f2079b331221528124ce0bd9cdd8a891e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 20 May 2019 22:18:49 -0700 Subject: Rename "slug" to "password" --- onionshare/__init__.py | 16 +++---- onionshare/common.py | 2 +- onionshare/settings.py | 2 +- onionshare/web/receive_mode.py | 8 ++-- onionshare/web/share_mode.py | 4 +- onionshare/web/web.py | 62 ++++++++++++++-------------- onionshare_gui/mode/__init__.py | 4 +- onionshare_gui/mode/receive_mode/__init__.py | 2 +- onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/mode/website_mode/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 6 +-- onionshare_gui/server_status.py | 6 +-- onionshare_gui/settings_dialog.py | 6 +-- onionshare_gui/threads.py | 8 ++-- share/locale/en.json | 2 +- 15 files changed, 66 insertions(+), 66 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 765a083e..e2ff50a2 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -121,9 +121,9 @@ def main(cwd=None): try: common.settings.load() if not common.settings.get('public_mode'): - web.generate_slug(common.settings.get('slug')) + web.generate_password(common.settings.get('password')) else: - web.slug = None + web.password = None app = OnionShare(common, onion, local_only, autostop_timer) app.set_stealth(stealth) app.choose_port() @@ -132,7 +132,7 @@ def main(cwd=None): if common.settings.get('public_mode'): url = 'http://{0:s}'.format(app.onion_host) else: - url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host) + url = 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host) # Delay the startup if a startup timer was set if autostart_timer > 0: @@ -200,22 +200,22 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.slug)) + t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.password)) t.daemon = True t.start() try: # Trap Ctrl-C - # Wait for web.generate_slug() to finish running + # Wait for web.generate_password() to finish running time.sleep(0.2) # start auto-stop timer thread if app.autostop_timer > 0: app.autostop_timer_thread.start() - # Save the web slug if we are using a persistent private key + # Save the web password if we are using a persistent private key if common.settings.get('save_private_key'): - if not common.settings.get('slug'): - common.settings.set('slug', web.slug) + if not common.settings.get('password'): + common.settings.set('password', web.password) common.settings.save() print('') diff --git a/onionshare/common.py b/onionshare/common.py index 325f11d4..9b871f04 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -143,7 +143,7 @@ class Common(object): os.makedirs(onionshare_data_dir, 0o700, True) return onionshare_data_dir - def build_slug(self): + def build_password(self): """ Returns a random string made from two words from the wordlist, such as "deter-trig". """ diff --git a/onionshare/settings.py b/onionshare/settings.py index 16b64a05..762c6dc2 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -111,7 +111,7 @@ class Settings(object): 'save_private_key': False, 'private_key': '', 'public_mode': False, - 'slug': '', + 'password': '', 'hidservauth_string': '', 'data_dir': self.build_default_data_dir(), 'locale': None # this gets defined in fill_in_defaults() diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 99451fc3..af146cb0 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -38,7 +38,7 @@ class ReceiveModeWeb(object): if self.common.settings.get('public_mode'): upload_action = '/upload' else: - upload_action = '/{}/upload'.format(self.web.slug) + upload_action = '/{}/upload'.format(self.web.password) r = make_response(render_template( 'receive.html', @@ -87,7 +87,7 @@ class ReceiveModeWeb(object): if self.common.settings.get('public_mode'): return redirect('/') else: - return redirect('/{}'.format(slug_candidate)) + return redirect('/{}'.format(password_candidate)) # Note that flash strings are in English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user @@ -117,7 +117,7 @@ class ReceiveModeWeb(object): if self.common.settings.get('public_mode'): path = '/' else: - path = '/{}'.format(slug_candidate) + path = '/{}'.format(password_candidate) return redirect('{}'.format(path)) else: if ajax: @@ -238,7 +238,7 @@ class ReceiveModeRequest(Request): if self.path == '/upload' or self.path == '/upload-ajax': self.upload_request = True else: - if self.path == '/{}/upload'.format(self.web.slug) or self.path == '/{}/upload-ajax'.format(self.web.slug): + if self.path == '/{}/upload'.format(self.web.password) or self.path == '/{}/upload-ajax'.format(self.web.password): self.upload_request = True if self.upload_request: diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index d5d3280f..bede4a36 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -64,10 +64,10 @@ class ShareModeWeb(object): else: self.filesize = self.download_filesize - if self.web.slug: + if self.web.password: r = make_response(render_template( 'send.html', - slug=self.web.slug, + password=self.web.password, file_info=self.file_info, filename=os.path.basename(self.download_filename), filesize=self.filesize, diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 43316b43..eb4c34a9 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -45,7 +45,7 @@ class Web(object): REQUEST_UPLOAD_FINISHED = 8 REQUEST_UPLOAD_CANCELED = 9 REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10 - REQUEST_INVALID_SLUG = 11 + REQUEST_INVALID_PASSWORD = 11 def __init__(self, common, is_gui, mode='share'): self.common = common @@ -97,14 +97,14 @@ class Web(object): ] self.q = queue.Queue() - self.slug = None + self.password = None - self.reset_invalid_slugs() + self.reset_invalid_passwords() self.done = False # shutting down the server only works within the context of flask, so the easiest way to do it is over http - self.shutdown_slug = self.common.random_string(16) + self.shutdown_password = self.common.random_string(16) # Keep track if the server is running self.running = False @@ -131,7 +131,7 @@ class Web(object): @self.auth.get_password def get_pw(username): if username == 'onionshare': - return self.slug + return self.password else: return None @@ -148,12 +148,12 @@ class Web(object): def not_found(e): return self.error404() - @self.app.route("//shutdown") - def shutdown(slug_candidate): + @self.app.route("//shutdown") + def shutdown(password_candidate): """ Stop the flask web server, from the context of an http request. """ - if slug_candidate == self.shutdown_slug: + if password_candidate == self.shutdown_password: self.force_shutdown() return "" abort(404) @@ -169,14 +169,14 @@ class Web(object): def error401(self): auth = request.authorization if auth: - if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_slugs: + if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_passwords: print('Invalid password guess: {}'.format(auth['password'])) - self.add_request(Web.REQUEST_INVALID_SLUG, data=auth['password']) + self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth['password']) - self.invalid_slugs.append(auth['password']) - self.invalid_slugs_count += 1 + self.invalid_passwords.append(auth['password']) + self.invalid_passwords_count += 1 - if self.invalid_slugs_count == 20: + if self.invalid_passwords_count == 20: self.add_request(Web.REQUEST_RATE_LIMIT) self.force_shutdown() print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") @@ -218,14 +218,14 @@ class Web(object): 'data': data }) - def generate_slug(self, persistent_slug=None): - self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) - if persistent_slug != None and persistent_slug != '': - self.slug = persistent_slug - self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) + def generate_password(self, persistent_password=None): + self.common.log('Web', 'generate_password', 'persistent_password={}'.format(persistent_password)) + if persistent_password != None and persistent_password != '': + self.password = persistent_password + self.common.log('Web', 'generate_password', 'persistent_password sent, so password is: "{}"'.format(self.password)) else: - self.slug = self.common.build_slug() - self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) + self.password = self.common.build_password() + self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password)) def verbose_mode(self): """ @@ -236,9 +236,9 @@ class Web(object): log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) - def reset_invalid_slugs(self): - self.invalid_slugs_count = 0 - self.invalid_slugs = [] + def reset_invalid_passwords(self): + self.invalid_passwords_count = 0 + self.invalid_passwords = [] def force_shutdown(self): """ @@ -254,11 +254,11 @@ class Web(object): pass self.running = False - def start(self, port, stay_open=False, public_mode=False, slug=None): + def start(self, port, stay_open=False, public_mode=False, password=None): """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, slug={}'.format(port, stay_open, public_mode, slug)) + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, password={}'.format(port, stay_open, public_mode, password)) self.stay_open = stay_open @@ -287,11 +287,11 @@ class Web(object): # Let the mode know that the user stopped the server self.stop_q.put(True) - # To stop flask, load http://shutdown:[shutdown_slug]@127.0.0.1/[shutdown_slug]/shutdown - # (We're putting the shutdown_slug in the path as well to make routing simpler) + # To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown + # (We're putting the shutdown_password in the path as well to make routing simpler) if self.running: - requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_slug), - auth=requests.auth.HTTPBasicAuth('onionshare', self.slug)) + requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_password), + auth=requests.auth.HTTPBasicAuth('onionshare', self.password)) - # Reset any slug that was in use - self.slug = None + # Reset any password that was in use + self.password = None diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 8f5ff32b..e92e36f8 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -24,7 +24,7 @@ from onionshare.common import AutoStopTimer from ..server_status import ServerStatus from ..threads import OnionThread -from ..threads import AutoStartTimer +from ..threads import AutoStartTimer from ..widgets import Alert class Mode(QtWidgets.QWidget): @@ -181,7 +181,7 @@ class Mode(QtWidgets.QWidget): self.app.port = None # Start the onion thread. If this share was scheduled for a future date, - # the OnionThread will start and exit 'early' to obtain the port, slug + # the OnionThread will start and exit 'early' to obtain the port, password # and onion address, but it will not start the WebThread yet. if self.server_status.autostart_timer_datetime: self.start_onion_thread(obtain_onion_early=True) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index d6b1c0f3..dbc0bc73 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -113,7 +113,7 @@ class ReceiveMode(Mode): """ # Reset web counters self.web.receive_mode.upload_count = 0 - self.web.reset_invalid_slugs() + self.web.reset_invalid_passwords() # Hide and reset the uploads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index f51fd0bb..143fd577 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -147,7 +147,7 @@ class ShareMode(Mode): """ # Reset web counters self.web.share_mode.download_count = 0 - self.web.reset_invalid_slugs() + self.web.reset_invalid_passwords() # Hide and reset the downloads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index c6009ebe..ef7df94e 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -143,7 +143,7 @@ class WebsiteMode(Mode): """ # Reset web counters self.web.website_mode.visit_count = 0 - self.web.reset_invalid_slugs() + self.web.reset_invalid_passwords() # Hide and reset the downloads if we have previously shared self.reset_info_counters() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 4945ca7e..c5e7dc24 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -471,11 +471,11 @@ class OnionShareGui(QtWidgets.QMainWindow): Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) if event["type"] == Web.REQUEST_OTHER: - if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): + if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_password): self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"])) - if event["type"] == Web.REQUEST_INVALID_SLUG: - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_slugs_count, strings._('invalid_slug_guess'), event["data"])) + 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"])) mode.timer_callback() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index b23a89a8..3a6e31cc 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -243,8 +243,8 @@ class ServerStatus(QtWidgets.QWidget): self.show_url() if self.common.settings.get('save_private_key'): - if not self.common.settings.get('slug'): - self.common.settings.set('slug', self.web.slug) + if not self.common.settings.get('password'): + self.common.settings.set('password', self.web.password) self.common.settings.save() if self.common.settings.get('autostart_timer'): @@ -421,5 +421,5 @@ class ServerStatus(QtWidgets.QWidget): if self.common.settings.get('public_mode'): url = 'http://{0:s}'.format(self.app.onion_host) else: - url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.slug, self.app.onion_host) + url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.password, self.app.onion_host) return url diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 3c0b83f4..ae5f5acf 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -54,7 +54,7 @@ class SettingsDialog(QtWidgets.QDialog): # General settings - # Use a slug or not ('public mode') + # Use a password or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) @@ -968,12 +968,12 @@ class SettingsDialog(QtWidgets.QDialog): if self.save_private_key_checkbox.isChecked(): settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) - settings.set('slug', self.old_settings.get('slug')) + settings.set('password', self.old_settings.get('password')) settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) else: settings.set('save_private_key', False) settings.set('private_key', '') - settings.set('slug', '') + settings.set('password', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 26a9ee6b..bee1b6bc 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -42,13 +42,13 @@ class OnionThread(QtCore.QThread): def run(self): self.mode.common.log('OnionThread', 'run') - # Choose port and slug early, because we need them to exist in advance for scheduled shares + # 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: self.mode.app.choose_port() if not self.mode.common.settings.get('public_mode'): - if not self.mode.web.slug: - self.mode.web.generate_slug(self.mode.common.settings.get('slug')) + if not self.mode.web.password: + self.mode.web.generate_password(self.mode.common.settings.get('password')) try: if self.mode.obtain_onion_early: @@ -86,7 +86,7 @@ class WebThread(QtCore.QThread): def run(self): self.mode.common.log('WebThread', 'run') - self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.slug) + self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.password) self.success.emit() diff --git a/share/locale/en.json b/share/locale/en.json index 6dea9860..2063a415 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_slug_guess": "Invalid password guess", + "invalid_password_guess": "Invalid password guess", "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", -- cgit v1.2.3-54-g00ecf From 97b5984afff1b00743380e775b961eac5ff3b3a7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 21 May 2019 10:08:54 -0700 Subject: Fix building the URL in CLI mode, and removing the slug from the download button in share mode --- onionshare/__init__.py | 19 +++++++++++++------ onionshare/web/share_mode.py | 25 +++++++------------------ share/templates/send.html | 4 ---- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index e2ff50a2..f0889263 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -27,6 +27,15 @@ from .web import Web from .onion import * from .onionshare import OnionShare + +def build_url(common, app, web): + # Build the URL + if common.settings.get('public_mode'): + return 'http://{0:s}'.format(app.onion_host) + else: + return 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host) + + def main(cwd=None): """ The main() function implements all of the logic that the command-line version of @@ -128,12 +137,6 @@ def main(cwd=None): app.set_stealth(stealth) app.choose_port() - # Build the URL - if common.settings.get('public_mode'): - url = 'http://{0:s}'.format(app.onion_host) - else: - url = 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host) - # Delay the startup if a startup timer was set if autostart_timer > 0: # Can't set a schedule that is later than the auto-stop timer @@ -142,6 +145,7 @@ def main(cwd=None): sys.exit() app.start_onion_service(False, True) + url = build_url(common, app, web) schedule = datetime.now() + timedelta(seconds=autostart_timer) if mode == 'receive': print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) @@ -218,6 +222,9 @@ def main(cwd=None): common.settings.set('password', web.password) common.settings.save() + # Build the URL + url = build_url(common, app, web) + print('') if autostart_timer > 0: print("Server started") diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index bede4a36..22c58559 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -64,24 +64,13 @@ class ShareModeWeb(object): else: self.filesize = self.download_filesize - if self.web.password: - r = make_response(render_template( - 'send.html', - password=self.web.password, - 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)) - else: - # If download is allowed to continue, serve download page - 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)) + 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)) return self.web.add_security_headers(r) @self.web.app.route("/download") diff --git a/share/templates/send.html b/share/templates/send.html index 2a56829a..7be9e100 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -15,11 +15,7 @@
  • Total size: {{ filesize_human }} {% if is_zipped %} (compressed){% endif %}
  • - {% if slug %} -
  • Download Files
  • - {% else %}
  • Download Files
  • - {% endif %}
-- cgit v1.2.3-54-g00ecf From 63ced5625028f42331eab729639534c8064ba352 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 21 May 2019 10:18:40 -0700 Subject: Update ReceiveMode to no longer rely on slugs --- onionshare/web/receive_mode.py | 30 +++++------------------------- share/static/js/receive.js | 2 +- share/templates/receive.html | 2 +- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index af146cb0..60f421fa 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -34,15 +34,7 @@ class ReceiveModeWeb(object): @self.web.app.route("/") def index(): self.web.add_request(self.web.REQUEST_LOAD, request.path) - - if self.common.settings.get('public_mode'): - upload_action = '/upload' - else: - upload_action = '/{}/upload'.format(self.web.password) - - r = make_response(render_template( - 'receive.html', - upload_action=upload_action)) + r = make_response(render_template('receive.html')) return self.web.add_security_headers(r) @self.web.app.route("/upload", methods=['POST']) @@ -83,11 +75,7 @@ class ReceiveModeWeb(object): return json.dumps({"error_flashes": [msg]}) else: flash(msg, 'error') - - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(password_candidate)) + return redirect('/') # Note that flash strings are in English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user @@ -114,11 +102,7 @@ class ReceiveModeWeb(object): if ajax: return json.dumps({"info_flashes": info_flashes}) else: - if self.common.settings.get('public_mode'): - path = '/' - else: - path = '/{}'.format(password_candidate) - return redirect('{}'.format(path)) + return redirect('/') else: if ajax: return json.dumps({"new_body": render_template('thankyou.html')}) @@ -234,12 +218,8 @@ class ReceiveModeRequest(Request): # Is this a valid upload request? self.upload_request = False if self.method == 'POST': - if self.web.common.settings.get('public_mode'): - if self.path == '/upload' or self.path == '/upload-ajax': - self.upload_request = True - else: - if self.path == '/{}/upload'.format(self.web.password) or self.path == '/{}/upload-ajax'.format(self.web.password): - self.upload_request = True + if self.path == '/upload' or self.path == '/upload-ajax': + self.upload_request = True if self.upload_request: # No errors yet diff --git a/share/static/js/receive.js b/share/static/js/receive.js index c29c726c..cbd60954 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -121,7 +121,7 @@ $(function(){ $('#uploads').append($upload_div); // Send the request - ajax.open('POST', window.location.pathname.replace(/\/$/, '') + '/upload-ajax', true); + ajax.open('POST', '/upload-ajax', true); ajax.send(formData); }); }); diff --git a/share/templates/receive.html b/share/templates/receive.html index 4f207a03..dd36ac72 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -45,7 +45,7 @@ -
+

-- cgit v1.2.3-54-g00ecf From 91238366b1f54568270ff0132f5b9c50cdbd0b5f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 22 May 2019 19:55:55 -0700 Subject: Don't need BaseShareMode yet --- onionshare/web/base_share_mode.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 onionshare/web/base_share_mode.py diff --git a/onionshare/web/base_share_mode.py b/onionshare/web/base_share_mode.py deleted file mode 100644 index 64cf3dce..00000000 --- a/onionshare/web/base_share_mode.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -import sys -import tempfile -import zipfile -import mimetypes -import gzip -from flask import Response, request, render_template, make_response - -from .. import strings - - -class ShareModeWeb(object): - """ - This is the base class that includes shared functionality between share mode - and website mode - """ - def __init__(self, common, web): - self.common = common - self.web = web -- cgit v1.2.3-54-g00ecf From 41be429b91f8b323644fe200f696df1890ac3de7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 22 May 2019 20:07:35 -0700 Subject: Make static folder URL have a high-entropy random path, to avoid filename collisions with files getting shared --- onionshare/web/receive_mode.py | 9 ++++++--- onionshare/web/share_mode.py | 9 ++++++--- onionshare/web/web.py | 14 ++++++++++---- onionshare/web/website_mode.py | 3 ++- share/templates/401.html | 6 +++--- share/templates/403.html | 6 +++--- share/templates/404.html | 6 +++--- share/templates/denied.html | 2 +- share/templates/listing.html | 10 +++++----- share/templates/receive.html | 16 ++++++++-------- share/templates/receive_noscript_xss.html | 6 +++--- share/templates/send.html | 12 ++++++------ share/templates/thankyou.html | 8 ++++---- 13 files changed, 60 insertions(+), 47 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 60f421fa..3f848d2f 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -34,7 +34,8 @@ 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) @self.web.app.route("/upload", methods=['POST']) @@ -105,10 +106,12 @@ class ReceiveModeWeb(object): return redirect('/') else: if ajax: - return json.dumps({"new_body": render_template('thankyou.html')}) + return json.dumps({ + "new_body": render_template('thankyou.html', static_url_path=self.web.static_url_path) + }) else: # It was the last upload and the timer ran out - r = make_response(render_template('thankyou.html')) + r = make_response(render_template('thankyou.html'), static_url_path=self.web.static_url_path) return self.web.add_security_headers(r) @self.web.app.route("/upload-ajax", methods=['POST']) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 22c58559..0dfa7e0a 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -55,7 +55,8 @@ class ShareModeWeb(object): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html')) + r = make_response(render_template('denied.html'), + static_url_path=self.web.static_url_path) return self.web.add_security_headers(r) # If download is allowed to continue, serve download page @@ -70,7 +71,8 @@ class ShareModeWeb(object): 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)) + is_zipped=self.is_zipped, + static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) @self.web.app.route("/download") @@ -82,7 +84,8 @@ class ShareModeWeb(object): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html')) + r = make_response(render_template('denied.html', + static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) # Each download has a unique id diff --git a/onionshare/web/web.py b/onionshare/web/web.py index eb4c34a9..1500a23c 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -51,8 +51,13 @@ 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) @@ -163,7 +168,8 @@ class Web(object): """ Display instructions for disabling Tor Browser's NoScript XSS setting """ - r = make_response(render_template('receive_noscript_xss.html')) + r = make_response(render_template('receive_noscript_xss.html', + static_url_path=self.static_url_path)) return self.add_security_headers(r) def error401(self): @@ -181,18 +187,18 @@ class Web(object): 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.") - r = make_response(render_template('401.html'), 401) + r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) return self.add_security_headers(r) def error404(self): self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('404.html'), 404) + 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'), 403) + r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) return self.add_security_headers(r) def add_security_headers(self, r): diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 354c5aa7..d2cd6db9 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -131,7 +131,8 @@ class WebsiteModeWeb(object): r = make_response(render_template('listing.html', path=path, files=files, - dirs=dirs)) + dirs=dirs, + static_url_path=self.web.static_url_path)) return self.web.add_security_headers(r) def set_file_info(self, filenames): diff --git a/share/templates/401.html b/share/templates/401.html index 9d3989a3..dc50f534 100644 --- a/share/templates/401.html +++ b/share/templates/401.html @@ -3,14 +3,14 @@ OnionShare: 401 Unauthorized Access - - + +
-

+

401 Unauthorized Access

diff --git a/share/templates/403.html b/share/templates/403.html index f3ea4e0e..2ebab09a 100644 --- a/share/templates/403.html +++ b/share/templates/403.html @@ -3,14 +3,14 @@ OnionShare: 403 Forbidden - - + +
-

+

You are not allowed to perform that action at this time.

diff --git a/share/templates/404.html b/share/templates/404.html index 1c5d7d2d..375c125d 100644 --- a/share/templates/404.html +++ b/share/templates/404.html @@ -3,14 +3,14 @@ OnionShare: 404 Not Found - - + +
-

+

404 Not Found

diff --git a/share/templates/denied.html b/share/templates/denied.html index 94fb379b..ad4d0b21 100644 --- a/share/templates/denied.html +++ b/share/templates/denied.html @@ -3,7 +3,7 @@ OnionShare - + diff --git a/share/templates/listing.html b/share/templates/listing.html index 8883eea9..e394f842 100644 --- a/share/templates/listing.html +++ b/share/templates/listing.html @@ -2,13 +2,13 @@ OnionShare - - + +
- +

OnionShare

@@ -22,7 +22,7 @@ {% for info in dirs %} - + {{ info.basename }} @@ -34,7 +34,7 @@ {% for info in files %} - + {{ info.basename }} diff --git a/share/templates/receive.html b/share/templates/receive.html index dd36ac72..23242501 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -2,13 +2,13 @@ OnionShare - - + +
- +

OnionShare

@@ -19,14 +19,14 @@ -->

- Warning: Due to a bug in Tor Browser and Firefox, uploads + Warning: Due to a bug in Tor Browser and Firefox, uploads sometimes never finish. To upload reliably, either set your Tor Browser security slider to Standard or turn off your Tor Browser's NoScript XSS setting.

-

+

Send Files

Select the files you want to send, then click "Send Files"...

@@ -51,8 +51,8 @@ - - - + + + diff --git a/share/templates/receive_noscript_xss.html b/share/templates/receive_noscript_xss.html index bce78524..84d35ba1 100644 --- a/share/templates/receive_noscript_xss.html +++ b/share/templates/receive_noscript_xss.html @@ -2,13 +2,13 @@ OnionShare - - + +
- +

OnionShare

diff --git a/share/templates/send.html b/share/templates/send.html index 7be9e100..e0076c0f 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -3,8 +3,8 @@ OnionShare - - + + @@ -18,7 +18,7 @@
  • Download Files
  • - +

    OnionShare

    @@ -31,7 +31,7 @@ {% for info in file_info.dirs %} - + {{ info.basename }} {{ info.size_human }} @@ -41,7 +41,7 @@ {% for info in file_info.files %} - + {{ info.basename }} {{ info.size_human }} @@ -49,7 +49,7 @@ {% endfor %} - + diff --git a/share/templates/thankyou.html b/share/templates/thankyou.html index c4b39cde..b7e2b97c 100644 --- a/share/templates/thankyou.html +++ b/share/templates/thankyou.html @@ -3,19 +3,19 @@ OnionShare is closed - - + +
    - +

    OnionShare

    -

    +

    Thank you for using OnionShare

    You may now close this window.

    -- cgit v1.2.3-54-g00ecf From 7ac81e6b7e8163a8bdaa32d2b874287028f56bcf Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 22 May 2019 20:15:49 -0700 Subject: Allow static resources without basic auth --- onionshare/web/web.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 1500a23c..c6e902ed 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -142,6 +142,11 @@ class Web(object): @self.app.before_request def conditional_auth_check(): + # Allow static files without basic authentication + if(request.path.startswith(self.static_url_path + '/')): + return None + + # If public mode is disabled, require authentication if not self.common.settings.get('public_mode'): @self.auth.login_required def _check_login(): -- cgit v1.2.3-54-g00ecf From 44431becc1602a3178538f2a619abe6ebae29237 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 22 May 2019 20:46:23 -0700 Subject: Give xvfb-run a screen to floods of Qt logs in CircleCI tests, so the test output is readable --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index accbc808..3a63d743 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ jobs: - run: name: run tests command: | - xvfb-run pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ + xvfb-run -s "-screen 0 1280x1024x24" pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv --no-qt-log tests/ test-3.6: <<: *test-template -- cgit v1.2.3-54-g00ecf From 4df989dc77b2ffee26afd529dee312665c526a9b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 22 May 2019 20:55:31 -0700 Subject: Rename slugs to passwords in the tests --- tests/GuiBaseTest.py | 16 +++++------ tests/GuiReceiveTest.py | 8 +++--- tests/GuiShareTest.py | 32 +++++++++++----------- tests/TorGuiBaseTest.py | 8 +++--- tests/TorGuiReceiveTest.py | 5 ++-- tests/TorGuiShareTest.py | 13 ++++----- ...ionshare_share_mode_password_persistent_test.py | 29 ++++++++++++++++++++ ...l_onionshare_share_mode_slug_persistent_test.py | 29 -------------------- tests/onionshare_share_mode_persistent_test.py | 4 +-- tests/test_onionshare_common.py | 16 +++++------ tests/test_onionshare_settings.py | 2 +- tests/test_onionshare_web.py | 24 ++++++++-------- 12 files changed, 92 insertions(+), 94 deletions(-) create mode 100644 tests/local_onionshare_share_mode_password_persistent_test.py delete mode 100644 tests/local_onionshare_share_mode_slug_persistent_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index d3fc9945..65178f46 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -127,7 +127,7 @@ class GuiBaseTest(object): # Upload a file files = {'file[]': open('/tmp/test.txt', 'rb')} if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.password) else: path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) @@ -138,7 +138,7 @@ class GuiBaseTest(object): if public_mode: url = "http://127.0.0.1:{}/download".format(self.gui.app.port) else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug) + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.password) r = requests.get(url) QtTest.QTest.qWait(2000) @@ -190,12 +190,12 @@ class GuiBaseTest(object): self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - def have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' + def have_a_password(self, mode, public_mode): + '''Test that we have a valid password''' if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + self.assertRegex(mode.server_status.web.password, r'(\w+)-(\w+)') else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)') def url_description_shown(self, mode): @@ -212,7 +212,7 @@ class GuiBaseTest(object): if public_mode: self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port)) else: - self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug)) + self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.password)) def server_status_indicator_says_started(self, mode): @@ -230,7 +230,7 @@ class GuiBaseTest(object): s.connect(('127.0.0.1', self.gui.app.port)) if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) + path = '/{}'.format(mode.server_status.web.password) else: path = '/' diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 40c3de95..6ecf608c 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -9,7 +9,7 @@ class GuiReceiveTest(GuiBaseTest): '''Test that we can upload the file''' files = {'file[]': open(file_to_upload, 'rb')} if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) else: path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) @@ -40,7 +40,7 @@ class GuiReceiveTest(GuiBaseTest): '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' files = {'file[]': open('/tmp/test.txt', 'rb')} if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) else: path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) @@ -61,7 +61,7 @@ class GuiReceiveTest(GuiBaseTest): 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''' if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) else: path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) @@ -93,7 +93,7 @@ class GuiReceiveTest(GuiBaseTest): self.settings_button_is_hidden() self.server_is_started(self.gui.receive_mode) self.web_server_is_running() - self.have_a_slug(self.gui.receive_mode, public_mode) + self.have_a_password(self.gui.receive_mode, public_mode) self.url_description_shown(self.gui.receive_mode) self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 29661712..02ae0eea 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -7,9 +7,9 @@ from .GuiBaseTest import GuiBaseTest class GuiShareTest(GuiBaseTest): # Persistence tests - def have_same_slug(self, slug): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + def have_same_password(self, password): + '''Test that we have the same password''' + self.assertEqual(self.gui.share_mode.server_status.web.password, password) # Share-specific tests @@ -17,7 +17,7 @@ class GuiShareTest(GuiBaseTest): '''Test that the number of items in the list is as expected''' self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num) - + def deleting_all_files_hides_delete_button(self): '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) @@ -35,14 +35,14 @@ class GuiShareTest(GuiBaseTest): # No more files, the delete button should be hidden self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + def add_a_file_and_delete_using_its_delete_widget(self): '''Test that we can also delete a file by clicking on its [X] widget''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) self.file_selection_widget_has_files(0) - + def file_selection_widget_readd_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') @@ -56,14 +56,14 @@ class GuiShareTest(GuiBaseTest): with open('/tmp/large_file', 'wb') as fout: fout.write(os.urandom(size)) self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file') - + def add_delete_buttons_hidden(self): '''Test that the add and delete buttons are hidden when the server starts''' self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + def download_share(self, public_mode): '''Test that we can download the share''' s = socks.socksocket() @@ -73,7 +73,7 @@ class GuiShareTest(GuiBaseTest): if public_mode: path = '/download' else: - path = '{}/download'.format(self.gui.share_mode.web.slug) + path = '{}/download'.format(self.gui.share_mode.web.password) http_request = 'GET {} HTTP/1.0\r\n'.format(path) http_request += 'Host: 127.0.0.1\r\n' @@ -130,7 +130,7 @@ class GuiShareTest(GuiBaseTest): self.add_a_file_and_delete_using_its_delete_widget() self.file_selection_widget_readd_files() - + def run_all_share_mode_started_tests(self, public_mode, startup_time=2000): """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) @@ -139,12 +139,12 @@ class GuiShareTest(GuiBaseTest): self.settings_button_is_hidden() self.server_is_started(self.gui.share_mode, startup_time) self.web_server_is_running() - self.have_a_slug(self.gui.share_mode, public_mode) + self.have_a_password(self.gui.share_mode, public_mode) self.url_description_shown(self.gui.share_mode) self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) - + def run_all_share_mode_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" self.web_page(self.gui.share_mode, 'Total size', public_mode) @@ -158,7 +158,7 @@ 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_tests(self, public_mode, stay_open): """End-to-end share tests""" self.run_all_share_mode_setup_tests() @@ -178,12 +178,12 @@ class GuiShareTest(GuiBaseTest): def run_all_share_mode_persistent_tests(self, public_mode, stay_open): - """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + """Same as end-to-end share tests but also test the password is the same on multiple shared""" self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(public_mode) - slug = self.gui.share_mode.server_status.web.slug + password = self.gui.share_mode.server_status.web.password self.run_all_share_mode_download_tests(public_mode, stay_open) - self.have_same_slug(slug) + self.have_same_password(password) def run_all_share_mode_timer_tests(self, public_mode): diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 8bd963bd..3f9952d0 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -76,7 +76,7 @@ class TorGuiBaseTest(GuiBaseTest): # Upload a file files = {'file[]': open('/tmp/test.txt', 'rb')} if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug) + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.password) else: path = 'http://{}/upload'.format(self.gui.app.onion_host) response = session.post(path, files=files) @@ -87,7 +87,7 @@ class TorGuiBaseTest(GuiBaseTest): if public_mode: path = "http://{}/download".format(self.gui.app.onion_host) else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug) + path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.password) response = session.get(path) QtTest.QTest.qWait(4000) @@ -111,7 +111,7 @@ class TorGuiBaseTest(GuiBaseTest): s.settimeout(60) s.connect((self.gui.app.onion_host, 80)) if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) + path = '/{}'.format(mode.server_status.web.password) else: path = '/' http_request = 'GET {} HTTP/1.0\r\n'.format(path) @@ -138,7 +138,7 @@ class TorGuiBaseTest(GuiBaseTest): if public_mode: self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host)) else: - self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.slug)) + self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.password)) # Stealth tests diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py index a21dd4fc..601f34b6 100644 --- a/tests/TorGuiReceiveTest.py +++ b/tests/TorGuiReceiveTest.py @@ -13,7 +13,7 @@ class TorGuiReceiveTest(TorGuiBaseTest): session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) files = {'file[]': open(file_to_upload, 'rb')} if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.password) else: path = 'http://{}/upload'.format(self.gui.app.onion_host) response = session.post(path, files=files) @@ -35,7 +35,7 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.server_is_started(self.gui.receive_mode, startup_time=45000) self.web_server_is_running() self.have_an_onion_service() - self.have_a_slug(self.gui.receive_mode, public_mode) + self.have_a_password(self.gui.receive_mode, public_mode) self.url_description_shown(self.gui.receive_mode) self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) @@ -56,4 +56,3 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.server_working_on_start_button_pressed(self.gui.receive_mode) self.server_is_started(self.gui.receive_mode, startup_time=45000) self.history_indicator(self.gui.receive_mode, public_mode) - diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index 36efacd1..352707eb 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -17,7 +17,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): if public_mode: path = "http://{}/download".format(self.gui.app.onion_host) else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug) + path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.password) response = session.get(path, stream=True) QtTest.QTest.qWait(4000) @@ -53,7 +53,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.server_is_started(self.gui.share_mode, startup_time=45000) self.web_server_is_running() self.have_an_onion_service() - self.have_a_slug(self.gui.share_mode, public_mode) + self.have_a_password(self.gui.share_mode, public_mode) self.url_description_shown(self.gui.share_mode) self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) @@ -74,16 +74,16 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): def run_all_share_mode_persistent_tests(self, public_mode, stay_open): - """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + """Same as end-to-end share tests but also test the password is the same on multiple shared""" self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(public_mode) - slug = self.gui.share_mode.server_status.web.slug + password = self.gui.share_mode.server_status.web.password onion = self.gui.app.onion_host self.run_all_share_mode_download_tests(public_mode, stay_open) self.have_same_onion(onion) - self.have_same_slug(slug) + self.have_same_password(password) + - def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() @@ -92,4 +92,3 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.autostop_timer_widget_hidden(self.gui.share_mode) self.server_timed_out(self.gui.share_mode, 125000) self.web_server_is_stopped() - diff --git a/tests/local_onionshare_share_mode_password_persistent_test.py b/tests/local_onionshare_share_mode_password_persistent_test.py new file mode 100644 index 00000000..5b515ca1 --- /dev/null +++ b/tests/local_onionshare_share_mode_password_persistent_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "password": "", + "save_private_key": True, + "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_persistent_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py deleted file mode 100644 index 58e1cfeb..00000000 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "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_persistent_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index d0fb5b22..0e461f7e 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -4,13 +4,13 @@ import unittest from .TorGuiShareTest import TorGuiShareTest -class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): +class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): test_settings = { "use_legacy_v2_onions": True, "public_mode": False, - "slug": "", + "password": "", "save_private_key": True, "close_after_first_download": False, } diff --git a/tests/test_onionshare_common.py b/tests/test_onionshare_common.py index f975dce7..d5e67381 100644 --- a/tests/test_onionshare_common.py +++ b/tests/test_onionshare_common.py @@ -33,13 +33,13 @@ LOG_MSG_REGEX = re.compile(r""" ^\[Jun\ 06\ 2013\ 11:05:00\] \ TestModule\.\.dummy_func \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE) -SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') +PASSWORD_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') # TODO: Improve the Common tests to test it all as a single class -class TestBuildSlug: +class TestBuildPassword: @pytest.mark.parametrize('test_input,expected', ( # VALID, two lowercase words, separated by a hyphen ('syrup-enzyme', True), @@ -60,8 +60,8 @@ class TestBuildSlug: ('too-many-hyphens-', False), ('symbols-!@#$%', False) )) - def test_build_slug_regex(self, test_input, expected): - """ Test that `SLUG_REGEX` accounts for the following patterns + def test_build_password_regex(self, test_input, expected): + """ Test that `PASSWORD_REGEX` accounts for the following patterns There are a few hyphenated words in `wordlist.txt`: * drop-down @@ -69,17 +69,17 @@ class TestBuildSlug: * t-shirt * yo-yo - These words cause a few extra potential slug patterns: + These words cause a few extra potential password patterns: * word-word * hyphenated-word-word * word-hyphenated-word * hyphenated-word-hyphenated-word """ - assert bool(SLUG_REGEX.match(test_input)) == expected + assert bool(PASSWORD_REGEX.match(test_input)) == expected - def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode): - assert common_obj.build_slug() != common_obj.build_slug() + def test_build_password_unique(self, common_obj, sys_onionshare_dev_mode): + assert common_obj.build_password() != common_obj.build_password() class TestDirSize: diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index bcc2f7cb..05878899 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -63,7 +63,7 @@ class TestSettings: 'use_legacy_v2_onions': False, 'save_private_key': False, 'private_key': '', - 'slug': '', + 'password': '', 'hidservauth_string': '', 'data_dir': os.path.expanduser('~/OnionShare'), 'public_mode': False diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py index 0c29859b..f9c6c2ec 100644 --- a/tests/test_onionshare_web.py +++ b/tests/test_onionshare_web.py @@ -44,7 +44,7 @@ def web_obj(common_obj, mode, num_files=0): common_obj.settings = Settings(common_obj) strings.load_strings(common_obj) web = Web(common_obj, False, mode) - web.generate_slug() + web.generate_password() web.stay_open = True web.running = True @@ -76,17 +76,17 @@ class TestWeb: res.get_data() assert res.status_code == 404 - res = c.get('/invalidslug'.format(web.slug)) + res = c.get('/invalidpassword'.format(web.password)) res.get_data() assert res.status_code == 404 # Load download page - res = c.get('/{}'.format(web.slug)) + res = c.get('/{}'.format(web.password)) res.get_data() assert res.status_code == 200 # Download - res = c.get('/{}/download'.format(web.slug)) + res = c.get('/{}/download'.format(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -99,7 +99,7 @@ class TestWeb: with web.app.test_client() as c: # Download the first time - res = c.get('/{}/download'.format(web.slug)) + res = c.get('/{}/download'.format(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -114,7 +114,7 @@ class TestWeb: with web.app.test_client() as c: # Download the first time - res = c.get('/{}/download'.format(web.slug)) + res = c.get('/{}/download'.format(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -130,12 +130,12 @@ class TestWeb: res.get_data() assert res.status_code == 404 - res = c.get('/invalidslug'.format(web.slug)) + res = c.get('/invalidpassword'.format(web.password)) res.get_data() assert res.status_code == 404 # Load upload page - res = c.get('/{}'.format(web.slug)) + res = c.get('/{}'.format(web.password)) res.get_data() assert res.status_code == 200 @@ -149,8 +149,8 @@ class TestWeb: data1 = res.get_data() assert res.status_code == 200 - # /[slug] should be a 404 - res = c.get('/{}'.format(web.slug)) + # /[password] should be a 404 + res = c.get('/{}'.format(web.password)) data2 = res.get_data() assert res.status_code == 404 @@ -164,8 +164,8 @@ class TestWeb: data1 = res.get_data() assert res.status_code == 404 - # Upload page should be accessible from /[slug] - res = c.get('/{}'.format(web.slug)) + # Upload page should be accessible from /[password] + res = c.get('/{}'.format(web.password)) data2 = res.get_data() assert res.status_code == 200 -- cgit v1.2.3-54-g00ecf From 18961fea2dda64bcda6c461818901fd2e73576b1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 23 May 2019 09:53:18 -0700 Subject: Fix web tests to use basic auth and passwords instead of slugs --- tests/test_onionshare_web.py | 64 +++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py index f9c6c2ec..313dbcea 100644 --- a/tests/test_onionshare_web.py +++ b/tests/test_onionshare_web.py @@ -27,8 +27,10 @@ import socket import sys import zipfile import tempfile +import base64 import pytest +from werkzeug.datastructures import Headers from onionshare.common import Common from onionshare import strings @@ -71,22 +73,23 @@ class TestWeb: web = web_obj(common_obj, 'share', 3) assert web.mode is 'share' with web.app.test_client() as c: - # Load 404 pages + # Load / without auth res = c.get('/') res.get_data() - assert res.status_code == 404 + assert res.status_code == 401 - res = c.get('/invalidpassword'.format(web.password)) + # Load / with invalid auth + res = c.get('/', headers=self._make_auth_headers('invalid')) res.get_data() - assert res.status_code == 404 + assert res.status_code == 401 - # Load download page - res = c.get('/{}'.format(web.password)) + # Load / with valid auth + res = c.get('/', headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 # Download - res = c.get('/{}/download'.format(web.password)) + res = c.get('/download', headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -99,7 +102,7 @@ class TestWeb: with web.app.test_client() as c: # Download the first time - res = c.get('/{}/download'.format(web.password)) + res = c.get('/download', headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -114,7 +117,7 @@ class TestWeb: with web.app.test_client() as c: # Download the first time - res = c.get('/{}/download'.format(web.password)) + res = c.get('/download', headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' @@ -125,17 +128,18 @@ class TestWeb: assert web.mode is 'receive' with web.app.test_client() as c: - # Load 404 pages + # Load / without auth res = c.get('/') res.get_data() - assert res.status_code == 404 + assert res.status_code == 401 - res = c.get('/invalidpassword'.format(web.password)) + # Load / with invalid auth + res = c.get('/', headers=self._make_auth_headers('invalid')) res.get_data() - assert res.status_code == 404 + assert res.status_code == 401 - # Load upload page - res = c.get('/{}'.format(web.password)) + # Load / with valid auth + res = c.get('/', headers=self._make_auth_headers(web.password)) res.get_data() assert res.status_code == 200 @@ -144,31 +148,37 @@ class TestWeb: common_obj.settings.set('public_mode', True) with web.app.test_client() as c: - # Upload page should be accessible from / + # Loading / should work without auth res = c.get('/') data1 = res.get_data() assert res.status_code == 200 - # /[password] should be a 404 - res = c.get('/{}'.format(web.password)) - data2 = res.get_data() - assert res.status_code == 404 - def test_public_mode_off(self, common_obj): web = web_obj(common_obj, 'receive') common_obj.settings.set('public_mode', False) with web.app.test_client() as c: - # / should be a 404 + # Load / without auth res = c.get('/') - data1 = res.get_data() - assert res.status_code == 404 + res.get_data() + assert res.status_code == 401 + + # But static resources should work without auth + res = c.get('{}/css/style.css'.format(web.static_url_path)) + res.get_data() + assert res.status_code == 200 - # Upload page should be accessible from /[password] - res = c.get('/{}'.format(web.password)) - data2 = res.get_data() + # Load / with valid auth + res = c.get('/', headers=self._make_auth_headers(web.password)) + res.get_data() assert res.status_code == 200 + def _make_auth_headers(self, password): + auth = base64.b64encode(b'onionshare:'+password.encode()).decode() + h = Headers() + h.add('Authorization', 'Basic ' + auth) + return h + class TestZipWriterDefault: @pytest.mark.parametrize('test_input', ( -- cgit v1.2.3-54-g00ecf From f56b148ddb4d922c595400226263bea6d2a97fe8 Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 24 May 2019 10:08:51 +0200 Subject: Resolve bugs from initial PR --- onionshare/__init__.py | 4 ++-- onionshare_gui/mode/website_mode/__init__.py | 1 - onionshare_gui/onionshare_gui.py | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index a96f2fca..1b099a1d 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -51,7 +51,7 @@ def main(cwd=None): parser.add_argument('--connect-timeout', metavar='', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)") parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)") parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them") - parser.add_argument('--website', action='store_true', dest='website', help=strings._("help_website")) + parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website") parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share") @@ -174,7 +174,7 @@ def main(cwd=None): if mode == 'website': # Prepare files to share - print(strings._("preparing_website")) + print("Preparing files to publish website...") try: web.website_mode.set_file_info(filenames) except OSError as e: diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 9018f5cb..50d72564 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -18,7 +18,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import os -import secrets import random import string diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 9fdf9395..6dec82b2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -374,13 +374,16 @@ class OnionShareGui(QtWidgets.QMainWindow): if not self.common.settings.get('autostop_timer'): self.share_mode.server_status.autostop_timer_container.hide() self.receive_mode.server_status.autostop_timer_container.hide() + self.website_mode.server_status.autostop_timer_container.hide() # If we switched off the auto-start timer setting, ensure the widget is hidden. if not self.common.settings.get('autostart_timer'): self.share_mode.server_status.autostart_timer_datetime = None self.receive_mode.server_status.autostart_timer_datetime = None + self.website_mode.server_status.autostart_timer_datetime = None 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_() -- cgit v1.2.3-54-g00ecf From 9785be0375b36d377fd1f0e4b43e0f52189c263f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 24 May 2019 13:38:41 -0700 Subject: Replace URLs that have slugs with basic auth in tests --- tests/GuiBaseTest.py | 22 ++++++++------ tests/GuiReceiveTest.py | 35 ++++++++++++---------- tests/GuiShareTest.py | 10 +++---- ...onshare_401_public_mode_skips_ratelimit_test.py | 28 +++++++++++++++++ ...local_onionshare_401_triggers_ratelimit_test.py | 27 +++++++++++++++++ ...onshare_404_public_mode_skips_ratelimit_test.py | 28 ----------------- ...local_onionshare_404_triggers_ratelimit_test.py | 27 ----------------- 7 files changed, 92 insertions(+), 85 deletions(-) create mode 100644 tests/local_onionshare_401_public_mode_skips_ratelimit_test.py create mode 100644 tests/local_onionshare_401_triggers_ratelimit_test.py delete mode 100644 tests/local_onionshare_404_public_mode_skips_ratelimit_test.py delete mode 100644 tests/local_onionshare_404_triggers_ratelimit_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 65178f46..659ea052 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -4,6 +4,7 @@ import requests import shutil import socket import socks +import base64 from PyQt5 import QtCore, QtTest @@ -126,20 +127,20 @@ class GuiBaseTest(object): if type(mode) == ReceiveMode: # Upload a file files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.password) + url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + if public_mode: + response = requests.post(url, files=files) else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) + response = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) QtTest.QTest.qWait(2000) if type(mode) == ShareMode: # Download files + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + r = requests.get(url) else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.password) - r = requests.get(url) + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) QtTest.QTest.qWait(2000) # Indicator should be visible, have a value of "1" @@ -212,7 +213,7 @@ class GuiBaseTest(object): if public_mode: self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port)) else: - self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.password)) + self.assertEqual(clipboard.text(), 'http://onionshare:{}@127.0.0.1:{}'.format(mode.server_status.web.password, self.gui.app.port)) def server_status_indicator_says_started(self, mode): @@ -234,8 +235,11 @@ class GuiBaseTest(object): else: path = '/' - http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request = 'GET / HTTP/1.0\r\n' http_request += 'Host: 127.0.0.1\r\n' + if not public_mode: + auth = base64.b64encode(b'onionshare:'+password.encode()).decode() + http_request += 'Authorization: Basic {}'.format(auth) http_request += '\r\n' s.sendall(http_request.encode('utf-8')) diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 6ecf608c..0d413c4f 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -8,14 +8,14 @@ class GuiReceiveTest(GuiBaseTest): def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False): '''Test that we can upload the file''' files = {'file[]': open(file_to_upload, 'rb')} + url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) + r = requests.post(url, files=files) else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) if identical_files_at_once: # Send a duplicate upload to test for collisions - response = requests.post(path, files=files) + r = requests.post(path, files=files) QtTest.QTest.qWait(2000) # Make sure the file is within the last 10 seconds worth of filenames @@ -39,11 +39,11 @@ class GuiReceiveTest(GuiBaseTest): def upload_file_should_fail(self, public_mode): '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' files = {'file[]': open('/tmp/test.txt', 'rb')} + url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) + r = requests.post(url, files=files) else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) QtCore.QTimer.singleShot(1000, self.accept_dialog) self.assertTrue('Error uploading, please inform the OnionShare user' in response.text) @@ -53,17 +53,14 @@ class GuiReceiveTest(GuiBaseTest): os.chmod('/tmp/OnionShare', mode) def try_public_paths_in_non_public_mode(self): - response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) + r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) self.assertEqual(response.status_code, 404) - response = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) + r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) self.assertEqual(response.status_code, 404) 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''' - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.password) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + 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 @@ -71,9 +68,15 @@ class GuiReceiveTest(GuiBaseTest): before_number_of_history_items = len(mode.history.item_list.items) # Click submit without including any files a few times - response = requests.post(path, files={}) - response = requests.post(path, files={}) - response = requests.post(path, files={}) + if not 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) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 02ae0eea..9b0bb70b 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -92,13 +92,13 @@ class GuiShareTest(GuiBaseTest): QtTest.QTest.qWait(2000) self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - def hit_404(self, public_mode): - '''Test that the server stops after too many 404s, or doesn't when in public_mode''' - bogus_path = '/gimme' - url = "http://127.0.0.1:{}/{}".format(self.gui.app.port, bogus_path) + 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) for _ in range(20): - r = requests.get(url) + password_guess = self.gui.common.build_password() + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password)) # A nasty hack to avoid the Alert dialog that blocks the rest of the test if not public_mode: diff --git a/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py new file mode 100644 index 00000000..f06ea37b --- /dev/null +++ b/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + "public_mode": True + } + 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_tests(True, True) + self.hit_401(True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_401_triggers_ratelimit_test.py b/tests/local_onionshare_401_triggers_ratelimit_test.py new file mode 100644 index 00000000..4100657b --- /dev/null +++ b/tests/local_onionshare_401_triggers_ratelimit_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class Local401RateLimitTest(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_tests(False, True) + self.hit_401(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py deleted file mode 100644 index 4fad5532..00000000 --- a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - "public_mode": 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_tests(True, True) - self.hit_404(True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py deleted file mode 100644 index 49be0f5b..00000000 --- a/tests/local_onionshare_404_triggers_ratelimit_test.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class Local404RateLimitTest(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_tests(False, True) - self.hit_404(False) - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From 15d66c1a6f559df09016b5a4805a8c85f6453d66 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 24 May 2019 17:59:04 -0700 Subject: Fix tests --- tests/GuiBaseTest.py | 52 +++++++--------------- tests/GuiReceiveTest.py | 34 ++++++++------ tests/GuiShareTest.py | 33 +++++--------- ...re_receive_mode_upload_non_writable_dir_test.py | 2 +- ...ode_upload_public_mode_non_writable_dir_test.py | 2 +- ...onshare_receive_mode_upload_public_mode_test.py | 2 +- tests/local_onionshare_receive_mode_upload_test.py | 2 +- 7 files changed, 52 insertions(+), 75 deletions(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 659ea052..2f340396 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -2,8 +2,6 @@ import json import os import requests import shutil -import socket -import socks import base64 from PyQt5 import QtCore, QtTest @@ -129,9 +127,9 @@ class GuiBaseTest(object): files = {'file[]': open('/tmp/test.txt', 'rb')} url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) if public_mode: - response = requests.post(url, files=files) + r = requests.post(url, files=files) else: - response = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) QtTest.QTest.qWait(2000) if type(mode) == ShareMode: @@ -186,9 +184,11 @@ class GuiBaseTest(object): def web_server_is_running(self): '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + try: + r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + self.assertTrue(True) + except requests.exceptions.ConnectionError: + self.assertTrue(False) def have_a_password(self, mode, public_mode): @@ -226,34 +226,14 @@ class GuiBaseTest(object): def web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - if not public_mode: - path = '/{}'.format(mode.server_status.web.password) + url = "http://127.0.0.1:{}/".format(self.gui.app.port) + if public_mode: + r = requests.get(url) else: - path = '/' + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) - http_request = 'GET / HTTP/1.0\r\n' - http_request += 'Host: 127.0.0.1\r\n' - if not public_mode: - auth = base64.b64encode(b'onionshare:'+password.encode()).decode() - http_request += 'Authorization: Basic {}'.format(auth) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() + self.assertTrue(string in r.text) def history_widgets_present(self, mode): @@ -277,10 +257,12 @@ class GuiBaseTest(object): def web_server_is_stopped(self): '''Test that the web server also stopped''' QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + try: + r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + self.assertTrue(False) + except requests.exceptions.ConnectionError: + self.assertTrue(True) def server_status_indicator_says_closed(self, mode, stay_open): diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 0d413c4f..442aa56f 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -7,18 +7,24 @@ from .GuiBaseTest import GuiBaseTest class GuiReceiveTest(GuiBaseTest): def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False): '''Test that we can upload the file''' + + # Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused + QtTest.QTest.qWait(2000) + files = {'file[]': open(file_to_upload, 'rb')} url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - if not public_mode: + if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + if identical_files_at_once: # Send a duplicate upload to test for collisions - r = requests.post(path, files=files) + r = requests.post(url, files=files) + QtTest.QTest.qWait(2000) - # Make sure the file is within the last 10 seconds worth of filenames + # Make sure the file is within the last 10 seconds worth of fileames exists = False now = datetime.now() for i in range(10): @@ -40,23 +46,23 @@ class GuiReceiveTest(GuiBaseTest): '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' files = {'file[]': open('/tmp/test.txt', 'rb')} url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - if not public_mode: + if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) QtCore.QTimer.singleShot(1000, self.accept_dialog) - self.assertTrue('Error uploading, please inform the OnionShare user' in response.text) + self.assertTrue('Error uploading, please inform the OnionShare user' in r.text) def upload_dir_permissions(self, mode=0o755): '''Manipulate the permissions on the upload dir in between tests''' os.chmod('/tmp/OnionShare', mode) - def try_public_paths_in_non_public_mode(self): + def try_without_auth_in_non_public_mode(self): r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) - self.assertEqual(response.status_code, 404) + self.assertEqual(r.status_code, 401) r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) - self.assertEqual(response.status_code, 404) + 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''' @@ -68,7 +74,7 @@ class GuiReceiveTest(GuiBaseTest): before_number_of_history_items = len(mode.history.item_list.items) # Click submit without including any files a few times - if not public_mode: + if public_mode: r = requests.post(url, files={}) r = requests.post(url, files={}) r = requests.post(url, files={}) @@ -102,11 +108,11 @@ class GuiReceiveTest(GuiBaseTest): self.server_status_indicator_says_started(self.gui.receive_mode) self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + def run_all_receive_mode_tests(self, public_mode): '''Upload files in receive mode and stop the share''' self.run_all_receive_mode_setup_tests(public_mode) if not public_mode: - self.try_public_paths_in_non_public_mode() + self.try_without_auth_in_non_public_mode() self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) @@ -128,7 +134,7 @@ class GuiReceiveTest(GuiBaseTest): self.server_is_started(self.gui.receive_mode) self.history_indicator(self.gui.receive_mode, public_mode) - def run_all_receive_mode_unwritable_dir_tests(self, public_mode, receive_allow_receiver_shutdown): + def run_all_receive_mode_unwritable_dir_tests(self, public_mode): '''Attempt to upload (unwritable) files in receive mode and stop the share''' self.run_all_receive_mode_setup_tests(public_mode) self.upload_dir_permissions(0o400) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 9b0bb70b..64e57b9f 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -2,6 +2,7 @@ import os import requests import socks import zipfile +import tempfile from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest @@ -66,29 +67,17 @@ class GuiShareTest(GuiBaseTest): def download_share(self, public_mode): '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) if public_mode: - path = '/download' + r = requests.get(url) else: - path = '{}/download'.format(self.gui.share_mode.web.password) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + + tmp_file = tempfile.NamedTemporaryFile() + with open(tmp_file.name, 'wb') as f: + f.write(r.content) + + zip = zipfile.ZipFile(tmp_file.name) QtTest.QTest.qWait(2000) self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) @@ -98,7 +87,7 @@ class GuiShareTest(GuiBaseTest): for _ in range(20): password_guess = self.gui.common.build_password() - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password)) + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password_guess)) # A nasty hack to avoid the Alert dialog that blocks the rest of the test if not public_mode: diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index 5737bae3..26feacc3 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -20,7 +20,7 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() - self.run_all_receive_mode_unwritable_dir_tests(False, True) + self.run_all_receive_mode_unwritable_dir_tests(False) if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py index e6024352..601c4bd2 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py @@ -21,7 +21,7 @@ class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() - self.run_all_receive_mode_unwritable_dir_tests(True, True) + self.run_all_receive_mode_unwritable_dir_tests(True) if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index 885ae4fe..1f3a8331 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -21,7 +21,7 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() - self.run_all_receive_mode_tests(True, True) + self.run_all_receive_mode_tests(True) if __name__ == "__main__": unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index 3d23730c..16036893 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -20,7 +20,7 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): @pytest.mark.skipif(pytest.__version__ < '2.9', reason="requires newer pytest") def test_gui(self): self.run_all_common_setup_tests() - self.run_all_receive_mode_tests(False, True) + self.run_all_receive_mode_tests(False) if __name__ == "__main__": unittest.main() -- cgit v1.2.3-54-g00ecf From dc556d89f51d94c007fdda9a9e2c859176f12eea Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 24 May 2019 18:07:57 -0700 Subject: Make GuiReceiveTest.upload_test use basic auth when identical_files_at_once is True --- tests/GuiReceiveTest.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 442aa56f..c4bfa884 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -15,12 +15,14 @@ class GuiReceiveTest(GuiBaseTest): url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) + if identical_files_at_once: + # Send a duplicate upload to test for collisions + r = requests.post(url, files=files) else: r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) - - if identical_files_at_once: - # Send a duplicate upload to test for collisions - r = requests.post(url, files=files) + if identical_files_at_once: + # Send a duplicate upload to test for collisions + r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) QtTest.QTest.qWait(2000) -- cgit v1.2.3-54-g00ecf From 074ae56e6421414ee65bbb94cfac0f384ff6c6a9 Mon Sep 17 00:00:00 2001 From: x80486 Date: Wed, 29 May 2019 20:48:48 -0400 Subject: Update/Tweak AppStream Metadata XML and Desktop Entry according to the latest standard(s) --- MANIFEST.in | 5 +-- install/onionshare.appdata.xml | 45 ----------------------- install/onionshare.desktop | 16 --------- install/org.onionshare.OnionShare.appdata.xml | 52 +++++++++++++++++++++++++++ install/org.onionshare.OnionShare.desktop | 16 +++++++++ setup.py | 5 +-- 6 files changed, 74 insertions(+), 65 deletions(-) delete mode 100644 install/onionshare.appdata.xml delete mode 100644 install/onionshare.desktop create mode 100644 install/org.onionshare.OnionShare.appdata.xml create mode 100644 install/org.onionshare.OnionShare.desktop 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/onionshare.appdata.xml deleted file mode 100644 index 2302a2e8..00000000 --- a/install/onionshare.appdata.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - onionshare.desktop - 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. -

    -

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

    -
    - - - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-server.png - Sharing files with OnionShare - - - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-client.png - Downloading OnionShare files using Tor Browser - - - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-server.png - Receiving files with OnionShare - - - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-client.png - Uploading files to OnionShare user using Tor Browser - - - https://onionshare.org/ - micah@micahflee.com -
    diff --git a/install/onionshare.desktop b/install/onionshare.desktop deleted file mode 100644 index 697668db..00000000 --- a/install/onionshare.desktop +++ /dev/null @@ -1,16 +0,0 @@ -[Desktop Entry] -Name=OnionShare -GenericName=OnionShare Client -Comment=Share a file securely and anonymously over Tor -Comment[da]=Del en fil sikkert og anonymt over Tor -Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk -Exec=/usr/bin/onionshare-gui -Terminal=false -Type=Application -Icon=onionshare80 -Categories=Network;FileTransfer; -Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting; -Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting; -Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting; -StartupNotify=true -StartupWMClass=onionshare diff --git a/install/org.onionshare.OnionShare.appdata.xml b/install/org.onionshare.OnionShare.appdata.xml new file mode 100644 index 00000000..6ae1b5b6 --- /dev/null +++ b/install/org.onionshare.OnionShare.appdata.xml @@ -0,0 +1,52 @@ + + + + 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. +

    +

    + 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 + Sharing files with OnionShare + + + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-client.png + Downloading OnionShare files using Tor Browser + + + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-server.png + Receiving files with OnionShare + + + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-client.png + Uploading files to OnionShare user using Tor Browser + + + https://github.com/micahflee/onionshare/issues/ + https://github.com/micahflee/onionshare/wiki/ + https://onionshare.org/ + Micah Lee + micah@micahflee.com + + + + +
    diff --git a/install/org.onionshare.OnionShare.desktop b/install/org.onionshare.OnionShare.desktop new file mode 100644 index 00000000..536d73c6 --- /dev/null +++ b/install/org.onionshare.OnionShare.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Name=OnionShare +GenericName=OnionShare Client +Comment=Share a file securely and anonymously over Tor +Comment[da]=Del en fil sikkert og anonymt over Tor +Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk +Exec=/usr/bin/onionshare-gui +Terminal=false +Type=Application +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; +Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting; +StartupNotify=true +StartupWMClass=onionshare 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')), -- cgit v1.2.3-54-g00ecf From 50b2311409cd93814324a4570e8bdc5d032748c8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 29 May 2019 18:21:53 -0700 Subject: Generate a new static_url_path each time the server is stopped and started again --- onionshare/web/web.py | 18 +++++++++++++----- onionshare_gui/threads.py | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index c6e902ed..1e040b54 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -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) @@ -238,6 +234,18 @@ 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 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: -- cgit v1.2.3-54-g00ecf From c3ba542ecb1aaa3e8b7b5cab4395595e184a860a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 29 May 2019 19:27:21 -0700 Subject: Strip NoScript XSS warning, because the Tor Browser bug it addressed has been fixed --- onionshare/web/web.py | 9 -------- share/static/img/warning.png | Bin 804 -> 0 bytes share/static/js/receive-noscript.js | 2 -- share/templates/receive.html | 14 ------------ share/templates/receive_noscript_xss.html | 35 ------------------------------ 5 files changed, 60 deletions(-) delete mode 100644 share/static/img/warning.png delete mode 100644 share/static/js/receive-noscript.js delete mode 100644 share/templates/receive_noscript_xss.html diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 1e040b54..1d2a3fec 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -164,15 +164,6 @@ class Web(object): return "" abort(404) - @self.app.route("/noscript-xss-instructions") - def noscript_xss_instructions(): - """ - Display instructions for disabling Tor Browser's NoScript XSS setting - """ - r = make_response(render_template('receive_noscript_xss.html', - static_url_path=self.static_url_path)) - return self.add_security_headers(r) - def error401(self): auth = request.authorization if auth: diff --git a/share/static/img/warning.png b/share/static/img/warning.png deleted file mode 100644 index 9be8cbaf..00000000 Binary files a/share/static/img/warning.png and /dev/null differ diff --git a/share/static/js/receive-noscript.js b/share/static/js/receive-noscript.js deleted file mode 100644 index 0f4ac1bc..00000000 --- a/share/static/js/receive-noscript.js +++ /dev/null @@ -1,2 +0,0 @@ -// Hide the noscript div, because our javascript is executing -document.getElementById('noscript').style.display = 'none'; diff --git a/share/templates/receive.html b/share/templates/receive.html index 23242501..59a3ebe4 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -13,19 +13,6 @@
    - -
    -

    - Warning: Due to a bug in Tor Browser and Firefox, uploads - sometimes never finish. To upload reliably, either set your Tor Browser - security slider - to Standard or - turn off your Tor Browser's NoScript XSS setting.

    -
    -

    Send Files

    @@ -51,7 +38,6 @@
    - diff --git a/share/templates/receive_noscript_xss.html b/share/templates/receive_noscript_xss.html deleted file mode 100644 index 84d35ba1..00000000 --- a/share/templates/receive_noscript_xss.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - OnionShare - - - - - -
    - -

    OnionShare

    -
    - -
    -

    Disable your Tor Browser's NoScript XSS setting

    - -

    If your security slider is set to Safest, JavaScript is disabled so XSS vulnerabilities won't affect you, - which makes it safe to disable NoScript's XSS protections.

    - -

    Here is how to disable this setting:

    - -
      -
    1. Click the menu icon in the top-right of Tor Browser and open "Add-ons"
    2. -
    3. Next to the NoScript add-on, click the "Preferences" button
    4. -
    5. Switch to the "Advanced" tab
    6. -
    7. Uncheck "Sanitize cross-site suspicious requests"
    8. -
    - -

    If you'd like to learn technical details about this issue, check - this issue - on GitHub.

    -
    - - -- cgit v1.2.3-54-g00ecf From 726f174dea6a6550a6631e2511ad7a080145975d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 30 May 2019 17:55:58 -0700 Subject: Remove old noscript css styles --- share/static/css/style.css | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/share/static/css/style.css b/share/static/css/style.css index e445e5de..f2ded524 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -222,20 +222,3 @@ li.info { color: #666666; margin: 0 0 20px 0; } - -div#noscript { - text-align: center; - color: #d709df; - padding: 1em; - line-height: 150%; - margin: 0 auto; -} - -div#noscript a, div#noscript a:visited { - color: #d709df; -} - -.disable-noscript-xss-wrapper { - max-width: 900px; - margin: 0 auto; -} -- cgit v1.2.3-54-g00ecf From b6b576bddb153cb250a6d1164b5164e44fa83212 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: 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: -- cgit v1.2.3-54-g00ecf From 0f40e9589c60723d68afb5cbaeb2be100f7d472b Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 5 Jun 2019 13:47:41 +0200 Subject: 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,25 +4,17 @@ 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 - self.common.log('WebsiteModeWeb', '__init__') - - self.web = web + def init(self): - # 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.common.log('WebsiteModeWeb', '__init__') self.define_routes() -- cgit v1.2.3-54-g00ecf From 5c0839a5574fa1eceea334994c5cfb2286176577 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 12:33:34 +0200 Subject: 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: -- cgit v1.2.3-54-g00ecf From 9805919fc7ec395da19979b20226c1de037c7969 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 12:41:12 +0200 Subject: 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 -- cgit v1.2.3-54-g00ecf From 4d733c224a320a7aa5d430d8440a50cb833cbc16 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 21:47:49 +0200 Subject: Refactor directory_listing function --- onionshare/web/base_mode.py | 49 +++++++++++++++++++----------------------- onionshare/web/share_mode.py | 11 ++-------- onionshare/web/website_mode.py | 31 +++++++++++++++++++++----- 3 files changed, 50 insertions(+), 41 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 = '' + + if self.web.mode == 'website': + files, dirs = build_directory_listing(filenames) + + 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, + 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)) - 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) 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 -- cgit v1.2.3-54-g00ecf From 66e85497efb023ab46eda9163bd2f667916bc134 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 22:56:48 +0200 Subject: 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 50b2311409cd93814324a4570e8bdc5d032748c8. --- 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: -- cgit v1.2.3-54-g00ecf From 35b524439ff0a1bf185dc22c01e92a1a937ac563 Mon Sep 17 00:00:00 2001 From: hiro Date: Thu, 13 Jun 2019 22:58:33 +0200 Subject: 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) -- cgit v1.2.3-54-g00ecf From 2604ef52b51424c42c2a06212ab745242de0fa58 Mon Sep 17 00:00:00 2001 From: hiro Date: Fri, 14 Jun 2019 18:21:12 +0200 Subject: 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 %} -- cgit v1.2.3-54-g00ecf From 9a60c8c87f3d9f05ef1a05fc472c94bc22616b49 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 22 Jun 2019 13:01:49 +0200 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/ca.json | 82 ++++++++++++++++++++++++++-------------------------- share/locale/da.json | 6 ++-- share/locale/sv.json | 10 +++---- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 64779f7d..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,50 +88,50 @@ "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", @@ -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/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.", -- cgit v1.2.3-54-g00ecf From 04acf849d649f3140e5b783b733c7e3120b2e846 Mon Sep 17 00:00:00 2001 From: Vinicius Zavam Date: Tue, 23 Jul 2019 15:09:11 +0000 Subject: 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( -- cgit v1.2.3-54-g00ecf From bb2adbe966237e1011d5d42db7922aea2ed213b7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 26 Jul 2019 11:22:57 +0000 Subject: Squashed commit of the following: commit 9a60c8c87f3d9f05ef1a05fc472c94bc22616b49 Author: Hosted Weblate Date: Sat Jun 22 13:01:49 2019 +0200 Translated using Weblate (Swedish) Translated using Weblate (Catalan) Translated using Weblate (Danish) commit 8083fd49298768d96d33684f65b05a2aa3dddd1c Author: Hosted Weblate Date: Thu May 16 17:49:23 2019 +0200 Translated using Weblate (Hungarian) Translated using Weblate (Turkish) Translated using Weblate (Telugu) Translated using Weblate (Swahili) Translated using Weblate (Malay) Translated using Weblate (Greek) Translated using Weblate (Spanish) Added translation using Weblate (Swahili) commit e0db8944813b3f3c48a2d61a6f12c5ed15dee704 Merge: 543744d a352dbe Author: emma peel Date: Wed May 8 10:04:34 2019 +0000 Merge branch 'develop' of github.com:weblate/onionshare-weblate into develop commit 543744d81dca1b0f75724083d073fe66fb0e6c0e Author: Hosted Weblate Date: Tue May 7 23:16:35 2019 +0200 Translated using Weblate (German) commit 574a0c395318d7f3aeca3c7b9ef8556d22178cfe Author: Hosted Weblate Date: Wed May 8 09:42:39 2019 +0200 Translated using Weblate (Romanian) Translated using Weblate (Italian) Translated using Weblate (German) Translated using Weblate (Dutch) Translated using Weblate (Catalan) Translated using Weblate (Romanian) Translated using Weblate (Italian) Translated using Weblate (German) Translated using Weblate (German) Translated using Weblate (German) Translated using Weblate (Turkish) Translated using Weblate (Greek) Translated using Weblate (German) Translated using Weblate (French) Translated using Weblate (Arabic) Translated using Weblate (Turkish) Translated using Weblate (Turkish) Translated using Weblate (German) commit a352dbe9ff3bd54904cbb4cf715ab5341a113acb Author: Hosted Weblate Date: Tue May 7 23:16:35 2019 +0200 Translated using Weblate (German) modified: share/locale/ar.json modified: share/locale/ca.json modified: share/locale/da.json modified: share/locale/de.json modified: share/locale/el.json modified: share/locale/es.json modified: share/locale/fr.json modified: share/locale/hu.json modified: share/locale/it.json modified: share/locale/ms.json modified: share/locale/nl.json modified: share/locale/ro.json modified: share/locale/sv.json new file: share/locale/sw.json modified: share/locale/te.json modified: share/locale/tr.json --- 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", -- cgit v1.2.3-54-g00ecf From 5a6aa69434382fd88fe13778265bdc103c87dff3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 27 Jul 2019 09:34:09 +0200 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Merge branch 'origin/develop' into Weblate. Merge branch 'origin/develop' into Weblate. Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/fr.json | 11 +++++++++-- share/locale/nb.json | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 28cf4ee9..58a9408c 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -152,7 +152,7 @@ "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", "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_rate_limit": "Quelqu’un a effectué trop de tentatives échouées pour deviner votre mot de passe, 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.", "gui_settings_stealth_option": "Utiliser l’autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", @@ -226,5 +226,12 @@ "days_first_letter": "j", "hours_first_letter": "h", "minutes_first_letter": "min", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "gui_website_url_description": "Quiconque aura cette adresse OnionShare pourra visiter votre site Web en utilisant le Navigateur Tor : ", + "systray_site_loaded_title": "Le site a été chargé", + "systray_site_loaded_message": "Le site OnionShare a été chargé", + "systray_website_started_title": "Début du partage du site Web", + "systray_website_started_message": "Quelqu’un visite votre site Web", + "gui_website_mode_no_files": "Aucun site Web n’a encore été partagé", + "gui_visit_started": "Quelqu’un a visité votre site Web {}" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 8041db76..e53cd068 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -230,5 +230,13 @@ "days_first_letter": "d", "hours_first_letter": "t", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "gui_website_url_description": "Hvem som helst med denne OnionShare-adressen kan besøke din nettside ved bruk av Tor-nettleseren: ", + "gui_mode_website_button": "Publiser nettside", + "systray_site_loaded_title": "Side innlastet", + "systray_site_loaded_message": "OnionShare-side innlastet", + "systray_website_started_title": "Starter deling av nettside", + "systray_website_started_message": "Noen besøker din nettside", + "gui_website_mode_no_files": "Ingen nettside delt enda", + "gui_visit_started": "Noen har besøkt din nettside {}" } -- cgit v1.2.3-54-g00ecf From 5818932674d55b6e7827c71d9e0155bd227752d3 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 27 Jul 2019 08:01:35 +0000 Subject: 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", -- cgit v1.2.3-54-g00ecf From 7cd69ae1884b580be1f3fe1604f514d48e0dacbb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 12:38:13 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From d7441af3689ea28b8c716e2e5f5e3e68b126849b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 13:16:00 -0700 Subject: 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() -- cgit v1.2.3-54-g00ecf From ab086a6fa05d676b8d6ad46301259c816f4b5b9d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 13:30:49 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From 88e75706ff5fadc54cd0d1f050f19ef83f28804e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 17:35:02 -0400 Subject: 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 -- cgit v1.2.3-54-g00ecf From bc2b37aa640c26a588c0d1c4019221bad8521684 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 17:39:32 -0400 Subject: 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 -- cgit v1.2.3-54-g00ecf From 7b2b8c22846141d9c38b22bdeac3b326817609f1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 18:05:53 -0400 Subject: Rename BaseModeWeb to SendBaseModeWeb, because this code is only actually shared by send modes (Share and Website, not Receive) --- onionshare/web/base_mode.py | 218 --------------------------------------- onionshare/web/receive_mode.py | 2 +- onionshare/web/send_base_mode.py | 218 +++++++++++++++++++++++++++++++++++++++ onionshare/web/share_mode.py | 4 +- onionshare/web/website_mode.py | 4 +- 5 files changed, 223 insertions(+), 223 deletions(-) delete mode 100644 onionshare/web/base_mode.py create mode 100644 onionshare/web/send_base_mode.py diff --git a/onionshare/web/base_mode.py b/onionshare/web/base_mode.py deleted file mode 100644 index 905414f6..00000000 --- a/onionshare/web/base_mode.py +++ /dev/null @@ -1,218 +0,0 @@ -import os -import sys -import tempfile -import mimetypes -from flask import Response, request, render_template, make_response, send_from_directory - -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 = {} - # 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 - - self.define_routes() - - - def init(self): - """ - Add custom initialization here. - """ - 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)) - - 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 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) - - 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/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/send_base_mode.py b/onionshare/web/send_base_mode.py new file mode 100644 index 00000000..0ca1b306 --- /dev/null +++ b/onionshare/web/send_base_mode.py @@ -0,0 +1,218 @@ +import os +import sys +import tempfile +import mimetypes +from flask import Response, request, render_template, make_response, send_from_directory + +from .. import strings + +class SendBaseModeWeb: + """ + All of the web logic shared between share and website mode (modes where the user sends files) + """ + def __init__(self, common, web): + super(SendBaseModeWeb, self).__init__() + self.common = common + 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 = {} + # 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 + + self.define_routes() + + + def init(self): + """ + Add custom initialization here. + """ + 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)) + + 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 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) + + 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 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 """ -- cgit v1.2.3-54-g00ecf From 973db941ecce380f5a74193baa5c33d6af999644 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 18:44:44 -0400 Subject: 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): """ -- cgit v1.2.3-54-g00ecf From a0759cedcd085d150d70a111629e4940f76e4f85 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:02:10 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From e7959f9ae655d47133be7162cbb51209dead0744 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:03:57 -0700 Subject: 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: -- cgit v1.2.3-54-g00ecf From df568d648ebe056fb46316f71740960ad01a34cd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 16:13:05 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From 1e1243c8b6d7a9a6a983ebbef0d105fb3c3a19a7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 19:59:00 -0700 Subject: 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('/')) -- cgit v1.2.3-54-g00ecf From 1c465b40fe13d7fe9db445d2d74d860a71c66a68 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:15:30 -0700 Subject: 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", -- cgit v1.2.3-54-g00ecf From 6561d71edde50e3df4e1e585940b9a9d0d8c9a2b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:36:30 -0700 Subject: 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 }} -- cgit v1.2.3-54-g00ecf From bf20085c4a2f802cad86647ffe3851365e6d57ae Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:45:19 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From 70619dd142522cb6b756469b222267341b8606fb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:46:27 -0700 Subject: 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, -- cgit v1.2.3-54-g00ecf From d7a7538686e6b24aef7bae15d90b165df0bbf84c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 20:53:21 -0700 Subject: 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=''): """ -- cgit v1.2.3-54-g00ecf From 877a73ab59e6903dcd3c56ee85d6136db5ea3bb3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Sep 2019 21:22:59 -0700 Subject: 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, -- cgit v1.2.3-54-g00ecf From 173b2d3f5ed1cf37fdb33e675e337eb4350498eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 2 Sep 2019 18:01:56 +1000 Subject: 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", -- cgit v1.2.3-54-g00ecf From ef78a9c7edc4e866fd32c8a5d2281498f523e312 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:19:42 +1000 Subject: 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 ++++++++++++++++++++++ ...are_mode_individual_file_view_stay_open_test.py | 26 ++++++++++++ ...onshare_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() -- cgit v1.2.3-54-g00ecf From 5defd4a10b47a4934c7585b86b41b45e7aa39fbb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:51:59 +1000 Subject: 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() -- cgit v1.2.3-54-g00ecf From f4f7b984e53655b4f843c384822fd0e082c19a25 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 11:53:17 +1000 Subject: 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): -- cgit v1.2.3-54-g00ecf From 458a0fb1d0a1a04496b48a6a719b17acefa921c9 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:00:23 +1000 Subject: 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""" -- cgit v1.2.3-54-g00ecf From 9ae98eb7cb64b83dcaaf2c063bda327cb068b0ce Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:23:27 +1000 Subject: 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() -- cgit v1.2.3-54-g00ecf From 273b893946cf89ec29766b7fb62c49846d8b7b76 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:35:30 +1000 Subject: 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"]) -- cgit v1.2.3-54-g00ecf From f6eb80d5b595db7bc48c72357461a0ae86a58f89 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:36:05 +1000 Subject: 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) -- cgit v1.2.3-54-g00ecf From cb439743ffe23b2d52356a662dc28f6f3fa605e8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 12:38:20 +1000 Subject: 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) -- cgit v1.2.3-54-g00ecf From 0aa5a89adedb1d8aaecac3e9561e3bfb4c6678fb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 2 Sep 2019 19:45:14 -0700 Subject: 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) -- cgit v1.2.3-54-g00ecf From 09f2f572982f39a411ae82891d677d929c29a52f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 3 Sep 2019 17:02:00 +1000 Subject: 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) -- cgit v1.2.3-54-g00ecf From c55925c1ce296ec8fe31bbe1b50c23133eda687a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 20:52:49 -0700 Subject: 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 | 146 ++++++++++++++++++--------------------- onionshare/web/share_mode.py | 1 - onionshare/web/website_mode.py | 2 +- 3 files changed, 69 insertions(+), 80 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,78 +32,24 @@ class SendBaseModeWeb: self.define_routes() self.init() - 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 directory_listing(self, filenames, path='', filesystem_path=None): - # 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) - 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_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.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() - # Clear the list of files - self.files = {} - self.root_files = {} - # Build the file list for filename in filenames: basename = os.path.basename(filename.rstrip('/')) @@ -144,11 +78,36 @@ class SendBaseModeWeb: self.set_file_info_custom(filenames, processed_size_callback) - def render_logic(self, path=''): - """ - Inherited class will implement this. - """ - pass + def directory_listing(self, filenames, path='', filesystem_path=None): + # 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) + 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 stream_individual_file(self, filesystem_path): """ @@ -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): """ -- cgit v1.2.3-54-g00ecf From 644b47082a716de9a7e2311b9f3c2a75c4d96fbe Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 21:46:32 -0700 Subject: 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 | 99 ++++++++++++++++++---------- 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, 183 insertions(+), 99 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 error404(self): + def error403(self): self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) + r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) return self.add_security_headers(r) - def error403(self): + 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) - 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,62 +345,89 @@ 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 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) + def update(self, downloaded_bytes): + self.downloaded_bytes = downloaded_bytes - # Label - self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.visited_dt.strftime("%b %d, %I:%M%p"))) + self.progress_bar.setValue(downloaded_bytes) + if downloaded_bytes == self.progress_bar.total_bytes: + self.progress_bar.hide() + self.status = HistoryItem.STATUS_FINISHED - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.label) - self.setLayout(layout) + 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) - def update(self): - self.label.setText(self.get_finished_label_text(self.started_dt)) - self.status = HistoryItem.STATUS_FINISHED + self.progress_bar.setFormat(pb_fmt) 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): """ List of items 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

    +
    +
    + + + -- cgit v1.2.3-54-g00ecf From 655bb5bad1ccadd3aaa0f4bd20119de350429be5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 21:59:49 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From ffe12bdeada15295ab452293308b70f778e711f3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:18:30 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From dee9e40051259509ffc23ab1dc5810a02b6a0f89 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:20:52 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From c2011e6915330910631e11695105cf83b88eb2cb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 3 Sep 2019 22:31:13 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From 3f7c4a4e2503c26dabc37f656c9875b64c63a0a6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 09:35:44 -0700 Subject: 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() -- cgit v1.2.3-54-g00ecf From f089ae5847164382a814623196b72638253715ac Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 09:45:53 -0700 Subject: 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 }) -- cgit v1.2.3-54-g00ecf From de67484f09058af70e443e0bc1ebc1c8af2a8ab2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 11:58:44 -0700 Subject: 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() -- cgit v1.2.3-54-g00ecf From ece908e170b2bab355085769bf50f03cffb1b5b4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 12:02:17 -0700 Subject: 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", -- cgit v1.2.3-54-g00ecf From 41b815f0b3799303d44d32741887f23c66b51452 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:24:18 -0700 Subject: 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)) + + 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.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.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 00000000..4965744d Binary files /dev/null and b/share/images/share_requests.png differ diff --git a/share/images/share_requests_none.png b/share/images/share_requests_none.png new file mode 100644 index 00000000..93a71ef3 Binary files /dev/null and b/share/images/share_requests_none.png differ 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", -- cgit v1.2.3-54-g00ecf From 2219e107c425ce9c28024cde903e1c6afd7286c1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:27:24 -0700 Subject: Rename images from share_ to history_, because they are used in all modes --- onionshare_gui/mode/history.py | 12 ++++++------ share/images/history_completed.png | Bin 0 -> 646 bytes share/images/history_completed_none.png | Bin 0 -> 437 bytes share/images/history_in_progress.png | Bin 0 -> 638 bytes share/images/history_in_progress_none.png | Bin 0 -> 412 bytes share/images/history_requests.png | Bin 0 -> 738 bytes share/images/history_requests_none.png | Bin 0 -> 754 bytes share/images/share_completed.png | Bin 646 -> 0 bytes share/images/share_completed_none.png | Bin 437 -> 0 bytes share/images/share_in_progress.png | Bin 638 -> 0 bytes share/images/share_in_progress_none.png | Bin 412 -> 0 bytes share/images/share_requests.png | Bin 738 -> 0 bytes share/images/share_requests_none.png | Bin 754 -> 0 bytes 13 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 share/images/history_completed.png create mode 100644 share/images/history_completed_none.png create mode 100644 share/images/history_in_progress.png create mode 100644 share/images/history_in_progress_none.png create mode 100644 share/images/history_requests.png create mode 100644 share/images/history_requests_none.png delete mode 100644 share/images/share_completed.png delete mode 100644 share/images/share_completed_none.png delete mode 100644 share/images/share_in_progress.png delete mode 100644 share/images/share_in_progress_none.png delete mode 100644 share/images/share_requests.png delete mode 100644 share/images/share_requests_none.png 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/history_completed.png b/share/images/history_completed.png new file mode 100644 index 00000000..e68fe5a2 Binary files /dev/null and b/share/images/history_completed.png differ diff --git a/share/images/history_completed_none.png b/share/images/history_completed_none.png new file mode 100644 index 00000000..8dbd6939 Binary files /dev/null and b/share/images/history_completed_none.png differ diff --git a/share/images/history_in_progress.png b/share/images/history_in_progress.png new file mode 100644 index 00000000..19694659 Binary files /dev/null and b/share/images/history_in_progress.png differ diff --git a/share/images/history_in_progress_none.png b/share/images/history_in_progress_none.png new file mode 100644 index 00000000..2d61dba4 Binary files /dev/null and b/share/images/history_in_progress_none.png differ diff --git a/share/images/history_requests.png b/share/images/history_requests.png new file mode 100644 index 00000000..4965744d Binary files /dev/null and b/share/images/history_requests.png differ diff --git a/share/images/history_requests_none.png b/share/images/history_requests_none.png new file mode 100644 index 00000000..93a71ef3 Binary files /dev/null and b/share/images/history_requests_none.png differ diff --git a/share/images/share_completed.png b/share/images/share_completed.png deleted file mode 100644 index e68fe5a2..00000000 Binary files a/share/images/share_completed.png and /dev/null differ diff --git a/share/images/share_completed_none.png b/share/images/share_completed_none.png deleted file mode 100644 index 8dbd6939..00000000 Binary files a/share/images/share_completed_none.png and /dev/null differ diff --git a/share/images/share_in_progress.png b/share/images/share_in_progress.png deleted file mode 100644 index 19694659..00000000 Binary files a/share/images/share_in_progress.png and /dev/null differ diff --git a/share/images/share_in_progress_none.png b/share/images/share_in_progress_none.png deleted file mode 100644 index 2d61dba4..00000000 Binary files a/share/images/share_in_progress_none.png and /dev/null differ diff --git a/share/images/share_requests.png b/share/images/share_requests.png deleted file mode 100644 index 4965744d..00000000 Binary files a/share/images/share_requests.png and /dev/null differ diff --git a/share/images/share_requests_none.png b/share/images/share_requests_none.png deleted file mode 100644 index 93a71ef3..00000000 Binary files a/share/images/share_requests_none.png and /dev/null differ -- cgit v1.2.3-54-g00ecf From 26c9256679c817ce608174ef980815e63dc4f995 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 8 Sep 2019 17:39:31 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From 8e238ab2d66c336029a54f54f6d989b46964d448 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 12:19:39 +1000 Subject: 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''' -- cgit v1.2.3-54-g00ecf From 90ebc3aab424637dbb75017df6b0204b82d6fe69 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 16:35:05 +1000 Subject: 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 ++++++++ ...nionshare_receive_mode_clear_all_button_test.py | 25 +++++++++++++++++++++ ..._onionshare_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() -- cgit v1.2.3-54-g00ecf From b1aa36e805736adcd1ebe7cec7a0ed210631a37b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 16:43:09 +1000 Subject: 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 -- cgit v1.2.3-54-g00ecf From 86a2b35d9ddc6c0fb9d068ca0f45c48fbfcaa219 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 9 Sep 2019 17:22:18 +1000 Subject: 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) -- cgit v1.2.3-54-g00ecf From 16fedabf400a696c452c644bcd7031af8b0e2b9e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 14:46:29 -0700 Subject: 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): -- cgit v1.2.3-54-g00ecf From bb0908d6a3a8e51c938c22e24b5fa62c7cd05483 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 15:17:40 -0700 Subject: 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 +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 +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 -- cgit v1.2.3-54-g00ecf From ecd65f37e35fecbce5efd38173a7573feebaef0c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:23:19 -0700 Subject: 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 -- cgit v1.2.3-54-g00ecf From a5e01a18e2ccf31ff31184d28c90554ea2c61f70 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:44:24 -0700 Subject: 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 %} + -- cgit v1.2.3-54-g00ecf From ce9a2f52d0240c9519175ece2dc6f72ba7c242ec Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 16:46:28 -0700 Subject: 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
    -- cgit v1.2.3-54-g00ecf From b000ef30990337e887644e3543b5eb9bdbdff94d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 17:06:03 -0700 Subject: 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): -- cgit v1.2.3-54-g00ecf From 76406d1b0cde82a5e83ae4460a630d1de09466ee Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 17:55:30 -0700 Subject: Update get Tor scripts to grab Tor Browser 8.8.5 --- install/get-tor-osx.py | 6 +++--- install/get-tor-windows.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 861dc935..a13756b7 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -35,9 +35,9 @@ import subprocess import requests def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.8/TorBrowser-8.0.8-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.0.8-osx64_en-US.dmg' - expected_dmg_sha256 = '1dc01b95146352593c3f18ece25f8735120565f921e22c2827df819effdddca3' + dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg' + dmg_filename = 'TorBrowser-8.5.5-osx64_en-US.dmg' + expected_dmg_sha256 = '9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 0f4ad8c3..6fc15c74 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -33,9 +33,9 @@ import subprocess import requests def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.8/torbrowser-install-8.0.8_en-US.exe' - exe_filename = 'torbrowser-install-8.0.8_en-US.exe' - expected_exe_sha256 = 'bfe32a737e9fa37bf0c8837dbf3385be41cd9e8f9a88850d8f2946bb736e784f' + exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe' + exe_filename = 'torbrowser-install-8.5.5_en-US.exe' + expected_exe_sha256 = 'a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') -- cgit v1.2.3-54-g00ecf From 7d0b0afaad6617c873297d80bec23921dfa180c3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 18:03:46 -0700 Subject: Update pip dependencies --- install/requirements-tests.txt | 4 ++-- install/requirements.txt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index e05e007c..4cb2106a 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==7.2.0 -pluggy==0.12.0 +pluggy==0.13.0 py==1.8.0 pytest==5.1.2 pytest-faulthandler==2.0.1 pytest-qt==3.2.2 six==1.12.0 -urllib3==1.25.3 +urllib3==1.25.3 \ No newline at end of file diff --git a/install/requirements.txt b/install/requirements.txt index 75c736e0..36b9fa4f 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,5 +1,5 @@ altgraph==0.16.1 -certifi==2019.6.16 +certifi==2019.9.11 chardet==3.0.4 Click==7.0 Flask==1.1.1 @@ -13,10 +13,10 @@ MarkupSafe==1.1.1 pefile==2019.4.18 pycryptodome==3.9.0 PyInstaller==3.5 -PyQt5==5.13.0 -PyQt5-sip==4.19.18 +PyQt5==5.13.1 +PyQt5-sip==4.19.19 PySocks==1.7.0 requests==2.22.0 stem==1.7.1 urllib3==1.25.3 -Werkzeug==0.15.5 +Werkzeug==0.15.6 -- cgit v1.2.3-54-g00ecf From 98e0f06be794ed0169480111e3044343d5ef8d8c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 15 Sep 2019 18:30:50 -0700 Subject: Update Windows instructions, except building PyInstaller from source --- BUILD.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/BUILD.md b/BUILD.md index ecf972b3..617e954c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -50,7 +50,7 @@ Download and install Python 3.7.4 from https://www.python.org/downloads/release/ 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.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`. +Install Qt 5.13.0 for macOS from https://www.qt.io/offline-installers. I downloaded `qt-opensource-mac-x64-5.13.1.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.13.10` > `macOS`. Now install pip dependencies. If you want to use a virtualenv, create it and activate it first: @@ -100,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.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`. +Install the Qt 5.13.0 from https://www.qt.io/offline-installers. I downloaded `qt-opensource-windows-x86-5.13.1.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.13.1` > `MSVC 2017 32-bit`. After that you can try both the CLI and the GUI version of OnionShare: @@ -113,28 +113,26 @@ python dev_scripts\onionshare-gui These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH. -Download and install the 32-bit [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=48145). I downloaded `vc_redist.x86.exe`. - Download and install 7-Zip from http://www.7-zip.org/download.html. I downloaded `7z1900.exe`. -Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-us/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio. +Download and install the standalone [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio. -Add the following directories to the path: +Add the following directories (you might want to make sure these are exact on your computer) to the path: * `C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x86` * `C:\Program Files (x86)\Windows Kits\10\Redist\10.0.18362.0\ucrt\DLLs\x86` -* `C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyQt5\Qt\bin` * `C:\Program Files (x86)\7-Zip` +* `C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyQt5\Qt\bin` #### If you want the .exe to not get falsely flagged as malicious by anti-virus software OnionShare uses PyInstaller to turn the python source code into Windows executable `.exe` file. Apparently, malware developers also use PyInstaller, and some anti-virus vendors have included snippets of PyInstaller code in their virus definitions. To avoid this, you have to compile the Windows PyInstaller bootloader yourself instead of using the pre-compiled one that comes with PyInstaller. -(If you don't care about this, you can install PyInstaller with `pip install PyInstaller==3.4`.) +(If you don't care about this, you can install PyInstaller with `pip install PyInstaller==3.5`.) Here's how to compile the PyInstaller bootloader: -Download and install [Microsoft Build Tools for Visual Studio 2019](https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019). I downloaded `vs_buildtools__265029578.1555959436.exe`. In the installer, check the box next to "Visual C++ build tools". Click "Individual components", and under "Compilers, build tools and runtimes", check "Windows Universal CRT SDK". Then click install. When installation is done, you may have to reboot your computer. +Download and install [Microsoft Build Tools for Visual Studio 2019](https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019). I downloaded `vs_buildtools__1285639570.1568593053.exe`. In the installer, check the box next to "Visual C++ build tools". Click "Individual components", and under "Compilers, build tools and runtimes", check "Windows Universal CRT SDK". Then click install. When installation is done, you may have to reboot your computer. Then, enable the 32-bit Visual C++ Toolset on the Command Line like this: -- cgit v1.2.3-54-g00ecf From 2524ddaf9485e484c87f2cea51414fc0d362187b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 12:10:17 +1000 Subject: Make setting the Content-Security-Policy header optional so it doesn't break website mode shares --- onionshare/settings.py | 1 + onionshare/web/web.py | 24 ++++++++++++-------- onionshare_gui/settings_dialog.py | 25 +++++++++++++++++++++ share/locale/en.json | 1 + tests/GuiWebsiteTest.py | 15 +++++++++++++ ...cal_onionshare_website_mode_csp_enabled_test.py | 26 ++++++++++++++++++++++ tests/local_onionshare_website_mode_test.py | 1 + tests/test_onionshare_settings.py | 3 ++- 8 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 tests/local_onionshare_website_mode_csp_enabled_test.py diff --git a/onionshare/settings.py b/onionshare/settings.py index 762c6dc2..28523b89 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -114,6 +114,7 @@ class Settings(object): 'password': '', 'hidservauth_string': '', 'data_dir': self.build_default_data_dir(), + 'csp_header_enabled': True, 'locale': None # this gets defined in fill_in_defaults() } self._settings = {} diff --git a/onionshare/web/web.py b/onionshare/web/web.py index ecd9edc2..825e690c 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -91,15 +91,6 @@ class Web: # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape - self.security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') - ] - self.q = queue.Queue() self.password = None @@ -293,6 +284,20 @@ class Web: pass self.running = False + def set_security_headers(self): + """ + Set the security headers for the web service each time we start it. + """ + self.security_headers = [ + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'OnionShare') + ] + if self.common.settings.get('csp_header_enabled'): + self.security_headers.append(('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;')) + def start(self, port, stay_open=False, public_mode=False, password=None): """ Start the flask web server. @@ -315,6 +320,7 @@ class Web: host = '127.0.0.1' self.running = True + self.set_security_headers() self.app.run(host=host, port=port, threaded=True) def stop(self, port): diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 25165688..432645e6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -214,10 +214,28 @@ class SettingsDialog(QtWidgets.QDialog): 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")) + # Option to disable Content Security Policy (for website sharing) + self.csp_header_enabled_checkbox = QtWidgets.QCheckBox() + self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked) + self.csp_header_enabled_checkbox.setText(strings._("gui_settings_csp_header_enabled_option")) + csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy")) + csp_header_label.setStyleSheet(self.common.css['settings_whats_this']) + csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + csp_header_label.setOpenExternalLinks(True) + csp_header_label.setMinimumSize(csp_header_label.sizeHint()) + csp_header_layout = QtWidgets.QHBoxLayout() + csp_header_layout.addWidget(self.csp_header_enabled_checkbox) + csp_header_layout.addWidget(csp_header_label) + csp_header_layout.addStretch() + csp_header_layout.setContentsMargins(0,0,0,0) + self.csp_header_widget = QtWidgets.QWidget() + self.csp_header_widget.setLayout(csp_header_layout) + # 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_layout.addWidget(self.csp_header_widget) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) @@ -517,6 +535,12 @@ class SettingsDialog(QtWidgets.QDialog): else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) + csp_header_enabled = self.old_settings.get('csp_header_enabled') + if csp_header_enabled: + self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Unchecked) + autostart_timer = self.old_settings.get('autostart_timer') if autostart_timer: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) @@ -982,6 +1006,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('csp_header_enabled', self.csp_header_enabled_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 aab6153d..6ca5c01f 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_csp_header_enabled_option": "Enable Content Security Policy header", "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", diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index 7b88bfdf..f58f4aa2 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -65,6 +65,20 @@ class GuiWebsiteTest(GuiShareTest): QtTest.QTest.qWait(2000) self.assertTrue('This is a test website hosted by OnionShare' in r.text) + def check_csp_header(self, public_mode, csp_header_enabled): + '''Test that the CSP header is present when enabled or vice versa''' + url = "http://127.0.0.1:{}/".format(self.gui.app.port) + if public_mode: + r = requests.get(url) + else: + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) + + QtTest.QTest.qWait(2000) + if csp_header_enabled: + self.assertTrue('Content-Security-Policy' in r.headers) + else: + self.assertFalse('Content-Security-Policy' in r.headers) + def run_all_website_mode_setup_tests(self): """Tests in website mode prior to starting a share""" self.click_mode(self.gui.website_mode) @@ -92,6 +106,7 @@ class GuiWebsiteTest(GuiShareTest): self.run_all_website_mode_setup_tests() self.run_all_website_mode_started_tests(public_mode, startup_time=2000) self.view_website(public_mode) + self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_enabled')) self.history_widgets_present(self.gui.website_mode) self.server_is_stopped(self.gui.website_mode, False) self.web_server_is_stopped() diff --git a/tests/local_onionshare_website_mode_csp_enabled_test.py b/tests/local_onionshare_website_mode_csp_enabled_test.py new file mode 100644 index 00000000..3cf79440 --- /dev/null +++ b/tests/local_onionshare_website_mode_csp_enabled_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiWebsiteTest import GuiWebsiteTest + +class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest): + @classmethod + def setUpClass(cls): + test_settings = { + "csp_header_enabled": True, + } + 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) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py index 051adb3c..5a7334a4 100644 --- a/tests/local_onionshare_website_mode_test.py +++ b/tests/local_onionshare_website_mode_test.py @@ -8,6 +8,7 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): @classmethod def setUpClass(cls): test_settings = { + "csp_header_enabled": False } cls.gui = GuiWebsiteTest.set_up(test_settings) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 05878899..d46c599b 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -66,7 +66,8 @@ class TestSettings: 'password': '', 'hidservauth_string': '', 'data_dir': os.path.expanduser('~/OnionShare'), - 'public_mode': False + 'public_mode': False, + 'csp_header_enabled': True } for key in settings_obj._settings: # Skip locale, it will not always default to the same thing -- cgit v1.2.3-54-g00ecf From 17063e54db1c3123bff6210ab787eadfe5e75965 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 12:30:20 +1000 Subject: Fix how security headers get added --- onionshare/web/web.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 825e690c..c1a9ce4c 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -91,6 +91,14 @@ class Web: # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape + self.security_headers = [ + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'OnionShare') + ] + self.q = queue.Queue() self.password = None @@ -231,6 +239,8 @@ class Web: """ for header, value in self.security_headers: r.headers.set(header, value) + if self.common.settings.get('csp_header_enabled'): + r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;') return r def _safe_select_jinja_autoescape(self, filename): @@ -284,20 +294,6 @@ class Web: pass self.running = False - def set_security_headers(self): - """ - Set the security headers for the web service each time we start it. - """ - self.security_headers = [ - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') - ] - if self.common.settings.get('csp_header_enabled'): - self.security_headers.append(('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;')) - def start(self, port, stay_open=False, public_mode=False, password=None): """ Start the flask web server. @@ -320,7 +316,6 @@ class Web: host = '127.0.0.1' self.running = True - self.set_security_headers() self.app.run(host=host, port=port, threaded=True) def stop(self, port): -- cgit v1.2.3-54-g00ecf From 670044fa56bfc807eac91d1320c85fd7aba8dfe3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 14:51:16 +1000 Subject: 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'): -- cgit v1.2.3-54-g00ecf From 09ba78efcf53a5bd76434c865b5c924ebe01dc54 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 16 Sep 2019 17:58:15 +1000 Subject: 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", -- cgit v1.2.3-54-g00ecf From 6e964c13eaed0682a08508be5d1935a281dbbed4 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 20 Sep 2019 12:27:55 +0200 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Merge branch 'origin/develop' into Weblate. Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Merge branch 'origin/develop' into Weblate. Merge branch 'origin/develop' into Weblate. Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/ca.json | 65 +++++++++++--------- share/locale/da.json | 13 +++- share/locale/de.json | 17 ++++-- share/locale/es.json | 169 +++++++++++++++++++++++++++------------------------ share/locale/fr.json | 13 +++- share/locale/is.json | 13 +++- share/locale/nb.json | 11 +++- share/locale/sv.json | 17 ++++-- share/locale/tr.json | 13 +++- 9 files changed, 206 insertions(+), 125 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 4380dc18..72d315bc 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -37,7 +37,7 @@ "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": "Deixa de compartir (queden {})", "gui_share_stop_server_autostop_timer_tooltip": "El temporitzador acaba a {}", "gui_receive_start_server": "Inicia el mode de recepció", "gui_receive_stop_server": "Atura el mode de recepció", @@ -62,7 +62,7 @@ "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": "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.", + "error_rate_limit": "Algú ha fet massa intents incorrectes intentant endevinar la vostra contrasenya. Per això l'OnionShare ha aturat el servidor. Torneu a començar el procés i envieu una adreça nova al receptor.", "zip_progress_bar_format": "S'està comprimint: %p%", "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.", @@ -114,7 +114,7 @@ "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ò 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ó. Assegureu-vos que esteu connectat a internet i que teniu en hora el rellotge del sistema.", + "settings_error_bundled_tor_timeout": "La connexió està trigant molt. Podeu revisar que tingueu connexió a Internet i que el rellotge del sistema estigui en hora?", "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: {}", @@ -136,31 +136,31 @@ "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 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 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_share_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot baixar els vostres fitxers fent servir el Navegador Tor: ", + "gui_receive_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot pujar fitxers al vostre ordinador fent servir el Navegador Tor: ", + "gui_url_label_persistent": "Aquest recurs no es tancarà ell 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_url_label_stay_open": "Aquest recurs no es tancarà ell sol.", + "gui_url_label_onetime": "Aquest recurs deixarà de compartir-se després de la primera baixada.", + "gui_url_label_onetime_and_persistent": "Aquest recurs no es tancarà ell 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 a compartir", "gui_status_indicator_share_working": "S'està iniciant…", "gui_status_indicator_share_started": "S'està compartint", - "gui_status_indicator_receive_stopped": "A punt per rebre", + "gui_status_indicator_receive_stopped": "A punt per a rebre", "gui_status_indicator_receive_working": "S'està iniciant…", "gui_status_indicator_receive_started": "S'està rebent", "gui_file_info": "{} fitxers, {}", "gui_file_info_single": "{} fitxer, {}", "history_in_progress_tooltip": "{} en procés", - "history_completed_tooltip": "{} completat/s", + "history_completed_tooltip": "{} completats", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", "receive_mode_warning": "Alerta: El mode de rebuda permet a qualsevol de pujar arxius al teu ordinador. Algú amb males intencions podria pendre el control de la teva màquina si obrissis arxius maliciosos que haguessin pujat. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.", - "gui_receive_mode_warning": "El mode de rebuda permet a qualsevol de pujar fitxers al teu ordinador.

    Algú amb males intencions podria pendre el control de la teva màquina si et pugessin i obrissis arxius maliciosos. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.", - "receive_mode_upload_starting": "S'està començant a rebre {}", + "gui_receive_mode_warning": "El mode de rebuda permet a qualsevol pujar fitxers al vostre ordinador.

    Alguns fitxers podrien guanyar el control de la vostra màquina si els obriu. Obriu només fitxers de persones de confiança, o si realment sabeu el que esteu fent.", + "receive_mode_upload_starting": "S'està començant la pujada. Total: {}", "receive_mode_received_file": "S'han rebut: {}", - "gui_mode_share_button": "Comparteix arxius", + "gui_mode_share_button": "Comparteix fitxers", "gui_mode_receive_button": "Rep fitxers", "gui_settings_receiving_label": "Configuració de rebuda", "gui_settings_downloads_label": "", @@ -181,7 +181,7 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "No s'ha pogut obrir la carpeta perquè el Nautilus no és disponible. El fitxer és a: {}", "gui_settings_language_label": "Llengua preferida", - "gui_settings_language_changed_notice": "Reobre OnionShare perquè el canvi de llengua tingui efecte.", + "gui_settings_language_changed_notice": "Reobre l'OnionShare perquè el canvi de llengua tingui efecte.", "timeout_upload_still_running": "S'està esperant que acabi la pujada", "gui_add_files": "Afegeix fitxers", "gui_add_folder": "Afegeix una carpeta", @@ -191,25 +191,25 @@ "receive_mode_data_dir": "Els arxius que rebis apareixeran aquí: {}", "gui_settings_data_dir_label": "Desa els fitxers a", "gui_settings_data_dir_browse_button": "Explora", - "systray_page_loaded_message": "L'adreça d'OnionShare s'ha carregat", + "systray_page_loaded_message": "S'ha carregat l'adreça d'OnionShare", "systray_share_started_title": "S'ha començat a compartir", "systray_share_started_message": "S'està començant a enviar els fitxers a algú", "systray_share_completed_title": "S'ha acabat de compartir", - "systray_share_completed_message": "Els fitxers s'han acabat d'enviar", + "systray_share_completed_message": "S'ha acabat d'enviar els fitxers", "systray_share_canceled_title": "S'ha deixat de compartir", - "systray_share_canceled_message": "Algú ha aturat la descàrrega que estava fent dels teus fitxers", + "systray_share_canceled_message": "Algú ha cancel·lat la recepció dels vostres fitxers", "systray_receive_started_title": "S'ha començat a rebre", - "systray_receive_started_message": "Algú t'està enviant fitxers", + "systray_receive_started_message": "Algú us està enviant fitxers", "gui_all_modes_history": "Historial", "gui_all_modes_clear_history": "Esborra-ho tot", - "gui_all_modes_transfer_started": "Ha començat a {}", - "gui_all_modes_transfer_finished_range": "S'ha transferit entre {} i {}", - "gui_all_modes_transfer_finished": "Transferit a {}", - "gui_all_modes_transfer_canceled_range": "S'ha canceŀlat. Ha funcionat entre {} i {}", - "gui_all_modes_transfer_canceled": "S'ha canceŀlat {}", - "gui_all_modes_progress_complete": "Han passat %p%, {0:s}.", + "gui_all_modes_transfer_started": "Ha començat el {}", + "gui_all_modes_transfer_finished_range": "S'ha transferit entre: {} - {}", + "gui_all_modes_transfer_finished": "Transferit el {}", + "gui_all_modes_transfer_canceled_range": "S'ha cancel·lat entre: {} - {}", + "gui_all_modes_transfer_canceled": "S'ha cancel·lat el {}", + "gui_all_modes_progress_complete": "%p%, {0:s} transferits.", "gui_all_modes_progress_starting": "{0:s}, %p% (s'està calculant)", - "gui_all_modes_progress_eta": "{0:s}, Temps aproximat: {1:s}, %p%", + "gui_all_modes_progress_eta": "{0:s}, Temps estimat: {1:s}, %p%", "gui_share_mode_no_files": "Encara no s'han enviat fitxers", "gui_share_mode_autostop_timer_waiting": "S'està esperant que finalitzi l'enviament", "gui_receive_mode_no_files": "Encara no s'ha rebut cap fitxer", @@ -226,5 +226,14 @@ "days_first_letter": "d", "hours_first_letter": "h", "minutes_first_letter": "min", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "invalid_password_guess": "Intent de contrasenya incorrecte", + "gui_website_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot visitar el vostre lloc web fent servir el Navegador Tor: ", + "gui_mode_website_button": "Publica el lloc web", + "systray_site_loaded_title": "S'ha carregat el lloc", + "systray_site_loaded_message": "S'ha carregat el lloc OnionShare", + "systray_website_started_title": "S'ha començat a compartir el lloc web", + "systray_website_started_message": "Algú està visitant el vostre lloc web", + "gui_website_mode_no_files": "Encara no s'han compartit llocs web", + "gui_visit_started": "Algú ha visitat el vostre lloc web {}" } diff --git a/share/locale/da.json b/share/locale/da.json index 33fd2541..1589e989 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -46,7 +46,7 @@ "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Annuller", - "error_rate_limit": "Nogen har foretaget for mange forkerte forsøg på din adresse, hvilket kan betyde at de prøver at gætte det, så OnionShare har stoppet serveren. Begynd at dele igen og send en ny adresse til modtageren for at dele.", + "error_rate_limit": "Nogen har foretaget for mange forkerte forsøg på at gætte din adgangskode, så OnionShare har stoppet serveren. Begynd at dele igen og send en ny adresse til modtageren for at dele.", "zip_progress_bar_format": "Komprimerer: %p%", "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", @@ -229,5 +229,14 @@ "days_first_letter": "d", "hours_first_letter": "t", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "invalid_password_guess": "Ugyldigt adgangskodegæt", + "gui_website_url_description": "Alle men OnionShare-adressen kan besøge dit websted med Tor Browser: ", + "gui_mode_website_button": "Udgiv websted", + "systray_site_loaded_title": "Stedet er indlæst", + "systray_site_loaded_message": "OnionShare-stedet er indlæst", + "systray_website_started_title": "Starter delingswebsted", + "systray_website_started_message": "Nogen besøger dit websted", + "gui_website_mode_no_files": "Intet websted delt endnu", + "gui_visit_started": "Nogen har besøgt dit websted {}" } diff --git a/share/locale/de.json b/share/locale/de.json index 0a0eb9c1..2fa5876a 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -91,7 +91,7 @@ "gui_quit_title": "Nicht so schnell", "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", - "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu versenden.", + "error_rate_limit": "Jemand hat zu viele falsche Versuche gemacht, dein Passwort zu erraten, deswegen hat OnionShare den Server gestoppt. Starte die Freigabe erneut und sende dem Empfänger eine neue Adresse zur Freigabe.", "zip_progress_bar_format": "Komprimierung: %p%", "error_stealth_not_supported": "Um die Clientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", @@ -130,8 +130,8 @@ "share_via_onionshare": "Teile es per OnionShare", "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse", - "gui_share_url_description": "Jeder mit dieser OnionShareAdresse kann deine Dateien mit dem Tor Browser herunterladen: ", - "gui_receive_url_description": "Jeder mit dieser OnionShareAdresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", + "gui_share_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Dateien mit dem Tor Browser herunterladen: ", + "gui_receive_url_description": "Jeder mit dieser OnionShare-Adresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.

    Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)", "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.", "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.", @@ -226,5 +226,14 @@ "days_first_letter": "d", "hours_first_letter": "h", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "invalid_password_guess": "Ungültige Passwortratschläge", + "gui_website_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Webseite mit dem Tor Browser ansehen: ", + "gui_mode_website_button": "Webseite veröffentlichen", + "systray_site_loaded_title": "Seite geladen", + "systray_site_loaded_message": "OnionShare-Seite geladen", + "systray_website_started_title": "Freigabe der Webseite starten", + "systray_website_started_message": "Jemand besucht deine Webseite", + "gui_website_mode_no_files": "Noch keine Webseite freigegeben", + "gui_visit_started": "Jemand hat deine Webseite besucht {}" } diff --git a/share/locale/es.json b/share/locale/es.json index 11b3e246..06a5918c 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -3,49 +3,49 @@ "give_this_url": "Entrega esta URL al receptor:", "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo válido.", - "other_page_loaded": "La URL está lista", - "closing_automatically": "Detenido porque la transferencia se completó", + "other_page_loaded": "Dirección cargada", + "closing_automatically": "Se detuvo porque finalizó la transferencia", "help_local_only": "No usar Tor (sólo para desarrollo)", "help_stay_open": "Continuar compartiendo luego que los archivos hayan sido enviados", "help_verbose": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir", + "gui_drag_and_drop": "Arrastre y coloque archivos y carpetas\npara comenzar a compartir", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", "gui_share_start_server": "Comenzar a compartir", "gui_share_stop_server": "Dejar de compartir", - "gui_copy_url": "Copiar URL", + "gui_copy_url": "Copiar la dirección", "gui_downloads": "Historial de descargas", - "gui_copied_url": "Dirección OnionShare copiada al portapapeles", + "gui_copied_url": "Se copió la dirección de OnionShare en el portapapeles", "config_onion_service": "Configurando el servicio cebolla en el puerto {0:d}.", "give_this_url_stealth": "Dale esta dirección y la línea de HidServAuth a la persona a la que le estás enviando el archivo:", "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible", - "close_on_autostop_timer": "Parado porque el temporizador de auto-parada expiró", + "close_on_autostop_timer": "Se detuvo porque se acabó el temporizador de parada automática", "timeout_download_still_running": "Esperando a que se complete la descarga", - "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas", + "large_filesize": "Atención: enviar un archivo grande puede demorar horas", "help_autostop_timer": "Dejar de compartir después de una determinada cantidad de segundos", "help_stealth": "Usar autorización de cliente (avanzada)", "help_config": "Ubicación del archivo de configuración JSON personalizado (opcional)", - "gui_copied_url_title": "Dirección de OnionShare copiada", - "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles", - "gui_please_wait": "Comenzando.... Haz clic para cancelar.", + "gui_copied_url_title": "Se copió la dirección de OnionShare", + "gui_copied_hidservauth": "Se copió el renglón de HidServAuth en el portapapeles", + "gui_please_wait": "Comenzando… Pulse para cancelar.", "gui_quit_title": "No tan rápido", - "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarla, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", - "zip_progress_bar_format": "Comprimiendo: %p%", - "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", + "error_rate_limit": "Alguien ha intentado adivinar su contraseña demasiadas veces, por lo que OnionShare ha detenido el servidor. Inicie la compartición de nuevo y envíe una dirección nueva al receptor.", + "zip_progress_bar_format": "Comprimiendo: %p %", + "error_stealth_not_supported": "Para utilizar la autorización de cliente, necesita como mínimo Tor 0.2.9.1-alpha (o Tor Browser 6.5) y python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare necesita por lo menos Tor 0.2.7.1 y python3-stem 1.4.0.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar autorización de cliente", - "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes hacer clic para copiar tu HidServAuth.", - "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", - "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", - "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", + "gui_settings_stealth_hidservauth_string": "Tras haber guardado su clave privada para reutilizarla, puede pulsar para copiar la ficha HidServAuth.", + "gui_settings_autoupdate_label": "Buscar actualizaciones", + "gui_settings_autoupdate_option": "Notificarme cuando haya una versión nueva disponible", + "gui_settings_autoupdate_check_button": "Buscar actualizaciones", "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare", - "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor", + "gui_settings_connection_type_automatic_option": "Intentar la configuración automática con el Navegador Tor", "gui_settings_connection_type_test_button": "Probar la conexión a Tor", - "gui_settings_tor_bridges": "Soporte de puentes de Tor", - "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", + "gui_settings_tor_bridges": "Compatibilidad con puentes de Tor", + "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que ha añadido funciona.\nVuelva a verificarlos o añada otros.", "settings_saved": "Ajustes guardados en {}", "give_this_url_receive": "Dele esta dirección al remitente:", "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:", @@ -60,52 +60,52 @@ "systray_upload_started_title": "Subida OnionShare Iniciada", "systray_upload_started_message": "Un usuario comenzó a subir archivos a tu computadora", "help_receive": "Recibir recursos compartidos en lugar de enviarlos", - "gui_share_stop_server_autostop_timer": "Dejar de Compartir ({})", + "gui_share_stop_server_autostop_timer": "Dejar de compartir (quedan {})", "gui_share_stop_server_autostop_timer_tooltip": "El temporizador de parada automática termina en {}", "gui_receive_start_server": "Iniciar el modo de recepción", "gui_receive_stop_server": "Detener el modo de recepción", - "gui_receive_stop_server_autostop_timer": "Detener el modo de recepción ({} restantes)", + "gui_receive_stop_server_autostop_timer": "Detener el modo de recepción (quedan {})", "gui_receive_stop_server_autostop_timer_tooltip": "El temporizador de parada automática termina en {}", "gui_copy_hidservauth": "Copiar HidServAuth", "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", - "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", - "settings_error_unknown": "No se puede conectar al controlador Tor porque tu configuración no tiene sentido.", - "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", - "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", - "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", - "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?", - "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", - "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o tu usuario no tiene permiso para leer el archivo cookie.", - "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", - "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?", - "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}.\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", - "error_tor_protocol_error": "Hubo un error con Tor: {}", - "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", + "gui_copied_hidservauth_title": "Se copió la ficha de HidServAuth", + "settings_error_unknown": "No se puede conectar con el controlador de Tor porque su configuración es incoherente.", + "settings_error_automatic": "No se puede conectar con controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en torproject.org) en segundo plano?", + "settings_error_socket_port": "No se puede conectar con el controlador de Tor en {}:{}.", + "settings_error_socket_file": "No se puede conectar con el controlador de Tor mediante el archivo de socket {}.", + "settings_error_auth": "Se conectó con {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador de Tor?", + "settings_error_missing_password": "Se conectó con el controlador de Tor, pero requiere una contraseña para autenticarse.", + "settings_error_unreadable_cookie_file": "Se conectó con el controlador de Tor, pero puede que la contraseña sea incorrecta o que no tenga permiso de lectura del archivo de «cookie».", + "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo de desarrollador en Windows o macOS.", + "settings_error_bundled_tor_timeout": "La conexión con Tor está demorando demasiado. ¿Quizás el equipo está desconectado de Internet o el reloj no está en hora?", + "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse con Tor en segundo plano:\n{}", + "settings_test_success": "Se conectó con el controlador de Tor.\n\nVersión de Tor: {}.\nAdmite servicios cebolla efímeros: {}.\nAdmite autenticación de cliente: {}.\nAdmite direcciones .onion de nueva generación: {}.", + "error_tor_protocol_error": "Se produjo un error en Tor: {}", + "error_tor_protocol_error_unknown": "Se produjo un error desconocido en Tor", "error_invalid_private_key": "Este tipo de clave privada no está soportado", - "connecting_to_tor": "Conectando a la red de Tor", - "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Estás usando {} y el último es {}.", - "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…", - "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", - "update_not_available": "Estás ejecutando la última versión de OnionShare.", - "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?", + "connecting_to_tor": "Conectando con la red de Tor", + "update_available": "Hay una versión nueva de OnionShare. Pulse aquí para descargarla.

    Utiliza la versión {}; la más reciente es la {}.", + "update_error_check_error": "No se pudo buscar actualizaciones: el sitio web de OnionShare comunica que la versión más reciente es «{}», pero eso es irreconocible.", + "update_error_invalid_latest_version": "No se pudo buscar actualizaciones: ¿quizás no se ha conectado con Tor o el sitio web de OnionShare está caído?", + "update_not_available": "Está ejecutando la versión más reciente de OnionShare.", + "gui_tor_connection_ask": "¿Quiere abrir la configuración para arreglar la conexión con Tor?", "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Salir", - "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 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_tor_connection_error_settings": "Pruebe a cambiar la forma en que OnionShare se conecta con la red Tor en la configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de haberse conectado a Internet, vuelva a abrir OnionShare y configure su conexión con Tor.", + "gui_tor_connection_lost": "Se desconectó de Tor.", + "gui_server_started_after_autostop_timer": "El temporizador de parada automática expiró antes de que se iniciara el servidor.\nCree un recurso compartido nuevo.", + "gui_server_autostop_timer_expired": "El temporizador de parada automática ya expiró.\nReinícielo para comenzar a compartir.", + "share_via_onionshare": "Compartir con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", "gui_save_private_key_checkbox": "Usar una dirección persistente", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", - "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Navegador Tor: ", - "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_receive_url_description": "Cualquiera que tenga esta dirección de OnionShare puede cargar archivos a su equipo mediante el navegador Tor: ", + "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción «Usar dirección persistente» en Configuración.)", "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", - "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción «Usar dirección persistente» en Configuración.)", "gui_status_indicator_share_stopped": "Listo para compartir", "gui_status_indicator_share_working": "Comenzando.…", "gui_status_indicator_share_started": "Compartir", @@ -124,18 +124,18 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?", + "gui_share_quit_warning": "Aún está enviando archivos. ¿Confirma que quiere salir de OnionShare?", "gui_quit_warning_quit": "Salir", "gui_quit_warning_dont_quit": "Cancelar", "gui_settings_whats_this": "¿Qué es esto?", "gui_settings_autoupdate_timestamp": "Última comprobación: {}", "gui_settings_autoupdate_timestamp_never": "Nunca", - "gui_settings_general_label": "Ajustes generales", + "gui_settings_general_label": "Configuración general", "gui_settings_sharing_label": "Configuración de compartición", - "gui_settings_close_after_first_download_option": "Dejar de compartir luego que los archivos hayan sido enviados", + "gui_settings_close_after_first_download_option": "Dejar de compartir luego de que los archivos se hayan enviado", "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", - "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", - "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket", + "gui_settings_connection_type_control_port_option": "Conectar mediante el puerto de control", + "gui_settings_connection_type_socket_file_option": "Conectar mediante un archivo de socket", "gui_settings_control_port_label": "Puerto de control", "gui_settings_socket_file_label": "Archivo Socket", "gui_settings_socks_label": "Puerto SOCKS", @@ -143,26 +143,26 @@ "gui_settings_authenticate_no_auth_option": "Sin autenticación, o autenticación por cookies", "gui_settings_authenticate_password_option": "Contraseña", "gui_settings_password_label": "Contraseña", - "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes", - "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?", + "gui_settings_tor_bridges_no_bridges_radio_option": "No utilizar puentes", + "gui_receive_quit_warning": "Aún está recibiendo archivos. ¿Confirma que quiere salir de OnionShare?", "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes insertables obfs4 incorporados", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes insertables obfs4 incorporados (requiere obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte insertable incorporado meek_lite (Azure)", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte insertable meek_lite (Azure) incorporado (requiere obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son muy costosos de correr para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_meek_lite_expensive_warning": "Atención: el funcionamiento de los puentes meek_lite es muy costoso para el Proyecto Tor.

    Utilícelos solo si no puede conectarse con Tor directamente, a través de obfs4 o a través de puentes normales.", "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", - "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", + "gui_settings_tor_bridges_custom_label": "Puede obtener puentes en https://bridges.torproject.org", "gui_settings_button_save": "Guardar", "gui_settings_button_cancel": "Cancelar", "gui_settings_button_help": "Ayuda", - "gui_settings_autostop_timer_checkbox": "Usar temporizador de auto-parada", - "gui_settings_autostop_timer": "Detener carpeta compartida en:", - "history_in_progress_tooltip": "{} en progreso", + "gui_settings_autostop_timer_checkbox": "Utilizar temporizador de parada", + "gui_settings_autostop_timer": "Detener compartición en:", + "history_in_progress_tooltip": "{} en curso", "history_completed_tooltip": "{} completado", "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}", "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de tu ordenador si los abres. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", - "receive_mode_upload_starting": "La subida con un tamaño total de {} está comenzando ahora", + "receive_mode_upload_starting": "Ha comenzado la carga. Total: {}", "receive_mode_received_file": "Recibido: {}", "gui_mode_share_button": "Compartir archivos", "gui_mode_receive_button": "Recibir archivos", @@ -184,27 +184,27 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", "timeout_upload_still_running": "Esperando a que se complete la subida", - "gui_add_files": "Añadir Archivos", - "gui_add_folder": "Añadir Carpeta", + "gui_add_files": "Añadir archivos", + "gui_add_folder": "Añadir carpeta", "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla", "error_cannot_create_data_dir": "No se pudo crear carpeta de datos OnionShare: {}", "receive_mode_data_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", "gui_settings_data_dir_label": "Guardar archivos en", "gui_settings_data_dir_browse_button": "Navegar", "systray_page_loaded_message": "Dirección OnionShare cargada", - "systray_share_started_title": "Compartir Iniciado", + "systray_share_started_title": "Compartición iniciada", "systray_share_started_message": "Se empezó a enviar archivos a alguien", - "systray_share_completed_title": "Compartir Completado", + "systray_share_completed_title": "Compartición completada", "systray_share_completed_message": "Finalizó envío de archivos", - "systray_share_canceled_title": "Compartir Cancelado", + "systray_share_canceled_title": "Compartición cancelada", "systray_share_canceled_message": "Alguien canceló la recepción de sus archivos", - "systray_receive_started_title": "Recepción Iniciada", + "systray_receive_started_title": "Recepción iniciada", "systray_receive_started_message": "Alguien le está enviando archivos", "gui_all_modes_history": "Historial", - "gui_all_modes_clear_history": "Limpiar Todo", + "gui_all_modes_clear_history": "Limpiar todo", "gui_all_modes_transfer_started": "Iniciado {}", "gui_all_modes_transfer_finished_range": "Transferido {} - {}", "gui_all_modes_transfer_finished": "Transferido {}", @@ -218,17 +218,26 @@ "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", "gui_all_modes_transfer_canceled": "Cancelado {}", "gui_settings_onion_label": "Configuración de Onion", - "gui_stop_server_autostop_timer_tooltip": "El temporizador de auto-parada finaliza a las {}", - "gui_start_server_autostart_timer_tooltip": "El temporizador de auto-inicio finaliza a las {}", - "gui_waiting_to_start": "Agendado para empezar en {}. Clic para cancelar.", - "gui_settings_autostart_timer_checkbox": "Usar temporizador de auto-arranque", + "gui_stop_server_autostop_timer_tooltip": "El temporizador de parada automática finaliza a las {}", + "gui_start_server_autostart_timer_tooltip": "El temporizador de inicio automático finaliza a las {}", + "gui_waiting_to_start": "Se programó el inicio en {}. Pulse para cancelar.", + "gui_settings_autostart_timer_checkbox": "Utilizar temporizador de inicio automático", "gui_settings_autostart_timer": "Iniciar compartición en:", - "gui_server_autostart_timer_expired": "La hora agendada ya ha pasado. Por favor actualizarla para comenzar a compartir.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "La hora de auto-inicio no puede ser la misma o anterior a la de auto-parada. Por favor actualizarla para comenzar a compartir.", + "gui_server_autostart_timer_expired": "La hora agendada ya ha pasado. Actualícela para comenzar a compartir.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "La hora de inicio automático no puede ser igual ni anterior a la de parada automática. Actualícela para comenzar a compartir.", "gui_status_indicator_share_scheduled": "Agendado…", "gui_status_indicator_receive_scheduled": "Agendado…", "days_first_letter": "d", "hours_first_letter": "h", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "invalid_password_guess": "Intento de contraseña incorrecto", + "gui_website_url_description": "Cualquiera que tenga esta dirección de OnionShare puede visitar su sitio web mediante el navegador Tor: ", + "gui_mode_website_button": "Publicar sitio web", + "systray_site_loaded_title": "Sitio cargado", + "systray_site_loaded_message": "Sitio de OnionShare cargado", + "systray_website_started_title": "Se comenzó a compartir el sitio web", + "systray_website_started_message": "Alguien está visitando su sitio web", + "gui_website_mode_no_files": "Aún no se han compartido sitios web", + "gui_visit_started": "Alguien ha visitado su sitio web {}" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 28cf4ee9..1279261d 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -152,7 +152,7 @@ "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", "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_rate_limit": "Quelqu’un a effectué trop de tentatives échouées pour deviner votre mot de passe, 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.", "gui_settings_stealth_option": "Utiliser l’autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", @@ -226,5 +226,14 @@ "days_first_letter": "j", "hours_first_letter": "h", "minutes_first_letter": "min", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "gui_website_url_description": "Quiconque aura cette adresse OnionShare pourra visiter votre site Web en utilisant le Navigateur Tor : ", + "systray_site_loaded_title": "Le site a été chargé", + "systray_site_loaded_message": "Le site OnionShare a été chargé", + "systray_website_started_title": "Début du partage du site Web", + "systray_website_started_message": "Quelqu’un visite votre site Web", + "gui_website_mode_no_files": "Aucun site Web n’a encore été partagé", + "gui_visit_started": "Quelqu’un a visité votre site Web {}", + "invalid_password_guess": "La tentative de mot de passe est invalide", + "gui_mode_website_button": "Publier un site Web" } diff --git a/share/locale/is.json b/share/locale/is.json index 2c4f7182..be4e022f 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "Þú ert að taka á móti skrám. Ertu viss um að þú viljir hætta í OnionShare?", "gui_quit_warning_quit": "Hætta", "gui_quit_warning_dont_quit": "Hætta við", - "error_rate_limit": "Einhver hefur gert of margar rangar tilraunir með þínu vistfangi, sem þýðir að hann gæti verið að reyna að giska á það, þannig að OnionShare hefur stöðvað þjóninn. Byrjaðu deiling aftur og sendu viðtakandanum nýtt vistfang til deilingar.", + "error_rate_limit": "Einhver hefur gert of margar rangar tilraunir til að giska á lykilorðið þitt, þannig að OnionShare hefur stöðvað þjóninn. Byrjaðu deiling aftur og sendu viðtakandanum nýtt vistfang til deilingar.", "zip_progress_bar_format": "Þjappa: %p%", "error_stealth_not_supported": "Til að nota biðlaraauðkenningu þarf a.m.k. bæði Tor 0.2.9.1-Alpha (eða Tor Browser 6,5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare krefst a.m.k. bæði Tor 0.2.7.1 og python3-stem 1.4.0.", @@ -225,5 +225,14 @@ "days_first_letter": "dag", "hours_first_letter": "klst", "minutes_first_letter": "mín", - "seconds_first_letter": "sek" + "seconds_first_letter": "sek", + "invalid_password_guess": "Ógilt lykilorð", + "gui_website_url_description": "Hver sem er með þetta OnionShare vistfang getur skoðað vefsvæðið þitt með því að nota Tor-vafrann: ", + "gui_mode_website_button": "Birta vefsvæði", + "systray_site_loaded_title": "Vefsvæðinu hefur verið hlaðið inn", + "systray_site_loaded_message": "OnionShare-vefsvæðinu hefur verið hlaðið inn", + "systray_website_started_title": "Byrja að deila vefsvæðinu", + "systray_website_started_message": "Einhver er að skoða vefinn þinn", + "gui_website_mode_no_files": "Ennþá hefur engu vefsvæði verið deilt", + "gui_visit_started": "Einhver hefur skoðað vefsvæðið {}" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 8041db76..2bcb549e 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -230,5 +230,14 @@ "days_first_letter": "d", "hours_first_letter": "t", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "gui_website_url_description": "Hvem som helst med denne OnionShare-adressen kan besøke din nettside ved bruk av Tor-nettleseren: ", + "gui_mode_website_button": "Publiser nettside", + "systray_site_loaded_title": "Side innlastet", + "systray_site_loaded_message": "OnionShare-side innlastet", + "systray_website_started_title": "Starter deling av nettside", + "systray_website_started_message": "Noen besøker din nettside", + "gui_website_mode_no_files": "Ingen nettside delt enda", + "gui_visit_started": "Noen har besøkt din nettside {}", + "invalid_password_guess": "Feil passord" } diff --git a/share/locale/sv.json b/share/locale/sv.json index 17facc8c..4a205fc2 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -63,7 +63,7 @@ "gui_receive_quit_warning": "Du håller på att ta emot filer. Är du säker på att du vill avsluta OnionShare?", "gui_quit_warning_quit": "Avsluta", "gui_quit_warning_dont_quit": "Avbryt", - "error_rate_limit": "Någon har gjort för många felaktiga försök på din adress, vilket innebär att de kan försöka gissa det, så OnionShare har stoppat servern. Börja dela igen och skicka mottagaren en ny adress att dela.", + "error_rate_limit": "Någon har gjort för många felaktiga försök att gissa ditt lösenord, därför har OnionShare har stoppat servern. Börja dela igen och skicka mottagaren en ny adress att dela.", "zip_progress_bar_format": "Komprimerar: %p%", "error_stealth_not_supported": "För att använda klientauktorisering behöver du minst både Tor 0.2.9.1-alpha (eller Tor Browser 6.5) och python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kräver minst både Tor 0.2.7.1 och python3-stem 1.4.0.", @@ -82,7 +82,7 @@ "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", "gui_settings_connection_type_bundled_option": "Använd Tor-versionen som är inbyggd i OnionShare", "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_control_port_option": "Anslut med kontrollporten", "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", "gui_settings_connection_type_test_button": "Testa anslutning till Tor", "gui_settings_control_port_label": "Kontrollport", @@ -92,7 +92,7 @@ "gui_settings_authenticate_no_auth_option": "Ingen autentisering eller kak-autentisering", "gui_settings_authenticate_password_option": "Lösenord", "gui_settings_password_label": "Lösenord", - "gui_settings_tor_bridges": "Stöd för Tor broar", + "gui_settings_tor_bridges": "Stöd för Tor-broar", "gui_settings_tor_bridges_no_bridges_radio_option": "Använd inte broar", "gui_settings_tor_bridges_obfs4_radio_option": "Använd inbyggda obfs4 pluggbara transporter", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Använd inbyggda obfs4 pluggbara transporter (kräver obfs4proxy)", @@ -225,5 +225,14 @@ "days_first_letter": "d", "hours_first_letter": "t", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "invalid_password_guess": "Ogiltig lösenordsgissning", + "gui_website_url_description": "Någon med denna OnionShare-adress kan besöka din webbplats med hjälp av Tor Browser: ", + "gui_mode_website_button": "Publicera webbplats", + "systray_site_loaded_title": "Webbplats inläst", + "systray_site_loaded_message": "Onionshare-webbplats inläst", + "systray_website_started_title": "Börjar dela webbplats", + "systray_website_started_message": "Någon besöker din webbplats", + "gui_website_mode_no_files": "Ingen webbplats delad ännu", + "gui_visit_started": "Någon har besökt din webbplats {}" } diff --git a/share/locale/tr.json b/share/locale/tr.json index 793a9d08..1e894e17 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -51,7 +51,7 @@ "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 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_rate_limit": "Birisi şifrenizi tahmin etmek için çok fazla yanlış girişimde bulundu, bu yüzden 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 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", @@ -196,5 +196,14 @@ "days_first_letter": "g", "hours_first_letter": "s", "minutes_first_letter": "d", - "seconds_first_letter": "sn" + "seconds_first_letter": "sn", + "invalid_password_guess": "Geçersiz parola tahmini", + "gui_website_url_description": "Bu OnionShare adresi olan herkes, Tor Browser'ı kullanarak web sitenizi ziyaret edebilir: ", + "gui_mode_website_button": "Web Sitesini Yayınla", + "systray_site_loaded_title": "Site Yüklendi", + "systray_site_loaded_message": "OnionShare sitesi yüklü", + "systray_website_started_title": "Paylaşım web sitesi başlatılıyor", + "systray_website_started_message": "Birisi web sitenizi ziyaret ediyor", + "gui_website_mode_no_files": "Henüz Paylaşılan Web Sitesi Yok", + "gui_visit_started": "Birisi web sitenizi ziyaret etti {}" } -- cgit v1.2.3-54-g00ecf From c40f1b6196e5bd8a5a77caad0236ffa6228b2c90 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 20 Sep 2019 18:31:19 -0700 Subject: Finish updating instructions for building PyInstaller from source --- BUILD.md | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/BUILD.md b/BUILD.md index 617e954c..542a4bcb 100644 --- a/BUILD.md +++ b/BUILD.md @@ -154,30 +154,15 @@ pip uninstall PyInstaller rmdir C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyInstaller /S ``` -Change to a folder where you keep source code, and clone the PyInstaller git repo: +Change to a folder where you keep source code, and clone the PyInstaller git repo and checkout the `v3.5` tag: ``` git clone https://github.com/pyinstaller/pyinstaller.git -``` - -To verify the git tag, you first need the signing key's PGP key, which means you need `gpg`. If you installed git from git-scm.com, you can run this from Git Bash: - -``` -gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65 -``` - -And now verify the tag: - -``` cd pyinstaller -git tag -v v3.4 +git tag -v v3.5 ``` -It should say `Good signature from "Hartmut Goebel `. If it verified successfully, checkout the tag: - -``` -git checkout v3.4 -``` +(Note that ideally you would verify the git tag, but the PGP key that has signed the `v3.5` git tag for is not published anywhere, so this isn't possible. See [this issue](https://github.com/pyinstaller/pyinstaller/issues/4430).) And compile the bootloader, following [these instructions](https://pythonhosted.org/PyInstaller/bootloader-building.html). To compile, run this: -- cgit v1.2.3-54-g00ecf From 2022b2b2aff404d13d25c0fce806eeaf40873f56 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 20 Sep 2019 18:42:40 -0700 Subject: Hide the system tray icon before closing --- onionshare_gui/onionshare_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8e77efe4..6406023f 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -540,6 +540,7 @@ class OnionShareGui(QtWidgets.QMainWindow): def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') + self.system_tray.hide() try: if self.mode == OnionShareGui.MODE_SHARE: server_status = self.share_mode.server_status -- cgit v1.2.3-54-g00ecf From d8c0bc4e4fb8ce95c5a472f1b86d7ec86547162b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Sep 2019 16:49:31 +1000 Subject: Invert the CSP header setting and put it in its own Website Mode settings group. Make the CSP header mandatory for share/receive modes, optional for website mode only. --- onionshare/settings.py | 2 +- onionshare/web/web.py | 3 +- onionshare_gui/settings_dialog.py | 59 +++++++++++++--------- share/locale/en.json | 3 +- tests/GuiWebsiteTest.py | 10 ++-- ...cal_onionshare_website_mode_csp_enabled_test.py | 2 +- tests/local_onionshare_website_mode_test.py | 2 +- tests/test_onionshare_settings.py | 2 +- 8 files changed, 49 insertions(+), 34 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 28523b89..7a017bf0 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -114,7 +114,7 @@ class Settings(object): 'password': '', 'hidservauth_string': '', 'data_dir': self.build_default_data_dir(), - 'csp_header_enabled': True, + 'csp_header_disabled': False, 'locale': None # this gets defined in fill_in_defaults() } self._settings = {} diff --git a/onionshare/web/web.py b/onionshare/web/web.py index c1a9ce4c..f3e1e07a 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -239,7 +239,8 @@ class Web: """ for header, value in self.security_headers: r.headers.set(header, value) - if self.common.settings.get('csp_header_enabled'): + # Set a CSP header unless in website mode and the user has disabled it + if not self.common.settings.get('csp_header_disabled') or self.mode != 'website': r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;') return r diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 432645e6..ec91a491 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -214,28 +214,10 @@ class SettingsDialog(QtWidgets.QDialog): 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")) - # Option to disable Content Security Policy (for website sharing) - self.csp_header_enabled_checkbox = QtWidgets.QCheckBox() - self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked) - self.csp_header_enabled_checkbox.setText(strings._("gui_settings_csp_header_enabled_option")) - csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy")) - csp_header_label.setStyleSheet(self.common.css['settings_whats_this']) - csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - csp_header_label.setOpenExternalLinks(True) - csp_header_label.setMinimumSize(csp_header_label.sizeHint()) - csp_header_layout = QtWidgets.QHBoxLayout() - csp_header_layout.addWidget(self.csp_header_enabled_checkbox) - csp_header_layout.addWidget(csp_header_label) - csp_header_layout.addStretch() - csp_header_layout.setContentsMargins(0,0,0,0) - self.csp_header_widget = QtWidgets.QWidget() - self.csp_header_widget.setLayout(csp_header_layout) - # 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_layout.addWidget(self.csp_header_widget) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) @@ -256,6 +238,36 @@ class SettingsDialog(QtWidgets.QDialog): receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label")) receiving_group.setLayout(receiving_group_layout) + # Option to disable Content Security Policy (for website sharing) + self.csp_header_disabled_checkbox = QtWidgets.QCheckBox() + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.csp_header_disabled_checkbox.setText(strings._("gui_settings_csp_header_disabled_option")) + csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy")) + csp_header_label.setStyleSheet(self.common.css['settings_whats_this']) + csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + csp_header_label.setOpenExternalLinks(True) + csp_header_label.setMinimumSize(csp_header_label.sizeHint()) + csp_header_layout = QtWidgets.QHBoxLayout() + csp_header_layout.addWidget(self.csp_header_disabled_checkbox) + csp_header_layout.addWidget(csp_header_label) + csp_header_layout.addStretch() + csp_header_layout.setContentsMargins(0,0,0,0) + self.csp_header_widget = QtWidgets.QWidget() + self.csp_header_widget.setLayout(csp_header_layout) + + # Website settings widget + website_settings_layout = QtWidgets.QVBoxLayout() + website_settings_layout.setContentsMargins(0, 0, 0, 0) + website_settings_layout.addWidget(self.csp_header_widget) + self.website_settings_widget = QtWidgets.QWidget() + self.website_settings_widget.setLayout(website_settings_layout) + + # Website mode options layout + website_group_layout = QtWidgets.QVBoxLayout() + website_group_layout.addWidget(self.website_settings_widget) + website_group = QtWidgets.QGroupBox(strings._("gui_settings_website_label")) + website_group.setLayout(website_group_layout) + # Automatic updates options # Autoupdate @@ -500,6 +512,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(onion_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) + left_col_layout.addWidget(website_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addLayout(language_layout) left_col_layout.addStretch() @@ -535,11 +548,11 @@ class SettingsDialog(QtWidgets.QDialog): else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - csp_header_enabled = self.old_settings.get('csp_header_enabled') - if csp_header_enabled: - self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Checked) + csp_header_disabled = self.old_settings.get('csp_header_disabled') + if csp_header_disabled: + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Checked) else: - self.csp_header_enabled_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) autostart_timer = self.old_settings.get('autostart_timer') if autostart_timer: @@ -1006,7 +1019,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('csp_header_enabled', self.csp_header_enabled_checkbox.isChecked()) + settings.set('csp_header_disabled', self.csp_header_disabled_checkbox.isChecked()) settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) diff --git a/share/locale/en.json b/share/locale/en.json index 6ca5c01f..38500043 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -52,7 +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_csp_header_enabled_option": "Enable Content Security Policy header", + "gui_settings_csp_header_disabled_option": "Disable Content Security Policy header", "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", @@ -142,6 +142,7 @@ "gui_mode_receive_button": "Receive Files", "gui_mode_website_button": "Publish Website", "gui_settings_receiving_label": "Receiving settings", + "gui_settings_website_label": "Website settings", "gui_settings_data_dir_label": "Save files to", "gui_settings_data_dir_browse_button": "Browse", "gui_settings_public_mode_checkbox": "Public mode", diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index f58f4aa2..798c619a 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -65,7 +65,7 @@ class GuiWebsiteTest(GuiShareTest): QtTest.QTest.qWait(2000) self.assertTrue('This is a test website hosted by OnionShare' in r.text) - def check_csp_header(self, public_mode, csp_header_enabled): + def check_csp_header(self, public_mode, csp_header_disabled): '''Test that the CSP header is present when enabled or vice versa''' url = "http://127.0.0.1:{}/".format(self.gui.app.port) if public_mode: @@ -74,10 +74,10 @@ class GuiWebsiteTest(GuiShareTest): r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.website_mode.server_status.web.password)) QtTest.QTest.qWait(2000) - if csp_header_enabled: - self.assertTrue('Content-Security-Policy' in r.headers) - else: + if csp_header_disabled: self.assertFalse('Content-Security-Policy' in r.headers) + else: + self.assertTrue('Content-Security-Policy' in r.headers) def run_all_website_mode_setup_tests(self): """Tests in website mode prior to starting a share""" @@ -106,7 +106,7 @@ class GuiWebsiteTest(GuiShareTest): self.run_all_website_mode_setup_tests() self.run_all_website_mode_started_tests(public_mode, startup_time=2000) self.view_website(public_mode) - self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_enabled')) + self.check_csp_header(public_mode, self.gui.common.settings.get('csp_header_disabled')) self.history_widgets_present(self.gui.website_mode) self.server_is_stopped(self.gui.website_mode, False) self.web_server_is_stopped() diff --git a/tests/local_onionshare_website_mode_csp_enabled_test.py b/tests/local_onionshare_website_mode_csp_enabled_test.py index 3cf79440..fbdc07ea 100644 --- a/tests/local_onionshare_website_mode_csp_enabled_test.py +++ b/tests/local_onionshare_website_mode_csp_enabled_test.py @@ -8,7 +8,7 @@ class LocalWebsiteModeCSPEnabledTest(unittest.TestCase, GuiWebsiteTest): @classmethod def setUpClass(cls): test_settings = { - "csp_header_enabled": True, + "csp_header_disabled": False, } cls.gui = GuiWebsiteTest.set_up(test_settings) diff --git a/tests/local_onionshare_website_mode_test.py b/tests/local_onionshare_website_mode_test.py index 5a7334a4..fc560f70 100644 --- a/tests/local_onionshare_website_mode_test.py +++ b/tests/local_onionshare_website_mode_test.py @@ -8,7 +8,7 @@ class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest): @classmethod def setUpClass(cls): test_settings = { - "csp_header_enabled": False + "csp_header_disabled": True } cls.gui = GuiWebsiteTest.set_up(test_settings) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index d46c599b..12200b70 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -67,7 +67,7 @@ class TestSettings: 'hidservauth_string': '', 'data_dir': os.path.expanduser('~/OnionShare'), 'public_mode': False, - 'csp_header_enabled': True + 'csp_header_disabled': False } for key in settings_obj._settings: # Skip locale, it will not always default to the same thing -- cgit v1.2.3-54-g00ecf From c3fc5735bc5fd7c86ecf62fe8e47a3ea9baeeccc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 10:23:33 -0700 Subject: Typo in Qt version --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 542a4bcb..260a5d7e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -50,7 +50,7 @@ Download and install Python 3.7.4 from https://www.python.org/downloads/release/ 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.13.0 for macOS from https://www.qt.io/offline-installers. I downloaded `qt-opensource-mac-x64-5.13.1.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.13.10` > `macOS`. +Install Qt 5.13.1 for macOS from https://www.qt.io/offline-installers. I downloaded `qt-opensource-mac-x64-5.13.1.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.13.1` > `macOS`. Now install pip dependencies. If you want to use a virtualenv, create it and activate it first: @@ -100,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.13.0 from https://www.qt.io/offline-installers. I downloaded `qt-opensource-windows-x86-5.13.1.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.13.1` > `MSVC 2017 32-bit`. +Install the Qt 5.13.1 from https://www.qt.io/offline-installers. I downloaded `qt-opensource-windows-x86-5.13.1.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.13.1` > `MSVC 2017 32-bit`. After that you can try both the CLI and the GUI version of OnionShare: -- cgit v1.2.3-54-g00ecf From 06ad2ad07317b0276ad8da2ae66aef921514e143 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 13:03:08 -0700 Subject: Version bump to 2.2.dev1 --- install/onionshare.nsi | 4 ++-- share/version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 68213528..52e9b526 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -5,8 +5,8 @@ # change these with each release !define INSTALLSIZE 115186 !define VERSIONMAJOR 2 -!define VERSIONMINOR 1 -!define VERSIONSTRING "2.1" +!define VERSIONMINOR 2 +!define VERSIONSTRING "2.2.dev1" RequestExecutionLevel admin diff --git a/share/version.txt b/share/version.txt index 879b416e..4ec9ad89 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.1 +2.2.dev1 \ No newline at end of file -- cgit v1.2.3-54-g00ecf From b9ed720590d149e43ee3f6a164c84e4a1b11a138 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 13:25:15 -0700 Subject: Fix icon path in PyInstaller file --- install/pyinstaller.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index 24664bf9..7eeb5153 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -58,7 +58,7 @@ if p == 'Darwin': app = BUNDLE( coll, name='OnionShare.app', - icon='install/onionshare.icns', + icon='onionshare.icns', bundle_identifier='com.micahflee.onionshare', info_plist={ 'CFBundleShortVersionString': version, -- cgit v1.2.3-54-g00ecf From 1f0c22d4054fe976b41d0e7dd8981c4cf76cfcf5 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 22 Sep 2019 22:39:36 +0200 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/da.json | 21 +++++++++++++-------- share/locale/de.json | 8 +++++--- share/locale/is.json | 25 +++++++++++++++---------- share/locale/nb.json | 3 ++- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index 1589e989..b50fedc1 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -99,7 +99,7 @@ "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 {}.", - "update_error_check_error": "Kunne ikke søge efter nye versioner: OnionShare-webstedet siger at den seneste version er den ugenkendte '{}' …", + "update_error_check_error": "Kunne ikke søge efter nye version: OnionShare-webstedet siger at den seneste version er den ugenkendte '{}' …", "update_error_invalid_latest_version": "Kunne ikke søge efter ny version: Måske har du ikke forbindelse til Tor, eller er OnionShare-webstedet nede?", "update_not_available": "Du kører den seneste OnionShare.", "gui_tor_connection_ask": "Åbn indstillingerne for at rette forbindelsen til Tor?", @@ -109,7 +109,7 @@ "gui_tor_connection_canceled": "Kunne ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at opsætte dens forbindelse til Tor.", "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.", "gui_server_started_after_autostop_timer": "Timeren med autostop løb ud inden serveren startede. Opret venligst en ny deling.", - "gui_server_autostop_timer_expired": "Timeren med autostop er allerede løbet ud. Opdater den venligst for at begynde at dele.", + "gui_server_autostop_timer_expired": "Timeren med autostop er allerede løbet ud. Juster den venligst for at begynde at dele.", "share_via_onionshare": "Del via OnionShare", "gui_save_private_key_checkbox": "Brug en vedvarende adresse", "gui_copied_url_title": "Kopierede OnionShare-adresse", @@ -171,7 +171,7 @@ "gui_upload_finished_range": "Uploadede {} til {}", "gui_upload_finished": "Uploadet {}", "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_language_changed_notice": "Genstart OnionShare for at det nye 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: ", @@ -222,8 +222,8 @@ "gui_waiting_to_start": "Planlagt til at starte om {}. Klik for at annullere.", "gui_settings_autostart_timer_checkbox": "Brug timer med autostart", "gui_settings_autostart_timer": "Start deling ved:", - "gui_server_autostart_timer_expired": "Det planlagte tidspunkt er allerede passeret. Opdater det venligst for at begynde at dele.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Timeren med autostop må ikke være det samme eller tidligere end timeren med autostart. Opdater den venligst for at begynde at dele.", + "gui_server_autostart_timer_expired": "Det planlagte tidspunkt er allerede passeret. Juster det venligst for at begynde at dele.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Timeren med autostop må ikke være det samme eller tidligere end timeren med autostart. Juster den venligst for at begynde at dele.", "gui_status_indicator_share_scheduled": "Planlagt …", "gui_status_indicator_receive_scheduled": "Planlagt …", "days_first_letter": "d", @@ -233,10 +233,15 @@ "invalid_password_guess": "Ugyldigt adgangskodegæt", "gui_website_url_description": "Alle men OnionShare-adressen kan besøge dit websted med Tor Browser: ", "gui_mode_website_button": "Udgiv websted", - "systray_site_loaded_title": "Stedet er indlæst", - "systray_site_loaded_message": "OnionShare-stedet er indlæst", + "systray_site_loaded_title": "Webstedet er indlæst", + "systray_site_loaded_message": "OnionShare-webstedet er indlæst", "systray_website_started_title": "Starter delingswebsted", "systray_website_started_message": "Nogen besøger dit websted", "gui_website_mode_no_files": "Intet websted delt endnu", - "gui_visit_started": "Nogen har besøgt dit websted {}" + "gui_visit_started": "Nogen har besøgt dit websted {}", + "incorrect_password": "Forkert adgangskode", + "gui_settings_individual_downloads_label": "Fravælg for at tillade download af individuelle filer", + "history_requests_tooltip": "{}-webanmodninger", + "systray_individual_file_downloaded_title": "Individuel fil er indlæst", + "systray_individual_file_downloaded_message": "Individuel fil {} vist" } diff --git a/share/locale/de.json b/share/locale/de.json index 2fa5876a..af889aec 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -127,7 +127,7 @@ "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.", "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", - "share_via_onionshare": "Teile es per OnionShare", + "share_via_onionshare": "Teilen über OnionShare", "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse", "gui_share_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Dateien mit dem Tor Browser herunterladen: ", @@ -231,9 +231,11 @@ "gui_website_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Webseite mit dem Tor Browser ansehen: ", "gui_mode_website_button": "Webseite veröffentlichen", "systray_site_loaded_title": "Seite geladen", - "systray_site_loaded_message": "OnionShare-Seite geladen", + "systray_site_loaded_message": "OnionShare Website geladen", "systray_website_started_title": "Freigabe der Webseite starten", "systray_website_started_message": "Jemand besucht deine Webseite", "gui_website_mode_no_files": "Noch keine Webseite freigegeben", - "gui_visit_started": "Jemand hat deine Webseite besucht {}" + "gui_visit_started": "Jemand hat deine Webseite besucht {}", + "incorrect_password": "Falsches Passwort", + "systray_individual_file_downloaded_title": "Individuelle Datei geladen" } diff --git a/share/locale/is.json b/share/locale/is.json index be4e022f..73e0dbc3 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -122,17 +122,17 @@ "error_invalid_private_key": "Þessi gerð einkalykils er ekki studd", "connecting_to_tor": "Tengist við Tor-netkerfið", "update_available": "Ný útgáfa OnionShare er komin út. Smelltu hér til að ná í hana.

    Þú ert að nota útgáfu {} og sú nýjasta er {}.", - "update_error_check_error": "Gat ekki athugað með nýjar uppfærslur: vefsvæði OnionShare tilkynnir að nýjasta útgáfan sé hin óskiljanlega '{}'…", + "update_error_check_error": "Gat ekki athugað með nýja uppfærslu: vefsvæði OnionShare tilkynnir að nýjasta útgáfan sé hin óskiljanlega '{}'…", "update_error_invalid_latest_version": "Gat ekki athugað með nýjar uppfærslur: mögulega ertu ekki tengd(ur) við Tor eða að vefsvæði OnionShare sé óvirkt í augnablikinu?", "update_not_available": "Þú ert þegar að keyra nýjustu útgáfu OnionShare.", "gui_tor_connection_ask": "Opna stillingarnar til að ráða fram úr tengingu við Tor?", "gui_tor_connection_ask_open_settings": "Já", "gui_tor_connection_ask_quit": "Hætta", "gui_tor_connection_error_settings": "Prófaðu að breyta í stillingunum hvernig OnionShare tengist við Tor-netkerfið.", - "gui_tor_connection_canceled": "Tókst ekki að tengjast Tor.\n\nGakktu úr skugga um að þú sért tengdur internetinu, opnaðu síðan aftur OnionShare og settu upp tengingu þess við Tor.", + "gui_tor_connection_canceled": "Tókst ekki að tengjast Tor.\n\nGakktu úr skugga um að þú sért tengd/ur internetinu, opnaðu síðan aftur OnionShare og settu upp tengingu þess við Tor.", "gui_tor_connection_lost": "Aftengt frá Tor.", - "gui_server_autostop_timer_expired": "Sjálfvirkri niðurtalningu er þegar lokið. Uppfærðu hana til að hefja deilingu.", - "share_via_onionshare": "Deila þessu með OnionShare", + "gui_server_autostop_timer_expired": "Sjálfvirkri niðurtalningu er þegar lokið. Lagaðu hana til að hefja deilingu.", + "share_via_onionshare": "Deila með OnionShare", "gui_use_legacy_v2_onions_checkbox": "Nota eldri vistföng", "gui_save_private_key_checkbox": "Nota viðvarandi vistföng", "gui_share_url_description": "Hver sem er með þetta OnionShare vistfang getur sótt skrárnar þínar með því að nota Tor-vafrann: ", @@ -180,7 +180,7 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "Get ekki opnað möppu því nautilus er ekki til taks. Skráin er hér: {}", "gui_settings_language_label": "Umbeðið tungumál", - "gui_settings_language_changed_notice": "Þú þarft að endurræsa OnionShare til að breyting á tungumáli taki gildi.", + "gui_settings_language_changed_notice": "Þú þarft að endurræsa OnionShare til að nýtt tungumál taki gildi.", "gui_add_files": "Bæta við skrám", "gui_add_folder": "Bæta við möppu", "gui_settings_onion_label": "Onion-stillingar", @@ -218,8 +218,8 @@ "gui_settings_autostart_timer_checkbox": "Nota sjálfvirka niðurtalningu ræsingar", "gui_settings_autostart_timer": "Byrja deilinguna:", "gui_server_started_after_autostop_timer": "Sjálfvirka niðurtalningin rann út áður en þjónninn ræstist. Útbúðu nýja sameign.", - "gui_server_autostart_timer_expired": "Áætlaðri tímasetningu er þegar lokið. Uppfærðu hana til að hefja deilingu.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Tímasetning sjálfvirkrar lokaniðurtalningar má ekki vera sú sama eða á undan sjálfvirkri ræsiniðurtalningu. Uppfærðu tímasetninguna til að hefja deilingu.", + "gui_server_autostart_timer_expired": "Áætlaðri tímasetningu er þegar lokið. Lagaðu hana til að hefja deilingu.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Tímasetning sjálfvirkrar lokaniðurtalningar má ekki vera sú sama eða á undan sjálfvirkri ræsiniðurtalningu. Lagaðu tímasetninguna til að hefja deilingu.", "gui_status_indicator_share_scheduled": "Áætlað…", "gui_status_indicator_receive_scheduled": "Áætlað…", "days_first_letter": "dag", @@ -229,10 +229,15 @@ "invalid_password_guess": "Ógilt lykilorð", "gui_website_url_description": "Hver sem er með þetta OnionShare vistfang getur skoðað vefsvæðið þitt með því að nota Tor-vafrann: ", "gui_mode_website_button": "Birta vefsvæði", - "systray_site_loaded_title": "Vefsvæðinu hefur verið hlaðið inn", - "systray_site_loaded_message": "OnionShare-vefsvæðinu hefur verið hlaðið inn", + "systray_site_loaded_title": "Vefsvæði hefur verið hlaðið inn", + "systray_site_loaded_message": "OnionShare-vefsvæði hefur verið hlaðið inn", "systray_website_started_title": "Byrja að deila vefsvæðinu", "systray_website_started_message": "Einhver er að skoða vefinn þinn", "gui_website_mode_no_files": "Ennþá hefur engu vefsvæði verið deilt", - "gui_visit_started": "Einhver hefur skoðað vefsvæðið {}" + "gui_visit_started": "Einhver hefur skoðað vefsvæðið {}", + "incorrect_password": "Rangt lykilorð", + "gui_settings_individual_downloads_label": "Taktu merkið úr til að leyfa niðurhal á stökum skrám", + "history_requests_tooltip": "{} vefbeiðnir", + "systray_individual_file_downloaded_title": "Stakri skrá hlaðið inn", + "systray_individual_file_downloaded_message": "Staka skráin {} skoðuð" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 2bcb549e..0d947205 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -239,5 +239,6 @@ "systray_website_started_message": "Noen besøker din nettside", "gui_website_mode_no_files": "Ingen nettside delt enda", "gui_visit_started": "Noen har besøkt din nettside {}", - "invalid_password_guess": "Feil passord" + "invalid_password_guess": "Feil passord", + "incorrect_password": "Feil passord" } -- cgit v1.2.3-54-g00ecf From dbb22c598016a708b6908121a58e54b3f4d9996e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 23 Sep 2019 07:39:31 +1000 Subject: Update changelog for v2.2 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70da866..55f1bcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # OnionShare Changelog +## 2.2 + +* New feature: Website mode, which allows publishing a flat HTML website as an onion service +* Allow individual files to be viewed or downloaded in Share mode, including the ability to browse into subdirectories and use breadcrumbs to navigate back +* Show a counter when individual files or pages are viewed +* Better History items including colors and status codes to differentiate between successful and failed requests +* Swap out the random /slug suffix for HTTP basic authentication (when in non-public mode) +* Hide the Tor connection settings if the ONIONSHARE_HIDE_TOR_SETTINGS environment variable is set (Tails compatibility) +* Remove the NoScript XSS warning in Receive Mode now that the NoScript/Tor Browser bug is fixed. The ajax upload method still exists when javascript is enabled. +* Better support for DragonFly BSD +* Updated various dependencies, including Flask, Werkzeug, urllib3, requests, and PyQt5 +* Updated Tor to 0.4.1.5 +* Other minor bug fixes + ## 2.1 * New feature: Auto-start timer, which allows scheduling when the server starts -- cgit v1.2.3-54-g00ecf From 01534a643b533183f007e5d960d9feb05fdad44f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 14:53:38 -0700 Subject: Change wording in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f1bcc6..7ca10992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.2 -* New feature: Website mode, which allows publishing a flat HTML website as an onion service +* New feature: Website mode, which allows publishing a static HTML website as an onion service * Allow individual files to be viewed or downloaded in Share mode, including the ability to browse into subdirectories and use breadcrumbs to navigate back * Show a counter when individual files or pages are viewed * Better History items including colors and status codes to differentiate between successful and failed requests -- cgit v1.2.3-54-g00ecf From 9e7a91aed2104b0fa0a10bfd533331a40ed51911 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 15:35:14 -0700 Subject: Show the website mode URL description in website mode, instead of the share mode one --- onionshare_gui/server_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index dbcc6ca8..85b1f230 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -209,7 +209,7 @@ class ServerStatus(QtWidgets.QWidget): if self.mode == ServerStatus.MODE_SHARE: self.url_description.setText(strings._('gui_share_url_description').format(info_image)) elif self.mode == ServerStatus.MODE_WEBSITE: - self.url_description.setText(strings._('gui_share_url_description').format(info_image)) + self.url_description.setText(strings._('gui_website_url_description').format(info_image)) else: self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) -- cgit v1.2.3-54-g00ecf From b723d45a61a41fb8b95592dd150358d56b99452d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 15:52:13 -0700 Subject: Add website mode screenshot --- README.md | 2 ++ screenshots/onionshare-website-server.png | Bin 0 -> 148709 bytes 2 files changed, 2 insertions(+) create mode 100644 screenshots/onionshare-website-server.png diff --git a/README.md b/README.md index fb134e93..0c14c121 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,5 @@ Test status: [![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style ![Receive mode OnionShare](/screenshots/onionshare-receive-server.png) ![Receive mode Tor Browser](/screenshots/onionshare-receive-client.png) + +![Website mode OnionShare](/screenshots/onionshare-website-server.png) \ No newline at end of file diff --git a/screenshots/onionshare-website-server.png b/screenshots/onionshare-website-server.png new file mode 100644 index 00000000..55e4fa27 Binary files /dev/null and b/screenshots/onionshare-website-server.png differ -- cgit v1.2.3-54-g00ecf From 9accff1aded81c5f57d187791f98282fcdd3a080 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 16:07:37 -0700 Subject: Remove references to website loaded notification that never actually get displayed --- onionshare_gui/mode/website_mode/__init__.py | 6 ------ share/locale/en.json | 2 -- 2 files changed, 8 deletions(-) diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index b277b6c3..7382f5a7 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -200,12 +200,6 @@ class WebsiteMode(Mode): """ self.primary_action.hide() - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message')) - def on_reload_settings(self): """ If there were some files listed for sharing, we should be ok to re-enable diff --git a/share/locale/en.json b/share/locale/en.json index a5a948b0..a8b7828e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -152,8 +152,6 @@ "systray_menu_exit": "Quit", "systray_page_loaded_title": "Page Loaded", "systray_page_loaded_message": "OnionShare address 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", -- cgit v1.2.3-54-g00ecf From 6679cedb1c4c2f7893b0ac033af6316207d0b180 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 16:10:49 -0700 Subject: Remove more references to website mode notifications that don't exist --- share/locale/en.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index a8b7828e..f1f5cb7b 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -160,10 +160,6 @@ "systray_share_canceled_message": "Someone canceled receiving your files", "systray_receive_started_title": "Receiving Started", "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 {}", -- cgit v1.2.3-54-g00ecf From 9e7118bc7a568e9b1f805623b8899d65d1959199 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Sep 2019 16:19:11 -0700 Subject: Remove one more reference, and remove them from all locales too --- share/locale/ca.json | 7 +------ share/locale/da.json | 9 +-------- share/locale/de.json | 8 +------- share/locale/en.json | 1 - share/locale/es.json | 7 +------ share/locale/fr.json | 5 ----- share/locale/is.json | 9 +-------- share/locale/nb.json | 5 ----- share/locale/sv.json | 7 +------ share/locale/tr.json | 7 +------ 10 files changed, 7 insertions(+), 58 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 72d315bc..1b7b15db 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -230,10 +230,5 @@ "invalid_password_guess": "Intent de contrasenya incorrecte", "gui_website_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot visitar el vostre lloc web fent servir el Navegador Tor: ", "gui_mode_website_button": "Publica el lloc web", - "systray_site_loaded_title": "S'ha carregat el lloc", - "systray_site_loaded_message": "S'ha carregat el lloc OnionShare", - "systray_website_started_title": "S'ha començat a compartir el lloc web", - "systray_website_started_message": "Algú està visitant el vostre lloc web", - "gui_website_mode_no_files": "Encara no s'han compartit llocs web", - "gui_visit_started": "Algú ha visitat el vostre lloc web {}" + "gui_website_mode_no_files": "Encara no s'han compartit llocs web" } diff --git a/share/locale/da.json b/share/locale/da.json index b50fedc1..dffe13cb 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -233,15 +233,8 @@ "invalid_password_guess": "Ugyldigt adgangskodegæt", "gui_website_url_description": "Alle men OnionShare-adressen kan besøge dit websted med Tor Browser: ", "gui_mode_website_button": "Udgiv websted", - "systray_site_loaded_title": "Webstedet er indlæst", - "systray_site_loaded_message": "OnionShare-webstedet er indlæst", - "systray_website_started_title": "Starter delingswebsted", - "systray_website_started_message": "Nogen besøger dit websted", "gui_website_mode_no_files": "Intet websted delt endnu", - "gui_visit_started": "Nogen har besøgt dit websted {}", "incorrect_password": "Forkert adgangskode", "gui_settings_individual_downloads_label": "Fravælg for at tillade download af individuelle filer", - "history_requests_tooltip": "{}-webanmodninger", - "systray_individual_file_downloaded_title": "Individuel fil er indlæst", - "systray_individual_file_downloaded_message": "Individuel fil {} vist" + "history_requests_tooltip": "{}-webanmodninger" } diff --git a/share/locale/de.json b/share/locale/de.json index af889aec..8b7837fe 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -230,12 +230,6 @@ "invalid_password_guess": "Ungültige Passwortratschläge", "gui_website_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Webseite mit dem Tor Browser ansehen: ", "gui_mode_website_button": "Webseite veröffentlichen", - "systray_site_loaded_title": "Seite geladen", - "systray_site_loaded_message": "OnionShare Website geladen", - "systray_website_started_title": "Freigabe der Webseite starten", - "systray_website_started_message": "Jemand besucht deine Webseite", "gui_website_mode_no_files": "Noch keine Webseite freigegeben", - "gui_visit_started": "Jemand hat deine Webseite besucht {}", - "incorrect_password": "Falsches Passwort", - "systray_individual_file_downloaded_title": "Individuelle Datei geladen" + "incorrect_password": "Falsches Passwort" } diff --git a/share/locale/en.json b/share/locale/en.json index f1f5cb7b..badb83ca 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -175,7 +175,6 @@ "gui_website_mode_no_files": "No Website Shared Yet", "gui_receive_mode_no_files": "No Files Received Yet", "gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving", - "gui_visit_started": "Someone has visited your website {}", "receive_mode_upload_starting": "Upload of total size {} is starting", "days_first_letter": "d", "hours_first_letter": "h", diff --git a/share/locale/es.json b/share/locale/es.json index 06a5918c..95ee4d0e 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -234,10 +234,5 @@ "invalid_password_guess": "Intento de contraseña incorrecto", "gui_website_url_description": "Cualquiera que tenga esta dirección de OnionShare puede visitar su sitio web mediante el navegador Tor: ", "gui_mode_website_button": "Publicar sitio web", - "systray_site_loaded_title": "Sitio cargado", - "systray_site_loaded_message": "Sitio de OnionShare cargado", - "systray_website_started_title": "Se comenzó a compartir el sitio web", - "systray_website_started_message": "Alguien está visitando su sitio web", - "gui_website_mode_no_files": "Aún no se han compartido sitios web", - "gui_visit_started": "Alguien ha visitado su sitio web {}" + "gui_website_mode_no_files": "Aún no se han compartido sitios web" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 1279261d..9cdecd3d 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -228,12 +228,7 @@ "minutes_first_letter": "min", "seconds_first_letter": "s", "gui_website_url_description": "Quiconque aura cette adresse OnionShare pourra visiter votre site Web en utilisant le Navigateur Tor : ", - "systray_site_loaded_title": "Le site a été chargé", - "systray_site_loaded_message": "Le site OnionShare a été chargé", - "systray_website_started_title": "Début du partage du site Web", - "systray_website_started_message": "Quelqu’un visite votre site Web", "gui_website_mode_no_files": "Aucun site Web n’a encore été partagé", - "gui_visit_started": "Quelqu’un a visité votre site Web {}", "invalid_password_guess": "La tentative de mot de passe est invalide", "gui_mode_website_button": "Publier un site Web" } diff --git a/share/locale/is.json b/share/locale/is.json index 73e0dbc3..23dd0e20 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -229,15 +229,8 @@ "invalid_password_guess": "Ógilt lykilorð", "gui_website_url_description": "Hver sem er með þetta OnionShare vistfang getur skoðað vefsvæðið þitt með því að nota Tor-vafrann: ", "gui_mode_website_button": "Birta vefsvæði", - "systray_site_loaded_title": "Vefsvæði hefur verið hlaðið inn", - "systray_site_loaded_message": "OnionShare-vefsvæði hefur verið hlaðið inn", - "systray_website_started_title": "Byrja að deila vefsvæðinu", - "systray_website_started_message": "Einhver er að skoða vefinn þinn", "gui_website_mode_no_files": "Ennþá hefur engu vefsvæði verið deilt", - "gui_visit_started": "Einhver hefur skoðað vefsvæðið {}", "incorrect_password": "Rangt lykilorð", "gui_settings_individual_downloads_label": "Taktu merkið úr til að leyfa niðurhal á stökum skrám", - "history_requests_tooltip": "{} vefbeiðnir", - "systray_individual_file_downloaded_title": "Stakri skrá hlaðið inn", - "systray_individual_file_downloaded_message": "Staka skráin {} skoðuð" + "history_requests_tooltip": "{} vefbeiðnir" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 0d947205..4ab5a64c 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -233,12 +233,7 @@ "seconds_first_letter": "s", "gui_website_url_description": "Hvem som helst med denne OnionShare-adressen kan besøke din nettside ved bruk av Tor-nettleseren: ", "gui_mode_website_button": "Publiser nettside", - "systray_site_loaded_title": "Side innlastet", - "systray_site_loaded_message": "OnionShare-side innlastet", - "systray_website_started_title": "Starter deling av nettside", - "systray_website_started_message": "Noen besøker din nettside", "gui_website_mode_no_files": "Ingen nettside delt enda", - "gui_visit_started": "Noen har besøkt din nettside {}", "invalid_password_guess": "Feil passord", "incorrect_password": "Feil passord" } diff --git a/share/locale/sv.json b/share/locale/sv.json index 4a205fc2..bdfdb31f 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -229,10 +229,5 @@ "invalid_password_guess": "Ogiltig lösenordsgissning", "gui_website_url_description": "Någon med denna OnionShare-adress kan besöka din webbplats med hjälp av Tor Browser: ", "gui_mode_website_button": "Publicera webbplats", - "systray_site_loaded_title": "Webbplats inläst", - "systray_site_loaded_message": "Onionshare-webbplats inläst", - "systray_website_started_title": "Börjar dela webbplats", - "systray_website_started_message": "Någon besöker din webbplats", - "gui_website_mode_no_files": "Ingen webbplats delad ännu", - "gui_visit_started": "Någon har besökt din webbplats {}" + "gui_website_mode_no_files": "Ingen webbplats delad ännu" } diff --git a/share/locale/tr.json b/share/locale/tr.json index 1e894e17..cbcb549f 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -200,10 +200,5 @@ "invalid_password_guess": "Geçersiz parola tahmini", "gui_website_url_description": "Bu OnionShare adresi olan herkes, Tor Browser'ı kullanarak web sitenizi ziyaret edebilir: ", "gui_mode_website_button": "Web Sitesini Yayınla", - "systray_site_loaded_title": "Site Yüklendi", - "systray_site_loaded_message": "OnionShare sitesi yüklü", - "systray_website_started_title": "Paylaşım web sitesi başlatılıyor", - "systray_website_started_message": "Birisi web sitenizi ziyaret ediyor", - "gui_website_mode_no_files": "Henüz Paylaşılan Web Sitesi Yok", - "gui_visit_started": "Birisi web sitenizi ziyaret etti {}" + "gui_website_mode_no_files": "Henüz Paylaşılan Web Sitesi Yok" } -- cgit v1.2.3-54-g00ecf From afa91c9dcd11d9acef547d74d4656f1a1d004fa5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Sep 2019 14:03:02 -0400 Subject: Add missing fedora dependency to BUILD.md --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 260a5d7e..67fc8691 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 python3-distutils +apt install -y python3-flask python3-flask-httpauth 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: -- cgit v1.2.3-54-g00ecf From 9bc645a1ee80c180540ccd4a6b89108367867a2d Mon Sep 17 00:00:00 2001 From: IS Date: Fri, 27 Sep 2019 21:37:28 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index af889aec..a0309c84 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -50,7 +50,7 @@ "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 Verteildienst zu starten", - "close_on_autostop_timer": "Angehalten da der Stoptimer abgelaufen ist", + "close_on_autostop_timer": "Angehalten, da der automatische Timer abgelaufen ist", "systray_upload_started_title": "OnionShare Upload wurde gestartet", "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", "help_autostop_timer": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", @@ -120,7 +120,7 @@ "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", - "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShare-Seite sagt, die aktuelle Version ist nicht wiederzuerkennen '{}'…", + "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShare-Seite sagt, die aktuelle Version ist die nicht erkennbare '{}'…", "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?", "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.", "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk in Ordnung zu bringen?", @@ -168,14 +168,14 @@ "gui_download_in_progress": "Download gestartet {}", "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", "gui_settings_language_label": "Bevorzugte Sprache", - "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", + "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die neue Sprache übernommen wird.", "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)", "timeout_upload_still_running": "Warte bis Upload vollständig ist", "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.", "gui_settings_connection_type_bundled_option": "Die integrierte Tor-Version von OnionShare nutzen", "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", "gui_server_started_after_autostop_timer": "Die Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte starte einen erneuten Teilvorgang.", - "gui_server_autostop_timer_expired": "Der Stoptimer ist bereits abgelaufen.\nBitte bearbeite diesen um das Teilen zu starten.", + "gui_server_autostop_timer_expired": "Der Stop-Timer ist bereits abgelaufen.\nBitte pass diesen an, um das Teilen zu starten.", "gui_status_indicator_share_stopped": "Bereit zum teilen", "history_in_progress_tooltip": "{} läuft", "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", @@ -219,8 +219,8 @@ "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": "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_server_autostart_timer_expired": "Die geplante Zeit ist bereits vergangen. Bitte passe diese an, 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 passe die Zeiten an, um das Teilen zu starten.", "gui_status_indicator_share_scheduled": "Geplant…", "gui_status_indicator_receive_scheduled": "Geplant…", "days_first_letter": "d", @@ -230,12 +230,14 @@ "invalid_password_guess": "Ungültige Passwortratschläge", "gui_website_url_description": "Jeder mit dieser OnionShare-Adresse kann deine Webseite mit dem Tor Browser ansehen: ", "gui_mode_website_button": "Webseite veröffentlichen", - "systray_site_loaded_title": "Seite geladen", + "systray_site_loaded_title": "Webseite geladen", "systray_site_loaded_message": "OnionShare Website geladen", "systray_website_started_title": "Freigabe der Webseite starten", "systray_website_started_message": "Jemand besucht deine Webseite", "gui_website_mode_no_files": "Noch keine Webseite freigegeben", "gui_visit_started": "Jemand hat deine Webseite besucht {}", "incorrect_password": "Falsches Passwort", - "systray_individual_file_downloaded_title": "Individuelle Datei geladen" + "systray_individual_file_downloaded_title": "Individuelle Datei geladen", + "gui_settings_individual_downloads_label": "Abwählen, um den Download einzelner Dateien zu erlauben", + "history_requests_tooltip": "Web-Anfragen" } -- cgit v1.2.3-54-g00ecf From a1cfe45ddda92acd3bd187d0e75766c1a97a19b5 Mon Sep 17 00:00:00 2001 From: kilobyte+weblate Date: Fri, 27 Sep 2019 21:51:17 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/de.json b/share/locale/de.json index a0309c84..e35a720a 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -175,7 +175,7 @@ "gui_settings_connection_type_bundled_option": "Die integrierte Tor-Version von OnionShare nutzen", "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", "gui_server_started_after_autostop_timer": "Die Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte starte einen erneuten Teilvorgang.", - "gui_server_autostop_timer_expired": "Der Stop-Timer ist bereits abgelaufen.\nBitte pass diesen an, um das Teilen zu starten.", + "gui_server_autostop_timer_expired": "Der Stop-Timer ist bereits abgelaufen. Bitte pass diesen an, um das Teilen zu starten.", "gui_status_indicator_share_stopped": "Bereit zum teilen", "history_in_progress_tooltip": "{} läuft", "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", -- cgit v1.2.3-54-g00ecf From 28ef327555e0ec25bc4f966ed53b05e2af7853ee Mon Sep 17 00:00:00 2001 From: Joan Montané Date: Wed, 25 Sep 2019 12:59:56 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 72d315bc..9180bfc1 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -122,18 +122,18 @@ "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", "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_check_error": "No s'ha pogut comprovar si hi ha una versió més nova. 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 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": "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_canceled": "No s'ha pogut establir la connexió amb la xarxa Tor.\n\nAssegureu-vos que teniu connexió a internet, torneu a obrir l'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.\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", + "share_via_onionshare": "Comparteix-ho amb l'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 baixar els vostres fitxers fent servir el Navegador Tor: ", @@ -181,7 +181,7 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "No s'ha pogut obrir la carpeta perquè el Nautilus no és disponible. El fitxer és a: {}", "gui_settings_language_label": "Llengua preferida", - "gui_settings_language_changed_notice": "Reobre l'OnionShare perquè el canvi de llengua tingui efecte.", + "gui_settings_language_changed_notice": "Torneu a obrir l'OnionShare perquè s'apliqui la llengua nova.", "timeout_upload_still_running": "S'està esperant que acabi la pujada", "gui_add_files": "Afegeix fitxers", "gui_add_folder": "Afegeix una carpeta", @@ -219,8 +219,8 @@ "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. Modifiqueu-ho per a començar la compartició.", + "gui_server_autostart_timer_expired": "L'hora programada ja ha passat. Ajusteu-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. Ajusteu-la per a començar la compartició.", "gui_status_indicator_share_scheduled": "Programat…", "gui_status_indicator_receive_scheduled": "Programat…", "days_first_letter": "d", @@ -230,10 +230,13 @@ "invalid_password_guess": "Intent de contrasenya incorrecte", "gui_website_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot visitar el vostre lloc web fent servir el Navegador Tor: ", "gui_mode_website_button": "Publica el lloc web", - "systray_site_loaded_title": "S'ha carregat el lloc", - "systray_site_loaded_message": "S'ha carregat el lloc OnionShare", + "systray_site_loaded_title": "S'ha carregat el lloc web", + "systray_site_loaded_message": "S'ha carregat el lloc web de l'OnionShare", "systray_website_started_title": "S'ha començat a compartir el lloc web", "systray_website_started_message": "Algú està visitant el vostre lloc web", "gui_website_mode_no_files": "Encara no s'han compartit llocs web", - "gui_visit_started": "Algú ha visitat el vostre lloc web {}" + "gui_visit_started": "Algú ha visitat el vostre lloc web {}", + "incorrect_password": "La contrasenya no és correcta", + "history_requests_tooltip": "{} peticions web", + "systray_individual_file_downloaded_title": "S'ha carregat el fitxer individual" } -- cgit v1.2.3-54-g00ecf From caacfc4044efe2d93d6f2d4ae44cd0333add7364 Mon Sep 17 00:00:00 2001 From: Ecron Date: Fri, 27 Sep 2019 11:02:44 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 9180bfc1..a414834c 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -78,7 +78,7 @@ "gui_settings_general_label": "Configuració general", "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_label": "Com hauria de connectar-se l'OnionShare al Tor?", "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare", "gui_settings_connection_type_automatic_option": "Intenta la configuració automàtica amb el Navegador Tor", "gui_settings_connection_type_control_port_option": "Connecta fent servir el port de control", @@ -132,7 +132,7 @@ "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 l'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.\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.", + "gui_server_autostop_timer_expired": "El temporitzador de finalització automàtica ja s'ha acabat. Ajusteu-lo per a poder compartir.", "share_via_onionshare": "Comparteix-ho amb l'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", @@ -238,5 +238,9 @@ "gui_visit_started": "Algú ha visitat el vostre lloc web {}", "incorrect_password": "La contrasenya no és correcta", "history_requests_tooltip": "{} peticions web", - "systray_individual_file_downloaded_title": "S'ha carregat el fitxer individual" + "systray_individual_file_downloaded_title": "S'ha carregat el fitxer individual", + "gui_settings_individual_downloads_label": "Desmarqueu per a permetre la baixada de fitxers individuals", + "systray_individual_file_downloaded_message": "S'ha visualitzat el fitxer individual {}", + "gui_settings_csp_header_disabled_option": "Desactiva la capçalera de la Política de Seguretat de Contingut", + "gui_settings_website_label": "Configuració del lloc web" } -- cgit v1.2.3-54-g00ecf From d14cdb621dc66787c8870b38c7b22fb3d6eb207d Mon Sep 17 00:00:00 2001 From: Jeroen Date: Fri, 27 Sep 2019 21:02:41 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 69 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 79e260f2..1815e0e7 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -44,7 +44,7 @@ "gui_share_quit_warning": "Je bent in het proces van bestanden versturen. Weet je zeker dat je OnionShare af wilt sluiten?", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Annuleren", - "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", + "error_rate_limit": "Iemand heeft teveel incorrecte pogingen gedaan om je wachwoord te raden. Daarom heeft OnionShare de server gestopt. Herstart het delen en stuur de ontvanger een nieuw adres.", "zip_progress_bar_format": "Comprimeren: %p%", "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.", @@ -56,7 +56,7 @@ "gui_settings_autoupdate_timestamp_never": "Nooit", "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie", "gui_settings_sharing_label": "Instelling voor delen", - "gui_settings_close_after_first_download_option": "Stop met delen na eerste download", + "gui_settings_close_after_first_download_option": "Stop met delen, nadat de bestanden verstuurd zijn", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare", "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser", @@ -97,8 +97,8 @@ "gui_tor_connection_ask_quit": "Afsluiten", "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.", "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.", - "gui_server_started_after_autostop_timer": "De auto-stop timer liep af voordat de server startte.\nMaak een nieuwe share aan.", - "gui_server_autostop_timer_expired": "De auto-stop timer is al verlopen.\nStel een nieuwe tijd in om te beginnen met delen.", + "gui_server_started_after_autostop_timer": "De auto-stop timer verliep, voordat de server startte. Maak een nieuwe share aan.", + "gui_server_autostop_timer_expired": "De auto-stop timer is al verlopen. Stel een nieuwe tijd in om te beginnen met delen.", "share_via_onionshare": "Deel via OnionShare", "give_this_url_receive": "Geef dit adres aan de afzender:", "give_this_url_receive_stealth": "Geef dit adres en de HidServAuth-regel aan de afzender:", @@ -108,7 +108,7 @@ "timeout_upload_still_running": "Wachten op voltooiing van de upload", "gui_share_start_server": "Start met delen", "gui_share_stop_server": "Stop met delen", - "gui_share_stop_server_autostop_timer": "Stop met Delen ({}s resterend)", + "gui_share_stop_server_autostop_timer": "Stop met Delen ({})", "gui_share_stop_server_autostop_timer_tooltip": "Auto-stop timer eindigt bij {}", "gui_receive_start_server": "Start Ontvangstmodus", "gui_receive_stop_server": "Stop Ontvangstmodus", @@ -142,8 +142,8 @@ "gui_receive_url_description": "1Iedereen2 met dit OnionShare adres kan bestanden op je computer 3plaatsen4 met de 5Tor Browser6: 7", "gui_url_label_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", "gui_url_label_stay_open": "Deze share stopt niet automatisch.", - "gui_url_label_onetime": "Deze share stopt na de eerste voltooiing.", - "gui_url_label_onetime_and_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", + "gui_url_label_onetime": "Deze share stopt na de eerste voltooiïng.", + "gui_url_label_onetime_and_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share zal het adres hergebruiken. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", "gui_status_indicator_share_stopped": "Klaar om te delen", "gui_status_indicator_share_working": "Starten…", "gui_status_indicator_share_started": "Aan het delen", @@ -159,7 +159,7 @@ "error_cannot_create_downloads_dir": "Kon de ontvangst modus map niet aanmaken: {}", "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}", "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", - "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten.

    Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", + "gui_receive_mode_warning": "Ontvangstmodus laat anderen bestanden op je computer zetten.

    Sommige van die bestanden kunnen mogelijk je computer overnemen, als je ze opent. Open alleen dingen van mensen die je vertrouwt, of als je heel zeker weet wat je doet.", "receive_mode_upload_starting": "Upload met totale grootte {} is aan het starten", "receive_mode_received_file": "Ontvangen: {}", "gui_mode_share_button": "Deel Bestanden", @@ -170,7 +170,7 @@ "gui_settings_public_mode_checkbox": "Openbaarmodus", "systray_close_server_title": "OnionShare Server Afgesloten", "systray_close_server_message": "Een gebruiker heeft de server gestopt", - "systray_page_loaded_title": "OnionShare Pagina Geladen", + "systray_page_loaded_title": "Pagina Geladen", "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen", "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen", "gui_uploads": "Upload Geschiedenis", @@ -182,8 +182,55 @@ "gui_download_in_progress": "Downloaden Gestart {}", "gui_open_folder_error_nautilus": "Kan de map niet openen omdat nautilus niet beschikbaar is. Het bestand staat hier: {}", "gui_settings_language_label": "Voorkeurstaal", - "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.", + "gui_settings_language_changed_notice": "Herstart OnionShare om de nieuwe taal te gebruiken.", "gui_add_files": "Voeg bestanden toe", "gui_add_folder": "Voeg map toe", - "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien" + "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien", + "gui_settings_data_dir_label": "Bewaar bestanden naar", + "gui_settings_data_dir_browse_button": "Browse", + "systray_page_loaded_message": "OnionShare adres geladen", + "systray_share_started_title": "Delen Begonnen", + "systray_share_started_message": "Begint bestanden aan iemand te sturen", + "systray_share_completed_title": "Delen Afgerond", + "systray_share_completed_message": "Klaar met versturen van bestanden", + "systray_share_canceled_title": "Delen geannulleerd", + "systray_share_canceled_message": "Iemand heeft het ontvangen van je bestanden geannulleerd", + "systray_receive_started_title": "Ontvangen Begonnen", + "systray_receive_started_message": "Iemand stuurt bestanden naar je", + "gui_all_modes_history": "Geschiedenis", + "gui_all_modes_clear_history": "Wis Alles", + "gui_all_modes_transfer_started": "Begonnen {}", + "gui_all_modes_transfer_finished_range": "Overgezet {} - {}", + "gui_all_modes_transfer_finished": "Overgezet {}", + "gui_all_modes_progress_complete": "%p%, {0:s} verlopen.", + "gui_all_modes_progress_starting": "{0:s}, %p% (aan het berekenen)", + "gui_share_mode_no_files": "Nog Geen Bestanden Verzonden", + "gui_receive_mode_no_files": "Nog Geen Bestanden Ontvangen", + "gui_all_modes_transfer_canceled_range": "Geannuleerd {} - {}", + "gui_all_modes_transfer_canceled": "Geannuleerd {}", + "gui_settings_onion_label": "Onion instellingen", + "gui_stop_server_autostop_timer_tooltip": "Auto-stop timer stopt om {}", + "gui_start_server_autostart_timer_tooltip": "Auto-start timer stopt om {}", + "gui_waiting_to_start": "Gepland te beginnen in {}. Klik om af te breken.", + "gui_settings_autostart_timer_checkbox": "Gebruik auto-start timer", + "gui_settings_autostart_timer": "Begin het delen om:", + "gui_server_autostart_timer_expired": "De geplande timer is al verlopen. Stel een nieuwe in om te beginnen met delen.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "De auto-stop tijd mag niet identiek of vroeger zijn dan de auto-start tijd. Stel hem opnieuw in om te beginnen met delen.", + "gui_status_indicator_share_scheduled": "Gepland…", + "gui_status_indicator_receive_scheduled": "Gepland…", + "gui_share_mode_autostop_timer_waiting": "Wachten tot verzenden klaar is", + "gui_receive_mode_autostop_timer_waiting": "Wachten tot ontvangen klaar is", + "gui_website_url_description": "1Iedereen2 met dit OnionShare-adres kan je bestanden 3bezoeken4 met de 5Tor Browser6: ", + "gui_mode_website_button": "Zet website online", + "systray_site_loaded_title": "Website geladen", + "systray_site_loaded_message": "OnionShare website geladen", + "systray_website_started_title": "Delen van website begint", + "systray_website_started_message": "Iemand bezoekt je website nu", + "gui_website_mode_no_files": "Nog Geen Website Gedeeld", + "gui_visit_started": "Iemand heeft je website bezocht {}", + "incorrect_password": "Foutief wachtwoord", + "gui_settings_individual_downloads_label": "Klik uit om het downloaden van individuele bestanden toe te staan", + "systray_individual_file_downloaded_title": "Individueel bestand geladen", + "systray_individual_file_downloaded_message": "Individueel bestand {} bekeken", + "gui_settings_website_label": "Instellingen voor website" } -- cgit v1.2.3-54-g00ecf From 32b0d29621bec935df1ff5882e512d55e2cc1028 Mon Sep 17 00:00:00 2001 From: Adolfo Jayme Barrientos Date: Fri, 27 Sep 2019 15:15:41 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 06a5918c..3c5c07f1 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -86,7 +86,7 @@ "error_invalid_private_key": "Este tipo de clave privada no está soportado", "connecting_to_tor": "Conectando con la red de Tor", "update_available": "Hay una versión nueva de OnionShare. Pulse aquí para descargarla.

    Utiliza la versión {}; la más reciente es la {}.", - "update_error_check_error": "No se pudo buscar actualizaciones: el sitio web de OnionShare comunica que la versión más reciente es «{}», pero eso es irreconocible.", + "update_error_check_error": "No se pudo buscar si hay actualizaciones: el sitio web de OnionShare comunica que la versión más reciente es «{}», pero eso es irreconocible.", "update_error_invalid_latest_version": "No se pudo buscar actualizaciones: ¿quizás no se ha conectado con Tor o el sitio web de OnionShare está caído?", "update_not_available": "Está ejecutando la versión más reciente de OnionShare.", "gui_tor_connection_ask": "¿Quiere abrir la configuración para arreglar la conexión con Tor?", @@ -96,7 +96,7 @@ "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de haberse conectado a Internet, vuelva a abrir OnionShare y configure su conexión con Tor.", "gui_tor_connection_lost": "Se desconectó de Tor.", "gui_server_started_after_autostop_timer": "El temporizador de parada automática expiró antes de que se iniciara el servidor.\nCree un recurso compartido nuevo.", - "gui_server_autostop_timer_expired": "El temporizador de parada automática ya expiró.\nReinícielo para comenzar a compartir.", + "gui_server_autostop_timer_expired": "El temporizador de parada automática ya expiró.\nAjústelo para comenzar a compartir.", "share_via_onionshare": "Compartir con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", "gui_save_private_key_checkbox": "Usar una dirección persistente", @@ -184,7 +184,7 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que el idioma nuevo se aplique.", "gui_upload_finished_range": "Cargado {} a {}", "timeout_upload_still_running": "Esperando a que se complete la subida", "gui_add_files": "Añadir archivos", @@ -223,8 +223,8 @@ "gui_waiting_to_start": "Se programó el inicio en {}. Pulse para cancelar.", "gui_settings_autostart_timer_checkbox": "Utilizar temporizador de inicio automático", "gui_settings_autostart_timer": "Iniciar compartición en:", - "gui_server_autostart_timer_expired": "La hora agendada ya ha pasado. Actualícela para comenzar a compartir.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "La hora de inicio automático no puede ser igual ni anterior a la de parada automática. Actualícela para comenzar a compartir.", + "gui_server_autostart_timer_expired": "La hora agendada ya ha pasado. Ajústela para comenzar a compartir.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "La hora de inicio automático no puede ser igual ni anterior a la de parada automática. Ajústela para comenzar a compartir.", "gui_status_indicator_share_scheduled": "Agendado…", "gui_status_indicator_receive_scheduled": "Agendado…", "days_first_letter": "d", @@ -234,10 +234,17 @@ "invalid_password_guess": "Intento de contraseña incorrecto", "gui_website_url_description": "Cualquiera que tenga esta dirección de OnionShare puede visitar su sitio web mediante el navegador Tor: ", "gui_mode_website_button": "Publicar sitio web", - "systray_site_loaded_title": "Sitio cargado", - "systray_site_loaded_message": "Sitio de OnionShare cargado", + "systray_site_loaded_title": "Sitio web cargado", + "systray_site_loaded_message": "Sitio web de OnionShare cargado", "systray_website_started_title": "Se comenzó a compartir el sitio web", "systray_website_started_message": "Alguien está visitando su sitio web", "gui_website_mode_no_files": "Aún no se han compartido sitios web", - "gui_visit_started": "Alguien ha visitado su sitio web {}" + "gui_visit_started": "Alguien ha visitado su sitio web {}", + "incorrect_password": "Contraseña incorrecta", + "gui_settings_individual_downloads_label": "Desmarque esta opción para permitir descargar archivos individuales", + "history_requests_tooltip": "{} solicitudes web", + "systray_individual_file_downloaded_title": "Archivo individual cargado", + "systray_individual_file_downloaded_message": "Archivo individual {} visto", + "gui_settings_csp_header_disabled_option": "Desactivar cabecera de la Normativa de seguridad de contenido", + "gui_settings_website_label": "Configuración de sitio web" } -- cgit v1.2.3-54-g00ecf From 96653d5647d3b2d26f1c7af85596c749e77a4c60 Mon Sep 17 00:00:00 2001 From: LocLab fr Date: Mon, 23 Sep 2019 16:59:15 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 1279261d..12467c07 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -30,7 +30,7 @@ "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", - "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que la nouvelle langue soit appliquée.", + "gui_settings_language_changed_notice": "Redémarrez OnionShare afin que la nouvelle langue soit appliquée.", "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", "give_this_url_receive": "Donnez cette adresse à l’expéditeur :", @@ -181,7 +181,7 @@ "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.", + "gui_server_autostop_timer_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai. Veuillez la modifier pour commencer le partage.", "close_on_autostop_timer": "Arrêté, car la minuterie d’arrêt automatique est arrivée au bout de son délai", "gui_add_files": "Ajouter des fichiers", "gui_add_folder": "Ajouter un dossier", @@ -228,12 +228,19 @@ "minutes_first_letter": "min", "seconds_first_letter": "s", "gui_website_url_description": "Quiconque aura cette adresse OnionShare pourra visiter votre site Web en utilisant le Navigateur Tor : ", - "systray_site_loaded_title": "Le site a été chargé", - "systray_site_loaded_message": "Le site OnionShare a été chargé", + "systray_site_loaded_title": "Le site Web a été chargé", + "systray_site_loaded_message": "Le site Web OnionShare a été chargé", "systray_website_started_title": "Début du partage du site Web", "systray_website_started_message": "Quelqu’un visite votre site Web", "gui_website_mode_no_files": "Aucun site Web n’a encore été partagé", "gui_visit_started": "Quelqu’un a visité votre site Web {}", "invalid_password_guess": "La tentative de mot de passe est invalide", - "gui_mode_website_button": "Publier un site Web" + "gui_mode_website_button": "Publier un site Web", + "incorrect_password": "Le mot de passe est erroné", + "gui_settings_individual_downloads_label": "Décocher pour permettre le téléchargement de fichiers individuels", + "history_requests_tooltip": "{} Requêtes Web", + "systray_individual_file_downloaded_title": "Le fichier individuel a été chargé", + "systray_individual_file_downloaded_message": "Le fichier individuel {} a été visualisé", + "gui_settings_csp_header_disabled_option": "Désactiver l’en-tête Stratégie de sécurité du contenu", + "gui_settings_website_label": "Paramètres des sites Web" } -- cgit v1.2.3-54-g00ecf From aa4beb1e631fb298e8ed90723b3fd0ccd8285837 Mon Sep 17 00:00:00 2001 From: Jonatan Nyberg Date: Thu, 26 Sep 2019 15:35:14 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index 4a205fc2..03edfeb9 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -8,7 +8,7 @@ "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern", "not_a_file": "{0:s} är inte en giltig fil.", "not_a_readable_file": "{0:s} är inte en läsbar fil.", - "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", + "no_available_port": "Kunde inte hitta en ledig port för att starta onion-tjänsten", "other_page_loaded": "Adress laddad", "close_on_autostop_timer": "Stoppad för att tiden för den automatiska stopp-tidtagaren löpte ut", "closing_automatically": "Stoppad för att hämtningen är klar", @@ -41,8 +41,8 @@ "gui_share_stop_server_autostop_timer": "Sluta dela ({})", "gui_share_stop_server_autostop_timer_tooltip": "Automatiska stopp-tidtagaren avslutar vid {}", "gui_receive_start_server": "Starta mottagarläge", - "gui_receive_stop_server": "Avsluta Mottagarläge", - "gui_receive_stop_server_autostop_timer": "Avsluta Mottagarläge ({} kvarstår)", + "gui_receive_stop_server": "Avsluta mottagarläge", + "gui_receive_stop_server_autostop_timer": "Avsluta mottagarläge ({} kvarstår)", "gui_receive_stop_server_autostop_timer_tooltip": "Automatiska stopp-tidtagaren avslutar vid {}", "gui_copy_url": "Kopiera Adress", "gui_copy_hidservauth": "Kopiera HidServAuth", @@ -78,7 +78,7 @@ "gui_settings_autoupdate_check_button": "Sök efter ny version", "gui_settings_general_label": "Allmänna inställningar", "gui_settings_sharing_label": "Delningsinställningar", - "gui_settings_close_after_first_download_option": "Fortsätt dela efter att filer har skickats", + "gui_settings_close_after_first_download_option": "Avbryt delning efter att filer har skickats", "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", "gui_settings_connection_type_bundled_option": "Använd Tor-versionen som är inbyggd i OnionShare", "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", @@ -123,18 +123,18 @@ "error_invalid_private_key": "Denna privata nyckeltyp stöds inte", "connecting_to_tor": "Ansluter till Tor-nätverket", "update_available": "Ny OnionShare utgiven. Klicka här för att få den.

    Du använder {} och den senaste är {}.", - "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", + "update_error_check_error": "Det gick inte att söka efter ny version: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: Kanske är du inte ansluten till Tor, eller är OnionShare-webbplatsen nere?", "update_not_available": "Du kör den senaste OnionShare.", "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Avsluta", "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_canceled": "Kunde inte ansluta till Tor.\n\nSe till att du är ansluten till Internet, öppna sedan OnionShare igen 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-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_server_autostop_timer_expired": "Tiden för den automatiska stopp-tidtagaren löpte redan ut. Vänligen justera den för att börja dela.", + "share_via_onionshare": "Dela med OnionShare", "gui_use_legacy_v2_onions_checkbox": "Använd äldre adresser", "gui_save_private_key_checkbox": "Använd en beständig adress", "gui_share_url_description": "Alla med denna OnionShare-adress kan hämta dina filer med hjälp av Tor Browser: ", @@ -158,7 +158,7 @@ "error_cannot_create_downloads_dir": "Det gick inte att skapa mappen mottagningsläge: {}", "receive_mode_downloads_dir": "Filer som skickas till dig visas i den här mappen: {}", "receive_mode_warning": "Varning: Mottagningsläge låter personer skicka filer till din dator. Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", - "gui_receive_mode_warning": "Mottagningsläge låter personer skicka filer till din dator.

    Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", + "gui_receive_mode_warning": "Mottagarläge låter personer skicka filer till din dator.

    Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", "receive_mode_upload_starting": "Sändning av total storlek {} börjar", "receive_mode_received_file": "Mottaget: {}", "gui_mode_share_button": "Dela filer", @@ -218,8 +218,8 @@ "gui_waiting_to_start": "Planerad för att starta i {}. Klicka för att avbryta.", "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.", + "gui_server_autostart_timer_expired": "Den schemalagda tiden har redan passerat. Vänligen justera den för att börja dela.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Den automatiska stopp-tiden kan inte vara samma eller tidigare än automatiska starttiden. Vänligen justera den för att börja dela.", "gui_status_indicator_share_scheduled": "Planerad…", "gui_status_indicator_receive_scheduled": "Planerad…", "days_first_letter": "d", @@ -230,9 +230,15 @@ "gui_website_url_description": "Någon med denna OnionShare-adress kan besöka din webbplats med hjälp av Tor Browser: ", "gui_mode_website_button": "Publicera webbplats", "systray_site_loaded_title": "Webbplats inläst", - "systray_site_loaded_message": "Onionshare-webbplats inläst", + "systray_site_loaded_message": "OnionShare-webbplats inläst", "systray_website_started_title": "Börjar dela webbplats", "systray_website_started_message": "Någon besöker din webbplats", "gui_website_mode_no_files": "Ingen webbplats delad ännu", - "gui_visit_started": "Någon har besökt din webbplats {}" + "gui_visit_started": "Någon har besökt din webbplats {}", + "incorrect_password": "Felaktigt lösenord", + "gui_settings_individual_downloads_label": "Avmarkera för att tillåta hämtning av enskilda filer", + "history_requests_tooltip": "{} webbförfrågningar", + "systray_individual_file_downloaded_title": "Enskild fil inläst", + "systray_individual_file_downloaded_message": "Individuell fil {} visad", + "gui_settings_website_label": "Webbplatsinställningar" } -- cgit v1.2.3-54-g00ecf From 9bceec2365dd67c2a48bd6fa3a63134628534f1f Mon Sep 17 00:00:00 2001 From: Vid Košenina Date: Fri, 27 Sep 2019 21:29:23 +0000 Subject: Translated using Weblate (Slovenian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sl/ --- share/locale/sl.json | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/share/locale/sl.json b/share/locale/sl.json index 29680bbb..70e04baa 100644 --- a/share/locale/sl.json +++ b/share/locale/sl.json @@ -1,19 +1,19 @@ { "config_onion_service": "", - "preparing_files": "", + "preparing_files": "Stiskanje datotek.", "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": "", - "close_on_autostop_timer": "", - "closing_automatically": "", + "not_a_readable_file": "{0:s} ni mogoče prebrati.", + "no_available_port": "Ni mogoče najti prostega vhoda, da bi lahko zagnali onion service", + "other_page_loaded": "Naslov naložen", + "close_on_autostop_timer": "Prenehal, ker je auto-stop timer pretekel", + "closing_automatically": "Prenehal, ker se je prenos končal", "timeout_download_still_running": "", - "large_filesize": "", + "large_filesize": "Opozorilo: Pošiljanje prevelikih deležel lahko traja ure", "systray_menu_exit": "Izhod", "systray_download_started_title": "", "systray_download_started_message": "", @@ -31,13 +31,13 @@ "help_verbose": "", "help_filename": "", "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", + "gui_drag_and_drop": "Povleci in spusti datoteke in mape\nza začetek skupne rabe", + "gui_add": "Dodaj", + "gui_delete": "Zbriši", "gui_choose_items": "Izberi", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_autostop_timer": "", + "gui_share_start_server": "Začni deliti", + "gui_share_stop_server": "Zaustavi deljenje", + "gui_share_stop_server_autostop_timer": "Zaustavi deljenje ({})", "gui_share_stop_server_autostop_timer_tooltip": "", "gui_receive_start_server": "", "gui_receive_stop_server": "", @@ -181,5 +181,13 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "gui_add_files": "Dodaj Datoteke", + "gui_add_folder": "Dodaj Mapo", + "gui_stop_server_autostop_timer_tooltip": "Samodejno zaustavi timer, ki se konča ob{}", + "days_first_letter": "d", + "hours_first_letter": "h", + "minutes_first_letter": "m", + "seconds_first_letter": "s", + "incorrect_password": "Napačno geslo" } -- cgit v1.2.3-54-g00ecf From 45311689e83496478f99aac96e31f2a0cfe4a88d Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Tue, 24 Sep 2019 20:42:57 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/nb.json | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/share/locale/nb.json b/share/locale/nb.json index 0d947205..2450c197 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "Du har ikke fått alle filene enda. Er du sikker på at du ønsker å avslutte OnionShare?", "gui_quit_warning_quit": "Avslutt", "gui_quit_warning_dont_quit": "Avbryt", - "error_rate_limit": "Noen har prøvd adressen din for mange ganger, noe som kan betyr at de prøver å gjette seg fram til den, OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.", + "error_rate_limit": "Noen har prøvd å gjette passordet ditt for mange ganger, så OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.", "zip_progress_bar_format": "Pakker sammen: %p%", "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-Browser 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", @@ -133,7 +133,7 @@ "gui_tor_connection_canceled": "Kunne ikke koble til Tor.\n\nForsikre deg om at du er koblet til Internett, åpne så OnionShare igjen, og sett opp dets tilkobling til Tor.", "gui_tor_connection_lost": "Frakoblet fra Tor.", "gui_server_started_after_autostop_timer": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", - "gui_server_autostop_timer_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", + "gui_server_autostop_timer_expired": "Tidsavbruddsuret har gått ut allerede. Juster det for å starte deling.", "share_via_onionshare": "OnionShare det", "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", "gui_save_private_key_checkbox": "Bruk en vedvarende adresse", @@ -223,8 +223,8 @@ "gui_waiting_to_start": "Planlagt start om {}. Klikk for å avbryte.", "gui_settings_autostart_timer_checkbox": "Bruk tidur for automatisk start", "gui_settings_autostart_timer": "Start delingen:", - "gui_server_autostart_timer_expired": "Planlagt tid allerede tilbakelagt. Oppdater den for å begynne deling.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Automatisk tidsavbruddsur kan ikke være likt eller predatere auomatisk starttid. Oppdater det for å starte deling.", + "gui_server_autostart_timer_expired": "Planlagt tid allerede tilbakelagt. Juster det for å starte deling.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Automatisk tidsavbruddsur kan ikke være likt eller predatere auomatisk starttid. Juster det for å starte deling.", "gui_status_indicator_share_scheduled": "Planlagt…", "gui_status_indicator_receive_scheduled": "Planlagt…", "days_first_letter": "d", @@ -233,12 +233,16 @@ "seconds_first_letter": "s", "gui_website_url_description": "Hvem som helst med denne OnionShare-adressen kan besøke din nettside ved bruk av Tor-nettleseren: ", "gui_mode_website_button": "Publiser nettside", - "systray_site_loaded_title": "Side innlastet", - "systray_site_loaded_message": "OnionShare-side innlastet", + "systray_site_loaded_title": "Nettside innlastet", + "systray_site_loaded_message": "OnionShare-nettside innlastet", "systray_website_started_title": "Starter deling av nettside", "systray_website_started_message": "Noen besøker din nettside", "gui_website_mode_no_files": "Ingen nettside delt enda", "gui_visit_started": "Noen har besøkt din nettside {}", "invalid_password_guess": "Feil passord", - "incorrect_password": "Feil passord" + "incorrect_password": "Feil passord", + "gui_settings_individual_downloads_label": "Forby nedlasting av enkeltfiler", + "history_requests_tooltip": "{} vevforespørsler", + "systray_individual_file_downloaded_title": "Enkeltfil innlastet", + "systray_individual_file_downloaded_message": "Enkeltfil {} sett" } -- cgit v1.2.3-54-g00ecf From 53eb6e2e3bd75ce6cff8753a99c04da0f800c354 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 30 Sep 2019 08:12:41 +1000 Subject: Move duplicate python3-flask-httpauth to the Fedora dependency list in BUILD.md --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 67fc8691..9456e617 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,13 +14,13 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-flask-httpauth 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 +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: ``` -dnf install -y python3-flask python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build +dnf install -y python3-flask python3-flask-httpauth python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build ``` After that you can try both the CLI and the GUI version of OnionShare: -- cgit v1.2.3-54-g00ecf From 0f257a3603d60b33eafd8cb2482621ae88c66453 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 30 Sep 2019 00:45:27 +0200 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/cs.json | 46 ++++++++++++++++++++++++++++++++++++++-------- share/locale/de.json | 3 ++- share/locale/id.json | 3 ++- share/locale/nb.json | 6 +++--- share/locale/nl.json | 18 +++++++++++++----- share/locale/pt_BR.json | 13 ++++++++----- share/locale/zh_Hant.json | 8 +++++--- 7 files changed, 71 insertions(+), 26 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 0bd1fdb0..7a5a4749 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -24,27 +24,27 @@ "gui_downloads": "Historie stahování", "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", - "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_please_wait": "Prosím čekejte...", + "gui_copied_hidservauth": "HidServAuth zkopírováno do schránky", + "gui_please_wait": "Spouštění... Klikněte pro zrušení.", "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", + "gui_share_quit_warning": "Jste si jistí, že chcete odejít? URL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", "gui_quit_warning_dont_quit": "Zůstat", "error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.", "zip_progress_bar_format": "Zpracovávám soubory: %p%", - "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", + "error_stealth_not_supported": "K autorizaci klienta potřebujete alespoň Tor 0.2.9.1-alpha (or Tor Browser 6.5) a python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.", "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_control_port_option": "Připojit použitím control port", + "gui_settings_connection_type_socket_file_option": "Připojit použitím socket file", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", - "gui_settings_authenticate_label": "Autentizační možnosti Toru", + "gui_settings_authenticate_label": "Autentizační nastavení Toru", "gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace", "gui_settings_authenticate_password_option": "Heslo", "gui_settings_password_label": "Heslo", @@ -78,5 +78,35 @@ "gui_receive_start_server": "Spustit mód přijímání", "gui_receive_stop_server": "Zastavit přijímání", "gui_receive_stop_server_autostop_timer": "Zastavit mód přijímání ({} zbývá)", - "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token" + "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token", + "gui_copied_url_title": "OnionShare Address zkopírována", + "gui_quit_title": "Ne tak rychle", + "gui_settings_stealth_option": "Autorizace klienta", + "gui_settings_stealth_hidservauth_string": "Uložení priváního klíče pro znovu použití znamená, že teď můžete zkopírovat Váš HidServAuth.", + "gui_settings_autoupdate_label": "Kontrola nové verze", + "gui_settings_autoupdate_option": "Upozornit na dostupnost nové verze", + "gui_settings_autoupdate_timestamp": "Poslední kontrola {}", + "gui_settings_autoupdate_timestamp_never": "Nikdy", + "gui_settings_autoupdate_check_button": "Kontrola nové verze", + "gui_settings_sharing_label": "Nastavení sdílení", + "gui_settings_close_after_first_download_option": "Zastavit sdílení po odeslání dat", + "gui_settings_connection_type_bundled_option": "Použít Tor verzi vestavěnou v OnionShare", + "gui_settings_connection_type_test_button": "Test připojení do Tor sítě", + "gui_settings_socks_label": "SOCKS port", + "gui_settings_tor_bridges": "Tor bridge podpora", + "gui_settings_tor_bridges_no_bridges_radio_option": "Nepoužívat Tor bridge", + "gui_settings_tor_bridges_obfs4_radio_option": "Použít vestavěný obfs4 přenos", + "gui_receive_quit_warning": "Jste v procesu příjímání dat. Jste si opravdu jistí, že chcete OnionShare ukončit?", + "gui_settings_whats_this": "Co je to?", + "gui_settings_general_label": "Obecná nastavení", + "gui_add_files": "Přidat soubory", + "gui_add_folder": "Přidat adresář", + "gui_settings_onion_label": "Onion nastavení", + "close_on_autostop_timer": "Zastaveno protože vypršel čas auto-stop časovače", + "gui_stop_server_autostop_timer_tooltip": "Auto-stop skončí v {}", + "gui_start_server_autostart_timer_tooltip": "Auto-start skončí v {}", + "gui_waiting_to_start": "Naplánovaný start v {}. Klikněte pro zrušení.", + "incorrect_password": "Nesprávné heslo", + "gui_settings_individual_downloads_label": "Odškrtnout k povolení stahování libovolných souborů", + "gui_settings_csp_header_disabled_option": "Zakázat Conent Security Policy hlavičku" } diff --git a/share/locale/de.json b/share/locale/de.json index e35a720a..c50e3bfc 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -239,5 +239,6 @@ "incorrect_password": "Falsches Passwort", "systray_individual_file_downloaded_title": "Individuelle Datei geladen", "gui_settings_individual_downloads_label": "Abwählen, um den Download einzelner Dateien zu erlauben", - "history_requests_tooltip": "Web-Anfragen" + "history_requests_tooltip": "{} Web-Anfragen", + "systray_individual_file_downloaded_message": "Individuelle Datei {} betrachtet" } diff --git a/share/locale/id.json b/share/locale/id.json index c40ada6b..8ead263f 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -184,5 +184,6 @@ "gui_settings_language_changed_notice": "", "gui_add_files": "Tambahkan berkas", "gui_add_folder": "Tambahkan Folder", - "gui_settings_onion_label": "Pengaturan Onion" + "gui_settings_onion_label": "Pengaturan Onion", + "incorrect_password": "Password salah" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 1bbb01ce..ea0f3319 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -5,7 +5,7 @@ "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.", - "preparing_files": "Pakker sammen filer.", + "preparing_files": "Pakker filer.", "give_this_url_stealth": "Gi denne adressen og HidServAuth-linjen til mottakeren:", "give_this_url_receive": "Gi denne adressen til avsenderen:", "give_this_url_receive_stealth": "Gi denne adressen og HidServAuth-linjen til avsenderen:", @@ -123,7 +123,7 @@ "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet", "connecting_to_tor": "Kobler til Tor-nettverket", "update_available": "Ny OnionShare-versjon tilgjenglig. Klikk her for å laste den ned.

    Du bruker {} og nyeste versjon er {}.", - "update_error_check_error": "Kunne ikke sjekke etter nye versjoner: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", + "update_error_check_error": "Kunne ikke sjekke etter ny versjon: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", "update_error_invalid_latest_version": "Kunne ikke sjekke etter ny versjon: Kanskje du ikke er koblet til Tor, eller kanskje OnionShare-nettsiden er nede?", "update_not_available": "Du kjører siste versjon av OnionShare.", "gui_tor_connection_ask": "Åpne innstillingene for å ordne opp i tilkoblingen til Tor?", @@ -134,7 +134,7 @@ "gui_tor_connection_lost": "Frakoblet fra Tor.", "gui_server_started_after_autostop_timer": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", "gui_server_autostop_timer_expired": "Tidsavbruddsuret har gått ut allerede. Juster det for å starte deling.", - "share_via_onionshare": "OnionShare det", + "share_via_onionshare": "Del via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", "gui_save_private_key_checkbox": "Bruk en vedvarende adresse", "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-Browser: ", diff --git a/share/locale/nl.json b/share/locale/nl.json index 1815e0e7..84492104 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -85,18 +85,18 @@ "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de ontwikkelaarsmodus op Windows of macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", - "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", + "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteunt ephemeral onion services: {}.\nOndersteunt client authentication: {}.\nOndersteunt next-gen .onion addresses: {}.", "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", "update_available": "Nieuwe OnionShare is uitgekomen. Klik hier om hem te krijgen.

    Jij gebruikt {} and de laatste is {}.", - "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", + "update_error_check_error": "Kon niet controleren op een nieuwe versie: de OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of de OnionShare website is niet beschikbaar?", "update_not_available": "Je draait de laatst beschikbare OnionShare.", "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afsluiten", "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.", - "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.", + "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nZorg dat je met het internet verbonden bent, herstart OnionShare en configureer de verbinding met Tor.", "gui_server_started_after_autostop_timer": "De auto-stop timer verliep, voordat de server startte. Maak een nieuwe share aan.", "gui_server_autostop_timer_expired": "De auto-stop timer is al verlopen. Stel een nieuwe tijd in om te beginnen met delen.", "share_via_onionshare": "Deel via OnionShare", @@ -120,7 +120,7 @@ "gui_quit_title": "Niet zo snel", "gui_receive_quit_warning": "Je bent in het proces van bestanden ontvangen. Weet je zeker dat je OnionShare af wilt sluiten?", "gui_settings_whats_this": "1Wat is dit?2", - "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je \nklikken om je HidServAuth te kopiëren.", + "gui_settings_stealth_hidservauth_string": "Je privésleutel is voor hergebruik opgeslagen. Je kunt nu klikken om je HidServAuth te kopiëren.", "gui_settings_general_label": "Algemene instellingen", "gui_settings_tor_bridges": "Tor bridge ondersteuning", "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", @@ -232,5 +232,13 @@ "gui_settings_individual_downloads_label": "Klik uit om het downloaden van individuele bestanden toe te staan", "systray_individual_file_downloaded_title": "Individueel bestand geladen", "systray_individual_file_downloaded_message": "Individueel bestand {} bekeken", - "gui_settings_website_label": "Instellingen voor website" + "gui_settings_website_label": "Instellingen voor website", + "error_cannot_create_data_dir": "Kon geen OnionShare datamap aanmaken: {}", + "gui_all_modes_progress_eta": "{0:s}, Verwachte Aankomsttijd: {1:s}, %p%", + "days_first_letter": "d", + "hours_first_letter": "h", + "minutes_first_letter": "m", + "seconds_first_letter": "s", + "history_requests_tooltip": "{} webverzoeken", + "gui_settings_csp_header_disabled_option": "Schakel Content Security Policy header uit" } diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index c28f9f29..3a01475e 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", "gui_quit_warning_quit": "Sair", "gui_quit_warning_dont_quit": "Cancelar", - "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço para, talvez, adivinhar sua senha. Por isso, OnionShare interrompeu o servidor. Comece novamente o compartilhamento e envie um novo endereço ao seu destinatário para continuar a compartilhar.", "zip_progress_bar_format": "Comprimindo: %p%", "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", @@ -132,8 +132,8 @@ "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", "gui_tor_connection_lost": "Desconectado do Tor.", "gui_server_started_after_autostop_timer": "O tempo esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", - "gui_server_autostop_timer_expired": "O cronômetro já esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", - "share_via_onionshare": "Compartilhar usando OnionShare", + "gui_server_autostop_timer_expired": "O cronômetro já esgotou.\nPor favor, ajuste-o para começar a compartilhar.", + "share_via_onionshare": "Compartilhar via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", "gui_save_private_key_checkbox": "Usar o mesmo endereço", "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", @@ -220,11 +220,14 @@ "gui_settings_autostart_timer_checkbox": "Usar cronômetro para começar automaticamente", "gui_settings_autostart_timer": "Começar o compartilhamento às:", "gui_server_autostart_timer_expired": "O horário marcado já passou. Por favor, atualize-o para começar a compartilhar.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "O horário para o término automático não pode ser o mesmo ou anterior aquele marcado para começar. Por favor, atualize-o para começar a compartilhar.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "O horário para o término automático não pode ser o mesmo ou anterior aquele marcado para começar. Por favor, ajuste-o para começar a compartilhar.", "gui_status_indicator_share_scheduled": "Marcado…", "gui_status_indicator_receive_scheduled": "Marcado…", "days_first_letter": "dia(s)", "hours_first_letter": "hora(s)", "minutes_first_letter": "minuto(s)", - "seconds_first_letter": "segundo(s)" + "seconds_first_letter": "segundo(s)", + "incorrect_password": "Senha Incorreta", + "gui_settings_individual_downloads_label": "Desmarque para permitir download de arquivos individuais", + "gui_settings_csp_header_disabled_option": "Desabilitar cabeçalho de política de segurança de conteúdo." } diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index 1cee99c7..0567004f 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -132,7 +132,7 @@ "gui_tor_connection_canceled": "無法連接到Tor。\n\n請確認您已連接上網路,然後再重新開啟OnionShare並設定Tor連線。", "gui_tor_connection_lost": "已斷開Tor連接。", "gui_server_started_after_autostop_timer": "自動停止計時器在伺服器啟動前就時間已到。\n請重新分享。", - "gui_server_autostop_timer_expired": "自動停止計時器時間已到。\n請更新它來開始分享。", + "gui_server_autostop_timer_expired": "自動停止計時器時間已到。\n請調整它來開始分享。", "share_via_onionshare": "使用OnionShare分享", "gui_use_legacy_v2_onions_checkbox": "使用傳統地址", "gui_save_private_key_checkbox": "使用永久地址", @@ -218,7 +218,7 @@ "gui_waiting_to_start": "預定在 {} 開始。點擊以取消。", "gui_settings_autostart_timer_checkbox": "使用自動開始計時器", "gui_settings_autostart_timer": "開始分享於:", - "gui_server_autostart_timer_expired": "排定的時間已經過了。請更新它以開始分享。", + "gui_server_autostart_timer_expired": "排定的時間已經過了。請調整它以開始分享。", "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "自動停止時間不能相同或早於自動開始時間。請更新它以開始分享。", "gui_status_indicator_share_scheduled": "預定…", "gui_status_indicator_receive_scheduled": "預定…", @@ -227,5 +227,7 @@ "days_first_letter": "天", "hours_first_letter": "時", "minutes_first_letter": "分", - "seconds_first_letter": "秒" + "seconds_first_letter": "秒", + "incorrect_password": "密碼錯誤", + "gui_settings_csp_header_disabled_option": "停用Content Security Policy標頭" } -- cgit v1.2.3-54-g00ecf From 79e33ff2da38d5bf21eab80a5b5840e5049b3c90 Mon Sep 17 00:00:00 2001 From: googan-star <56072948+googan-star@users.noreply.github.com> Date: Wed, 2 Oct 2019 10:55:50 +0000 Subject: Prettify JSON dump --- onionshare/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 7a017bf0..0cccb8e9 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -199,7 +199,7 @@ class Settings(object): Save settings to file. """ self.common.log('Settings', 'save') - open(self.filename, 'w').write(json.dumps(self._settings)) + open(self.filename, 'w').write(json.dumps(self._settings, indent=2)) self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename)) def get(self, key): -- cgit v1.2.3-54-g00ecf From 430fc14226d70eebf729b0bce3b6eab8b0124646 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 7 Oct 2019 22:25:13 +0200 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ Added translation using Weblate (Serbian (latin)) Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/cs.json | 46 ++++++++++-- share/locale/de.json | 3 +- share/locale/id.json | 3 +- share/locale/nb.json | 6 +- share/locale/nl.json | 18 +++-- share/locale/pt_BR.json | 80 +++++++++++--------- share/locale/sr_Latn.json | 183 ++++++++++++++++++++++++++++++++++++++++++++++ share/locale/tr.json | 15 ++-- share/locale/zh_Hant.json | 8 +- 9 files changed, 300 insertions(+), 62 deletions(-) create mode 100644 share/locale/sr_Latn.json diff --git a/share/locale/cs.json b/share/locale/cs.json index 0bd1fdb0..7a5a4749 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -24,27 +24,27 @@ "gui_downloads": "Historie stahování", "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", - "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_please_wait": "Prosím čekejte...", + "gui_copied_hidservauth": "HidServAuth zkopírováno do schránky", + "gui_please_wait": "Spouštění... Klikněte pro zrušení.", "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", + "gui_share_quit_warning": "Jste si jistí, že chcete odejít? URL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", "gui_quit_warning_dont_quit": "Zůstat", "error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.", "zip_progress_bar_format": "Zpracovávám soubory: %p%", - "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", + "error_stealth_not_supported": "K autorizaci klienta potřebujete alespoň Tor 0.2.9.1-alpha (or Tor Browser 6.5) a python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.", "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_control_port_option": "Připojit použitím control port", + "gui_settings_connection_type_socket_file_option": "Připojit použitím socket file", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", - "gui_settings_authenticate_label": "Autentizační možnosti Toru", + "gui_settings_authenticate_label": "Autentizační nastavení Toru", "gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace", "gui_settings_authenticate_password_option": "Heslo", "gui_settings_password_label": "Heslo", @@ -78,5 +78,35 @@ "gui_receive_start_server": "Spustit mód přijímání", "gui_receive_stop_server": "Zastavit přijímání", "gui_receive_stop_server_autostop_timer": "Zastavit mód přijímání ({} zbývá)", - "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token" + "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token", + "gui_copied_url_title": "OnionShare Address zkopírována", + "gui_quit_title": "Ne tak rychle", + "gui_settings_stealth_option": "Autorizace klienta", + "gui_settings_stealth_hidservauth_string": "Uložení priváního klíče pro znovu použití znamená, že teď můžete zkopírovat Váš HidServAuth.", + "gui_settings_autoupdate_label": "Kontrola nové verze", + "gui_settings_autoupdate_option": "Upozornit na dostupnost nové verze", + "gui_settings_autoupdate_timestamp": "Poslední kontrola {}", + "gui_settings_autoupdate_timestamp_never": "Nikdy", + "gui_settings_autoupdate_check_button": "Kontrola nové verze", + "gui_settings_sharing_label": "Nastavení sdílení", + "gui_settings_close_after_first_download_option": "Zastavit sdílení po odeslání dat", + "gui_settings_connection_type_bundled_option": "Použít Tor verzi vestavěnou v OnionShare", + "gui_settings_connection_type_test_button": "Test připojení do Tor sítě", + "gui_settings_socks_label": "SOCKS port", + "gui_settings_tor_bridges": "Tor bridge podpora", + "gui_settings_tor_bridges_no_bridges_radio_option": "Nepoužívat Tor bridge", + "gui_settings_tor_bridges_obfs4_radio_option": "Použít vestavěný obfs4 přenos", + "gui_receive_quit_warning": "Jste v procesu příjímání dat. Jste si opravdu jistí, že chcete OnionShare ukončit?", + "gui_settings_whats_this": "Co je to?", + "gui_settings_general_label": "Obecná nastavení", + "gui_add_files": "Přidat soubory", + "gui_add_folder": "Přidat adresář", + "gui_settings_onion_label": "Onion nastavení", + "close_on_autostop_timer": "Zastaveno protože vypršel čas auto-stop časovače", + "gui_stop_server_autostop_timer_tooltip": "Auto-stop skončí v {}", + "gui_start_server_autostart_timer_tooltip": "Auto-start skončí v {}", + "gui_waiting_to_start": "Naplánovaný start v {}. Klikněte pro zrušení.", + "incorrect_password": "Nesprávné heslo", + "gui_settings_individual_downloads_label": "Odškrtnout k povolení stahování libovolných souborů", + "gui_settings_csp_header_disabled_option": "Zakázat Conent Security Policy hlavičku" } diff --git a/share/locale/de.json b/share/locale/de.json index e35a720a..c50e3bfc 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -239,5 +239,6 @@ "incorrect_password": "Falsches Passwort", "systray_individual_file_downloaded_title": "Individuelle Datei geladen", "gui_settings_individual_downloads_label": "Abwählen, um den Download einzelner Dateien zu erlauben", - "history_requests_tooltip": "Web-Anfragen" + "history_requests_tooltip": "{} Web-Anfragen", + "systray_individual_file_downloaded_message": "Individuelle Datei {} betrachtet" } diff --git a/share/locale/id.json b/share/locale/id.json index c40ada6b..8ead263f 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -184,5 +184,6 @@ "gui_settings_language_changed_notice": "", "gui_add_files": "Tambahkan berkas", "gui_add_folder": "Tambahkan Folder", - "gui_settings_onion_label": "Pengaturan Onion" + "gui_settings_onion_label": "Pengaturan Onion", + "incorrect_password": "Password salah" } diff --git a/share/locale/nb.json b/share/locale/nb.json index 1bbb01ce..ea0f3319 100644 --- a/share/locale/nb.json +++ b/share/locale/nb.json @@ -5,7 +5,7 @@ "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.", - "preparing_files": "Pakker sammen filer.", + "preparing_files": "Pakker filer.", "give_this_url_stealth": "Gi denne adressen og HidServAuth-linjen til mottakeren:", "give_this_url_receive": "Gi denne adressen til avsenderen:", "give_this_url_receive_stealth": "Gi denne adressen og HidServAuth-linjen til avsenderen:", @@ -123,7 +123,7 @@ "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet", "connecting_to_tor": "Kobler til Tor-nettverket", "update_available": "Ny OnionShare-versjon tilgjenglig. Klikk her for å laste den ned.

    Du bruker {} og nyeste versjon er {}.", - "update_error_check_error": "Kunne ikke sjekke etter nye versjoner: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", + "update_error_check_error": "Kunne ikke sjekke etter ny versjon: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", "update_error_invalid_latest_version": "Kunne ikke sjekke etter ny versjon: Kanskje du ikke er koblet til Tor, eller kanskje OnionShare-nettsiden er nede?", "update_not_available": "Du kjører siste versjon av OnionShare.", "gui_tor_connection_ask": "Åpne innstillingene for å ordne opp i tilkoblingen til Tor?", @@ -134,7 +134,7 @@ "gui_tor_connection_lost": "Frakoblet fra Tor.", "gui_server_started_after_autostop_timer": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", "gui_server_autostop_timer_expired": "Tidsavbruddsuret har gått ut allerede. Juster det for å starte deling.", - "share_via_onionshare": "OnionShare det", + "share_via_onionshare": "Del via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", "gui_save_private_key_checkbox": "Bruk en vedvarende adresse", "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-Browser: ", diff --git a/share/locale/nl.json b/share/locale/nl.json index 1815e0e7..84492104 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -85,18 +85,18 @@ "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de ontwikkelaarsmodus op Windows of macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", - "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", + "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteunt ephemeral onion services: {}.\nOndersteunt client authentication: {}.\nOndersteunt next-gen .onion addresses: {}.", "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", "update_available": "Nieuwe OnionShare is uitgekomen. Klik hier om hem te krijgen.

    Jij gebruikt {} and de laatste is {}.", - "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", + "update_error_check_error": "Kon niet controleren op een nieuwe versie: de OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of de OnionShare website is niet beschikbaar?", "update_not_available": "Je draait de laatst beschikbare OnionShare.", "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afsluiten", "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.", - "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.", + "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nZorg dat je met het internet verbonden bent, herstart OnionShare en configureer de verbinding met Tor.", "gui_server_started_after_autostop_timer": "De auto-stop timer verliep, voordat de server startte. Maak een nieuwe share aan.", "gui_server_autostop_timer_expired": "De auto-stop timer is al verlopen. Stel een nieuwe tijd in om te beginnen met delen.", "share_via_onionshare": "Deel via OnionShare", @@ -120,7 +120,7 @@ "gui_quit_title": "Niet zo snel", "gui_receive_quit_warning": "Je bent in het proces van bestanden ontvangen. Weet je zeker dat je OnionShare af wilt sluiten?", "gui_settings_whats_this": "1Wat is dit?2", - "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je \nklikken om je HidServAuth te kopiëren.", + "gui_settings_stealth_hidservauth_string": "Je privésleutel is voor hergebruik opgeslagen. Je kunt nu klikken om je HidServAuth te kopiëren.", "gui_settings_general_label": "Algemene instellingen", "gui_settings_tor_bridges": "Tor bridge ondersteuning", "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", @@ -232,5 +232,13 @@ "gui_settings_individual_downloads_label": "Klik uit om het downloaden van individuele bestanden toe te staan", "systray_individual_file_downloaded_title": "Individueel bestand geladen", "systray_individual_file_downloaded_message": "Individueel bestand {} bekeken", - "gui_settings_website_label": "Instellingen voor website" + "gui_settings_website_label": "Instellingen voor website", + "error_cannot_create_data_dir": "Kon geen OnionShare datamap aanmaken: {}", + "gui_all_modes_progress_eta": "{0:s}, Verwachte Aankomsttijd: {1:s}, %p%", + "days_first_letter": "d", + "hours_first_letter": "h", + "minutes_first_letter": "m", + "seconds_first_letter": "s", + "history_requests_tooltip": "{} webverzoeken", + "gui_settings_csp_header_disabled_option": "Schakel Content Security Policy header uit" } diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index c28f9f29..f0de1154 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -9,7 +9,7 @@ "not_a_file": "{0:s} não é um arquivo válido.", "not_a_readable_file": "{0:s} não é um arquivo legível.", "no_available_port": "Não foi possível encontrar uma porta disponível para iniciar o serviço onion", - "other_page_loaded": "Endereço carregado", + "other_page_loaded": "O endereço carregou", "close_on_autostop_timer": "Interrompido ao final da contagem do cronômetro automático", "closing_automatically": "Interrompido após o término da transferência", "timeout_download_still_running": "Esperando que o download termine", @@ -39,19 +39,19 @@ "gui_share_stop_server": "Parar de compartilhar", "gui_share_stop_server_autostop_timer": "Parar de compartilhar daqui a ({})", "gui_share_stop_server_autostop_timer_tooltip": "O cronômetro automático termina às", - "gui_receive_start_server": "Modo Começar a Receber", - "gui_receive_stop_server": "Modo Parar de Receber", - "gui_receive_stop_server_autostop_timer": "Modo Parar de Receber ({} para terminar)", + "gui_receive_start_server": "Começar o Modo Recepção", + "gui_receive_stop_server": "Parar o Modo Recepção", + "gui_receive_stop_server_autostop_timer": "Parar o Modo Recepção ({} para terminar)", "gui_receive_stop_server_autostop_timer_tooltip": "O cronômetro automático termina às {}", "gui_copy_url": "Copiar endereço", "gui_copy_hidservauth": "Copiar HidServAuth", "gui_downloads": "Histórico de download", "gui_no_downloads": "Nenhum download por enquanto", "gui_canceled": "Cancelado", - "gui_copied_url_title": "Endereço OnionShare copiado", - "gui_copied_url": "URL foi copiado na área de transferência", - "gui_copied_hidservauth_title": "HidServAuth copiado", - "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência", + "gui_copied_url_title": "O endereço OnionShare foi copiado", + "gui_copied_url": "O endereço OnionShare foi copiado na área de transferência", + "gui_copied_hidservauth_title": "O HidServAuth foi copiado", + "gui_copied_hidservauth": "A linha HidServAuth foi copiada na área de transferência", "gui_please_wait": "Começando...Clique para cancelar.", "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", @@ -62,9 +62,9 @@ "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", "gui_quit_warning_quit": "Sair", "gui_quit_warning_dont_quit": "Cancelar", - "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "error_rate_limit": "Alguém tentou por várias vezes adivinhar sua senha. Por isso, OnionShare interrompeu o servidor. Comece o compartilhamento novamente e envie um novo endereço ao seu destinatário para compartilhar.", "zip_progress_bar_format": "Comprimindo: %p%", - "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", + "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa ao menos de Tor 0.2.9.1-alpha (ou Navegador Tor 6.5) e de python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Configurações", "gui_settings_whats_this": "O que é isso?", @@ -76,14 +76,14 @@ "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_autoupdate_check_button": "Procurar a nova versão", "gui_settings_general_label": "Configurações gerais", - "gui_settings_sharing_label": "Configurações de compartilhar", + "gui_settings_sharing_label": "Configurações de compartilhamento", "gui_settings_close_after_first_download_option": "Parar de compartilhar após o envio dos arquivos", "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", - "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", + "gui_settings_connection_type_bundled_option": "Usar a versão de Tor já instalada no OnionShare", "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", "gui_settings_connection_type_control_port_option": "Conectar usando porta de controle", "gui_settings_connection_type_socket_file_option": "Conectar usando um arquivo socket", - "gui_settings_connection_type_test_button": "Testar a conexão a Tor", + "gui_settings_connection_type_test_button": "Testar a conexão ao Tor", "gui_settings_control_port_label": "Porta de controle", "gui_settings_socket_file_label": "Arquivo socket", "gui_settings_socks_label": "Porta SOCKS", @@ -91,16 +91,16 @@ "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", "gui_settings_authenticate_password_option": "Senha", "gui_settings_password_label": "Senha", - "gui_settings_tor_bridges": "Suporte a pontes Tor", + "gui_settings_tor_bridges": "Suporte para pontes Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Aviso: as pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) já instalados", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) já instalados (requer obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Aviso: as pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar ao Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas", "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.", + "gui_settings_tor_bridges_invalid": "Nenhuma das ponte adicionadas funciona.\nTente usá-las de novo ou adicione outras.", "gui_settings_button_save": "Salvar", "gui_settings_button_cancel": "Cancelar", "gui_settings_button_help": "Ajuda", @@ -108,32 +108,32 @@ "gui_settings_autostop_timer": "Encerrar o compartilhamento às:", "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?", - "settings_error_socket_port": "Não pode ligar ao controlador do Tor em {}:{}.", + "settings_error_socket_port": "Não foi possível conectar ao controlador do Tor às {}:{}.", "settings_error_socket_file": "Não foi possível conectar ao controlador Tor usando o arquivo de socket {}.", "settings_error_auth": "Conectado a {}:{}, mas não foi possível autenticar. Talvez este não seja um controlador Tor?", "settings_error_missing_password": "Conectado ao controlador Tor, mas é preciso ter uma senha para autenticar.", - "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a senha esteja incorreta, ou o seu usuário não possui autorização para ler o arquivo de cookie.", + "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a senha esteja incorreta ou o seu usuário não possua autorização para ler o arquivo de cookie.", "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", - "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", - "settings_error_bundled_tor_broken": "OnionShare não pôde se conectar a Tor em segundo plano:\n{}", + "settings_error_bundled_tor_timeout": "A conexão ao Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", + "settings_error_bundled_tor_broken": "OnionShare não pôde se conectar ao Tor em segundo plano:\n{}", "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", "error_tor_protocol_error": "Houve um erro com Tor: {}", "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor", "error_invalid_private_key": "Este tipo de chave privada não possui suporte", "connecting_to_tor": "Conectando à rede Tor", "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", - "update_error_check_error": "Não foi possível verificar por novas versões: o website do OnionShare está dizendo que a última versão é a irreconhecível '{}'…", - "update_error_invalid_latest_version": "Não foi possível verificar por uma nova versão: talvez você não esteja conectada ao Tor, ou talvez o website do OnionShare esteja fora do ar?", + "update_error_check_error": "Não foi possível verificar se há uma nova versão: o website do OnionShare está dizendo que a última versão é a irreconhecível '{}'…", + "update_error_invalid_latest_version": "Não foi possível verificar se há uma nova versão: talvez você não esteja conectada ao Tor, ou talvez o website do OnionShare esteja fora do ar?", "update_not_available": "Você está rodando a última versão de OnionShare.", "gui_tor_connection_ask": "Abrir as configurações para consertar a conexão ao Tor?", "gui_tor_connection_ask_open_settings": "Sim", "gui_tor_connection_ask_quit": "Sair", "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.", - "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", + "gui_tor_connection_canceled": "Não foi possível conectar ao Tor.\n\nTenha certeza que você está conectado à Internet, então abra OnionShare novamente e configure sua conexão ao Tor.", "gui_tor_connection_lost": "Desconectado do Tor.", "gui_server_started_after_autostop_timer": "O tempo esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", - "gui_server_autostop_timer_expired": "O cronômetro já esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", - "share_via_onionshare": "Compartilhar usando OnionShare", + "gui_server_autostop_timer_expired": "O cronômetro já esgotou.\nPor favor, ajuste-o para começar a compartilhar.", + "share_via_onionshare": "Compartilhar via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", "gui_save_private_key_checkbox": "Usar o mesmo endereço", "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", @@ -157,7 +157,7 @@ "error_cannot_create_downloads_dir": "Não foi possível a pasta do modo de recepção: {}", "receive_mode_downloads_dir": "Os arquivos enviados para você aparecem na seguinte pasta: {}", "receive_mode_warning": "Atenção: O modo de recepção permite que as pessoas enviem arquivos para o seu computador. Alguns arquivos podem tomar o controle do seu computador se você abri-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", - "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.

    Alguns arquivos podem tomar o controle do seu computador se você abri-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", + "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.

    Alguns arquivos podem tomar o controle do seu computador se você abri-los. Apenas abra arquivos enviados por pessoas em quem você confia, ou se souber o que está fazendo.", "receive_mode_upload_starting": "Um upload de tamanho total {} está sendo iniciado", "receive_mode_received_file": "Recebido: {}", "gui_mode_share_button": "Compartilhar Arquivos", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare encerrado", "systray_close_server_message": "Um usuário encerrou o servidor", - "systray_page_loaded_title": "Página Carregada", + "systray_page_loaded_title": "A página carregou", "systray_download_page_loaded_message": "Um usuário carregou a página de download", "systray_upload_page_loaded_message": "Um usuário carregou a página de upload", "gui_uploads": "Histórico de Uploads", @@ -181,7 +181,7 @@ "gui_download_in_progress": "Download Iniciado {}", "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", "gui_settings_language_label": "Idioma", - "gui_settings_language_changed_notice": "Reinicie OnionShare para que sua alteração de idioma tenha efeito.", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que o novo idioma seja aplicado.", "timeout_upload_still_running": "Esperando o término do upload", "gui_add_files": "Adicionar Arquivos", "gui_add_folder": "Adicionar Pasta", @@ -209,22 +209,30 @@ "gui_share_mode_autostop_timer_waiting": "Esperando para completar o envio", "gui_receive_mode_no_files": "Nenhum arquivo recebido", "gui_receive_mode_autostop_timer_waiting": "Esperando para completar o recebimento", - "gui_settings_onion_label": "Configurando Onion", - "systray_page_loaded_message": "Endereço OnionShare foi carregado", + "gui_settings_onion_label": "Configurações do Onion", + "systray_page_loaded_message": "O endereço OnionShare carregou", "gui_all_modes_progress_complete": "%p%, {0:s} em curso.", "gui_all_modes_progress_starting": "{0:s}, %p% (calculando)", "gui_all_modes_progress_eta": "{0:s}, Tempo aproximado: {1:s}, %p%", - "gui_stop_server_autostop_timer_tooltip": "O cronômetro automático encerra às {}", - "gui_start_server_autostart_timer_tooltip": "O cronômetro para começar automaticamente esgota às {}", + "gui_stop_server_autostop_timer_tooltip": "O cronômetro de interrupção automática encerra às {}", + "gui_start_server_autostart_timer_tooltip": "O cronômetro de iniciação automática esgota às {}", "gui_waiting_to_start": "Marcado para começar daqui a {}. Clique para cancelar.", "gui_settings_autostart_timer_checkbox": "Usar cronômetro para começar automaticamente", "gui_settings_autostart_timer": "Começar o compartilhamento às:", "gui_server_autostart_timer_expired": "O horário marcado já passou. Por favor, atualize-o para começar a compartilhar.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "O horário para o término automático não pode ser o mesmo ou anterior aquele marcado para começar. Por favor, atualize-o para começar a compartilhar.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "O horário para o término automático não pode ser o mesmo ou anterior aquele marcado para começar. Por favor, ajuste-o para começar a compartilhar.", "gui_status_indicator_share_scheduled": "Marcado…", "gui_status_indicator_receive_scheduled": "Marcado…", "days_first_letter": "dia(s)", "hours_first_letter": "hora(s)", "minutes_first_letter": "minuto(s)", - "seconds_first_letter": "segundo(s)" + "seconds_first_letter": "segundo(s)", + "incorrect_password": "senha incorreta", + "gui_settings_individual_downloads_label": "Desmarque para permitir download de arquivos individuais", + "gui_settings_csp_header_disabled_option": "Desabilitar cabeçalho Política de Segurança de Conteúdo", + "gui_website_url_description": "Qualquer pessoa com este endereço OnionShare pode visitar seu site usando o navegador Tor: ", + "gui_mode_website_button": "Publicar site", + "gui_website_mode_no_files": "Nenhum site compartilhado ainda", + "history_requests_tooltip": "{} solicitações da web", + "gui_settings_website_label": "Configurações do site" } diff --git a/share/locale/sr_Latn.json b/share/locale/sr_Latn.json new file mode 100644 index 00000000..bdabb89c --- /dev/null +++ b/share/locale/sr_Latn.json @@ -0,0 +1,183 @@ +{ + "preparing_files": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "incorrect_password": "", + "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": "", + "gui_settings_onion_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_csp_header_disabled_option": "", + "gui_settings_individual_downloads_label": "", + "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": "", + "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_website_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": "", + "history_requests_tooltip": "", + "error_cannot_create_data_dir": "", + "gui_receive_mode_warning": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_mode_website_button": "", + "gui_settings_receiving_label": "", + "gui_settings_website_label": "", + "gui_settings_data_dir_label": "", + "gui_settings_data_dir_browse_button": "", + "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_website_mode_no_files": "", + "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/tr.json b/share/locale/tr.json index cbcb549f..7eeb7b1f 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -121,7 +121,7 @@ "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 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.", + "gui_server_autostop_timer_expired": "Otomatik durma sayacı zaten sona ermiş. Paylaşmaya başlamak için sayacı ayarlayın.", "share_via_onionshare": "OnionShare ile paylaş", "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", @@ -156,7 +156,7 @@ "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.", + "gui_settings_language_changed_notice": "Dil değişikliğinin uygulanabilmesi 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", @@ -187,8 +187,8 @@ "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_server_autostart_timer_expired": "Zamanlanan süre zaten bitti. Paylaşmaya başlamak için ayarlayın.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Otomatik durdurma zamanı, otomatik başlatma zamanı ile aynı veya daha önce olamaz. Paylaşmaya başlamak için ayarlayın.", "gui_status_indicator_share_scheduled": "Zamanlanmış…", "gui_status_indicator_receive_scheduled": "Zamanlanmış…", "gui_share_mode_autostop_timer_waiting": "Gönderme işleminin bitmesi bekleniyor", @@ -200,5 +200,10 @@ "invalid_password_guess": "Geçersiz parola tahmini", "gui_website_url_description": "Bu OnionShare adresi olan herkes, Tor Browser'ı kullanarak web sitenizi ziyaret edebilir: ", "gui_mode_website_button": "Web Sitesini Yayınla", - "gui_website_mode_no_files": "Henüz Paylaşılan Web Sitesi Yok" + "gui_website_mode_no_files": "Henüz Paylaşılan Web Sitesi Yok", + "incorrect_password": "Hatalı Parola", + "gui_settings_individual_downloads_label": "Tekil dosyaları indirmeye izin vermek için işareti kaldırın", + "history_requests_tooltip": "{} web isteği", + "gui_settings_csp_header_disabled_option": "İçerik Güvenlik Politikası Üstbilgisini Kaldır", + "gui_settings_website_label": "Website ayarları" } diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index 1cee99c7..0567004f 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -132,7 +132,7 @@ "gui_tor_connection_canceled": "無法連接到Tor。\n\n請確認您已連接上網路,然後再重新開啟OnionShare並設定Tor連線。", "gui_tor_connection_lost": "已斷開Tor連接。", "gui_server_started_after_autostop_timer": "自動停止計時器在伺服器啟動前就時間已到。\n請重新分享。", - "gui_server_autostop_timer_expired": "自動停止計時器時間已到。\n請更新它來開始分享。", + "gui_server_autostop_timer_expired": "自動停止計時器時間已到。\n請調整它來開始分享。", "share_via_onionshare": "使用OnionShare分享", "gui_use_legacy_v2_onions_checkbox": "使用傳統地址", "gui_save_private_key_checkbox": "使用永久地址", @@ -218,7 +218,7 @@ "gui_waiting_to_start": "預定在 {} 開始。點擊以取消。", "gui_settings_autostart_timer_checkbox": "使用自動開始計時器", "gui_settings_autostart_timer": "開始分享於:", - "gui_server_autostart_timer_expired": "排定的時間已經過了。請更新它以開始分享。", + "gui_server_autostart_timer_expired": "排定的時間已經過了。請調整它以開始分享。", "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "自動停止時間不能相同或早於自動開始時間。請更新它以開始分享。", "gui_status_indicator_share_scheduled": "預定…", "gui_status_indicator_receive_scheduled": "預定…", @@ -227,5 +227,7 @@ "days_first_letter": "天", "hours_first_letter": "時", "minutes_first_letter": "分", - "seconds_first_letter": "秒" + "seconds_first_letter": "秒", + "incorrect_password": "密碼錯誤", + "gui_settings_csp_header_disabled_option": "停用Content Security Policy標頭" } -- cgit v1.2.3-54-g00ecf From 15fb84b4b8528dd5b207f687a89e66f37860bca0 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 7 Oct 2019 22:33:31 +0200 Subject: Translated using Weblate (Serbian (latin)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sr_Latn/ Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ Translated using Weblate (Ukrainian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/uk/ Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ Translated using Weblate (Irish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ga/ --- share/locale/da.json | 4 +- share/locale/el.json | 24 ++- share/locale/fa.json | 162 ++++++++++++--------- share/locale/ga.json | 23 ++- share/locale/hu.json | 29 ++-- share/locale/is.json | 4 +- share/locale/it.json | 8 +- share/locale/ja.json | 20 ++- share/locale/nl.json | 2 +- share/locale/pt_BR.json | 8 +- share/locale/ro.json | 275 +++++++++++++++++++++-------------- share/locale/sr_Latn.json | 362 +++++++++++++++++++++++----------------------- share/locale/uk.json | 22 ++- share/locale/zh_Hans.json | 28 ++-- share/locale/zh_Hant.json | 16 +- 15 files changed, 562 insertions(+), 425 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index dffe13cb..1eac766b 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -236,5 +236,7 @@ "gui_website_mode_no_files": "Intet websted delt endnu", "incorrect_password": "Forkert adgangskode", "gui_settings_individual_downloads_label": "Fravælg for at tillade download af individuelle filer", - "history_requests_tooltip": "{}-webanmodninger" + "history_requests_tooltip": "{}-webanmodninger", + "gui_settings_csp_header_disabled_option": "Deaktivér indholdets header om sikkerhedspolitik", + "gui_settings_website_label": "Webstedsindstillinger" } diff --git a/share/locale/el.json b/share/locale/el.json index 90655c18..0fa50def 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -62,7 +62,7 @@ "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_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", @@ -122,7 +122,7 @@ "error_invalid_private_key": "Αυτο το ιδιωτικό κλειδί δεν υποστηρίζεται", "connecting_to_tor": "Γίνεται σύνδεση στο δίκτυο Tor", "update_available": "Βγήκε ενα νέο OnionShare. Κάντε κλικ εδώ για να το λάβετε.

    Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.", - "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος του OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση είναι η μη κατανοητή: '{}'…", + "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέα έκδοση. Ο ιστότοπος του OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση είναι η μη κατανοητή: '{}'…", "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένος/η στο Tor ή ο ιστότοπος OnionShare έχει πέσει;", "update_not_available": "Έχετε την πιό πρόσφατη έκδοση του OnionShare.", "gui_tor_connection_ask": "Άνοιγμα των ρυθμίσεων για να επιλύσετε την σύνδεση με το Tor;", @@ -132,8 +132,8 @@ "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_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 Browser: ", @@ -181,7 +181,7 @@ "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": "Προσθέστε φάκελο", @@ -219,12 +219,20 @@ "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_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": "ώ", "minutes_first_letter": "λ", - "seconds_first_letter": "δ" + "seconds_first_letter": "δ", + "gui_website_url_description": "Οποιοσδήποτε με αυτή τη διεύθυνση OnionShare μπορεί να επισκεφτεί την ιστοσελία χρησιμοποιώντας τον Tor Browser: ", + "gui_mode_website_button": "Δημοσίευση ιστοσελίδας", + "gui_website_mode_no_files": "Η ιστοσελίδα δεν έχει μοιραστεί ακόμα", + "incorrect_password": "Λάθος κωδικός", + "gui_settings_individual_downloads_label": "Απεπιλέξτε για να επιτρέψετε τη λήψη μεμονωμένων αρχείων", + "history_requests_tooltip": "{} αιτήματα δικτύου", + "gui_settings_csp_header_disabled_option": "Απενεργοποίηση της κεφαλίδας Content Security Policy", + "gui_settings_website_label": "Ρυθμίσεις ιστοσελίδας" } diff --git a/share/locale/fa.json b/share/locale/fa.json index 1f2d327e..d18fe13f 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -1,19 +1,19 @@ { "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} قابل خواندن نمی باشد.", + "not_a_readable_file": "{0:s} قابل خواندن نیست.", "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد", "other_page_loaded": "آدرس بارگذاری شد", "close_on_autostop_timer": "متوقف شد چون تایمر توقف خودکار به پایان رسید", "closing_automatically": "متوقف شد چون انتقال انجام شد", "timeout_download_still_running": "انتظار برای تکمیل دانلود", - "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد", + "large_filesize": "هشدار: یک هم‌رسانی بزرگ ممکن است ساعت‌ها طول بکشد", "systray_menu_exit": "خروج", "systray_download_started_title": "دانلود OnionShare آغاز شد", "systray_download_started_message": "یک کاربر شروع به دانلود فایل های شما کرد", @@ -31,17 +31,17 @@ "help_verbose": "لاگ کردن خطاهای OnionShare روی stdout، و خطاهای وب بر روی دیسک", "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری", "help_config": "مکان فایل کانفیگ JSON کاستوم (اختیاری)", - "gui_drag_and_drop": "فایل ها و پوشه ها را بکشید و رها کنید\nتا اشتراک گذاری آغاز شود", + "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_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", @@ -58,98 +58,98 @@ "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_share_quit_warning": "شما در پروسه ارسال پرونده هستید. مطمئنید که می‌خواهید از OnionShare خارج شوید؟", + "gui_receive_quit_warning": "شما در پروسه دریافت پرونده هستید. مطمئنید که می‌خواهید از OnionShare خارج شوید؟", "gui_quit_warning_quit": "خروج", "gui_quit_warning_dont_quit": "لغو", - "error_rate_limit": "شخصی تعداد زیادی قصد ناصحیح روی آدرس شما داشته است، این می تواند بدین معنا باشد که در حال تلاش برای حدس زدن آن هستند، بنابراین OnionShare سرور را متوقف کرده است. دوباره اشتراک گذاری را آغاز کنید و به گیرنده یک آدرس جدید برای اشتراک ارسال کنید.", + "error_rate_limit": "شخصی تعداد زیادی تلاش ناموفق برای حدس زدن گذرواژه شما داشته است، بنابراین OnionShare کارساز را متوقف کرده است. هم‌رسانی را دوباره آغاز کنید و به گیرنده، یک نشانی جدید برای هم‌رسانی بفرستید.", "zip_progress_bar_format": "فشرده سازی: %p%", "error_stealth_not_supported": "برای استفاده از احراز هویت کلاینت، شما نیاز به داشتن Tor 0.2.9.1-alpha (یا مرورگر Tor 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_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_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", "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل", - "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت", + "gui_settings_connection_type_socket_file_option": "اتصال از طریق پرونده سوکت", "gui_settings_connection_type_test_button": "تست اتصال به Tor", "gui_settings_control_port_label": "پورت کنترل", - "gui_settings_socket_file_label": "فایل سوکت‌", + "gui_settings_socket_file_label": "پرونده سوکت‌", "gui_settings_socks_label": "پورت SOCKS", "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor", "gui_settings_authenticate_no_auth_option": "هیچ احراز هویت، یا احراز هویت کوکی", "gui_settings_authenticate_password_option": "رمز عبور", "gui_settings_password_label": "رمز عبور", - "gui_settings_tor_bridges": "پشتیبانی بریج Tor", - "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج", + "gui_settings_tor_bridges": "پشتیبانی پل Tor", + "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 برای پروژه Tor بسیار هزینه بر هستند.

    فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق 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_meek_lite_expensive_warning": "هشدار: پل‌های meek_lite برای پروژه Tor بسیار هزینه بر هستند.

    فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق 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": "توقف اشتراک در:", + "gui_settings_autostop_timer_checkbox": "استفاده از زمان‌سنج توقف خودکار", + "gui_settings_autostop_timer": "توقف هم‌رسانی در:", "settings_error_unknown": "ناتوانی در اتصال به کنترل کننده Tor بدلیل نامفهوم بودن تنظیمات.", "settings_error_automatic": "ناتوانی در اتصال به کنترل کننده Tor. آیا مرورگر Tor (در دسترس از طریق torproject.org) در پس زمینه در حال اجراست؟", "settings_error_socket_port": "ناتوانی در اتصال به کنترل کننده Tor در {}:{}.", - "settings_error_socket_file": "ناتوانی در اتصال به کنترل کننده Tor از طریق فایل سوکت {}.", - "settings_error_auth": "متصل به {}:{}، اما ناتوانی در احراز هویت. شاید این یک کنترل کننده Tor نمی باشد؟", - "settings_error_missing_password": "متصل به کنترل کننده Tor، اما نیاز به یک رمز عبور برای احراز هویت می باشد.", - "settings_error_unreadable_cookie_file": "اتصال به کنترل کننده Tor برقرار است، اما رمز عبور ممکن است اشتباه باشد، یا کاربری شما اجازه خواندن فایل کوکی را ندارد.", - "settings_error_bundled_tor_not_supported": "استفاده از نسخه Tor که با OnionShare می آید در حالت توسعه روی ویندوز یا مک کار نمی کند.", - "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 نسل بعدی: {}.", + "settings_error_socket_file": "ناتوانی در اتصال به کنترل کننده Tor از طریق پرونده سوکت {}.", + "settings_error_auth": "متصل به {}:{}، اما ناتوانی در احراز هویت. شاید این یک کنترل کننده Tor نیست؟", + "settings_error_missing_password": "متصل به کنترل کننده Tor، اما نیاز به یک رمز عبور برای احراز هویت است.", + "settings_error_unreadable_cookie_file": "اتصال به کنترل کننده Tor برقرار است، اما رمز عبور ممکن است اشتباه باشد، یا کاربری شما اجازه خواندن پرونده کوکی را ندارد.", + "settings_error_bundled_tor_not_supported": "استفاده از نسخه Tor که با OnionShare می‌آید در حالت توسعه روی ویندوز یا مک کار نمی‌کند.", + "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", - "update_available": "نسخه جدید OnionShare وجود دارد. اینجا کلیک کنید تا آن را دریافت کنید.

    شما در حال استفاده از {} می باشید و آخرین نسخه {} می باشد.", - "update_error_check_error": "ناتوانی در بررسی برای نسخه های جدید: سایت OnionShare میگوید که آخرین نسخه '{}' ناشناس می باشد…", - "update_error_invalid_latest_version": "ناتوانی در بررسی نسخه جدید: شاید شما به Tor متصل نیستید، یا سایت OnionShare کار نمی کند؟", - "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.", + "update_available": "نسخه جدید OnionShare وجود دارد. اینجا کلیک کنید تا آن را دریافت کنید.

    شما در حال استفاده از {} هستید و آخرین نسخه {} است.", + "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": "تایمر توقف خودکار قبل از آغاز سرور به پایان رسید.\nلطفا یک اشتراک جدید درست کنید.", - "gui_server_autostop_timer_expired": "تایمر توقف خودکار به پایان رسید.\nلطفا برای آغاز اشتراک گذاری آن را به روز رسانی کنید.", - "share_via_onionshare": "OnionShare کنید", - "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده", + "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_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_share_started": "در حال هم‌رسانی", "gui_status_indicator_receive_stopped": "آماده دریافت", "gui_status_indicator_receive_working": "در حال شروع…", "gui_status_indicator_receive_started": "درحال دریافت", - "gui_file_info": "{} فایل ها، {}", - "gui_file_info_single": "{} فایل، {}", + "gui_file_info": "{} پرونده‌ها، {}", + "gui_file_info_single": "{} پرونده، {}", "history_in_progress_tooltip": "{} در حال انجام", "history_completed_tooltip": "{} کامل شد", "info_in_progress_uploads_tooltip": "{} آپلود در حال انجام", @@ -157,11 +157,11 @@ "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_receive_button": "دریافت فایل ها", + "gui_mode_share_button": "هم‌رسانی پرونده‌ها", + "gui_mode_receive_button": "دریافت پرونده‌ها", "gui_settings_receiving_label": "تنظیمات دریافت", "gui_settings_downloads_label": "ذخیره فایل ها در", "gui_settings_downloads_button": "فهرست", @@ -179,26 +179,26 @@ "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": "ری استارت OnionShare برای دیدن نتیجه اعمال تغییر در زبان.", + "gui_settings_language_changed_notice": "برای اعمال شدن زبان جدید، OnionShare را از نو راه‌اندازی کنید.", "timeout_upload_still_running": "انتظار برای تکمیل آپلود", - "gui_add_files": "افزودن فایل ها", + "gui_add_files": "افزودن پرونده‌ها", "gui_add_folder": "افزودن پوشه", "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_completed_title": "اشتراک گذاری تکمیل شد", - "systray_share_completed_message": "ارسال فایل ها به پایان رسید", - "systray_share_canceled_title": "اشتراک گذاری لغو شد", - "systray_share_canceled_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": "شخصی در حال ارسال فایل به شماست", + "systray_receive_started_message": "شخصی در حال ارسال پرونده به شماست", "gui_all_modes_history": "تاریخچه", "gui_all_modes_clear_history": "پاکسازی همه", "gui_all_modes_transfer_started": "{} آغاز شد", @@ -207,10 +207,32 @@ "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_share_mode_no_files": "هیچ پرونده‌ای هنوز فرستاده نشده است", + "gui_share_mode_autostop_timer_waiting": "در انتظار پایان یافتن ارسال", + "gui_receive_mode_no_files": "هیچ پرونده‌ای هنوز دریافت نشده است", + "gui_receive_mode_autostop_timer_waiting": "در انتظار برای پایان یافتن دریافت", "gui_all_modes_transfer_canceled_range": "{} - {} لغو شد", - "gui_all_modes_transfer_canceled": "{} لغو شد" + "gui_all_modes_transfer_canceled": "{} لغو شد", + "gui_settings_onion_label": "تنظیمات Onion", + "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": "زمان‌بندی‌شده…", + "days_first_letter": "ر", + "hours_first_letter": "س", + "minutes_first_letter": "د", + "seconds_first_letter": "ث", + "gui_website_url_description": "هرکسی با این نشانی OnionShare می‌تواند با استفاده از مرورگر تور سایت شما را بازدید کند: ", + "gui_mode_website_button": "انتشار سایت", + "gui_website_mode_no_files": "هنوز سایتی هم‌رسانی نشده است", + "incorrect_password": "گذرواژه نادرست", + "gui_settings_individual_downloads_label": "برای دادن اجازه بارگیری پرونده‌ها مجزا، از انتخاب خارج کنید", + "history_requests_tooltip": "{} درخواست وب", + "gui_settings_csp_header_disabled_option": "غیرفعال‌سازی سرایند سیاست امنیت محتوا", + "gui_settings_website_label": "تنظیمات سایت" } diff --git a/share/locale/ga.json b/share/locale/ga.json index 249494bc..f04e96e2 100644 --- a/share/locale/ga.json +++ b/share/locale/ga.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "Tá tú le linn roinnt comhad a íoslódáil. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?", "gui_quit_warning_quit": "Scoir", "gui_quit_warning_dont_quit": "Cealaigh", - "error_rate_limit": "Rinne duine éigin an iomarca iarrachtaí míchearta ar do sheoladh, agus dá bharr sin stop OnionShare an freastalaí. Tosaigh ag comhroinnt arís agus cuir seoladh nua chuig an bhfaighteoir.", + "error_rate_limit": "Rinne duine éigin an iomarca iarrachtaí míchearta ar d'fhocal faire, agus dá bharr sin stop OnionShare an freastalaí. Tosaigh ag comhroinnt arís agus cuir seoladh nua chuig an bhfaighteoir.", "zip_progress_bar_format": "Á chomhbhrú: %p%", "error_stealth_not_supported": "Chun údarú cliaint a úsáid, teastaíonn uait Tor 0.2.9.1-alpha (nó Brabhsálaí 6.5) agus python3-stem 1.5.0.", "error_ephemeral_not_supported": "Teastaíonn uait ar a laghad Tor 0.2.7.1 agus python3-stem 1.4.0 chun OnionShare a úsáid.", @@ -122,7 +122,7 @@ "error_invalid_private_key": "Ní thacaítear le heochair phríobháideach den sórt seo", "connecting_to_tor": "Ag ceangal le líonra Tor", "update_available": "Leagan nua de OnionShare ar fáil. Cliceáil anseo lena íoslódáil.

    Tá {} agat agus is é {} an leagan is déanaí.", - "update_error_check_error": "Theip orainn nuashonruithe a lorg: Deir suíomh Gréasáin OnionShare gurb é '{}' an leagan is déanaí, leagan nach n-aithnímid…", + "update_error_check_error": "Theip orainn nuashonrú a lorg: Deir suíomh Gréasáin OnionShare gurb é '{}' an leagan is déanaí, leagan nach n-aithnímid…", "update_error_invalid_latest_version": "Theip orainn nuashonruithe a lorg: B'fhéidir nach bhfuil ceangailte le Tor, nó nach bhfuil suíomh OnionShare ag obair faoi láthair?", "update_not_available": "Tá an leagan is déanaí de OnionShare agat cheana.", "gui_tor_connection_ask": "An bhfuil fonn ort na socruithe líonra a oscailt chun an fhadhb a réiteach?", @@ -133,7 +133,7 @@ "gui_tor_connection_lost": "Dícheangailte ó Tor.", "gui_server_started_after_autostop_timer": "Bhí an t-amadóir uathstoptha caite sular thosaigh an freastalaí. Caithfidh tú comhroinnt nua a chruthú.", "gui_server_autostop_timer_expired": "Tá an t-amadóir uathstoptha caite cheana. Caithfidh tú é a athshocrú sular féidir leat comhaid a chomhroinnt.", - "share_via_onionshare": "Comhroinn trí OnionShare é", + "share_via_onionshare": "Comhroinn trí OnionShare", "gui_use_legacy_v2_onions_checkbox": "Úsáid seoltaí sean-nóis", "gui_save_private_key_checkbox": "Úsáid seoladh seasmhach", "gui_share_url_description": "Tá aon duine a bhfuil an seoladh OnionShare aige/aici in ann do chuid comhad a íoslódáil le Brabhsálaí Tor: ", @@ -208,5 +208,20 @@ "gui_all_modes_transfer_finished_range": "Aistrithe {} - {}", "gui_all_modes_transfer_finished": "Aistrithe {}", "gui_all_modes_transfer_canceled_range": "Cealaithe {} - {}", - "gui_all_modes_transfer_canceled": "Cealaithe {}" + "gui_all_modes_transfer_canceled": "Cealaithe {}", + "systray_share_completed_message": "Seoladh na comhaid", + "systray_share_canceled_title": "Cealaíodh an Chomhroinnt", + "systray_share_canceled_message": "Chealaigh duine éigin an chomhroinnt", + "gui_all_modes_progress_starting": "{0:s}, %p% (á áireamh)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Níl aon chomhaid seolta fós", + "days_first_letter": "l", + "hours_first_letter": "u", + "minutes_first_letter": "n", + "seconds_first_letter": "s", + "gui_mode_website_button": "Foilsigh an Suíomh", + "incorrect_password": "Focal faire mícheart", + "history_requests_tooltip": "{} iarratas gréasáin", + "gui_settings_csp_header_disabled_option": "Díchumasaigh an ceanntásc Content Security Policy", + "gui_settings_website_label": "Socruithe an tsuímh" } diff --git a/share/locale/hu.json b/share/locale/hu.json index c5ee3299..3a6bba0c 100644 --- a/share/locale/hu.json +++ b/share/locale/hu.json @@ -8,10 +8,10 @@ "ctrlc_to_stop": "", "not_a_file": "", "not_a_readable_file": "{0:s} nem egy olvasható fájl.", - "no_available_port": "", - "other_page_loaded": "", - "close_on_autostop_timer": "", - "closing_automatically": "", + "no_available_port": "Nem található elérhető port az onion szolgáltatás indításához", + "other_page_loaded": "Cím betöltve", + "close_on_autostop_timer": "Leállítva, mert az auto-sop időzítő lejárt", + "closing_automatically": "Leállítva, mert az átvitel véget ért", "timeout_download_still_running": "", "large_filesize": "", "systray_menu_exit": "Kilépés", @@ -32,15 +32,15 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "Delete", + "gui_add": "Hozzáadás", + "gui_delete": "Törlés", "gui_choose_items": "Kiválaszt", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_autostop_timer": "", + "gui_share_start_server": "Megosztás kezdése", + "gui_share_stop_server": "Megosztás leállítása", + "gui_share_stop_server_autostop_timer": "Megosztás leállítása ({})", "gui_share_stop_server_autostop_timer_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", + "gui_receive_start_server": "Fogadó mód indítása", + "gui_receive_stop_server": "Fogadó mód leállítása", "gui_receive_stop_server_autostop_timer": "", "gui_receive_stop_server_autostop_timer_tooltip": "", "gui_copy_url": "", @@ -181,5 +181,10 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "Előnyben részesített nyelv", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "gui_add_files": "Fájlok hozzáadása", + "gui_add_folder": "Mappák hozzáadása", + "gui_stop_server_autostop_timer_tooltip": "Auto-stop időzítő megáll: {}", + "gui_start_server_autostart_timer_tooltip": "Auto-start időzítő megáll: {}", + "incorrect_password": "Rossz jelszó" } diff --git a/share/locale/is.json b/share/locale/is.json index 23dd0e20..d90213e1 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -232,5 +232,7 @@ "gui_website_mode_no_files": "Ennþá hefur engu vefsvæði verið deilt", "incorrect_password": "Rangt lykilorð", "gui_settings_individual_downloads_label": "Taktu merkið úr til að leyfa niðurhal á stökum skrám", - "history_requests_tooltip": "{} vefbeiðnir" + "history_requests_tooltip": "{} vefbeiðnir", + "gui_settings_csp_header_disabled_option": "Gera haus fyrir öryggisstefnu efnis (Content Security Policy) óvirkan", + "gui_settings_website_label": "Stillingar vefsvæðis" } diff --git a/share/locale/it.json b/share/locale/it.json index 1ad1e1b5..ad8bc508 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -127,14 +127,14 @@ "error_invalid_private_key": "Questo tipo di chiave privata non è supportato", "connecting_to_tor": "In connessione alla rete Tor", "update_available": "E' disponibile una nuova versione di OnionShare. Clicca qui per scaricarla.

    Stai usando {} e l'ultima versione è {}.", - "update_error_check_error": "Non è possibile verificare per nuove versioni: il sito OnionShare dice che l'ultima versione non è riconoscibile '{}'…", + "update_error_check_error": "Non è possibile verificare per la nuova versione: il sito OnionShare dice che l'ultima versione non è riconoscibile '{}'…", "update_error_invalid_latest_version": "Non è possibile controllare per una nuova versione: Magari non sei connesso a Tor, o il sito OnionShare non funziona?", "update_not_available": "Stai usando la ultima versione di OnionShare.", "gui_tor_connection_ask": "Apri le impostazione per trovare la connessione a Tor?", "gui_tor_connection_ask_open_settings": "Sì", "gui_tor_connection_ask_quit": "Esci", "gui_tor_connection_error_settings": "Prova a modificare le impostazioni di come OnionShare si connette alla rete Tor.", - "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nVerifica la connessione a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.", + "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nAssicurati di essere connesso a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.", "gui_tor_connection_lost": "Disconnesso da Tor.", "gui_server_started_after_autostop_timer": "Il timer ad arresto automatico si è fermato prima dell'avvio del server. Si prega di fare una nuova condivisione.", "gui_server_autostop_timer_expired": "Il timer di arresto automatico è già scaduto. Si prega di aggiornarlo per iniziare la condivisione.", @@ -225,5 +225,7 @@ "days_first_letter": "d", "hours_first_letter": "h", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "incorrect_password": "Password non corretta", + "gui_settings_individual_downloads_label": "Disabilita per consentire il download di file singoli" } diff --git a/share/locale/ja.json b/share/locale/ja.json index 03f28f1e..66926c77 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -65,7 +65,7 @@ "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?", "gui_quit_warning_quit": "終了", "gui_quit_warning_dont_quit": "キャンセル", - "error_rate_limit": "誰かが何度も間違えたアドレスをアクセスして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスに送って下さい。", + "error_rate_limit": "誰かが何度パスワードを推測しようとして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスを送って下さい。", "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_ephemeral_not_supported": "OnionShareは少なくともTor 0.2.7.1とpython3-stem 1.4.0が必要です。", @@ -135,7 +135,7 @@ "gui_tor_connection_canceled": "Torと接続できませんでした。\n\nインターネット接続を確認してから、OnionShareを再開してTorとの接続を設定して下さい。", "gui_tor_connection_lost": "Torから切断されました。", "gui_server_started_after_autostop_timer": "サーバーが起動した前、自動停止タイマーがタイムアウトしました。\n再びファイル共有をして下さい。", - "gui_server_autostop_timer_expired": "自動停止タイマーはすでにタイムアウトしています。\n共有し始めるにはタイマーをアップデートして下さい。", + "gui_server_autostop_timer_expired": "自動停止タイマーはすでにタイムアウトしています。共有し始めるにはタイマーを調整して下さい。", "share_via_onionshare": "OnionShareで共有する", "gui_connect_to_tor_for_onion_settings": "onionサービス設定を見るのにTorと接続して下さい", "gui_use_legacy_v2_onions_checkbox": "レガシーアドレスを使用する", @@ -184,7 +184,7 @@ "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を再起動して下さい。", "error_cannot_create_data_dir": "OnionShareのデータフォルダーを作成できませんでした: {}", "receive_mode_data_dir": "受信されるファイルをこのフォルダーにあります: {}", "gui_settings_data_dir_label": "ファイルの保存", @@ -218,12 +218,20 @@ "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_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": "時間", "minutes_first_letter": "分", - "seconds_first_letter": "秒" + "seconds_first_letter": "秒", + "gui_website_url_description": "誰でも このOnionShareアドレスを知る限り、Torブラウザで サイトを訪れることができます: ", + "gui_mode_website_button": "ウェブサイトを発行する", + "gui_website_mode_no_files": "共有されたウェブサイトは未だありません", + "incorrect_password": "不正なパスワード", + "gui_settings_individual_downloads_label": "個別ファイルのダウンロード許可を与えるのにチェックを外す", + "history_requests_tooltip": "{} ウェブリクエスト", + "gui_settings_csp_header_disabled_option": "コンテンツセキュリティポリシーヘッダーを無効にする", + "gui_settings_website_label": "ウェブサイト設定" } diff --git a/share/locale/nl.json b/share/locale/nl.json index 84492104..0d414bc6 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -187,7 +187,7 @@ "gui_add_folder": "Voeg map toe", "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien", "gui_settings_data_dir_label": "Bewaar bestanden naar", - "gui_settings_data_dir_browse_button": "Browse", + "gui_settings_data_dir_browse_button": "Surf", "systray_page_loaded_message": "OnionShare adres geladen", "systray_share_started_title": "Delen Begonnen", "systray_share_started_message": "Begint bestanden aan iemand te sturen", diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index f0de1154..f3646a51 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -230,9 +230,9 @@ "incorrect_password": "senha incorreta", "gui_settings_individual_downloads_label": "Desmarque para permitir download de arquivos individuais", "gui_settings_csp_header_disabled_option": "Desabilitar cabeçalho Política de Segurança de Conteúdo", - "gui_website_url_description": "Qualquer pessoa com este endereço OnionShare pode visitar seu site usando o navegador Tor: ", - "gui_mode_website_button": "Publicar site", - "gui_website_mode_no_files": "Nenhum site compartilhado ainda", + "gui_website_url_description": "Qualquer um com este endereço OnionShare pode visitar seu site usando o navegador Tor: ", + "gui_mode_website_button": "Publicar Website", + "gui_website_mode_no_files": "Nenhum website compartilhado ainda", "history_requests_tooltip": "{} solicitações da web", - "gui_settings_website_label": "Configurações do site" + "gui_settings_website_label": "Configurações do Website" } diff --git a/share/locale/ro.json b/share/locale/ro.json index e0b4f8bc..c6b9c4d0 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -1,19 +1,19 @@ { "config_onion_service": "", - "preparing_files": "Comprima fisierele.", + "preparing_files": "Comprimare fișiere.", "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": "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": "Oprit pentru ca transferul s-a incheiat cu succes.", + "not_a_readable_file": "Fișierul {0:s} nu poate fi citit.", + "no_available_port": "Nu s-a putut găsi un port liber pentru a porni serviciul onion", + "other_page_loaded": "Adresă încărcată", + "close_on_autostop_timer": "Oprit deoarece s-a oprit cronometrul automat", + "closing_automatically": "Oprit pentru că transferul s-a încheiat cu succes", "timeout_download_still_running": "", - "large_filesize": "Avertisment: Transferul unui volum mare de date poate dura ore.", + "large_filesize": "Avertisment: Transferul unui volum mare de date poate dura ore", "systray_menu_exit": "Închidere", "systray_download_started_title": "", "systray_download_started_message": "", @@ -31,145 +31,145 @@ "help_verbose": "", "help_filename": "", "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", + "gui_drag_and_drop": "Tragere și plasare fișiere și directoare\npentru a începe partajarea", + "gui_add": "Adaugă", "gui_delete": "Şterge", "gui_choose_items": "Alegeți", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_autostop_timer": "", + "gui_share_start_server": "Începe partajarea", + "gui_share_stop_server": "Oprește partajarea", + "gui_share_stop_server_autostop_timer": "Oprire partajare ({})", "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": "Începeți modul de primire", + "gui_receive_stop_server": "Opriți modul de primire", + "gui_receive_stop_server_autostop_timer": "Opriți modul de primire (au rămas {})", "gui_receive_stop_server_autostop_timer_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", + "gui_copy_url": "Copiere adresă", + "gui_copy_hidservauth": "Copiere HidServAuth", "gui_downloads": "", "gui_no_downloads": "", "gui_canceled": "Anulat", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_copied_url_title": "Adresă OnionShare copiată", + "gui_copied_url": "Adresa OnionShare a fost copiată în memoria clipboard", + "gui_copied_hidservauth_title": "Am copiat HidServAuth", + "gui_copied_hidservauth": "Linia HidServAuth a fost copiată în clipboard", + "gui_please_wait": "Începem ... Faceți clic pentru a anula.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_quit_title": "Nu atât de repede", + "gui_share_quit_warning": "Sunteți în proces de trimitere fișiere. Sigur vreți să închideți OnionShare?", + "gui_receive_quit_warning": "Sunteți în proces de primire fișiere. Sigur vreți să închideți OnionShare?", "gui_quit_warning_quit": "Închidere", "gui_quit_warning_dont_quit": "Anulare", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_rate_limit": "Cineva a făcut prea multe încercări greșite pentru a ghici parola, astfel încât OnionShare a oprit serverul. Începeți partajarea din nou și trimiteți destinatarului o nouă adresă de partajat.", + "zip_progress_bar_format": "Compresare: %p%", + "error_stealth_not_supported": "Pentru a folosi autorizarea clientului, aveți nevoie de versiunile minim Tor 0.2.9.1-alfa (sau Tor Browser 6.5) cât și de python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare are nevoie de minim versiunea Tor 0.2.7.1 cât și de Python3-stem 1.4.0.", "gui_settings_window_title": "Setari", - "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_whats_this": "Ce este asta?", + "gui_settings_stealth_option": "Utilizați autorizarea clientului", + "gui_settings_stealth_hidservauth_string": "După ce v-ați salvat cheia privată pentru reutilizare, înseamnă că puteți face clic acum pentru a copia HidServAuth.", + "gui_settings_autoupdate_label": "Verificați dacă există o versiune nouă", + "gui_settings_autoupdate_option": "Anunțați-mă când este disponibilă o nouă versiune", + "gui_settings_autoupdate_timestamp": "Ultima verificare: {}", "gui_settings_autoupdate_timestamp_never": "Niciodata", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "Verificați versiunea nouă", "gui_settings_general_label": "Setări generale", - "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_sharing_label": "Setări de partajare", + "gui_settings_close_after_first_download_option": "Opriți partajarea după ce fișierele au fost trimise", + "gui_settings_connection_type_label": "Cum ar trebui să se conecteze OnionShare la Tor?", + "gui_settings_connection_type_bundled_option": "Utilizați versiunea Tor încorporată în OnionShare", + "gui_settings_connection_type_automatic_option": "Încercați configurarea automată cu Tor Browser", + "gui_settings_connection_type_control_port_option": "Conectați-vă cu portul de control", + "gui_settings_connection_type_socket_file_option": "Conectați-vă cu fișierul socket", + "gui_settings_connection_type_test_button": "Testează conexiunea la Tor", "gui_settings_control_port_label": "Port de control", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "Fișier socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Setări de autentificare Tor", + "gui_settings_authenticate_no_auth_option": "Fără autentificare sau autentificare cookie", "gui_settings_authenticate_password_option": "Parolă", "gui_settings_password_label": "Parolă", - "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_tor_bridges": "Suport pentru Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Nu folosiți poduri", + "gui_settings_tor_bridges_obfs4_radio_option": "Folosiți transporturi conectabile obfs4 integrate", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Folosiți transporturi conectabile obfs4 conectate (necesită obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizați transporturi conectabile meek_lite (Azure) încorporate", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Folosiți transporturi conectabile meek_lite (Azure) încorporate (necesită obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Avertisment: podurile meek_lite sunt foarte costisitoare pentru rularea Proiectului Tor.

    Utilizați-le numai dacă nuvă puteți conecta direct la Tor, prin transporturi obfs4 sau alte poduri normale.", + "gui_settings_tor_bridges_custom_radio_option": "Folosiți poduri personalizate", + "gui_settings_tor_bridges_custom_label": "Puteți obține poduri de la https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Niciunul din podurile adăugate nu funcționează.\nVerificați-le încă o dată sau adăugați altele.", "gui_settings_button_save": "Salvare", "gui_settings_button_cancel": "Anulare", "gui_settings_button_help": "Ajutor", - "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": "Folosiți cronometrul auto-stop", + "gui_settings_autostop_timer": "Opriți partajarea la:", + "settings_error_unknown": "Nu se poate face conectarea la controlerul Tor, deoarece setările dvs. nu au sens.", + "settings_error_automatic": "Nu s-a putut face conectarea la controlerul Tor. Tor Browser (disponibil de la torproject.org) rulează în fundal?", + "settings_error_socket_port": "Nu se poate face conectarea la controlerul Tor la {}:{}.", + "settings_error_socket_file": "Nu se poate face conectarea la controlerul Tor folosind fișierul socket {}.", + "settings_error_auth": "Conectat la {}:{}, dar nu se poate face autentificarea. Poate că nu este un controler Tor?", + "settings_error_missing_password": "Conectat la controlerul Tor, dar este nevoie de o parolă pentru autentificare.", + "settings_error_unreadable_cookie_file": "Conectat la controlerul Tor, dar parola poate fi greșită sau utilizatorului nu i se permite să citească fișierul cookie.", + "settings_error_bundled_tor_not_supported": "Utilizarea versiunii Tor care vine cu OnionShare nu funcționează în modul dezvoltator pe Windows sau macOS.", + "settings_error_bundled_tor_timeout": "Durează prea mult timp pentru a vă conecta la Tor. Poate nu sunteți conectat la Internet sau aveți un ceas al sistemului setat inexact?", + "settings_error_bundled_tor_broken": "OnionShare nu se poate conecta la Tor în fundal:\n{}", + "settings_test_success": "Conectat la controlerul Tor.\n\nVersiunea Tor: {}\nSuportă servicii efemere onion: {}.\nSuportă autentificarea clientului: {}.\nSuportă adrese next-gen .onion: {}.", + "error_tor_protocol_error": "A apărut o eroare cu Tor: {}", + "error_tor_protocol_error_unknown": "A apărut o eroare necunoscută cu Tor", "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "connecting_to_tor": "Conectarea la rețeaua Tor", + "update_available": "Noua versiune OnionShare. Clic aici pentru a o obține.

    Folosiți versiunea {} și ultima versiune este {}.", + "update_error_check_error": "Nu s-a putut verifica dacă există o versiune nouă: site-ul OnionShare spune că ultima versiune nu poate fi recunoscută '{}'…", + "update_error_invalid_latest_version": "Nu s-a putut verifica dacă există o versiune nouă: Poate nu sunteți conectat la Tor, sau site-ul OnionShare este închis?", + "update_not_available": "Rulează ultima versiune OnionShare.", + "gui_tor_connection_ask": "Deschideți setările pentru a sorta conexiunea la Tor?", "gui_tor_connection_ask_open_settings": "Da", "gui_tor_connection_ask_quit": "Închidere", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "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_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "Impartasirea creatiilor", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", + "gui_tor_connection_error_settings": "Încercați să schimbați în setări modul în care OnionShare se conectează la rețeaua Tor.", + "gui_tor_connection_canceled": "Nu se poate realiza conexiunea la Tor.\n\nVerificați dacă sunteți conectat la Internet, apoi redeschideți OnionShare și setați conexiunea la Tor.", + "gui_tor_connection_lost": "Deconectat de la Tor.", + "gui_server_started_after_autostop_timer": "Cronometrul de oprire automată a expirat înainte de pornirea serverului. Vă rugăm să faceți o nouă partajare.", + "gui_server_autostop_timer_expired": "Timpul pentru cronometrul auto-stop a expirat deja. Vă rugăm să îl modificați pentru a începe distribuirea.", + "share_via_onionshare": "Partajați prin OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Folosire adrese moștenite", + "gui_save_private_key_checkbox": "Folosiți o adresă persistentă", + "gui_share_url_description": "Oricine are această adresă OnionShare poate descărca fișierele dvs. folosind Tor Browser: ", + "gui_receive_url_description": "Oricine are această adresă OnionShare poate încărca fișiere pe computerul dvs. folosind Tor Browser: ", + "gui_url_label_persistent": "Această partajare nu se va opri automat.

    Fiecare acțiune ulterioară reutilizează adresa. (Pentru a utiliza adrese unice, dezactivați „Utilizați adresa persistentă” din setări.)", + "gui_url_label_stay_open": "Această partajare nu se va opri automat.", + "gui_url_label_onetime": "Această partajare se va opri după prima finalizare.", + "gui_url_label_onetime_and_persistent": "Această partajare nu se va opri automat.

    Fiecare acțiune ulterioară va reutiliza adresa. (Pentru a utiliza adrese unice, dezactivați „Utilizați adresa persistentă” din setări.)", + "gui_status_indicator_share_stopped": "Pregătit pentru partajare", + "gui_status_indicator_share_working": "Pornire…", + "gui_status_indicator_share_started": "Partajare", + "gui_status_indicator_receive_stopped": "Pregătit pentru primire", + "gui_status_indicator_receive_working": "Pornire…", "gui_status_indicator_receive_started": "Primire", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "gui_file_info": "{} fișiere, {}", + "gui_file_info_single": "{} fișier, {}", + "history_in_progress_tooltip": "{} în progres", + "history_completed_tooltip": "{} complet", "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": "Modul de recepție permite utilizatorilor să încarce fișiere pe computerul dvs.


    Unele fișiere pot prelua controlul computerului dacă le deschideți. Deschideți doar fișierele de la persoanele de încredere sau dacă știți ce faceți. ", + "receive_mode_upload_starting": "Începe încărcarea din dimensiunea totală {}", "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "gui_mode_share_button": "Partajare fișiere", + "gui_mode_receive_button": "Primire fișiere", + "gui_settings_receiving_label": "Setări de primire", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "Răsfoiește", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "Mod public", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "Pagină încărcată", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,7 +179,58 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_open_folder_error_nautilus": "Nu se poate deschide folderul deoarece nautilus nu este disponibil. Fișierul este aici: {}", + "gui_settings_language_label": "Limba preferată", + "gui_settings_language_changed_notice": "Reporniți OnionShare pentru a aplica noul limbaj.", + "gui_add_files": "Adaugă fișiere", + "gui_add_folder": "Adaugă director", + "gui_connect_to_tor_for_onion_settings": "Conectați-vă la Tor pentru a vedea setările serviciului onion", + "error_cannot_create_data_dir": "Nu s-a putut crea folderul de date OnionShare: {}", + "gui_settings_data_dir_label": "Salvare fișiere în", + "gui_settings_data_dir_browse_button": "Navigare", + "systray_page_loaded_message": "Adresa OnionShare a fost încărcată", + "systray_share_started_title": "Partajarea a început", + "systray_share_started_message": "Începeți să trimiteți cuiva fișiere", + "systray_share_completed_title": "Partajare completă", + "systray_share_completed_message": "Am terminat trimiterea fișierelor", + "systray_share_canceled_title": "Partajarea a fost anulată", + "systray_share_canceled_message": "Cineva a anulat primirea fișierelor", + "systray_receive_started_title": "A început primirea", + "systray_receive_started_message": "Cineva vă trimite fișiere", + "gui_all_modes_history": "Istoric", + "gui_all_modes_clear_history": "Ștergere toate", + "gui_all_modes_transfer_started": "Pornit {}", + "gui_all_modes_transfer_finished_range": "Transferat {} - {}", + "gui_all_modes_transfer_finished": "Transferat {}", + "gui_all_modes_progress_complete": "au trecut %p%, {0:s}.", + "gui_all_modes_progress_starting": "{0:s}, %p% (se calculează)", + "gui_all_modes_progress_eta": "{0:s}, Timp estimat: {1:s}, %p%", + "gui_share_mode_no_files": "Niciun fișier trimis încă", + "gui_receive_mode_no_files": "Încă nu au fost primite fișiere", + "gui_all_modes_transfer_canceled_range": "Anulat {} - {}", + "gui_all_modes_transfer_canceled": "Anulat {}", + "gui_settings_onion_label": "Setări Onion", + "gui_stop_server_autostop_timer_tooltip": "Cronometrul de oprire automată se oprește la {}", + "gui_start_server_autostart_timer_tooltip": "Cronometrul de pornire automată se oprește la {}", + "gui_waiting_to_start": "Programat pentru a începe în {}. Click pentru a anula.", + "gui_settings_autostart_timer_checkbox": "Folosiți cronometrul de pornire automată", + "gui_settings_autostart_timer": "Porniți partajarea la:", + "gui_server_autostart_timer_expired": "Ora programată a trecut deja. Vă rugăm să o reajustați pentru a începe distribuirea.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Ora de oprire automată nu poate fi aceeași sau mai devreme decât ora de pornire automată. Vă rugăm să o ajustați pentru a începe distribuirea.", + "gui_status_indicator_share_scheduled": "Programat …", + "gui_status_indicator_receive_scheduled": "Programat …", + "gui_share_mode_autostop_timer_waiting": "Se așteaptă să se termine trimiterea", + "gui_receive_mode_autostop_timer_waiting": "Se așteaptă să se termine primirea", + "days_first_letter": "zi", + "hours_first_letter": "ore", + "minutes_first_letter": "min", + "seconds_first_letter": "s", + "gui_website_url_description": "Oricine are această adresă OnionShare poate vizita website-ul dvs. folosind Tor Browser: ", + "gui_mode_website_button": "Publicare site web", + "gui_website_mode_no_files": "Niciun site nu a fost partajat încă", + "incorrect_password": "Parolă incorectă", + "gui_settings_individual_downloads_label": "Debifează pentru a permite descărcarea fișierelor individuale", + "history_requests_tooltip": "{} solicitări web", + "gui_settings_csp_header_disabled_option": "Dezactivează antetul Politicii de securitate a conținutului", + "gui_settings_website_label": "Setări pentru website" } diff --git a/share/locale/sr_Latn.json b/share/locale/sr_Latn.json index bdabb89c..7fc22ce2 100644 --- a/share/locale/sr_Latn.json +++ b/share/locale/sr_Latn.json @@ -1,183 +1,183 @@ { - "preparing_files": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "incorrect_password": "", - "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": "", - "gui_settings_onion_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_csp_header_disabled_option": "", - "gui_settings_individual_downloads_label": "", - "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": "", - "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_website_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": "", - "history_requests_tooltip": "", - "error_cannot_create_data_dir": "", - "gui_receive_mode_warning": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_mode_website_button": "", - "gui_settings_receiving_label": "", - "gui_settings_website_label": "", - "gui_settings_data_dir_label": "", - "gui_settings_data_dir_browse_button": "", - "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_website_mode_no_files": "", - "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": "" + "preparing_files": "Komprimujem fajlove.", + "not_a_readable_file": "(0:s) nije čitljiv fajl.", + "no_available_port": "Ne mogu da pronađem raspoloživi port da bih počeo onion servis", + "other_page_loaded": "Adresa učitana", + "incorrect_password": "Pogrešna lozinka", + "close_on_autostop_timer": "Prekid rada zato što je isteklo vreme na auto-stop tajmeru", + "closing_automatically": "Prekid rada zato što je prenos završen", + "large_filesize": "Upozorenje: Slanje velikih fajlova može trajati satima", + "gui_drag_and_drop": "Prevuci i otpusti datoteke i fascikle\nda bi započeo deljenje", + "gui_add": "Dodaj", + "gui_add_files": "Dodaj datoteke", + "gui_add_folder": "Dodat fascikle", + "gui_delete": "Obriši", + "gui_choose_items": "Odaberi", + "gui_share_start_server": "Započni deljenje", + "gui_share_stop_server": "Prekini deljenje", + "gui_share_stop_server_autostop_timer": "Prekini deljenje ({})", + "gui_stop_server_autostop_timer_tooltip": "Auto-stop tajmer se zaustavlja na {}", + "gui_start_server_autostart_timer_tooltip": "Auto-start tajmer se zaustavlja na {}", + "gui_receive_start_server": "Započni režim primanja", + "gui_receive_stop_server": "Prekini režim primanja", + "gui_receive_stop_server_autostop_timer": "Prekini režim primanja ({} preostalo)", + "gui_copy_url": "Kopiraj adresu", + "gui_copy_hidservauth": "Kopiraj HidServAuth", + "gui_canceled": "Obustavljeno", + "gui_copied_url_title": "Kopirana OnionShare adresa", + "gui_copied_url": "OnionShare adresa kopirana u privremenu memoriju", + "gui_copied_hidservauth_title": "Kopiran HidServAuth", + "gui_copied_hidservauth": "HidServAuth linija kopirana u privremenu memoriju", + "gui_waiting_to_start": "Planirano da počne u {}. Klikni da obustaviš.", + "gui_please_wait": "Počinje… Klikni da obustaviš.", + "gui_quit_title": "Ne tako brzo", + "gui_share_quit_warning": "Proces slanja datoteka u toku. Jeste li sigurni da želite da zaustavite OnionShare?", + "gui_receive_quit_warning": "Proces primanja datoteka u toku. Jeste li sigurni da želite da zaustavite OnionShare?", + "gui_quit_warning_quit": "Izađi", + "gui_quit_warning_dont_quit": "Odustani", + "error_rate_limit": "Neko je načinio suviše pogrešnih pokušaja da pogodi tvoju lozinku, tako da je OnionShare zaustavio server. Počni deljenje ponovo i pošalji primaocu novu adresu za deljenje.", + "zip_progress_bar_format": "Komprimujem: %p%", + "error_stealth_not_supported": "Da bi koristion klijen autorizaciju, potrebni su ti barem Tor 0.2.9.1-alpha (ili Tor Browser 6.5) i python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare zahteva barem Tor 0.2.7.1 i python3-stem 1.4.0.", + "gui_settings_window_title": "Podešavanja", + "gui_settings_whats_this": "Šta je ovo?", + "gui_settings_stealth_option": "Koristi klijent autorizaciju", + "gui_settings_stealth_hidservauth_string": "Ako si sačuvao svoj privatni ključ za ponovnu upotrenu, sada možeš kliknuti da iskopiraš svoj HidServAuth.", + "gui_settings_autoupdate_label": "Proveri da li postoji nova verzija", + "gui_settings_autoupdate_option": "Obavesti me kada nova verzija bude na raspolaganju", + "gui_settings_autoupdate_timestamp": "Poslednja provera: {}", + "gui_settings_autoupdate_timestamp_never": "Nikada", + "gui_settings_autoupdate_check_button": "Proveri da li postoji nova verzija", + "gui_settings_general_label": "Generalna podešavanja", + "gui_settings_onion_label": "Onion podešavanja", + "gui_settings_sharing_label": "Podešavanja deljenja", + "gui_settings_close_after_first_download_option": "Prekini deljenje kada datoteke budu poslate", + "gui_settings_csp_header_disabled_option": "Onemogući zaglavlje Pravilnika o sigurnosti sadržaja", + "gui_settings_individual_downloads_label": "Poništi izbor da bi dozvolio preuzimanje pojedinačnih datoteka", + "gui_settings_connection_type_label": "Kako bi OnionShare trebalo povezati sa Torom?", + "gui_settings_connection_type_bundled_option": "Koristi verziju Tora ugrađenu u OnionShare", + "gui_settings_connection_type_automatic_option": "Pokušaj automatsku konfiguraciju pomoću Tor pretraživača", + "gui_settings_connection_type_control_port_option": "Poveži se koristeći kontrolni port", + "gui_settings_connection_type_socket_file_option": "Poveži se koristeći datoteku priključka", + "gui_settings_connection_type_test_button": "Testiranje veze sa Torom", + "gui_settings_control_port_label": "Kontrolni port", + "gui_settings_socket_file_label": "Datoteka priključka", + "gui_settings_socks_label": "SOCKS priključak", + "gui_settings_authenticate_label": "Podešavanje autentifikacije Tora", + "gui_settings_authenticate_no_auth_option": "Bez autentifikacije ili autentifikacija kolačićem", + "gui_settings_authenticate_password_option": "Lozinka", + "gui_settings_password_label": "Lozinka", + "gui_settings_tor_bridges": "Most podrška za Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ne koristi mostove", + "gui_settings_tor_bridges_obfs4_radio_option": "Koristi ugrađene obfs4 dodatne prenose", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Koristi ugrađene obfs4 dodatne prenose (potreban obfs4proksi)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Koristi ugrađene meek_lite (Azure) dodatne prenose", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Koristi ugrađene meek_lite (Azure) dodatne prenose (potreban obfs4proksi)", + "gui_settings_meek_lite_expensive_warning": "Upozorenje: meek_lite mostovi su vrlo skupi za Tor projekat da ih koristi.

    Koristi ih samo ako ne možeš da se povežeš na Tor direktno, preko obfs4 transporta ili drugih redovnih mostova.", + "gui_settings_tor_bridges_custom_radio_option": "Koristi prilagođene mostove", + "gui_settings_tor_bridges_custom_label": "Mostove možeš dobiti od https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Nijedan od mostova koje si dodao ne funkcioniše.\nProveri ih ponovo ili dodaj druge.", + "gui_settings_button_save": "Sačuvaj", + "gui_settings_button_cancel": "Odustani", + "gui_settings_button_help": "Pomoć", + "gui_settings_autostop_timer_checkbox": "Koristi tajmer za automatsko zaustavljanje", + "gui_settings_autostop_timer": "Zaustavi deljenje na:", + "gui_settings_autostart_timer_checkbox": "Koristi tajmer automatskog pokretanja", + "gui_settings_autostart_timer": "Započni deljenje na:", + "settings_error_unknown": "Nije moguće povezati se sa Tor kontrolerom jer tvoje postavke nemaju smisla.", + "settings_error_automatic": "Nije moguće povezati se sa Tor kontrolerom. Da li Tor pregledač (dostupan na torproject.org) radi u pozadini?", + "settings_error_socket_port": "Nije moguće povezati se sa Tor kontrolerom na {}: {}.", + "settings_error_socket_file": "Nije moguće povezati se na Tor kontroler pomoću datoteke priključka {}.", + "settings_error_auth": "Povezan na {}: {}, ali nije moguća autentifikacija. Možda ovo nije Tor kontroler?", + "settings_error_missing_password": "Povezan sa Tor kontrolerom, ali on zahteva lozinku za autentifikaciju.", + "settings_error_unreadable_cookie_file": "Povezan sa Tor kontrolerom, ali je lozinka možda pogrešna ili tvomj korisniku nije dozvoljeno da pročita datoteku kolačića.", + "settings_error_bundled_tor_not_supported": "Korišćenje verzije Tora koja se isporučuje sa OnionShare ne radi u razvojnom režimu u operativnom sistemu Windows ili macOS.", + "settings_error_bundled_tor_timeout": "Predugo traje povezivanje sa Torom. Možda nisi povezan sa Internetom ili imaš netačan sistemski sat?", + "settings_error_bundled_tor_broken": "OnionShare ne može da se poveže sa Torom u pozadini:\n{}", + "settings_test_success": "Povezan sa Tor kontrolerom.\n\nTor verzija: {}\nPodržava povremene onion usluge: {}.\nPodržava autentifikaciju klijenta: {}.\nPodržava next-gen .onion adrese: {}.", + "error_tor_protocol_error": "Došlo je do greške sa Torom: {}", + "error_tor_protocol_error_unknown": "Došlo je do nepoznate greške sa Torom", + "connecting_to_tor": "Povezivanje sa Tor mrežom", + "update_available": "Novi OnionShare objavljen. Klikni ovde da bi ga dobio.

    Trenutno koristiš {} a najnoviji je {}.", + "update_error_check_error": "Nije moguće proveriti novu verziju: na sajtu OnionShare piše da je najnovija verzija neprepoznatljiva ' {} '…", + "update_error_invalid_latest_version": "Nije moguće proveriti novu verziju: možda niste povezani sa Torom ili je sajt OnionShare oboren?", + "update_not_available": "Da li koristite najnoviji OnionShare.", + "gui_tor_connection_ask": "Da li otvoriti podešavanja da biste podesili vezu sa Torom?", + "gui_tor_connection_ask_open_settings": "Da", + "gui_tor_connection_ask_quit": "Odustani", + "gui_tor_connection_error_settings": "Pokušaj da u podešavanjima promeniš način na koji se OnionShare povezuje sa Tor mrežom.", + "gui_tor_connection_canceled": "Nije moguće povezati se sa Torom.\n\nProveri da li si povezan sa Internetom, a zatim ponovo pokreni OnionShare i podesi vezu sa Torom.", + "gui_tor_connection_lost": "Prekinuta veza sa Torom.", + "gui_server_started_after_autostop_timer": "Tajmer automatskog zaustavljanja je odbrojao pre početka rada servera. Unesi novi deo.", + "gui_server_autostop_timer_expired": "Tajmer automatskog zaustavljanja je već odbrojao. Podesi ga da bi započelo deljenje.", + "gui_server_autostart_timer_expired": "Predviđeno vreme je već prošlo. Podesi ga da bi započelo deljenje.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Vreme automatskog zaustavljanja ne može biti isto ili ranije od vremena početka automatskog pokretanja. Podesi ga da bi započelo deljenje.", + "share_via_onionshare": "Deljenje pomoću OnionShare", + "gui_connect_to_tor_for_onion_settings": "Poveži se sa Torom da bi video postavke onion servisa", + "gui_use_legacy_v2_onions_checkbox": "Koristi nasleđene adrese", + "gui_save_private_key_checkbox": "Koristi trajnu adresu", + "gui_share_url_description": "Svako sa ovom OnionShare sdresom može preuzeti tvoje datoteke koristeći Tor Browser: ", + "gui_website_url_description": "Svako sa ovom OnionShare adresom može posetiti tvoju veb-stranicu koristeći Tor Browser: ", + "gui_receive_url_description": "Svako sa ovom OnionShare adresom može poslati datoteke na tvoj računar koristeći Tor Browser: ", + "gui_url_label_persistent": "Ovo deljenje neće se automatski zaustaviti.

    Svako sledeće deljenje ponovo koristi istu adresu. (Da bi koristio jednokratnu adresu, isključi opciju \"koristi trajnu adresu\" u podešavanjima.)", + "gui_url_label_stay_open": "Ovaj deljenje neće se automatski zaustaviti.", + "gui_url_label_onetime": "Ovaj deljenje će se zaustaviti nakon prvog dovršenja.", + "gui_url_label_onetime_and_persistent": "Ovaj deljenje neće se automatski zaustaviti.

    Svako naredno deljenje ponovo će koristiti istu adresu. (Da bi koristio jednokratnu adresu, isključi opciju \"Koristi trajnu adresu\" u podešavanjima.)", + "gui_status_indicator_share_stopped": "Spremno za deljenje", + "gui_status_indicator_share_working": "Počinje…", + "gui_status_indicator_share_scheduled": "Planirano…", + "gui_status_indicator_share_started": "Deljenje", + "gui_status_indicator_receive_stopped": "Spremno za prijem", + "gui_status_indicator_receive_working": "Počinje…", + "gui_status_indicator_receive_scheduled": "Planirano…", + "gui_status_indicator_receive_started": "Primanje", + "gui_file_info": "{} datoteke, {}", + "gui_file_info_single": "{} datoteka, {}", + "history_in_progress_tooltip": "{} u toku", + "history_completed_tooltip": "{} završeno", + "history_requests_tooltip": "{} web zahtevi", + "error_cannot_create_data_dir": "Nije moguće kreirati OnionShare fasciklu sa podacima: {}", + "gui_receive_mode_warning": "Režim prijema dozvoljava korisnicima da šalju datoteke na tvoj računar.

    Neke datoteke mogu da preuzmu kontrolu nad tvojim računarom ako ih otvoriš. Otvaraj samo stvari od ljudi kojima veruješ ili ako znaš šta radiš. ", + "gui_mode_share_button": "Podeli datoteke", + "gui_mode_receive_button": "Prijem datoteka", + "gui_mode_website_button": "Objavljivanje web stranice", + "gui_settings_receiving_label": "Podešavanja prijema", + "gui_settings_website_label": "Podešavanja web lokacije", + "gui_settings_data_dir_label": "Snimi datoteke u", + "gui_settings_data_dir_browse_button": "Potraži", + "gui_settings_public_mode_checkbox": "Javni režim", + "gui_open_folder_error_nautilus": "Nije moguće otvoriti fasciklu jer Nautilus nije dostupan. Datoteka je ovde: {}", + "gui_settings_language_label": "Željeni jezik", + "gui_settings_language_changed_notice": "Ponovo pokreni OnionShare da bi novi jezik bio primenjen.", + "systray_menu_exit": "Izađi", + "systray_page_loaded_title": "Stranica učitana", + "systray_page_loaded_message": "OnionShare adresa učitana", + "systray_share_started_title": "Deljenje započeto", + "systray_share_started_message": "Početak slanja datoteka nekome", + "systray_share_completed_title": "Deljenje završeno", + "systray_share_completed_message": "Slanje datoteka završeno", + "systray_share_canceled_title": "Deljenje otkazano", + "systray_share_canceled_message": "Neko je otkazao primanje tvojih datoteka", + "systray_receive_started_title": "Prijem započet", + "systray_receive_started_message": "Neko ti šalje datoteke", + "gui_all_modes_history": "Istorija", + "gui_all_modes_clear_history": "Izbriši sve", + "gui_all_modes_transfer_started": "Započeto {}", + "gui_all_modes_transfer_finished_range": "Preneto {}-{}", + "gui_all_modes_transfer_finished": "Preneto {}", + "gui_all_modes_transfer_canceled_range": "Otkazano {}-{}", + "gui_all_modes_transfer_canceled": "Otkazano {}", + "gui_all_modes_progress_complete": "% p%, {0: s} je proteklo.", + "gui_all_modes_progress_starting": "{0: s},% p% (proračunavam)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Nijedna datoteka još nije poslata", + "gui_share_mode_autostop_timer_waiting": "Čekam na završetak slanja", + "gui_website_mode_no_files": "Još nijedna web stranica nije podeljena", + "gui_receive_mode_no_files": "Još nijedna datoteka nije primljena", + "gui_receive_mode_autostop_timer_waiting": "Čekam na završetak prijema", + "receive_mode_upload_starting": "Slanje ukupne veličine od {} počinje", + "days_first_letter": "d", + "hours_first_letter": "h", + "minutes_first_letter": "m", + "seconds_first_letter": "s" } diff --git a/share/locale/uk.json b/share/locale/uk.json index 2c01766c..93080c67 100644 --- a/share/locale/uk.json +++ b/share/locale/uk.json @@ -34,7 +34,7 @@ "gui_receive_quit_warning": "Відбувається отримання файлів. Ви впевнені, що бажаєте вийти з OnionShare?", "gui_quit_warning_quit": "Вийти", "gui_quit_warning_dont_quit": "Відміна", - "error_rate_limit": "Хтось зробив занадто багато невдалих спроб за вашою адресою, схоже на намагання вгадати її, тому OnionShare зупинив сервер. Почніть поширення і надішліть одержувачу нову адресу для поширення.", + "error_rate_limit": "Хтось здійснив занадто багато невдалих спроб підключитися до вашого серверу, тому OnionShare зупинив сервер. Розпочніть поширення знову і надішліть одержувачу нову адресу для поширення.", "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_ephemeral_not_supported": "OnionShare потребує принаймні Tor 0.2.7.1 і python3-stem 1.4.0.", @@ -96,20 +96,20 @@ "error_tor_protocol_error_unknown": "Сталася невідома помилка з Tor", "connecting_to_tor": "Підключення до мережі Tor", "update_available": "Новий OnionShare вийшов. Натисніть тут щоб його отримати.

    Ваша версія {}, а остання {}.", - "update_error_check_error": "Не вдалося перевірити наявність нових версій: сайт OnionShare говорить, що остання версія є невпізнанним '{}'…", + "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_canceled": "Не вдалося підключитися до Tor. \n\nПереконайтеся, що ви підключені до мережі Інтернет, відкрийте OnionShare знову і налаштуйте підключення до Tor.", "gui_tor_connection_lost": "Відключено від Tor.", "gui_server_started_after_autostop_timer": "Таймер автоспину збіг перед початком роботи сервера. Будь ласка, зробіть нове поширення.", - "gui_server_autostop_timer_expired": "Таймер автоспину вже збіг. Будь ласка, поновіть його, щоб почати поширення.", + "gui_server_autostop_timer_expired": "Час автоспину не може бути однаковим або раніше часу автоматичного запуску. Будь ласка, поновіть його, щоб почати поширення.", "gui_server_autostart_timer_expired": "Запланований час вже минув. Будь ласка, поновіть його, щоб почати поширення.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Час автоспину не може бути однаковим або ранішим за час автоматичного запуску. Будь ласка, перевірте його для початку поширення.", - "share_via_onionshare": "OnionShare це", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Таймер автоспину вже збіг. Будь ласка, поновіть його, щоб почати поширення.", + "share_via_onionshare": "Поширюйте через OnionShare", "gui_connect_to_tor_for_onion_settings": "Підключіться до Tor, щоб побачити параметри служби onion", "gui_use_legacy_v2_onions_checkbox": "Використовувати адреси попередньої версії", "gui_save_private_key_checkbox": "Використовувати постійну адресу", @@ -171,5 +171,13 @@ "days_first_letter": "д", "hours_first_letter": "г", "minutes_first_letter": "х", - "seconds_first_letter": "с" + "seconds_first_letter": "с", + "gui_website_url_description": "Будь-хто з цією адресою OnionShare може відвідати ваш веб-сайт за допомогою Tor Browser: ", + "gui_mode_website_button": "Опублікувати веб-сайт", + "gui_website_mode_no_files": "Немає опублікованих веб-сайтів", + "incorrect_password": "Неправильний пароль", + "gui_settings_individual_downloads_label": "Зніміть прапорець, щоб дозволити завантаження окремих файлів", + "history_requests_tooltip": "{} веб-запити", + "gui_settings_csp_header_disabled_option": "Відключити заголовок Політики захисту контенту (CSP)", + "gui_settings_website_label": "Налаштування веб-сайту" } diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index a4a4f9f7..3f3cbbf7 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "您有文件在接收中……确定要退出 OnionShare 吗?", "gui_quit_warning_quit": "退出", "gui_quit_warning_dont_quit": "取消", - "error_rate_limit": "有人对您的地址发出了过多的错误请求,这说明他们可能在尝试猜测您的地址,因此 OinionShare 已停止服务。请重新开启共享并且向接收人发送新的共享地址。", + "error_rate_limit": "有人发出了过多错误请求来猜测您的地址,因此 OinionShare 已停止服务。请重新开启共享并且向接收人发送新的共享地址。", "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_ephemeral_not_supported": "OnionShare 最低版本要求为 Tor 0.2.7.1 和 python3-stem 1.4.0。", @@ -129,11 +129,11 @@ "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_canceled": "无法连接至 Tor。\n\n请确保您已连接至互联网,然后重启 OnionShare 并设置与 Tor 的连接。", "gui_tor_connection_lost": "已断开与 Tor 的连接。", "gui_server_started_after_autostop_timer": "在服务器启动之前,自动停止的定时器的计时已到。请建立一个新的共享。", - "gui_server_autostop_timer_expired": "自动停止的定时器计时已到。请更新以开始共享。", - "share_via_onionshare": "用 OnionShare 来共享", + "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 Browser 下载您的文件:", @@ -181,7 +181,7 @@ "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 以使应用新的语言。", "gui_add_files": "添加文件", "gui_add_folder": "添加文件夹", "gui_connect_to_tor_for_onion_settings": "连接至 Tor 以查看 onion 服务的设置", @@ -216,14 +216,22 @@ "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_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": "已计划……", "days_first_letter": "天", "hours_first_letter": "小时", "minutes_first_letter": "分", - "seconds_first_letter": "秒" + "seconds_first_letter": "秒", + "gui_website_url_description": "任何使用此 OnionShare 地址的人可以使用 Tor 浏览器访问你的网站:", + "gui_mode_website_button": "发布网站", + "gui_website_mode_no_files": "尚未分享网站", + "incorrect_password": "密码错误", + "gui_settings_individual_downloads_label": "取消选择来允许下载单独的文件", + "history_requests_tooltip": "{}个网络请求", + "gui_settings_csp_header_disabled_option": "禁用内容安全策略标题", + "gui_settings_website_label": "网站设置" } diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index 0567004f..2ef90d43 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "仍在接收檔案,您確定要結束OnionShare嗎?", "gui_quit_warning_quit": "結束", "gui_quit_warning_dont_quit": "取消", - "error_rate_limit": "有人嘗試過多次您的地址,代表他們可能是用猜的,因此OnionShare已經停止服務。再次啟動分享並傳送新的地址給接收者以開始分享。", + "error_rate_limit": "有人嘗試猜測您的密碼太多次,因此OnionShare已經停止服務。再次啟動分享並傳送新的地址給接收者以開始分享。", "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_ephemeral_not_supported": "OnionShare 需要至少 Tor 0.2.7.1 以及 python3-stem 1.4.0.", @@ -132,7 +132,7 @@ "gui_tor_connection_canceled": "無法連接到Tor。\n\n請確認您已連接上網路,然後再重新開啟OnionShare並設定Tor連線。", "gui_tor_connection_lost": "已斷開Tor連接。", "gui_server_started_after_autostop_timer": "自動停止計時器在伺服器啟動前就時間已到。\n請重新分享。", - "gui_server_autostop_timer_expired": "自動停止計時器時間已到。\n請調整它來開始分享。", + "gui_server_autostop_timer_expired": "自動停止計時器時間已到。請調整它來開始分享。", "share_via_onionshare": "使用OnionShare分享", "gui_use_legacy_v2_onions_checkbox": "使用傳統地址", "gui_save_private_key_checkbox": "使用永久地址", @@ -181,7 +181,7 @@ "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以使用新的語言。", "gui_add_files": "新增檔案", "gui_add_folder": "新增資料夾", "gui_settings_onion_label": "Onion設定", @@ -219,7 +219,7 @@ "gui_settings_autostart_timer_checkbox": "使用自動開始計時器", "gui_settings_autostart_timer": "開始分享於:", "gui_server_autostart_timer_expired": "排定的時間已經過了。請調整它以開始分享。", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "自動停止時間不能相同或早於自動開始時間。請更新它以開始分享。", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "自動停止時間不能相同或早於自動開始時間。請調整它以開始分享。", "gui_status_indicator_share_scheduled": "預定…", "gui_status_indicator_receive_scheduled": "預定…", "gui_share_mode_autostop_timer_waiting": "等待完成傳送", @@ -229,5 +229,11 @@ "minutes_first_letter": "分", "seconds_first_letter": "秒", "incorrect_password": "密碼錯誤", - "gui_settings_csp_header_disabled_option": "停用Content Security Policy標頭" + "gui_settings_csp_header_disabled_option": "停用Content Security Policy標頭", + "gui_website_url_description": "有OnionShare位址的任何人都可以使用Tor瀏覽器造訪你的網站: : ", + "gui_mode_website_button": "發佈網站", + "gui_website_mode_no_files": "尚未分享網站", + "gui_settings_individual_downloads_label": "取消選取以允許下載個別的檔案", + "history_requests_tooltip": "{}個網頁請求", + "gui_settings_website_label": "網站設定" } -- cgit v1.2.3-54-g00ecf From 303772789048bcf24d4f937ae47014af4b20781c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 12 Oct 2019 21:01:25 -0700 Subject: Format all code using black --- install/check_lacked_trans.py | 69 +- install/get-tor-osx.py | 83 +- install/get-tor-windows.py | 50 +- install/scripts/onionshare-nautilus.py | 34 +- onionshare/__init__.py | 206 +++-- onionshare/common.py | 249 +++--- onionshare/onion.py | 438 +++++++---- onionshare/onionshare.py | 20 +- onionshare/settings.py | 157 ++-- onionshare/strings.py | 9 +- onionshare/web/receive_mode.py | 284 ++++--- onionshare/web/send_base_mode.py | 139 ++-- onionshare/web/share_mode.py | 185 +++-- onionshare/web/web.py | 186 +++-- onionshare/web/website_mode.py | 40 +- onionshare_gui/__init__.py | 51 +- onionshare_gui/mode/__init__.py | 124 ++- onionshare_gui/mode/file_selection.py | 91 ++- onionshare_gui/mode/history.py | 283 ++++--- onionshare_gui/mode/receive_mode/__init__.py | 101 ++- onionshare_gui/mode/share_mode/__init__.py | 76 +- onionshare_gui/mode/share_mode/threads.py | 15 +- onionshare_gui/mode/website_mode/__init__.py | 51 +- onionshare_gui/onionshare_gui.py | 406 +++++++--- onionshare_gui/server_status.py | 288 +++++-- onionshare_gui/settings_dialog.py | 862 ++++++++++++++------- onionshare_gui/threads.py | 65 +- onionshare_gui/tor_connection_dialog.py | 43 +- onionshare_gui/update_checker.py | 98 ++- onionshare_gui/widgets.py | 22 +- setup.py | 142 ++-- tests/GuiBaseTest.py | 227 +++--- tests/GuiReceiveTest.py | 85 +- tests/GuiShareTest.py | 171 ++-- tests/GuiWebsiteTest.py | 61 +- tests/SettingsGuiBaseTest.py | 158 +++- tests/TorGuiBaseTest.py | 114 +-- tests/TorGuiReceiveTest.py | 31 +- tests/TorGuiShareTest.py | 29 +- tests/conftest.py | 53 +- ...onshare_401_public_mode_skips_ratelimit_test.py | 9 +- ...local_onionshare_401_triggers_ratelimit_test.py | 8 +- ...e_quitting_during_share_prompts_warning_test.py | 8 +- ...nionshare_receive_mode_clear_all_button_test.py | 7 +- tests/local_onionshare_receive_mode_timer_test.py | 9 +- ...re_receive_mode_upload_non_writable_dir_test.py | 8 +- ...ode_upload_public_mode_non_writable_dir_test.py | 9 +- ...onshare_receive_mode_upload_public_mode_test.py | 9 +- tests/local_onionshare_receive_mode_upload_test.py | 8 +- ...l_onionshare_settings_dialog_legacy_tor_test.py | 2 +- ...local_onionshare_settings_dialog_no_tor_test.py | 2 +- ...local_onionshare_settings_dialog_v3_tor_test.py | 2 +- ...e_autostart_and_autostop_timer_mismatch_test.py | 4 +- ...l_onionshare_share_mode_autostart_timer_test.py | 9 +- ...re_share_mode_autostart_timer_too_short_test.py | 13 +- ...ocal_onionshare_share_mode_cancel_share_test.py | 8 +- ..._onionshare_share_mode_clear_all_button_test.py | 8 +- ...onshare_share_mode_download_public_mode_test.py | 8 +- ...nionshare_share_mode_download_stay_open_test.py | 8 +- tests/local_onionshare_share_mode_download_test.py | 7 +- ...are_mode_individual_file_view_stay_open_test.py | 8 +- ...onshare_share_mode_individual_file_view_test.py | 8 +- ...al_onionshare_share_mode_large_download_test.py | 7 +- ...ionshare_share_mode_password_persistent_test.py | 4 +- tests/local_onionshare_share_mode_timer_test.py | 9 +- ...l_onionshare_share_mode_timer_too_short_test.py | 13 +- ...l_onionshare_share_mode_unreadable_file_test.py | 7 +- ...cal_onionshare_website_mode_csp_enabled_test.py | 10 +- tests/local_onionshare_website_mode_test.py | 10 +- .../onionshare_790_cancel_on_second_share_test.py | 7 +- ...onshare_receive_mode_upload_public_mode_test.py | 9 +- tests/onionshare_receive_mode_upload_test.py | 8 +- tests/onionshare_share_mode_cancel_share_test.py | 8 +- ...onshare_share_mode_download_public_mode_test.py | 8 +- ...nionshare_share_mode_download_stay_open_test.py | 8 +- tests/onionshare_share_mode_download_test.py | 7 +- tests/onionshare_share_mode_persistent_test.py | 4 +- tests/onionshare_share_mode_stealth_test.py | 9 +- tests/onionshare_share_mode_timer_test.py | 9 +- ...nshare_share_mode_tor_connection_killed_test.py | 6 +- tests/onionshare_share_mode_v2_onion_test.py | 8 +- tests/test_helpers.py | 2 +- tests/test_onionshare.py | 13 +- tests/test_onionshare_common.py | 285 ++++--- tests/test_onionshare_settings.py | 181 +++-- tests/test_onionshare_strings.py | 19 +- tests/test_onionshare_web.py | 106 +-- 87 files changed, 4317 insertions(+), 2398 deletions(-) diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 5ccce923..62986707 100755 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -33,12 +33,25 @@ import fileinput, argparse, re, os, codecs, json, sys def arg_parser(): desc = __doc__.strip().splitlines()[0] p = argparse.ArgumentParser(description=desc) - p.add_argument('-d', default='.', help='onionshare directory', - metavar='ONIONSHARE_DIR', dest='onionshare_dir') - p.add_argument('--show-all-keys', action='store_true', - help='show translation key in source and exit'), - p.add_argument('-l', default='all', help='language code (default: all)', - metavar='LANG_CODE', dest='lang_code') + p.add_argument( + "-d", + default=".", + help="onionshare directory", + metavar="ONIONSHARE_DIR", + dest="onionshare_dir", + ) + p.add_argument( + "--show-all-keys", + action="store_true", + help="show translation key in source and exit", + ), + p.add_argument( + "-l", + default="all", + help="language code (default: all)", + metavar="LANG_CODE", + dest="lang_code", + ) return p @@ -54,27 +67,29 @@ def main(): dir = args.onionshare_dir - src = files_in(dir, 'onionshare') + \ - files_in(dir, 'onionshare_gui') + \ - files_in(dir, 'onionshare_gui/mode') + \ - files_in(dir, 'onionshare_gui/mode/share_mode') + \ - files_in(dir, 'onionshare_gui/mode/receive_mode') + \ - files_in(dir, 'onionshare_gui/mode/website_mode') + \ - files_in(dir, 'install/scripts') + \ - files_in(dir, 'tests') - pysrc = [p for p in src if p.endswith('.py')] + src = ( + files_in(dir, "onionshare") + + files_in(dir, "onionshare_gui") + + files_in(dir, "onionshare_gui/mode") + + files_in(dir, "onionshare_gui/mode/share_mode") + + files_in(dir, "onionshare_gui/mode/receive_mode") + + files_in(dir, "onionshare_gui/mode/website_mode") + + files_in(dir, "install/scripts") + + files_in(dir, "tests") + ) + pysrc = [p for p in src if p.endswith(".py")] lang_code = args.lang_code translate_keys = set() # load translate key from python source - for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded('utf-8')): + for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded("utf-8")): # search `strings._('translate_key')` # `strings._('translate_key', True)` - m = re.findall(r'strings\._\((.*?)\)', line) + m = re.findall(r"strings\._\((.*?)\)", line) if m: for match in m: - key = match.split(',')[0].strip('''"' ''') + key = match.split(",")[0].strip(""""' """) translate_keys.add(key) if args.show_all_keys: @@ -82,12 +97,16 @@ def main(): print(k) sys.exit() - if lang_code == 'all': - locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('.json')] + if lang_code == "all": + locale_files = [f for f in files_in(dir, "share/locale") if f.endswith(".json")] else: - locale_files = [f for f in files_in(dir, 'share/locale') if f.endswith('%s.json' % lang_code)] + locale_files = [ + f + for f in files_in(dir, "share/locale") + if f.endswith("%s.json" % lang_code) + ] for locale_file in locale_files: - with codecs.open(locale_file, 'r', encoding='utf-8') as f: + with codecs.open(locale_file, "r", encoding="utf-8") as f: trans = json.load(f) # trans -> {"key1": "translate-text1", "key2": "translate-text2", ...} locale_keys = set(trans.keys()) @@ -97,11 +116,11 @@ def main(): locale, ext = os.path.splitext(os.path.basename(locale_file)) for k in sorted(disused): - print(locale, 'disused', k) + print(locale, "disused", k) for k in sorted(lacked): - print(locale, 'lacked', k) + print(locale, "lacked", k) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index a13756b7..c68a81a6 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -34,17 +34,24 @@ import shutil import subprocess import requests + def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.5.5-osx64_en-US.dmg' - expected_dmg_sha256 = '9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511' + dmg_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/TorBrowser-8.5.5-osx64_en-US.dmg" + dmg_filename = "TorBrowser-8.5.5-osx64_en-US.dmg" + expected_dmg_sha256 = ( + "9c1b7840bd251a4c52f0c919991e57cafb9178c55e11fa49f83ffacce3c20511" + ) # Build paths - root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) - working_path = os.path.join(root_path, 'build', 'tor') - dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'Tor Browser.app', 'Contents') + root_path = os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + working_path = os.path.join(root_path, "build", "tor") + dmg_tor_path = os.path.join( + "/Volumes", "Tor Browser", "Tor Browser.app", "Contents" + ) dmg_path = os.path.join(working_path, dmg_filename) - dist_path = os.path.join(root_path, 'dist', 'OnionShare.app', 'Contents') + dist_path = os.path.join(root_path, "dist", "OnionShare.app", "Contents") # Make sure the working folder exists if not os.path.exists(working_path): @@ -54,10 +61,10 @@ def main(): if not os.path.exists(dmg_path): print("Downloading {}".format(dmg_url)) r = requests.get(dmg_url) - open(dmg_path, 'wb').write(r.content) + open(dmg_path, "wb").write(r.content) dmg_sha256 = hashlib.sha256(r.content).hexdigest() else: - dmg_data = open(dmg_path, 'rb').read() + dmg_data = open(dmg_path, "rb").read() dmg_sha256 = hashlib.sha256(dmg_data).hexdigest() # Compare the hash @@ -68,34 +75,52 @@ def main(): sys.exit(-1) # Mount the dmg, copy data to the working path - subprocess.call(['hdiutil', 'attach', dmg_path]) + subprocess.call(["hdiutil", "attach", dmg_path]) # Make sure Resources/tor exists before copying files - if os.path.exists(os.path.join(dist_path, 'Resources', 'Tor')): - shutil.rmtree(os.path.join(dist_path, 'Resources', 'Tor')) - os.makedirs(os.path.join(dist_path, 'Resources', 'Tor')) - if os.path.exists(os.path.join(dist_path, 'MacOS', 'Tor')): - shutil.rmtree(os.path.join(dist_path, 'MacOS', 'Tor')) - os.makedirs(os.path.join(dist_path, 'MacOS', 'Tor')) + if os.path.exists(os.path.join(dist_path, "Resources", "Tor")): + shutil.rmtree(os.path.join(dist_path, "Resources", "Tor")) + os.makedirs(os.path.join(dist_path, "Resources", "Tor")) + if os.path.exists(os.path.join(dist_path, "MacOS", "Tor")): + shutil.rmtree(os.path.join(dist_path, "MacOS", "Tor")) + os.makedirs(os.path.join(dist_path, "MacOS", "Tor")) # Modify the tor script to adjust the path - tor_script = open(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'tor'), 'r').read() - tor_script = tor_script.replace('../../../MacOS/Tor', '../../MacOS/Tor') - open(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 'w').write(tor_script) + tor_script = open( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "tor"), "r" + ).read() + tor_script = tor_script.replace("../../../MacOS/Tor", "../../MacOS/Tor") + open(os.path.join(dist_path, "Resources", "Tor", "tor"), "w").write(tor_script) # Copy into dist - shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip')) - shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip6'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip6')) - os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 0o755) - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real')) - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib')) - os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip"), + os.path.join(dist_path, "Resources", "Tor", "geoip"), + ) + shutil.copyfile( + os.path.join(dmg_tor_path, "Resources", "TorBrowser", "Tor", "geoip6"), + os.path.join(dist_path, "Resources", "Tor", "geoip6"), + ) + os.chmod(os.path.join(dist_path, "Resources", "Tor", "tor"), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "tor.real"), + os.path.join(dist_path, "MacOS", "Tor", "tor.real"), + ) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "libevent-2.1.6.dylib"), + os.path.join(dist_path, "MacOS", "Tor", "libevent-2.1.6.dylib"), + ) + os.chmod(os.path.join(dist_path, "MacOS", "Tor", "tor.real"), 0o755) # obfs4proxy binary - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy')) - os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy'), 0o755) + shutil.copyfile( + os.path.join(dmg_tor_path, "MacOS", "Tor", "PluggableTransports", "obfs4proxy"), + os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"), + ) + os.chmod(os.path.join(dist_path, "Resources", "Tor", "obfs4proxy"), 0o755) # Eject dmg - subprocess.call(['diskutil', 'eject', '/Volumes/Tor Browser']) + subprocess.call(["diskutil", "eject", "/Volumes/Tor Browser"]) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 6fc15c74..99608706 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -32,15 +32,22 @@ import shutil import subprocess import requests + def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe' - exe_filename = 'torbrowser-install-8.5.5_en-US.exe' - expected_exe_sha256 = 'a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0' + exe_url = "https://archive.torproject.org/tor-package-archive/torbrowser/8.5.5/torbrowser-install-8.5.5_en-US.exe" + exe_filename = "torbrowser-install-8.5.5_en-US.exe" + expected_exe_sha256 = ( + "a3aa7e626d1d2365dcecc6f17055f467f31c4ff9558a769e51d4b90640e48bb0" + ) # Build paths - root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) - working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') + root_path = os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + working_path = os.path.join(os.path.join(root_path, "build"), "tor") exe_path = os.path.join(working_path, exe_filename) - dist_path = os.path.join(os.path.join(os.path.join(root_path, 'dist'), 'onionshare'), 'tor') + dist_path = os.path.join( + os.path.join(os.path.join(root_path, "dist"), "onionshare"), "tor" + ) # Make sure the working folder exists if not os.path.exists(working_path): @@ -50,10 +57,10 @@ def main(): if not os.path.exists(exe_path): print("Downloading {}".format(exe_url)) r = requests.get(exe_url) - open(exe_path, 'wb').write(r.content) + open(exe_path, "wb").write(r.content) exe_sha256 = hashlib.sha256(r.content).hexdigest() else: - exe_data = open(exe_path, 'rb').read() + exe_data = open(exe_path, "rb").read() exe_sha256 = hashlib.sha256(exe_data).hexdigest() # Compare the hash @@ -64,8 +71,22 @@ def main(): sys.exit(-1) # Extract the bits we need from the exe - cmd = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Tor', '-o%s' % os.path.join(working_path, 'Tor')] - cmd2 = ['7z', 'e', '-y', exe_path, 'Browser\TorBrowser\Data\Tor\geoip*', '-o%s' % os.path.join(working_path, 'Data')] + cmd = [ + "7z", + "e", + "-y", + exe_path, + "Browser\TorBrowser\Tor", + "-o%s" % os.path.join(working_path, "Tor"), + ] + cmd2 = [ + "7z", + "e", + "-y", + exe_path, + "Browser\TorBrowser\Data\Tor\geoip*", + "-o%s" % os.path.join(working_path, "Data"), + ] subprocess.Popen(cmd).wait() subprocess.Popen(cmd2).wait() @@ -73,8 +94,11 @@ def main(): if os.path.exists(dist_path): shutil.rmtree(dist_path) os.makedirs(dist_path) - shutil.copytree( os.path.join(working_path, 'Tor'), os.path.join(dist_path, 'Tor') ) - shutil.copytree( os.path.join(working_path, 'Data'), os.path.join(dist_path, 'Data', 'Tor') ) + shutil.copytree(os.path.join(working_path, "Tor"), os.path.join(dist_path, "Tor")) + shutil.copytree( + os.path.join(working_path, "Data"), os.path.join(dist_path, "Data", "Tor") + ) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/install/scripts/onionshare-nautilus.py b/install/scripts/onionshare-nautilus.py index ed50fb23..dad2330c 100644 --- a/install/scripts/onionshare-nautilus.py +++ b/install/scripts/onionshare-nautilus.py @@ -5,7 +5,8 @@ import locale import subprocess import urllib import gi -gi.require_version('Nautilus', '3.0') + +gi.require_version("Nautilus", "3.0") from gi.repository import Nautilus from gi.repository import GObject @@ -15,12 +16,12 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): def __init__(self): # Get the localized string for "Share via OnionShare" label self.label = None - default_label = 'Share via OnionShare' + default_label = "Share via OnionShare" try: # Re-implement localization in python2 - default_locale = 'en' - locale_dir = os.path.join(sys.prefix, 'share/onionshare/locale') + default_locale = "en" + locale_dir = os.path.join(sys.prefix, "share/onionshare/locale") if os.path.exists(locale_dir): # Load all translations strings = {} @@ -28,7 +29,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): for filename in os.listdir(locale_dir): abs_filename = os.path.join(locale_dir, filename) lang, ext = os.path.splitext(filename) - if ext == '.json': + if ext == ".json": with open(abs_filename) as f: translations[lang] = json.load(f) @@ -42,7 +43,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): if key in translations[lang]: strings[key] = translations[lang][key] - self.label = strings['share_via_onionshare'] + self.label = strings["share_via_onionshare"] except: self.label = default_label @@ -63,30 +64,29 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider): self.label = 'Share via OnionShare' """ - def url2path(self,url): + def url2path(self, url): file_uri = url.get_activation_uri() arg_uri = file_uri[7:] path = urllib.url2pathname(arg_uri) return path def exec_onionshare(self, filenames): -# Would prefer this method but there is a conflict between GTK 2.0 vs GTK 3.0 components being loaded at once -# (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported -# sys.argv = ["", "--filenames"] + filenames -# sys.exit(onionshare_gui.main()) - path = os.path.join(os.sep, 'usr', 'bin', 'onionshare-gui') + # Would prefer this method but there is a conflict between GTK 2.0 vs GTK 3.0 components being loaded at once + # (nautilus:3090): Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported + # sys.argv = ["", "--filenames"] + filenames + # sys.exit(onionshare_gui.main()) + path = os.path.join(os.sep, "usr", "bin", "onionshare-gui") cmd = [path, "--filenames"] + filenames subprocess.Popen(cmd) def get_file_items(self, window, files): - menuitem = Nautilus.MenuItem(name='OnionShare::Nautilus', - label=self.label, - tip='', - icon='') + menuitem = Nautilus.MenuItem( + name="OnionShare::Nautilus", label=self.label, tip="", icon="" + ) menu = Nautilus.Menu() menu.append_item(menuitem) menuitem.connect("activate", self.menu_activate_cb, files) - return menuitem, + return (menuitem,) def menu_activate_cb(self, menu, files): file_list = [] diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 65e605f6..108ffd0b 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -30,10 +30,10 @@ from .onionshare import OnionShare def build_url(common, app, web): # Build the URL - if common.settings.get('public_mode'): - return 'http://{0:s}'.format(app.onion_host) + if common.settings.get("public_mode"): + return "http://{0:s}".format(app.onion_host) else: - return 'http://onionshare:{0:s}@{1:s}'.format(web.password, app.onion_host) + return "http://onionshare:{0:s}@{1:s}".format(web.password, app.onion_host) def main(cwd=None): @@ -47,23 +47,84 @@ def main(cwd=None): print("OnionShare {0:s} | https://onionshare.org/".format(common.version)) # OnionShare CLI in OSX needs to change current working directory (#132) - if common.platform == 'Darwin': + if common.platform == "Darwin": if cwd: os.chdir(cwd) # Parse arguments - parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28)) - parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)") - parser.add_argument('--stay-open', action='store_true', dest='stay_open', help="Continue sharing after files have been sent") - parser.add_argument('--auto-start-timer', metavar='', dest='autostart_timer', default=0, help="Schedule this share to start N seconds from now") - parser.add_argument('--auto-stop-timer', metavar='', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds") - parser.add_argument('--connect-timeout', metavar='', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)") - parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)") - parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them") - parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website") - parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") - parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share") + parser = argparse.ArgumentParser( + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=28) + ) + parser.add_argument( + "--local-only", + action="store_true", + dest="local_only", + help="Don't use Tor (only for development)", + ) + parser.add_argument( + "--stay-open", + action="store_true", + dest="stay_open", + help="Continue sharing after files have been sent", + ) + parser.add_argument( + "--auto-start-timer", + metavar="", + dest="autostart_timer", + default=0, + help="Schedule this share to start N seconds from now", + ) + parser.add_argument( + "--auto-stop-timer", + metavar="", + dest="autostop_timer", + default=0, + help="Stop sharing after a given amount of seconds", + ) + parser.add_argument( + "--connect-timeout", + metavar="", + dest="connect_timeout", + default=120, + help="Give up connecting to Tor after a given amount of seconds (default: 120)", + ) + parser.add_argument( + "--stealth", + action="store_true", + dest="stealth", + help="Use client authorization (advanced)", + ) + parser.add_argument( + "--receive", + action="store_true", + dest="receive", + help="Receive shares instead of sending them", + ) + parser.add_argument( + "--website", + action="store_true", + dest="website", + help="Publish a static website", + ) + parser.add_argument( + "--config", + metavar="config", + default=False, + help="Custom JSON config file location (optional)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Log OnionShare errors to stdout, and web errors to disk", + ) + parser.add_argument( + "filename", + metavar="filename", + nargs="*", + help="List of files or folders to share", + ) args = parser.parse_args() filenames = args.filename @@ -82,14 +143,14 @@ def main(cwd=None): config = args.config if receive: - mode = 'receive' + mode = "receive" elif website: - mode = 'website' + mode = "website" else: - mode = 'share' + mode = "share" # In share an website mode, you must supply a list of filenames - if mode == 'share' or mode == 'website': + if mode == "share" or mode == "website": # Make sure filenames given if not using receiver mode if len(filenames) == 0: parser.print_help() @@ -122,7 +183,9 @@ def main(cwd=None): # Start the Onion object onion = Onion(common) try: - onion.connect(custom_settings=False, config=config, connect_timeout=connect_timeout) + onion.connect( + custom_settings=False, config=config, connect_timeout=connect_timeout + ) except KeyboardInterrupt: print("") sys.exit() @@ -132,8 +195,8 @@ def main(cwd=None): # Start the onionshare app try: common.settings.load() - if not common.settings.get('public_mode'): - web.generate_password(common.settings.get('password')) + if not common.settings.get("public_mode"): + web.generate_password(common.settings.get("password")) else: web.password = None app = OnionShare(common, onion, local_only, autostop_timer) @@ -144,30 +207,54 @@ def main(cwd=None): if autostart_timer > 0: # Can't set a schedule that is later than the auto-stop timer if app.autostop_timer > 0 and app.autostop_timer < autostart_timer: - print("The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing.") + print( + "The auto-stop time can't be the same or earlier than the auto-start time. Please update it to start sharing." + ) sys.exit() app.start_onion_service(False, True) url = build_url(common, app, web) schedule = datetime.now() + timedelta(seconds=autostart_timer) - if mode == 'receive': - print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) - print('') - print("Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.") - print('') + if mode == "receive": + print( + "Files sent to you appear in this folder: {}".format( + common.settings.get("data_dir") + ) + ) + print("") + print( + "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing." + ) + print("") if stealth: - print("Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(app.auth_string) else: - print("Give this address to your sender, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address to your sender, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) else: if stealth: - print("Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(app.auth_string) else: - print("Give this address to your recipient, and tell them it won't be accessible until: {}".format(schedule.strftime("%I:%M:%S%p, %b %d, %y"))) + print( + "Give this address to your recipient, and tell them it won't be accessible until: {}".format( + schedule.strftime("%I:%M:%S%p, %b %d, %y") + ) + ) print(url) - print('') + print("") print("Waiting for the scheduled time before starting...") app.onion.cleanup(False) time.sleep(autostart_timer) @@ -182,7 +269,7 @@ def main(cwd=None): print(e.args[0]) sys.exit() - if mode == 'website': + if mode == "website": # Prepare files to share try: web.website_mode.set_file_info(filenames) @@ -190,7 +277,7 @@ def main(cwd=None): print(e.strerror) sys.exit(1) - if mode == 'share': + if mode == "share": # Prepare files to share print("Compressing files.") try: @@ -202,12 +289,15 @@ def main(cwd=None): # Warn about sending large files over Tor if web.share_mode.download_filesize >= 157286400: # 150mb - print('') + print("") print("Warning: Sending a large share could take hours") - print('') + print("") # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), web.password)) + t = threading.Thread( + target=web.start, + args=(app.port, stay_open, common.settings.get("public_mode"), web.password), + ) t.daemon = True t.start() @@ -220,23 +310,29 @@ def main(cwd=None): app.autostop_timer_thread.start() # Save the web password if we are using a persistent private key - if common.settings.get('save_private_key'): - if not common.settings.get('password'): - common.settings.set('password', web.password) + if common.settings.get("save_private_key"): + if not common.settings.get("password"): + common.settings.set("password", web.password) common.settings.save() # Build the URL url = build_url(common, app, web) - print('') + print("") if autostart_timer > 0: print("Server started") else: - if mode == 'receive': - print("Files sent to you appear in this folder: {}".format(common.settings.get('data_dir'))) - print('') - print("Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.") - print('') + if mode == "receive": + print( + "Files sent to you appear in this folder: {}".format( + common.settings.get("data_dir") + ) + ) + print("") + print( + "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing." + ) + print("") if stealth: print("Give this address and HidServAuth to the sender:") @@ -253,7 +349,7 @@ def main(cwd=None): else: print("Give this address to the recipient:") print(url) - print('') + print("") print("Press Ctrl+C to stop the server") # Wait for app to close @@ -261,14 +357,17 @@ def main(cwd=None): if app.autostop_timer > 0: # if the auto-stop timer was set and has run out, stop the server if not app.autostop_timer_thread.is_alive(): - if mode == 'share' or (mode == 'website'): + if mode == "share" or (mode == "website"): # If there were no attempts to download the share, or all downloads are done, we can stop if web.share_mode.cur_history_id == 0 or web.done: print("Stopped because auto-stop timer ran out") web.stop(app.port) break - if mode == 'receive': - if web.receive_mode.cur_history_id == 0 or not web.receive_mode.uploads_in_progress: + if mode == "receive": + if ( + web.receive_mode.cur_history_id == 0 + or not web.receive_mode.uploads_in_progress + ): print("Stopped because auto-stop timer ran out") web.stop(app.port) break @@ -284,5 +383,6 @@ def main(cwd=None): app.cleanup() onion.cleanup() -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/onionshare/common.py b/onionshare/common.py index ab503fdc..3373462b 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -36,16 +36,17 @@ class Common(object): """ The Common object is shared amongst all parts of OnionShare. """ + def __init__(self, verbose=False): self.verbose = verbose # The platform OnionShare is running on self.platform = platform.system() - if self.platform.endswith('BSD') or self.platform == 'DragonFly': - self.platform = 'BSD' + if self.platform.endswith("BSD") or self.platform == "DragonFly": + self.platform = "BSD" # The current version of OnionShare - with open(self.get_resource_path('version.txt')) as f: + with open(self.get_resource_path("version.txt")) as f: self.version = f.read().strip() def load_settings(self, config=None): @@ -64,7 +65,7 @@ class Common(object): final_msg = "[{}] {}.{}".format(timestamp, module, func) if msg: - final_msg = '{}: {}'.format(final_msg, msg) + final_msg = "{}: {}".format(final_msg, msg) print(final_msg) def get_resource_path(self, filename): @@ -73,72 +74,105 @@ class Common(object): systemwide, and whether regardless of platform """ # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes - if self.platform == 'Windows': - filename = filename.replace('/', '\\') + if self.platform == "Windows": + filename = filename.replace("/", "\\") - if getattr(sys, 'onionshare_dev_mode', False): + if getattr(sys, "onionshare_dev_mode", False): # Look for resources directory relative to python file - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') + prefix = os.path.join( + os.path.dirname( + os.path.dirname( + os.path.abspath(inspect.getfile(inspect.currentframe())) + ) + ), + "share", + ) if not os.path.exists(prefix): # While running tests during stdeb bdist_deb, look 3 directories up for the share folder - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') - - elif self.platform == 'BSD' or self.platform == 'Linux': + prefix = os.path.join( + os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(prefix))) + ), + "share", + ) + + elif self.platform == "BSD" or self.platform == "Linux": # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode - prefix = os.path.join(sys.prefix, 'share/onionshare') + prefix = os.path.join(sys.prefix, "share/onionshare") - elif getattr(sys, 'frozen', False): + elif getattr(sys, "frozen", False): # Check if app is "frozen" # https://pythonhosted.org/PyInstaller/#run-time-information - if self.platform == 'Darwin': - prefix = os.path.join(sys._MEIPASS, 'share') - elif self.platform == 'Windows': - prefix = os.path.join(os.path.dirname(sys.executable), 'share') + if self.platform == "Darwin": + prefix = os.path.join(sys._MEIPASS, "share") + elif self.platform == "Windows": + prefix = os.path.join(os.path.dirname(sys.executable), "share") return os.path.join(prefix, filename) def get_tor_paths(self): - if self.platform == 'Linux': - tor_path = '/usr/bin/tor' - tor_geo_ip_file_path = '/usr/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' - obfs4proxy_file_path = '/usr/bin/obfs4proxy' - elif self.platform == 'Windows': - base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor') - tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') - obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') - tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') - tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - elif self.platform == 'Darwin': - base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path('')))) - tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') - tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') - tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') - obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy') - elif self.platform == 'BSD': - tor_path = '/usr/local/bin/tor' - tor_geo_ip_file_path = '/usr/local/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' - obfs4proxy_file_path = '/usr/local/bin/obfs4proxy' - - return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) + if self.platform == "Linux": + tor_path = "/usr/bin/tor" + tor_geo_ip_file_path = "/usr/share/tor/geoip" + tor_geo_ipv6_file_path = "/usr/share/tor/geoip6" + obfs4proxy_file_path = "/usr/bin/obfs4proxy" + elif self.platform == "Windows": + base_path = os.path.join( + os.path.dirname(os.path.dirname(self.get_resource_path(""))), "tor" + ) + tor_path = os.path.join(os.path.join(base_path, "Tor"), "tor.exe") + obfs4proxy_file_path = os.path.join( + os.path.join(base_path, "Tor"), "obfs4proxy.exe" + ) + tor_geo_ip_file_path = os.path.join( + os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip" + ) + tor_geo_ipv6_file_path = os.path.join( + os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip6" + ) + elif self.platform == "Darwin": + base_path = os.path.dirname( + os.path.dirname(os.path.dirname(self.get_resource_path(""))) + ) + tor_path = os.path.join(base_path, "Resources", "Tor", "tor") + tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip") + tor_geo_ipv6_file_path = os.path.join( + base_path, "Resources", "Tor", "geoip6" + ) + obfs4proxy_file_path = os.path.join( + base_path, "Resources", "Tor", "obfs4proxy" + ) + elif self.platform == "BSD": + tor_path = "/usr/local/bin/tor" + tor_geo_ip_file_path = "/usr/local/share/tor/geoip" + tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6" + obfs4proxy_file_path = "/usr/local/bin/obfs4proxy" + + return ( + tor_path, + tor_geo_ip_file_path, + tor_geo_ipv6_file_path, + obfs4proxy_file_path, + ) def build_data_dir(self): """ Returns the path of the OnionShare data directory. """ - if self.platform == 'Windows': + if self.platform == "Windows": try: - appdata = os.environ['APPDATA'] - onionshare_data_dir = '{}\\OnionShare'.format(appdata) + appdata = os.environ["APPDATA"] + onionshare_data_dir = "{}\\OnionShare".format(appdata) except: # If for some reason we don't have the 'APPDATA' environment variable # (like running tests in Linux while pretending to be in Windows) - onionshare_data_dir = os.path.expanduser('~/.config/onionshare') - elif self.platform == 'Darwin': - onionshare_data_dir = os.path.expanduser('~/Library/Application Support/OnionShare') + onionshare_data_dir = os.path.expanduser("~/.config/onionshare") + elif self.platform == "Darwin": + onionshare_data_dir = os.path.expanduser( + "~/Library/Application Support/OnionShare" + ) else: - onionshare_data_dir = os.path.expanduser('~/.config/onionshare') + onionshare_data_dir = os.path.expanduser("~/.config/onionshare") os.makedirs(onionshare_data_dir, 0o700, True) return onionshare_data_dir @@ -147,11 +181,11 @@ class Common(object): """ Returns a random string made from two words from the wordlist, such as "deter-trig". """ - with open(self.get_resource_path('wordlist.txt')) as f: + with open(self.get_resource_path("wordlist.txt")) as f: wordlist = f.read().split() r = random.SystemRandom() - return '-'.join(r.choice(wordlist) for _ in range(2)) + return "-".join(r.choice(wordlist) for _ in range(2)) def define_css(self): """ @@ -160,7 +194,7 @@ class Common(object): """ self.css = { # OnionShareGui styles - 'mode_switcher_selected_style': """ + "mode_switcher_selected_style": """ QPushButton { color: #ffffff; background-color: #4e064f; @@ -169,8 +203,7 @@ class Common(object): font-weight: bold; border-radius: 0; }""", - - 'mode_switcher_unselected_style': """ + "mode_switcher_unselected_style": """ QPushButton { color: #ffffff; background-color: #601f61; @@ -178,23 +211,20 @@ class Common(object): font-weight: normal; border-radius: 0; }""", - - 'settings_button': """ + "settings_button": """ QPushButton { background-color: #601f61; border: 0; border-left: 1px solid #69266b; border-radius: 0; }""", - - 'server_status_indicator_label': """ + "server_status_indicator_label": """ QLabel { font-style: italic; color: #666666; padding: 2px; }""", - - 'status_bar': """ + "status_bar": """ QStatusBar { font-style: italic; color: #666666; @@ -202,16 +232,14 @@ class Common(object): QStatusBar::item { border: 0px; }""", - # Common styles between modes and their child widgets - 'mode_info_label': """ + "mode_info_label": """ QLabel { font-size: 12px; color: #666666; } """, - - 'server_status_url': """ + "server_status_url": """ QLabel { background-color: #ffffff; color: #000000; @@ -220,14 +248,12 @@ class Common(object): font-size: 12px; } """, - - 'server_status_url_buttons': """ + "server_status_url_buttons": """ QPushButton { color: #3f7fcf; } """, - - 'server_status_button_stopped': """ + "server_status_button_stopped": """ QPushButton { background-color: #5fa416; color: #ffffff; @@ -235,8 +261,7 @@ class Common(object): border: 0; border-radius: 5px; }""", - - 'server_status_button_working': """ + "server_status_button_working": """ QPushButton { background-color: #4c8211; color: #ffffff; @@ -245,8 +270,7 @@ class Common(object): border-radius: 5px; font-style: italic; }""", - - 'server_status_button_started': """ + "server_status_button_started": """ QPushButton { background-color: #d0011b; color: #ffffff; @@ -254,8 +278,7 @@ class Common(object): border: 0; border-radius: 5px; }""", - - 'downloads_uploads_empty': """ + "downloads_uploads_empty": """ QWidget { background-color: #ffffff; border: 1px solid #999999; @@ -265,13 +288,11 @@ class Common(object): border: 0px; } """, - - 'downloads_uploads_empty_text': """ + "downloads_uploads_empty_text": """ QLabel { color: #999999; }""", - - 'downloads_uploads_label': """ + "downloads_uploads_label": """ QLabel { font-weight: bold; font-size 14px; @@ -279,14 +300,12 @@ class Common(object): background-color: none; border: none; }""", - - 'downloads_uploads_clear': """ + "downloads_uploads_clear": """ QPushButton { color: #3f7fcf; } """, - - 'download_uploads_indicator': """ + "download_uploads_indicator": """ QLabel { color: #ffffff; background-color: #f44449; @@ -296,8 +315,7 @@ class Common(object): border-radius: 7px; text-align: center; }""", - - 'downloads_uploads_progress_bar': """ + "downloads_uploads_progress_bar": """ QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; @@ -309,24 +327,20 @@ class Common(object): background-color: #4e064f; width: 10px; }""", - - 'history_individual_file_timestamp_label': """ + "history_individual_file_timestamp_label": """ QLabel { color: #666666; }""", - - 'history_individual_file_status_code_label_2xx': """ + "history_individual_file_status_code_label_2xx": """ QLabel { color: #008800; }""", - - 'history_individual_file_status_code_label_4xx': """ + "history_individual_file_status_code_label_4xx": """ QLabel { color: #cc0000; }""", - # Share mode and child widget styles - 'share_zip_progess_bar': """ + "share_zip_progess_bar": """ QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; @@ -338,21 +352,18 @@ class Common(object): background-color: #4e064f; width: 10px; }""", - - 'share_filesize_warning': """ + "share_filesize_warning": """ QLabel { padding: 10px 0; font-weight: bold; color: #333333; } """, - - 'share_file_selection_drop_here_label': """ + "share_file_selection_drop_here_label": """ QLabel { color: #999999; }""", - - 'share_file_selection_drop_count_label': """ + "share_file_selection_drop_count_label": """ QLabel { color: #ffffff; background-color: #f44449; @@ -360,60 +371,51 @@ class Common(object): padding: 5px 10px; border-radius: 10px; }""", - - 'share_file_list_drag_enter': """ + "share_file_list_drag_enter": """ FileList { border: 3px solid #538ad0; } """, - - 'share_file_list_drag_leave': """ + "share_file_list_drag_leave": """ FileList { border: none; } """, - - 'share_file_list_item_size': """ + "share_file_list_item_size": """ QLabel { color: #666666; font-size: 11px; }""", - # Receive mode and child widget styles - 'receive_file': """ + "receive_file": """ QWidget { background-color: #ffffff; } """, - - 'receive_file_size': """ + "receive_file_size": """ QLabel { color: #666666; font-size: 11px; }""", - # Settings dialog - 'settings_version': """ + "settings_version": """ QLabel { color: #666666; }""", - - 'settings_tor_status': """ + "settings_tor_status": """ QLabel { background-color: #ffffff; color: #000000; padding: 10px; }""", - - 'settings_whats_this': """ + "settings_whats_this": """ QLabel { font-size: 12px; }""", - - 'settings_connect_to_tor': """ + "settings_connect_to_tor": """ QLabel { font-style: italic; - }""" + }""", } @staticmethod @@ -423,7 +425,7 @@ class Common(object): """ b = os.urandom(num_bytes) h = hashlib.sha256(b).digest()[:16] - s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') + s = base64.b32encode(h).lower().replace(b"=", b"").decode("utf-8") if not output_len: return s return s[:output_len] @@ -435,14 +437,14 @@ class Common(object): """ thresh = 1024.0 if b < thresh: - return '{:.1f} B'.format(b) - units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') + return "{:.1f} B".format(b) + units = ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB") u = 0 b /= thresh while b >= thresh: b /= thresh u += 1 - return '{:.1f} {}'.format(b, units[u]) + return "{:.1f} {}".format(b, units[u]) @staticmethod def format_seconds(seconds): @@ -460,7 +462,7 @@ class Common(object): human_readable.append("{:.0f}m".format(minutes)) if seconds or not human_readable: human_readable.append("{:.0f}s".format(seconds)) - return ''.join(human_readable) + return "".join(human_readable) @staticmethod def estimated_time_remaining(bytes_downloaded, total_bytes, started): @@ -504,6 +506,7 @@ class AutoStopTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ + def __init__(self, common, time): threading.Thread.__init__(self) @@ -513,6 +516,8 @@ class AutoStopTimer(threading.Thread): self.time = time def run(self): - self.common.log('AutoStopTimer', 'Server will shut down after {} seconds'.format(self.time)) + self.common.log( + "AutoStopTimer", "Server will shut down after {} seconds".format(self.time) + ) time.sleep(self.time) return 1 diff --git a/onionshare/onion.py b/onionshare/onion.py index b0499449..727cf5f1 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -28,90 +28,113 @@ from distutils.version import LooseVersion as Version from . import common, strings from .settings import Settings + class TorErrorAutomatic(Exception): """ OnionShare is failing to connect and authenticate to the Tor controller, using automatic settings that should work with Tor Browser. """ + pass + class TorErrorInvalidSetting(Exception): """ This exception is raised if the settings just don't make sense. """ + pass + class TorErrorSocketPort(Exception): """ OnionShare can't connect to the Tor controller using the supplied address and port. """ + pass + class TorErrorSocketFile(Exception): """ OnionShare can't connect to the Tor controller using the supplied socket file. """ + pass + class TorErrorMissingPassword(Exception): """ OnionShare connected to the Tor controller, but it requires a password. """ + pass + class TorErrorUnreadableCookieFile(Exception): """ OnionShare connected to the Tor controller, but your user does not have permission to access the cookie file. """ + pass + class TorErrorAuthError(Exception): """ OnionShare connected to the address and port, but can't authenticate. It's possible that a Tor controller isn't listening on this port. """ + pass + class TorErrorProtocolError(Exception): """ This exception is raised if onionshare connects to the Tor controller, but it isn't acting like a Tor controller (such as in Whonix). """ + pass + class TorTooOld(Exception): """ This exception is raised if onionshare needs to use a feature of Tor or stem (like stealth ephemeral onion services) but the version you have installed is too old. """ + pass + class BundledTorNotSupported(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but it's not supported on that platform, or in dev mode. """ + class BundledTorTimeout(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but Tor doesn't finish connecting promptly. """ + class BundledTorCanceled(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, and the user cancels connecting to Tor """ + class BundledTorBroken(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, but the process seems to fail to run. """ + class Onion(object): """ Onion is an abstraction layer for connecting to the Tor control port and @@ -126,10 +149,11 @@ class Onion(object): call this function and pass in a status string while connecting to tor. This is necessary for status updates to reach the GUI. """ + def __init__(self, common): self.common = common - self.common.log('Onion', '__init__') + self.common.log("Onion", "__init__") self.stealth = False self.service_id = None @@ -137,13 +161,20 @@ class Onion(object): self.scheduled_auth_cookie = None # Is bundled tor supported? - if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if ( + self.common.platform == "Windows" or self.common.platform == "Darwin" + ) and getattr(sys, "onionshare_dev_mode", False): self.bundle_tor_supported = False else: self.bundle_tor_supported = True # Set the path of the tor binary, for bundled tor - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + ) = self.common.get_tor_paths() # The tor process self.tor_proc = None @@ -154,8 +185,14 @@ class Onion(object): # Start out not connected to Tor self.connected_to_tor = False - def connect(self, custom_settings=False, config=False, tor_status_update_func=None, connect_timeout=120): - self.common.log('Onion', 'connect') + def connect( + self, + custom_settings=False, + config=False, + tor_status_update_func=None, + connect_timeout=120, + ): + self.common.log("Onion", "connect") # Either use settings that are passed in, or use them from common if custom_settings: @@ -171,95 +208,157 @@ class Onion(object): # The Tor controller self.c = None - if self.settings.get('connection_type') == 'bundled': + if self.settings.get("connection_type") == "bundled": if not self.bundle_tor_supported: - raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported')) + raise BundledTorNotSupported( + strings._("settings_error_bundled_tor_not_supported") + ) # Create a torrc for this session - self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir()) - self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name)) + self.tor_data_directory = tempfile.TemporaryDirectory( + dir=self.common.build_data_dir() + ) + self.common.log( + "Onion", + "connect", + "tor_data_directory={}".format(self.tor_data_directory.name), + ) # Create the torrc - with open(self.common.get_resource_path('torrc_template')) as f: + with open(self.common.get_resource_path("torrc_template")) as f: torrc_template = f.read() - self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') + self.tor_cookie_auth_file = os.path.join( + self.tor_data_directory.name, "cookie" + ) try: self.tor_socks_port = self.common.get_available_port(1000, 65535) except: - raise OSError(strings._('no_available_port')) - self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') + raise OSError(strings._("no_available_port")) + self.tor_torrc = os.path.join(self.tor_data_directory.name, "torrc") - if self.common.platform == 'Windows' or self.common.platform == "Darwin": + if self.common.platform == "Windows" or self.common.platform == "Darwin": # Windows doesn't support unix sockets, so it must use a network port. # macOS can't use unix sockets either because socket filenames are limited to # 100 chars, and the macOS sandbox forces us to put the socket file in a place # with a really long path. - torrc_template += 'ControlPort {{control_port}}\n' + torrc_template += "ControlPort {{control_port}}\n" try: self.tor_control_port = self.common.get_available_port(1000, 65535) except: - raise OSError(strings._('no_available_port')) + raise OSError(strings._("no_available_port")) self.tor_control_socket = None else: # Linux and BSD can use unix sockets - torrc_template += 'ControlSocket {{control_socket}}\n' + torrc_template += "ControlSocket {{control_socket}}\n" self.tor_control_port = None - self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') - - torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name) - torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port)) - torrc_template = torrc_template.replace('{{control_socket}}', str(self.tor_control_socket)) - torrc_template = torrc_template.replace('{{cookie_auth_file}}', self.tor_cookie_auth_file) - torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path) - torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path) - torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port)) - - with open(self.tor_torrc, 'w') as f: + self.tor_control_socket = os.path.join( + self.tor_data_directory.name, "control_socket" + ) + + torrc_template = torrc_template.replace( + "{{data_directory}}", self.tor_data_directory.name + ) + torrc_template = torrc_template.replace( + "{{control_port}}", str(self.tor_control_port) + ) + torrc_template = torrc_template.replace( + "{{control_socket}}", str(self.tor_control_socket) + ) + torrc_template = torrc_template.replace( + "{{cookie_auth_file}}", self.tor_cookie_auth_file + ) + torrc_template = torrc_template.replace( + "{{geo_ip_file}}", self.tor_geo_ip_file_path + ) + torrc_template = torrc_template.replace( + "{{geo_ipv6_file}}", self.tor_geo_ipv6_file_path + ) + torrc_template = torrc_template.replace( + "{{socks_port}}", str(self.tor_socks_port) + ) + + with open(self.tor_torrc, "w") as f: f.write(torrc_template) # Bridge support - if self.settings.get('tor_bridges_use_obfs4'): - f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - with open(self.common.get_resource_path('torrc_template-obfs4')) as o: + if self.settings.get("tor_bridges_use_obfs4"): + f.write( + "ClientTransportPlugin obfs4 exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + with open( + self.common.get_resource_path("torrc_template-obfs4") + ) as o: for line in o: f.write(line) - elif self.settings.get('tor_bridges_use_meek_lite_azure'): - f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o: + elif self.settings.get("tor_bridges_use_meek_lite_azure"): + f.write( + "ClientTransportPlugin meek_lite exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + with open( + self.common.get_resource_path("torrc_template-meek_lite_azure") + ) as o: for line in o: f.write(line) - if self.settings.get('tor_bridges_use_custom_bridges'): - if 'obfs4' in self.settings.get('tor_bridges_use_custom_bridges'): - f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - elif 'meek_lite' in self.settings.get('tor_bridges_use_custom_bridges'): - f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - f.write(self.settings.get('tor_bridges_use_custom_bridges')) - f.write('\nUseBridges 1') + if self.settings.get("tor_bridges_use_custom_bridges"): + if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): + f.write( + "ClientTransportPlugin obfs4 exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + elif "meek_lite" in self.settings.get( + "tor_bridges_use_custom_bridges" + ): + f.write( + "ClientTransportPlugin meek_lite exec {}\n".format( + self.obfs4proxy_file_path + ) + ) + f.write(self.settings.get("tor_bridges_use_custom_bridges")) + f.write("\nUseBridges 1") # Execute a tor subprocess start_ts = time.time() - if self.common.platform == 'Windows': + if self.common.platform == "Windows": # In Windows, hide console window when opening tor.exe subprocess startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo) + self.tor_proc = subprocess.Popen( + [self.tor_path, "-f", self.tor_torrc], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + ) else: - self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.tor_proc = subprocess.Popen( + [self.tor_path, "-f", self.tor_torrc], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) # Wait for the tor controller to start time.sleep(2) # Connect to the controller try: - if self.common.platform == 'Windows' or self.common.platform == "Darwin": + if ( + self.common.platform == "Windows" + or self.common.platform == "Darwin" + ): self.c = Controller.from_port(port=self.tor_control_port) self.c.authenticate() else: self.c = Controller.from_socket_file(path=self.tor_control_socket) self.c.authenticate() except Exception as e: - raise BundledTorBroken(strings._('settings_error_bundled_tor_broken').format(e.args[0])) + raise BundledTorBroken( + strings._("settings_error_bundled_tor_broken").format(e.args[0]) + ) while True: try: @@ -268,47 +367,60 @@ class Onion(object): raise BundledTorCanceled() res_parts = shlex.split(res) - progress = res_parts[2].split('=')[1] - summary = res_parts[4].split('=')[1] + progress = res_parts[2].split("=")[1] + summary = res_parts[4].split("=")[1] # "\033[K" clears the rest of the line - print("Connecting to the Tor network: {}% - {}{}".format(progress, summary, "\033[K"), end="\r") + print( + "Connecting to the Tor network: {}% - {}{}".format( + progress, summary, "\033[K" + ), + end="\r", + ) if callable(tor_status_update_func): if not tor_status_update_func(progress, summary): # If the dialog was canceled, stop connecting to Tor - self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor') + self.common.log( + "Onion", + "connect", + "tor_status_update_func returned false, canceling connecting to Tor", + ) print() return False - if summary == 'Done': + if summary == "Done": print("") break time.sleep(0.2) # If using bridges, it might take a bit longer to connect to Tor - if self.settings.get('tor_bridges_use_custom_bridges') or \ - self.settings.get('tor_bridges_use_obfs4') or \ - self.settings.get('tor_bridges_use_meek_lite_azure'): - # Only override timeout if a custom timeout has not been passed in - if connect_timeout == 120: - connect_timeout = 150 + if ( + self.settings.get("tor_bridges_use_custom_bridges") + or self.settings.get("tor_bridges_use_obfs4") + or self.settings.get("tor_bridges_use_meek_lite_azure") + ): + # Only override timeout if a custom timeout has not been passed in + if connect_timeout == 120: + connect_timeout = 150 if time.time() - start_ts > connect_timeout: print("") try: self.tor_proc.terminate() - raise BundledTorTimeout(strings._('settings_error_bundled_tor_timeout')) + raise BundledTorTimeout( + strings._("settings_error_bundled_tor_timeout") + ) except FileNotFoundError: pass - elif self.settings.get('connection_type') == 'automatic': + elif self.settings.get("connection_type") == "automatic": # Automatically try to guess the right way to connect to Tor Browser # Try connecting to control port found_tor = False # If the TOR_CONTROL_PORT environment variable is set, use that - env_port = os.environ.get('TOR_CONTROL_PORT') + env_port = os.environ.get("TOR_CONTROL_PORT") if env_port: try: self.c = Controller.from_port(port=int(env_port)) @@ -327,11 +439,13 @@ class Onion(object): pass # If this still didn't work, try guessing the default socket file path - socket_file_path = '' + socket_file_path = "" if not found_tor: try: - if self.common.platform == 'Darwin': - socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket') + if self.common.platform == "Darwin": + socket_file_path = os.path.expanduser( + "~/Library/Application Support/TorBrowser-Data/Tor/control.socket" + ) self.c = Controller.from_socket_file(path=socket_file_path) found_tor = True @@ -342,74 +456,108 @@ class Onion(object): # guessing the socket file name next if not found_tor: try: - if self.common.platform == 'Linux' or self.common.platform == 'BSD': - socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.common.platform == 'Darwin': - socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.common.platform == 'Windows': + if self.common.platform == "Linux" or self.common.platform == "BSD": + socket_file_path = "/run/user/{}/Tor/control.socket".format( + os.geteuid() + ) + elif self.common.platform == "Darwin": + socket_file_path = "/run/user/{}/Tor/control.socket".format( + os.geteuid() + ) + elif self.common.platform == "Windows": # Windows doesn't support unix sockets - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) self.c = Controller.from_socket_file(path=socket_file_path) except: - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) # Try authenticating try: self.c.authenticate() except: - raise TorErrorAutomatic(strings._('settings_error_automatic')) + raise TorErrorAutomatic(strings._("settings_error_automatic")) else: # Use specific settings to connect to tor # Try connecting try: - if self.settings.get('connection_type') == 'control_port': - self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port')) - elif self.settings.get('connection_type') == 'socket_file': - self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path')) + if self.settings.get("connection_type") == "control_port": + self.c = Controller.from_port( + address=self.settings.get("control_port_address"), + port=self.settings.get("control_port_port"), + ) + elif self.settings.get("connection_type") == "socket_file": + self.c = Controller.from_socket_file( + path=self.settings.get("socket_file_path") + ) else: raise TorErrorInvalidSetting(strings._("settings_error_unknown")) except: - if self.settings.get('connection_type') == 'control_port': - raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port'))) + if self.settings.get("connection_type") == "control_port": + raise TorErrorSocketPort( + strings._("settings_error_socket_port").format( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) + ) else: - raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path'))) - + raise TorErrorSocketFile( + strings._("settings_error_socket_file").format( + self.settings.get("socket_file_path") + ) + ) # Try authenticating try: - if self.settings.get('auth_type') == 'no_auth': + if self.settings.get("auth_type") == "no_auth": self.c.authenticate() - elif self.settings.get('auth_type') == 'password': - self.c.authenticate(self.settings.get('auth_password')) + elif self.settings.get("auth_type") == "password": + self.c.authenticate(self.settings.get("auth_password")) else: raise TorErrorInvalidSetting(strings._("settings_error_unknown")) except MissingPassword: - raise TorErrorMissingPassword(strings._('settings_error_missing_password')) + raise TorErrorMissingPassword( + strings._("settings_error_missing_password") + ) except UnreadableCookieFile: - raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file')) + raise TorErrorUnreadableCookieFile( + strings._("settings_error_unreadable_cookie_file") + ) except AuthenticationFailure: - raise TorErrorAuthError(strings._('settings_error_auth').format(self.settings.get('control_port_address'), self.settings.get('control_port_port'))) + raise TorErrorAuthError( + strings._("settings_error_auth").format( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) + ) # If we made it this far, we should be connected to Tor self.connected_to_tor = True # Get the tor version self.tor_version = self.c.get_version().version_str - self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version)) + self.common.log( + "Onion", "connect", "Connected to tor {}".format(self.tor_version) + ) # Do the versions of stem and tor that I'm using support ephemeral onion services? - list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None) - self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1' + list_ephemeral_hidden_services = getattr( + self.c, "list_ephemeral_hidden_services", None + ) + self.supports_ephemeral = ( + callable(list_ephemeral_hidden_services) and self.tor_version >= "0.2.7.1" + ) # Do the versions of stem and tor that I'm using support stealth onion services? try: - res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False) + res = self.c.create_ephemeral_hidden_service( + {1: 1}, basic_auth={"onionshare": None}, await_publication=False + ) tmp_service_id = res.service_id self.c.remove_ephemeral_hidden_service(tmp_service_id) self.supports_stealth = True @@ -420,7 +568,7 @@ class Onion(object): # Does this version of Tor support next-gen ('v3') onions? # Note, this is the version of Tor where this bug was fixed: # https://trac.torproject.org/projects/tor/ticket/28619 - self.supports_v3_onions = self.tor_version >= Version('0.3.5.7') + self.supports_v3_onions = self.tor_version >= Version("0.3.5.7") def is_authenticated(self): """ @@ -431,13 +579,12 @@ class Onion(object): else: return False - def start_onion_service(self, port, await_publication, save_scheduled_key=False): """ Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ - self.common.log('Onion', 'start_onion_service') + self.common.log("Onion", "start_onion_service") # Settings may have changed in the frontend but not updated in our settings object, # such as persistence. Reload the settings now just to be sure. self.settings.load() @@ -445,27 +592,27 @@ class Onion(object): self.auth_string = None if not self.supports_ephemeral: - raise TorTooOld(strings._('error_ephemeral_not_supported')) + raise TorTooOld(strings._("error_ephemeral_not_supported")) if self.stealth and not self.supports_stealth: - raise TorTooOld(strings._('error_stealth_not_supported')) + raise TorTooOld(strings._("error_stealth_not_supported")) if not save_scheduled_key: print("Setting up onion service on port {0:d}.".format(int(port))) if self.stealth: - if self.settings.get('hidservauth_string'): - hidservauth_string = self.settings.get('hidservauth_string').split()[2] - basic_auth = {'onionshare':hidservauth_string} + if self.settings.get("hidservauth_string"): + hidservauth_string = self.settings.get("hidservauth_string").split()[2] + basic_auth = {"onionshare": hidservauth_string} else: if self.scheduled_auth_cookie: - basic_auth = {'onionshare':self.scheduled_auth_cookie} + basic_auth = {"onionshare": self.scheduled_auth_cookie} else: - basic_auth = {'onionshare':None} + basic_auth = {"onionshare": None} else: basic_auth = None - if self.settings.get('private_key'): - key_content = self.settings.get('private_key') + if self.settings.get("private_key"): + key_content = self.settings.get("private_key") if self.is_v2_key(key_content): key_type = "RSA1024" else: @@ -483,7 +630,9 @@ class Onion(object): else: key_type = "NEW" # Work out if we can support v3 onion services, which are preferred - if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'): + if self.supports_v3_onions and not self.settings.get( + "use_legacy_v2_onions" + ): key_content = "ED25519-V3" else: # fall back to v2 onion services @@ -491,31 +640,48 @@ class Onion(object): # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 - if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): + if ( + key_type == "NEW" + and key_content == "ED25519-V3" + and not self.settings.get("use_legacy_v2_onions") + ): basic_auth = None self.stealth = False - debug_message = 'key_type={}'.format(key_type) + debug_message = "key_type={}".format(key_type) if key_type == "NEW": - debug_message += ', key_content={}'.format(key_content) - self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message)) + debug_message += ", key_content={}".format(key_content) + self.common.log("Onion", "start_onion_service", "{}".format(debug_message)) try: if basic_auth != None: - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service( + {80: port}, + await_publication=await_publication, + basic_auth=basic_auth, + key_type=key_type, + key_content=key_content, + ) else: # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service( + {80: port}, + await_publication=await_publication, + key_type=key_type, + key_content=key_content, + ) except ProtocolError as e: - raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0])) + raise TorErrorProtocolError( + strings._("error_tor_protocol_error").format(e.args[0]) + ) self.service_id = res.service_id - onion_host = self.service_id + '.onion' + onion_host = self.service_id + ".onion" # A new private key was generated and is in the Control port response. - if self.settings.get('save_private_key'): - if not self.settings.get('private_key'): - self.settings.set('private_key', res.private_key) + if self.settings.get("save_private_key"): + if not self.settings.get("private_key"): + self.settings.set("private_key", res.private_key) # If we were scheduling a future share, register the private key for later re-use if save_scheduled_key: @@ -529,24 +695,30 @@ class Onion(object): # in the first place. # If we sent the basic_auth (due to a saved hidservauth_string in the settings), # there is no response here, so use the saved value from settings. - if self.settings.get('save_private_key'): - if self.settings.get('hidservauth_string'): - self.auth_string = self.settings.get('hidservauth_string') + if self.settings.get("save_private_key"): + if self.settings.get("hidservauth_string"): + self.auth_string = self.settings.get("hidservauth_string") else: auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) - self.settings.set('hidservauth_string', self.auth_string) + self.auth_string = "HidServAuth {} {}".format( + onion_host, auth_cookie + ) + self.settings.set("hidservauth_string", self.auth_string) else: if not self.scheduled_auth_cookie: auth_cookie = list(res.client_auth.values())[0] - self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie) + self.auth_string = "HidServAuth {} {}".format( + onion_host, auth_cookie + ) if save_scheduled_key: # Register the HidServAuth for the scheduled share self.scheduled_auth_cookie = auth_cookie else: self.scheduled_auth_cookie = None else: - self.auth_string = 'HidServAuth {} {}'.format(onion_host, self.scheduled_auth_cookie) + self.auth_string = "HidServAuth {} {}".format( + onion_host, self.scheduled_auth_cookie + ) if not save_scheduled_key: # We've used the scheduled share's HidServAuth. Reset it to None for future shares self.scheduled_auth_cookie = None @@ -555,23 +727,29 @@ class Onion(object): self.settings.save() return onion_host else: - raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown')) + raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown")) def cleanup(self, stop_tor=True): """ Stop onion services that were created earlier. If there's a tor subprocess running, kill it. """ - self.common.log('Onion', 'cleanup') + self.common.log("Onion", "cleanup") # Cleanup the ephemeral onion services, if we have any try: onions = self.c.list_ephemeral_hidden_services() for onion in onions: try: - self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion)) + self.common.log( + "Onion", "cleanup", "trying to remove onion {}".format(onion) + ) self.c.remove_ephemeral_hidden_service(onion) except: - self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion)) + self.common.log( + "Onion", + "cleanup", + "could not remove onion {}.. moving on anyway".format(onion), + ) pass except: pass @@ -608,14 +786,14 @@ class Onion(object): """ Returns a (address, port) tuple for the Tor SOCKS port """ - self.common.log('Onion', 'get_tor_socks_port') + self.common.log("Onion", "get_tor_socks_port") - if self.settings.get('connection_type') == 'bundled': - return ('127.0.0.1', self.tor_socks_port) - elif self.settings.get('connection_type') == 'automatic': - return ('127.0.0.1', 9150) + if self.settings.get("connection_type") == "bundled": + return ("127.0.0.1", self.tor_socks_port) + elif self.settings.get("connection_type") == "automatic": + return ("127.0.0.1", 9150) else: - return (self.settings.get('socks_address'), self.settings.get('socks_port')) + return (self.settings.get("socks_address"), self.settings.get("socks_port")) def is_v2_key(self, key): """ diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index e746bae1..41a4e5a8 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -22,17 +22,19 @@ import os, shutil from . import common, strings from .onion import TorTooOld, TorErrorProtocolError -from .common import AutoStopTimer +from .common import AutoStopTimer + class OnionShare(object): """ OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ + def __init__(self, common, onion, local_only=False, autostop_timer=0): self.common = common - self.common.log('OnionShare', '__init__') + self.common.log("OnionShare", "__init__") # The Onion object self.onion = onion @@ -54,7 +56,7 @@ class OnionShare(object): self.autostop_timer_thread = None def set_stealth(self, stealth): - self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) + self.common.log("OnionShare", "set_stealth", "stealth={}".format(stealth)) self.stealth = stealth self.onion.stealth = stealth @@ -66,13 +68,13 @@ class OnionShare(object): try: self.port = self.common.get_available_port(17600, 17650) except: - raise OSError(strings._('no_available_port')) + raise OSError(strings._("no_available_port")) def start_onion_service(self, await_publication=True, save_scheduled_key=False): """ Start the onionshare onion service. """ - self.common.log('OnionShare', 'start_onion_service') + self.common.log("OnionShare", "start_onion_service") if not self.port: self.choose_port() @@ -81,10 +83,12 @@ class OnionShare(object): self.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer) if self.local_only: - self.onion_host = '127.0.0.1:{0:d}'.format(self.port) + self.onion_host = "127.0.0.1:{0:d}".format(self.port) return - self.onion_host = self.onion.start_onion_service(self.port, await_publication, save_scheduled_key) + self.onion_host = self.onion.start_onion_service( + self.port, await_publication, save_scheduled_key + ) if self.stealth: self.auth_string = self.onion.auth_string @@ -93,7 +97,7 @@ class OnionShare(object): """ Shut everything down and clean up temporary files, etc. """ - self.common.log('OnionShare', 'cleanup') + self.common.log("OnionShare", "cleanup") # Cleanup files try: diff --git a/onionshare/settings.py b/onionshare/settings.py index 0cccb8e9..a44e0c37 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -39,17 +39,22 @@ class Settings(object): which is to attempt to connect automatically using default Tor Browser settings. """ + def __init__(self, common, config=False): self.common = common - self.common.log('Settings', '__init__') + self.common.log("Settings", "__init__") # If a readable config file was provided, use that instead if config: if os.path.isfile(config): self.filename = config else: - self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') + self.common.log( + "Settings", + "__init__", + "Supplied config does not exist or is unreadable. Falling back to default location", + ) self.filename = self.build_filename() else: @@ -60,62 +65,62 @@ class Settings(object): # mapped to the language name, in that language self.available_locales = { #'bn': 'বাংলা', # Bengali (commented out because not at 90% translation) - 'ca': 'Català', # Catalan - 'zh_Hant': '正體中文 (繁體)', # Traditional Chinese - 'zh_Hans': '中文 (简体)', # Simplified Chinese - 'da': 'Dansk', # Danish - 'en': 'English', # English - 'fi': 'Suomi', # Finnish - 'fr': 'Français', # French - 'de': 'Deutsch', # German - 'el': 'Ελληνικά', # Greek - 'is': 'Íslenska', # Icelandic - 'ga': 'Gaeilge', # Irish - 'it': 'Italiano', # Italian - 'ja': '日本語', # Japanese - 'nb': 'Norsk Bokmål', # Norwegian Bokmål + "ca": "Català", # Catalan + "zh_Hant": "正體中文 (繁體)", # Traditional Chinese + "zh_Hans": "中文 (简体)", # Simplified Chinese + "da": "Dansk", # Danish + "en": "English", # English + "fi": "Suomi", # Finnish + "fr": "Français", # French + "de": "Deutsch", # German + "el": "Ελληνικά", # Greek + "is": "Íslenska", # Icelandic + "ga": "Gaeilge", # Irish + "it": "Italiano", # Italian + "ja": "日本語", # Japanese + "nb": "Norsk Bokmål", # Norwegian Bokmål #'fa': 'فارسی', # Persian (commented out because not at 90% translation) - 'pl': 'Polski', # Polish - 'pt_BR': 'Português (Brasil)', # Portuguese Brazil - 'pt_PT': 'Português (Portugal)', # Portuguese Portugal - 'ru': 'Русский', # Russian - 'es': 'Español', # Spanish - 'sv': 'Svenska', # Swedish - 'te': 'తెలుగు', # Telugu - 'tr': 'Türkçe', # Turkish - 'uk': 'Українська', # Ukrainian + "pl": "Polski", # Polish + "pt_BR": "Português (Brasil)", # Portuguese Brazil + "pt_PT": "Português (Portugal)", # Portuguese Portugal + "ru": "Русский", # Russian + "es": "Español", # Spanish + "sv": "Svenska", # Swedish + "te": "తెలుగు", # Telugu + "tr": "Türkçe", # Turkish + "uk": "Українська", # Ukrainian } # These are the default settings. They will get overwritten when loading from disk self.default_settings = { - 'version': self.common.version, - 'connection_type': 'bundled', - 'control_port_address': '127.0.0.1', - 'control_port_port': 9051, - 'socks_address': '127.0.0.1', - 'socks_port': 9050, - 'socket_file_path': '/var/run/tor/control', - 'auth_type': 'no_auth', - 'auth_password': '', - 'close_after_first_download': True, - 'autostop_timer': False, - 'autostart_timer': False, - 'use_stealth': False, - 'use_autoupdate': True, - 'autoupdate_timestamp': None, - 'no_bridges': True, - 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_azure': False, - 'tor_bridges_use_custom_bridges': '', - 'use_legacy_v2_onions': False, - 'save_private_key': False, - 'private_key': '', - 'public_mode': False, - 'password': '', - 'hidservauth_string': '', - 'data_dir': self.build_default_data_dir(), - 'csp_header_disabled': False, - 'locale': None # this gets defined in fill_in_defaults() + "version": self.common.version, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "auth_type": "no_auth", + "auth_password": "", + "close_after_first_download": True, + "autostop_timer": False, + "autostart_timer": False, + "use_stealth": False, + "use_autoupdate": True, + "autoupdate_timestamp": None, + "no_bridges": True, + "tor_bridges_use_obfs4": False, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_custom_bridges": "", + "use_legacy_v2_onions": False, + "save_private_key": False, + "private_key": "", + "public_mode": False, + "password": "", + "hidservauth_string": "", + "data_dir": self.build_default_data_dir(), + "csp_header_disabled": False, + "locale": None, # this gets defined in fill_in_defaults() } self._settings = {} self.fill_in_defaults() @@ -130,14 +135,14 @@ class Settings(object): self._settings[key] = self.default_settings[key] # Choose the default locale based on the OS preference, and fall-back to English - if self._settings['locale'] is None: + if self._settings["locale"] is None: language_code, encoding = locale.getdefaultlocale() # Default to English if not language_code: - language_code = 'en_US' + language_code = "en_US" - if language_code == 'pt_PT' and language_code == 'pt_BR': + if language_code == "pt_PT" and language_code == "pt_BR": # Portuguese locales include country code default_locale = language_code else: @@ -145,14 +150,14 @@ class Settings(object): default_locale = language_code[:2] if default_locale not in self.available_locales: - default_locale = 'en' - self._settings['locale'] = default_locale + default_locale = "en" + self._settings["locale"] = default_locale def build_filename(self): """ Returns the path of the settings file. """ - return os.path.join(self.common.build_data_dir(), 'onionshare.json') + return os.path.join(self.common.build_data_dir(), "onionshare.json") def build_default_data_dir(self): """ @@ -163,26 +168,28 @@ class Settings(object): # We can't use os.path.expanduser() in macOS because in the sandbox it # returns the path to the sandboxed homedir real_homedir = pwd.getpwuid(os.getuid()).pw_dir - return os.path.join(real_homedir, 'OnionShare') + return os.path.join(real_homedir, "OnionShare") elif self.common.platform == "Windows": # On Windows, os.path.expanduser() needs to use backslash, or else it # retains the forward slash, which breaks opening the folder in explorer. - return os.path.expanduser('~\OnionShare') + return os.path.expanduser("~\OnionShare") else: # All other OSes - return os.path.expanduser('~/OnionShare') + return os.path.expanduser("~/OnionShare") def load(self): """ Load the settings from file. """ - self.common.log('Settings', 'load') + self.common.log("Settings", "load") # If the settings file exists, load it if os.path.exists(self.filename): try: - self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) - with open(self.filename, 'r') as f: + self.common.log( + "Settings", "load", "Trying to load {}".format(self.filename) + ) + with open(self.filename, "r") as f: self._settings = json.load(f) self.fill_in_defaults() except: @@ -190,7 +197,7 @@ class Settings(object): # Make sure data_dir exists try: - os.makedirs(self.get('data_dir'), exist_ok=True) + os.makedirs(self.get("data_dir"), exist_ok=True) except: pass @@ -198,22 +205,24 @@ class Settings(object): """ Save settings to file. """ - self.common.log('Settings', 'save') - open(self.filename, 'w').write(json.dumps(self._settings, indent=2)) - self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename)) + self.common.log("Settings", "save") + open(self.filename, "w").write(json.dumps(self._settings, indent=2)) + self.common.log( + "Settings", "save", "Settings saved in {}".format(self.filename) + ) def get(self, key): return self._settings[key] def set(self, key, val): # If typecasting int values fails, fallback to default values - if key == 'control_port_port' or key == 'socks_port': + if key == "control_port_port" or key == "socks_port": try: val = int(val) except: - if key == 'control_port_port': - val = self.default_settings['control_port_port'] - elif key == 'socks_port': - val = self.default_settings['socks_port'] + if key == "control_port_port": + val = self.default_settings["control_port_port"] + elif key == "socks_port": + val = self.default_settings["socks_port"] self._settings[key] = val diff --git a/onionshare/strings.py b/onionshare/strings.py index 643186dd..76360a42 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -35,14 +35,14 @@ def load_strings(common): # Load all translations translations = {} for locale in common.settings.available_locales: - locale_dir = common.get_resource_path('locale') + locale_dir = common.get_resource_path("locale") filename = os.path.join(locale_dir, "{}.json".format(locale)) - with open(filename, encoding='utf-8') as f: + with open(filename, encoding="utf-8") as f: translations[locale] = json.load(f) # Build strings - default_locale = 'en' - current_locale = common.settings.get('locale') + default_locale = "en" + current_locale = common.settings.get("locale") strings = {} for s in translations[default_locale]: if s in translations[current_locale] and translations[current_locale][s] != "": @@ -57,4 +57,5 @@ def translated(k): """ return strings[k] + _ = translated diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 83040683..90f000b9 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -12,9 +12,10 @@ class ReceiveModeWeb: """ All of the web logic for receive mode """ + def __init__(self, common, web): self.common = common - self.common.log('ReceiveModeWeb', '__init__') + self.common.log("ReceiveModeWeb", "__init__") self.web = web @@ -30,59 +31,84 @@ class ReceiveModeWeb: """ The web app routes for receiving files """ + @self.web.app.route("/") def index(): history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'status_code': 200 - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + "{}".format(request.path), + {"id": history_id, "status_code": 200}, + ) self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response(render_template('receive.html', - static_url_path=self.web.static_url_path)) + r = make_response( + render_template( + "receive.html", static_url_path=self.web.static_url_path + ) + ) return self.web.add_security_headers(r) - @self.web.app.route("/upload", methods=['POST']) + @self.web.app.route("/upload", methods=["POST"]) def upload(ajax=False): """ Handle the upload files POST request, though at this point, the files have already been uploaded and saved to their correct locations. """ - files = request.files.getlist('file[]') + files = request.files.getlist("file[]") filenames = [] for f in files: - if f.filename != '': + if f.filename != "": filename = secure_filename(f.filename) filenames.append(filename) local_path = os.path.join(request.receive_mode_dir, filename) basename = os.path.basename(local_path) # Tell the GUI the receive mode directory for this file - self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, { - 'id': request.history_id, - 'filename': basename, - 'dir': request.receive_mode_dir - }) - - self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print('\n' + "Received: {}".format(local_path)) + self.web.add_request( + self.web.REQUEST_UPLOAD_SET_DIR, + request.path, + { + "id": request.history_id, + "filename": basename, + "dir": request.receive_mode_dir, + }, + ) + + self.common.log( + "ReceiveModeWeb", + "define_routes", + "/upload, uploaded {}, saving to {}".format( + f.filename, local_path + ), + ) + print("\n" + "Received: {}".format(local_path)) if request.upload_error: - self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error') - - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": request.receive_mode_dir - }) - print("Could not create OnionShare data folder: {}".format(request.receive_mode_dir)) - - msg = 'Error uploading, please inform the OnionShare user' + self.common.log( + "ReceiveModeWeb", + "define_routes", + "/upload, there was an upload error", + ) + + self.web.add_request( + self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, + request.path, + {"receive_mode_dir": request.receive_mode_dir}, + ) + print( + "Could not create OnionShare data folder: {}".format( + request.receive_mode_dir + ) + ) + + msg = "Error uploading, please inform the OnionShare user" if ajax: return json.dumps({"error_flashes": [msg]}) else: - flash(msg, 'error') - return redirect('/') + flash(msg, "error") + return redirect("/") # Note that flash strings are in English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user @@ -90,37 +116,45 @@ class ReceiveModeWeb: info_flashes = [] if len(filenames) == 0: - msg = 'No files uploaded' + msg = "No files uploaded" if ajax: info_flashes.append(msg) else: - flash(msg, 'info') + flash(msg, "info") else: - msg = 'Sent ' + msg = "Sent " for filename in filenames: - msg += '{}, '.format(filename) - msg = msg.rstrip(', ') + msg += "{}, ".format(filename) + msg = msg.rstrip(", ") if ajax: info_flashes.append(msg) else: - flash(msg, 'info') + flash(msg, "info") if self.can_upload: if ajax: return json.dumps({"info_flashes": info_flashes}) else: - return redirect('/') + return redirect("/") else: if ajax: - return json.dumps({ - "new_body": render_template('thankyou.html', static_url_path=self.web.static_url_path) - }) + return json.dumps( + { + "new_body": render_template( + "thankyou.html", + static_url_path=self.web.static_url_path, + ) + } + ) else: # It was the last upload and the timer ran out - r = make_response(render_template('thankyou.html'), static_url_path=self.web.static_url_path) + r = make_response( + render_template("thankyou.html"), + static_url_path=self.web.static_url_path, + ) return self.web.add_security_headers(r) - @self.web.app.route("/upload-ajax", methods=['POST']) + @self.web.app.route("/upload-ajax", methods=["POST"]) def upload_ajax_public(): if not self.can_upload: return self.web.error403() @@ -132,13 +166,14 @@ class ReceiveModeWSGIMiddleware(object): Custom WSGI middleware in order to attach the Web object to environ, so ReceiveModeRequest can access it. """ + def __init__(self, app, web): self.app = app self.web = web def __call__(self, environ, start_response): - environ['web'] = self.web - environ['stop_q'] = self.web.stop_q + environ["web"] = self.web + environ["stop_q"] = self.web.stop_q return self.app(environ, start_response) @@ -148,6 +183,7 @@ class ReceiveModeFile(object): written to it, in order to track the progress of uploads. It starts out with a .part file extension, and when it's complete it removes that extension. """ + def __init__(self, request, filename, write_func, close_func): self.onionshare_request = request self.onionshare_filename = filename @@ -155,24 +191,44 @@ class ReceiveModeFile(object): self.onionshare_close_func = close_func self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename) - self.filename_in_progress = '{}.part'.format(self.filename) + self.filename_in_progress = "{}.part".format(self.filename) # Open the file self.upload_error = False try: - self.f = open(self.filename_in_progress, 'wb+') + self.f = open(self.filename_in_progress, "wb+") except: # This will only happen if someone is messing with the data dir while # OnionShare is running, but if it does make sure to throw an error self.upload_error = True - self.f = tempfile.TemporaryFile('wb+') + self.f = tempfile.TemporaryFile("wb+") # Make all the file-like methods and attributes actually access the # TemporaryFile, except for write - attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', - 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', - 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', - 'truncate', 'writable', 'writelines'] + attrs = [ + "closed", + "detach", + "fileno", + "flush", + "isatty", + "mode", + "name", + "peek", + "raw", + "read", + "read1", + "readable", + "readinto", + "readinto1", + "readline", + "readlines", + "seek", + "seekable", + "tell", + "truncate", + "writable", + "writelines", + ] for attr in attrs: setattr(self, attr, getattr(self.f, attr)) @@ -214,20 +270,21 @@ class ReceiveModeRequest(Request): A custom flask Request object that keeps track of how much data has been uploaded for each file, for receive mode. """ + def __init__(self, environ, populate_request=True, shallow=False): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) - self.web = environ['web'] - self.stop_q = environ['stop_q'] + self.web = environ["web"] + self.stop_q = environ["stop_q"] - self.web.common.log('ReceiveModeRequest', '__init__') + self.web.common.log("ReceiveModeRequest", "__init__") # Prevent running the close() method more than once self.closed = False # Is this a valid upload request? self.upload_request = False - if self.method == 'POST': - if self.path == '/upload' or self.path == '/upload-ajax': + if self.method == "POST": + if self.path == "/upload" or self.path == "/upload-ajax": self.upload_request = True if self.upload_request: @@ -238,7 +295,9 @@ class ReceiveModeRequest(Request): now = datetime.now() date_dir = now.strftime("%Y-%m-%d") time_dir = now.strftime("%H.%M.%S") - self.receive_mode_dir = os.path.join(self.web.common.settings.get('data_dir'), date_dir, time_dir) + self.receive_mode_dir = os.path.join( + self.web.common.settings.get("data_dir"), date_dir, time_dir + ) # Create that directory, which shouldn't exist yet try: @@ -250,7 +309,7 @@ class ReceiveModeRequest(Request): # Keep going until we find a directory name that's available i = 1 while True: - new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i) + new_receive_mode_dir = "{}-{}".format(self.receive_mode_dir, i) try: os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False) self.receive_mode_dir = new_receive_mode_dir @@ -260,15 +319,29 @@ class ReceiveModeRequest(Request): i += 1 # Failsafe if i == 100: - self.web.common.log('ReceiveModeRequest', '__init__', 'Error finding available receive mode directory') + self.web.common.log( + "ReceiveModeRequest", + "__init__", + "Error finding available receive mode directory", + ) self.upload_error = True break except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": self.receive_mode_dir - }) - print("Could not create OnionShare data folder: {}".format(self.receive_mode_dir)) - self.web.common.log('ReceiveModeRequest', '__init__', 'Permission denied creating receive mode directory') + self.web.add_request( + self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, + request.path, + {"receive_mode_dir": self.receive_mode_dir}, + ) + print( + "Could not create OnionShare data folder: {}".format( + self.receive_mode_dir + ) + ) + self.web.common.log( + "ReceiveModeRequest", + "__init__", + "Permission denied creating receive mode directory", + ) self.upload_error = True # If there's an error so far, finish early @@ -285,23 +358,29 @@ class ReceiveModeRequest(Request): self.history_id = self.web.receive_mode.cur_history_id self.web.receive_mode.cur_history_id += 1 - # Figure out the content length + # Figure out the content length try: - self.content_length = int(self.headers['Content-Length']) + self.content_length = int(self.headers["Content-Length"]) except: self.content_length = 0 - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) + print( + "{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format( + self.web.common.human_readable_filesize(self.content_length) + ), + ) + ) # Don't tell the GUI that a request has started until we start receiving files self.told_gui_about_request = False self.previous_file = None - def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): + def _get_file_stream( + self, total_content_length, content_type, filename=None, content_length=None + ): """ This gets called for each file that gets uploaded, and returns an file-like writable stream. @@ -309,24 +388,26 @@ class ReceiveModeRequest(Request): if self.upload_request: if not self.told_gui_about_request: # Tell the GUI about the request - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.history_id, - 'content_length': self.content_length - }) + self.web.add_request( + self.web.REQUEST_STARTED, + self.path, + {"id": self.history_id, "content_length": self.content_length}, + ) self.web.receive_mode.uploads_in_progress.append(self.history_id) self.told_gui_about_request = True self.filename = secure_filename(filename) - self.progress[self.filename] = { - 'uploaded_bytes': 0, - 'complete': False - } + self.progress[self.filename] = {"uploaded_bytes": 0, "complete": False} - f = ReceiveModeFile(self, self.filename, self.file_write_func, self.file_close_func) + f = ReceiveModeFile( + self, self.filename, self.file_write_func, self.file_close_func + ) if f.upload_error: - self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file') + self.web.common.log( + "ReceiveModeRequest", "_get_file_stream", "Error creating file" + ) self.upload_error = True return f @@ -341,22 +422,25 @@ class ReceiveModeRequest(Request): return self.closed = True - self.web.common.log('ReceiveModeRequest', 'close') + self.web.common.log("ReceiveModeRequest", "close") try: if self.told_gui_about_request: history_id = self.history_id - if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']: + if ( + not self.web.stop_q.empty() + or not self.progress[self.filename]["complete"] + ): # Inform the GUI that the upload has canceled - self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_UPLOAD_CANCELED, self.path, {"id": history_id} + ) else: # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_UPLOAD_FINISHED, self.path, {"id": history_id} + ) self.web.receive_mode.uploads_in_progress.remove(history_id) except AttributeError: @@ -370,28 +454,34 @@ class ReceiveModeRequest(Request): return if self.upload_request: - self.progress[filename]['uploaded_bytes'] += length + self.progress[filename]["uploaded_bytes"] += length if self.previous_file != filename: self.previous_file = filename - print('\r=> {:15s} {}'.format( - self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), - filename - ), end='') + print( + "\r=> {:15s} {}".format( + self.web.common.human_readable_filesize( + self.progress[filename]["uploaded_bytes"] + ), + filename, + ), + end="", + ) # Update the GUI on the upload progress if self.told_gui_about_request: - self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { - 'id': self.history_id, - 'progress': self.progress - }) + self.web.add_request( + self.web.REQUEST_PROGRESS, + self.path, + {"id": self.history_id, "progress": self.progress}, + ) def file_close_func(self, filename, upload_error=False): """ This function gets called when a specific file is closed. """ - self.progress[filename]['complete'] = True + self.progress[filename]["complete"] = True # If the file tells us there was an upload error, let the request know as well if upload_error: diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py index 24ad55d7..86d34016 100644 --- a/onionshare/web/send_base_mode.py +++ b/onionshare/web/send_base_mode.py @@ -12,6 +12,7 @@ class SendBaseModeWeb: """ All of the web logic shared between share and website mode (modes where the user sends files) """ + def __init__(self, common, web): super(SendBaseModeWeb, self).__init__() self.common = common @@ -41,20 +42,24 @@ class SendBaseModeWeb: """ # If there's just one folder, replace filenames with a list of files inside that folder if len(filenames) == 1 and os.path.isdir(filenames[0]): - filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])] + filenames = [ + os.path.join(filenames[0], x) for x in os.listdir(filenames[0]) + ] # Re-initialize - self.files = {} # Dictionary mapping file paths to filenames on disk - self.root_files = {} # This is only the root files and dirs, as opposed to all of them + self.files = {} # Dictionary mapping file paths to filenames on disk + self.root_files = ( + {} + ) # This is only the root files and dirs, as opposed to all of them self.cleanup_filenames = [] self.cur_history_id = 0 - self.file_info = {'files': [], 'dirs': []} + self.file_info = {"files": [], "dirs": []} self.gzip_individual_files = {} self.init() # Build the file list for filename in filenames: - basename = os.path.basename(filename.rstrip('/')) + basename = os.path.basename(filename.rstrip("/")) # If it's a filename, add it if os.path.isfile(filename): @@ -63,42 +68,50 @@ class SendBaseModeWeb: # If it's a directory, add it recursively elif os.path.isdir(filename): - self.root_files[basename + '/'] = filename + self.root_files[basename + "/"] = filename for root, _, nested_filenames in os.walk(filename): # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder", # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar". # The normalized_root should be "some_folder/foobar" - normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/') + normalized_root = os.path.join( + basename, root[len(filename) :].lstrip("/") + ).rstrip("/") # Add the dir itself - self.files[normalized_root + '/'] = root + self.files[normalized_root + "/"] = root # Add the files in this dir for nested_filename in nested_filenames: - self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename) + self.files[ + os.path.join(normalized_root, nested_filename) + ] = os.path.join(root, nested_filename) self.set_file_info_custom(filenames, processed_size_callback) - def directory_listing(self, filenames, path='', filesystem_path=None): + def directory_listing(self, filenames, path="", filesystem_path=None): # Tell the GUI about the directory listing history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, '/{}'.format(path), { - 'id': history_id, - 'method': request.method, - 'status_code': 200 - }) - - breadcrumbs = [('☗', '/')] - parts = path.split('/')[:-1] + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + "/{}".format(path), + {"id": history_id, "method": request.method, "status_code": 200}, + ) + + breadcrumbs = [("☗", "/")] + parts = path.split("/")[:-1] for i in range(len(parts)): - breadcrumbs.append(('{}'.format(parts[i]), '/{}/'.format('/'.join(parts[0:i+1])))) + breadcrumbs.append( + ("{}".format(parts[i]), "/{}/".format("/".join(parts[0 : i + 1]))) + ) breadcrumbs_leaf = breadcrumbs.pop()[0] # If filesystem_path is None, this is the root directory listing files, dirs = self.build_directory_listing(filenames, filesystem_path) - r = self.directory_listing_template(path, files, dirs, breadcrumbs, breadcrumbs_leaf) + r = self.directory_listing_template( + path, files, dirs, breadcrumbs, breadcrumbs_leaf + ) return self.web.add_security_headers(r) def build_directory_listing(self, filenames, filesystem_path): @@ -114,16 +127,11 @@ class SendBaseModeWeb: is_dir = os.path.isdir(this_filesystem_path) if is_dir: - dirs.append({ - 'basename': filename - }) + dirs.append({"basename": filename}) else: size = os.path.getsize(this_filesystem_path) size_human = self.common.human_readable_filesize(size) - files.append({ - 'basename': filename, - 'size_human': size_human - }) + files.append({"basename": filename, "size_human": size_human}) return files, dirs def stream_individual_file(self, filesystem_path): @@ -136,7 +144,7 @@ class SendBaseModeWeb: # gzip compress the individual file, if it hasn't already been compressed if use_gzip: if filesystem_path not in self.gzip_individual_files: - gzip_filename = tempfile.mkstemp('wb+')[1] + gzip_filename = tempfile.mkstemp("wb+")[1] self._gzip_compress(filesystem_path, gzip_filename, 6, None) self.gzip_individual_files[filesystem_path] = gzip_filename @@ -154,10 +162,11 @@ class SendBaseModeWeb: # Tell GUI the individual file started history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, { - 'id': history_id, - 'filesize': filesize - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_STARTED, + path, + {"id": history_id, "filesize": filesize}, + ) # Only GET requests are allowed, any other method should fail if request.method != "GET": @@ -166,11 +175,11 @@ class SendBaseModeWeb: def generate(): chunk_size = 102400 # 100kb - fp = open(file_to_download, 'rb') + fp = open(file_to_download, "rb") done = False while not done: chunk = fp.read(chunk_size) - if chunk == b'': + if chunk == b"": done = True else: try: @@ -179,59 +188,79 @@ class SendBaseModeWeb: # Tell GUI the progress downloaded_bytes = fp.tell() percent = (1.0 * downloaded_bytes / filesize) * 100 - if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': + if ( + not self.web.is_gui + or self.common.platform == "Linux" + or self.common.platform == "BSD" + ): sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) + "\r{0:s}, {1:.2f}% ".format( + self.common.human_readable_filesize( + downloaded_bytes + ), + percent, + ) + ) sys.stdout.flush() - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, { - 'id': history_id, - 'bytes': downloaded_bytes, - 'filesize': filesize - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, + path, + { + "id": history_id, + "bytes": downloaded_bytes, + "filesize": filesize, + }, + ) done = False except: # Looks like the download was canceled done = True # Tell the GUI the individual file was canceled - self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, + path, + {"id": history_id}, + ) fp.close() - if self.common.platform != 'Darwin': + if self.common.platform != "Darwin": sys.stdout.write("\n") basename = os.path.basename(filesystem_path) r = Response(generate()) if use_gzip: - r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', filesize) - r.headers.set('Content-Disposition', 'inline', filename=basename) + r.headers.set("Content-Encoding", "gzip") + r.headers.set("Content-Length", filesize) + r.headers.set("Content-Disposition", "inline", filename=basename) r = self.web.add_security_headers(r) (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: - r.headers.set('Content-Type', content_type) + r.headers.set("Content-Type", content_type) return r def should_use_gzip(self): """ Should we use gzip for this browser? """ - return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + return (not self.is_zipped) and ( + "gzip" in request.headers.get("Accept-Encoding", "").lower() + ) - def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): + def _gzip_compress( + self, input_filename, output_filename, level, processed_size_callback=None + ): """ Compress a file with gzip, without loading the whole thing into memory Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror """ bytes_processed = 0 - blocksize = 1 << 16 # 64kB - with open(input_filename, 'rb') as input_file: - output_file = gzip.open(output_filename, 'wb', level) + blocksize = 1 << 16 # 64kB + with open(input_filename, "rb") as input_file: + output_file = gzip.open(output_filename, "wb", level) while True: if processed_size_callback is not None: processed_size_callback(bytes_processed) @@ -269,7 +298,7 @@ class SendBaseModeWeb: """ pass - def render_logic(self, path=''): + def render_logic(self, path=""): """ Inherited class will implement this. """ diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 8a3f5969..21dea639 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -13,18 +13,22 @@ class ShareModeWeb(SendBaseModeWeb): """ All of the web logic for share mode """ + def init(self): - self.common.log('ShareModeWeb', 'init') + self.common.log("ShareModeWeb", "init") # Allow downloading individual files if "Stop sharing after files have been sent" is unchecked - self.download_individual_files = not self.common.settings.get('close_after_first_download') + self.download_individual_files = not self.common.settings.get( + "close_after_first_download" + ) def define_routes(self): """ The web app routes for sharing files """ - @self.web.app.route('/', defaults={'path': ''}) - @self.web.app.route('/') + + @self.web.app.route("/", defaults={"path": ""}) + @self.web.app.route("/") def index(path): """ Render the template for the onionshare landing page. @@ -35,8 +39,10 @@ class ShareModeWeb(SendBaseModeWeb): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html'), - static_url_path=self.web.static_url_path) + r = make_response( + render_template("denied.html"), + static_url_path=self.web.static_url_path, + ) return self.web.add_security_headers(r) # If download is allowed to continue, serve download page @@ -56,13 +62,16 @@ class ShareModeWeb(SendBaseModeWeb): # currently a download deny_download = not self.web.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template('denied.html', - static_url_path=self.web.static_url_path)) + r = make_response( + render_template( + "denied.html", static_url_path=self.web.static_url_path + ) + ) return self.web.add_security_headers(r) # Prepare some variables to use inside generate() function below # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') + shutdown_func = request.environ.get("werkzeug.server.shutdown") path = request.path # If this is a zipped file, then serve as-is. If it's not zipped, then, @@ -79,10 +88,9 @@ class ShareModeWeb(SendBaseModeWeb): # Tell GUI the download started history_id = self.cur_history_id self.cur_history_id += 1 - self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': history_id, - 'use_gzip': use_gzip - }) + self.web.add_request( + self.web.REQUEST_STARTED, path, {"id": history_id, "use_gzip": use_gzip} + ) basename = os.path.basename(self.download_filename) @@ -93,19 +101,19 @@ class ShareModeWeb(SendBaseModeWeb): chunk_size = 102400 # 100kb - fp = open(file_to_download, 'rb') + fp = open(file_to_download, "rb") self.web.done = False canceled = False while not self.web.done: # The user has canceled the download, so stop serving the file if not self.web.stop_q.empty(): - self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_CANCELED, path, {"id": history_id} + ) break chunk = fp.read(chunk_size) - if chunk == b'': + if chunk == b"": self.web.done = True else: try: @@ -116,15 +124,26 @@ class ShareModeWeb(SendBaseModeWeb): percent = (1.0 * downloaded_bytes / self.filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': + if ( + not self.web.is_gui + or self.common.platform == "Linux" + or self.common.platform == "BSD" + ): sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) + "\r{0:s}, {1:.2f}% ".format( + self.common.human_readable_filesize( + downloaded_bytes + ), + percent, + ) + ) sys.stdout.flush() - self.web.add_request(self.web.REQUEST_PROGRESS, path, { - 'id': history_id, - 'bytes': downloaded_bytes - }) + self.web.add_request( + self.web.REQUEST_PROGRESS, + path, + {"id": history_id, "bytes": downloaded_bytes}, + ) self.web.done = False except: # looks like the download was canceled @@ -132,13 +151,13 @@ class ShareModeWeb(SendBaseModeWeb): canceled = True # tell the GUI the download has canceled - self.web.add_request(self.web.REQUEST_CANCELED, path, { - 'id': history_id - }) + self.web.add_request( + self.web.REQUEST_CANCELED, path, {"id": history_id} + ) fp.close() - if self.common.platform != 'Darwin': + if self.common.platform != "Darwin": sys.stdout.write("\n") # Download is finished @@ -151,44 +170,51 @@ class ShareModeWeb(SendBaseModeWeb): self.web.running = False try: if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') + raise RuntimeError("Not running with the Werkzeug Server") shutdown_func() except: pass r = Response(generate()) if use_gzip: - r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', self.filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) + r.headers.set("Content-Encoding", "gzip") + r.headers.set("Content-Length", self.filesize) + r.headers.set("Content-Disposition", "attachment", filename=basename) r = self.web.add_security_headers(r) # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: - r.headers.set('Content-Type', content_type) + r.headers.set("Content-Type", content_type) return r - def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): - return make_response(render_template( - 'send.html', - file_info=self.file_info, - files=files, - dirs=dirs, - breadcrumbs=breadcrumbs, - breadcrumbs_leaf=breadcrumbs_leaf, - filename=os.path.basename(self.download_filename), - filesize=self.filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped, - static_url_path=self.web.static_url_path, - download_individual_files=self.download_individual_files)) + def directory_listing_template( + self, path, files, dirs, breadcrumbs, breadcrumbs_leaf + ): + return make_response( + render_template( + "send.html", + file_info=self.file_info, + files=files, + dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, + filename=os.path.basename(self.download_filename), + filesize=self.filesize, + filesize_human=self.common.human_readable_filesize( + self.download_filesize + ), + is_zipped=self.is_zipped, + static_url_path=self.web.static_url_path, + download_individual_files=self.download_individual_files, + ) + ) def set_file_info_custom(self, filenames, processed_size_callback): self.common.log("ShareModeWeb", "set_file_info_custom") self.web.cancel_compression = False self.build_zipfile_list(filenames, processed_size_callback) - def render_logic(self, path=''): + def render_logic(self, path=""): if path in self.files: filesystem_path = self.files[path] @@ -198,7 +224,7 @@ class ShareModeWeb(SendBaseModeWeb): filenames = [] for filename in os.listdir(filesystem_path): if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') + filenames.append(filename + "/") else: filenames.append(filename) filenames.sort() @@ -221,7 +247,7 @@ class ShareModeWeb(SendBaseModeWeb): else: # Special case loading / - if path == '': + if path == "": # Root directory listing filenames = list(self.root_files) filenames.sort() @@ -237,28 +263,34 @@ class ShareModeWeb(SendBaseModeWeb): self.common.log("ShareModeWeb", "build_zipfile_list") for filename in filenames: info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) + "filename": filename, + "basename": os.path.basename(filename.rstrip("/")), } if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) + info["size"] = os.path.getsize(filename) + info["size_human"] = self.common.human_readable_filesize(info["size"]) + self.file_info["files"].append(info) if os.path.isdir(filename): - info['size'] = self.common.dir_size(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['dirs'].append(info) - self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) - self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + info["size"] = self.common.dir_size(filename) + info["size_human"] = self.common.human_readable_filesize(info["size"]) + self.file_info["dirs"].append(info) + self.file_info["files"] = sorted( + self.file_info["files"], key=lambda k: k["basename"] + ) + self.file_info["dirs"] = sorted( + self.file_info["dirs"], key=lambda k: k["basename"] + ) # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] + if len(self.file_info["files"]) == 1 and len(self.file_info["dirs"]) == 0: + self.download_filename = self.file_info["files"][0]["filename"] + self.download_filesize = self.file_info["files"][0]["size"] # Compress the file with gzip now, so we don't have to do it on each request - self.gzip_filename = tempfile.mkstemp('wb+')[1] - self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback) + self.gzip_filename = tempfile.mkstemp("wb+")[1] + self._gzip_compress( + self.download_filename, self.gzip_filename, 6, processed_size_callback + ) self.gzip_filesize = os.path.getsize(self.gzip_filename) # Make sure the gzip file gets cleaned up when onionshare stops @@ -268,17 +300,19 @@ class ShareModeWeb(SendBaseModeWeb): else: # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.zip_writer = ZipWriter( + self.common, processed_size_callback=processed_size_callback + ) self.download_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) + for info in self.file_info["files"]: + self.zip_writer.add_file(info["filename"]) # Canceling early? if self.web.cancel_compression: self.zip_writer.close() return False - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): + for info in self.file_info["dirs"]: + if not self.zip_writer.add_dir(info["filename"]): return False self.zip_writer.close() @@ -298,6 +332,7 @@ class ZipWriter(object): with. If a zip_filename is not passed in, it will use the default onionshare filename. """ + def __init__(self, common, zip_filename=None, processed_size_callback=None): self.common = common self.cancel_compression = False @@ -305,9 +340,11 @@ class ZipWriter(object): if zip_filename: self.zip_filename = zip_filename else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) + self.zip_filename = "{0:s}/onionshare_{1:s}.zip".format( + tempfile.mkdtemp(), self.common.random_string(4, 6) + ) - self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) + self.z = zipfile.ZipFile(self.zip_filename, "w", allowZip64=True) self.processed_size_callback = processed_size_callback if self.processed_size_callback is None: self.processed_size_callback = lambda _: None @@ -326,7 +363,7 @@ class ZipWriter(object): """ Add a directory, and all of its children, to the zip archive. """ - dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' + dir_to_strip = os.path.dirname(filename.rstrip("/")) + "/" for dirpath, dirnames, filenames in os.walk(filename): for f in filenames: # Canceling early? @@ -335,7 +372,7 @@ class ZipWriter(object): full_filename = os.path.join(dirpath, f) if not os.path.islink(full_filename): - arc_filename = full_filename[len(dir_to_strip):] + arc_filename = full_filename[len(dir_to_strip) :] self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) self._size += os.path.getsize(full_filename) self.processed_size_callback(self._size) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index f3e1e07a..4c4207e6 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -10,7 +10,15 @@ from distutils.version import LooseVersion as Version from urllib.request import urlopen import flask -from flask import Flask, request, render_template, abort, make_response, send_file, __version__ as flask_version +from flask import ( + Flask, + request, + render_template, + abort, + make_response, + send_file, + __version__ as flask_version, +) from flask_httpauth import HTTPBasicAuth from .. import strings @@ -24,6 +32,7 @@ from .website_mode import WebsiteModeWeb def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): pass + try: flask.cli.show_server_banner = stubbed_show_server_banner except: @@ -34,6 +43,7 @@ class Web: """ The Web object is the OnionShare web server, powered by flask """ + REQUEST_LOAD = 0 REQUEST_STARTED = 1 REQUEST_PROGRESS = 2 @@ -50,14 +60,16 @@ class Web: REQUEST_OTHER = 13 REQUEST_INVALID_PASSWORD = 14 - def __init__(self, common, is_gui, mode='share'): + def __init__(self, common, is_gui, mode="share"): self.common = common - self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode)) + self.common.log("Web", "__init__", "is_gui={}, mode={}".format(is_gui, mode)) # The flask app - self.app = Flask(__name__, - static_folder=self.common.get_resource_path('static'), - template_folder=self.common.get_resource_path('templates')) + self.app = Flask( + __name__, + static_folder=self.common.get_resource_path("static"), + template_folder=self.common.get_resource_path("templates"), + ) self.app.secret_key = self.common.random_string(8) self.generate_static_url_path() self.auth = HTTPBasicAuth() @@ -77,7 +89,7 @@ class Web: # Are we using receive mode? self.mode = mode - if self.mode == 'receive': + if self.mode == "receive": # Use custom WSGI middleware, to modify environ self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) # Use a custom Request class to track upload progess @@ -87,16 +99,16 @@ class Web: # by default. To prevent content injection through template variables in # earlier versions of Flask, we force autoescaping in the Jinja2 template # engine if we detect a Flask version with insecure default behavior. - if Version(flask_version) < Version('0.11'): + if Version(flask_version) < Version("0.11"): # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape self.security_headers = [ - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') + ("X-Frame-Options", "DENY"), + ("X-Xss-Protection", "1; mode=block"), + ("X-Content-Type-Options", "nosniff"), + ("Referrer-Policy", "no-referrer"), + ("Server", "OnionShare"), ] self.q = queue.Queue() @@ -119,19 +131,19 @@ class Web: self.share_mode = None self.receive_mode = None self.website_mode = None - if self.mode == 'share': + if self.mode == "share": self.share_mode = ShareModeWeb(self.common, self) - elif self.mode == 'receive': + elif self.mode == "receive": self.receive_mode = ReceiveModeWeb(self.common, self) - elif self.mode == 'website': + elif self.mode == "website": self.website_mode = WebsiteModeWeb(self.common, self) def get_mode(self): - if self.mode == 'share': + if self.mode == "share": return self.share_mode - elif self.mode == 'receive': + elif self.mode == "receive": return self.receive_mode - elif self.mode == 'website': + elif self.mode == "website": return self.website_mode else: return None @@ -139,14 +151,20 @@ class Web: def generate_static_url_path(self): # The static URL path has a 128-bit random number in it to avoid having name # collisions with files that might be getting shared - self.static_url_path = '/static_{}'.format(self.common.random_string(16)) - self.common.log('Web', 'generate_static_url_path', 'new static_url_path is {}'.format(self.static_url_path)) + self.static_url_path = "/static_{}".format(self.common.random_string(16)) + self.common.log( + "Web", + "generate_static_url_path", + "new static_url_path is {}".format(self.static_url_path), + ) # Update the flask route to handle the new static URL path self.app.static_url_path = self.static_url_path self.app.add_url_rule( - self.static_url_path + '/', - endpoint='static', view_func=self.app.send_static_file) + self.static_url_path + "/", + endpoint="static", + view_func=self.app.send_static_file, + ) def define_common_routes(self): """ @@ -155,7 +173,7 @@ class Web: @self.auth.get_password def get_pw(username): - if username == 'onionshare': + if username == "onionshare": return self.password else: return None @@ -163,11 +181,12 @@ class Web: @self.app.before_request def conditional_auth_check(): # Allow static files without basic authentication - if(request.path.startswith(self.static_url_path + '/')): + if request.path.startswith(self.static_url_path + "/"): return None # If public mode is disabled, require authentication - if not self.common.settings.get('public_mode'): + if not self.common.settings.get("public_mode"): + @self.auth.login_required def _check_login(): return None @@ -191,46 +210,63 @@ class Web: return "" abort(404) - if self.mode != 'website': + if self.mode != "website": + @self.app.route("/favicon.ico") def favicon(): - return send_file('{}/img/favicon.ico'.format(self.common.get_resource_path('static'))) + return send_file( + "{}/img/favicon.ico".format(self.common.get_resource_path("static")) + ) def error401(self): auth = request.authorization if auth: - if auth['username'] == 'onionshare' and auth['password'] not in self.invalid_passwords: - print('Invalid password guess: {}'.format(auth['password'])) - self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth['password']) - - self.invalid_passwords.append(auth['password']) + if ( + auth["username"] == "onionshare" + and auth["password"] not in self.invalid_passwords + ): + print("Invalid password guess: {}".format(auth["password"])) + self.add_request(Web.REQUEST_INVALID_PASSWORD, data=auth["password"]) + + self.invalid_passwords.append(auth["password"]) self.invalid_passwords_count += 1 if self.invalid_passwords_count == 20: self.add_request(Web.REQUEST_RATE_LIMIT) self.force_shutdown() - print("Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.") + print( + "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share." + ) - r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) + r = make_response( + render_template("401.html", static_url_path=self.static_url_path), 401 + ) return self.add_security_headers(r) def error403(self): self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403) + r = make_response( + render_template("403.html", static_url_path=self.static_url_path), 403 + ) return self.add_security_headers(r) def error404(self, history_id): - self.add_request(self.REQUEST_INDIVIDUAL_FILE_STARTED, '{}'.format(request.path), { - 'id': history_id, - 'status_code': 404 - }) + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + "{}".format(request.path), + {"id": history_id, "status_code": 404}, + ) self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) + r = make_response( + render_template("404.html", static_url_path=self.static_url_path), 404 + ) return self.add_security_headers(r) def error405(self): - r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405) + r = make_response( + render_template("405.html", static_url_path=self.static_url_path), 405 + ) return self.add_security_headers(r) def add_security_headers(self, r): @@ -240,39 +276,53 @@ class Web: for header, value in self.security_headers: r.headers.set(header, value) # Set a CSP header unless in website mode and the user has disabled it - if not self.common.settings.get('csp_header_disabled') or self.mode != 'website': - r.headers.set('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;') + if ( + not self.common.settings.get("csp_header_disabled") + or self.mode != "website" + ): + r.headers.set( + "Content-Security-Policy", + "default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:;", + ) return r def _safe_select_jinja_autoescape(self, filename): if filename is None: return True - return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + return filename.endswith((".html", ".htm", ".xml", ".xhtml")) def add_request(self, request_type, path=None, data=None): """ Add a request to the queue, to communicate with the GUI. """ - self.q.put({ - 'type': request_type, - 'path': path, - 'data': data - }) + self.q.put({"type": request_type, "path": path, "data": data}) def generate_password(self, persistent_password=None): - self.common.log('Web', 'generate_password', 'persistent_password={}'.format(persistent_password)) - if persistent_password != None and persistent_password != '': + self.common.log( + "Web", + "generate_password", + "persistent_password={}".format(persistent_password), + ) + if persistent_password != None and persistent_password != "": self.password = persistent_password - self.common.log('Web', 'generate_password', 'persistent_password sent, so password is: "{}"'.format(self.password)) + self.common.log( + "Web", + "generate_password", + 'persistent_password sent, so password is: "{}"'.format(self.password), + ) else: self.password = self.common.build_password() - self.common.log('Web', 'generate_password', 'built random password: "{}"'.format(self.password)) + self.common.log( + "Web", + "generate_password", + 'built random password: "{}"'.format(self.password), + ) def verbose_mode(self): """ Turn on verbose mode, which will log flask errors to a file. """ - flask_log_filename = os.path.join(self.common.build_data_dir(), 'flask.log') + flask_log_filename = os.path.join(self.common.build_data_dir(), "flask.log") log_handler = logging.FileHandler(flask_log_filename) log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) @@ -287,9 +337,9 @@ class Web: """ # Shutdown the flask service try: - func = request.environ.get('werkzeug.server.shutdown') + func = request.environ.get("werkzeug.server.shutdown") if func is None: - raise RuntimeError('Not running with the Werkzeug Server') + raise RuntimeError("Not running with the Werkzeug Server") func() except: pass @@ -299,7 +349,13 @@ class Web: """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, password={}'.format(port, stay_open, public_mode, password)) + self.common.log( + "Web", + "start", + "port={}, stay_open={}, public_mode={}, password={}".format( + port, stay_open, public_mode, password + ), + ) self.stay_open = stay_open @@ -311,10 +367,10 @@ class Web: pass # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) - if os.path.exists('/usr/share/anon-ws-base-files/workstation'): - host = '0.0.0.0' + if os.path.exists("/usr/share/anon-ws-base-files/workstation"): + host = "0.0.0.0" else: - host = '127.0.0.1' + host = "127.0.0.1" self.running = True self.app.run(host=host, port=port, threaded=True) @@ -323,7 +379,7 @@ class Web: """ Stop the flask web server by loading /shutdown. """ - self.common.log('Web', 'stop', 'stopping server') + self.common.log("Web", "stop", "stopping server") # Let the mode know that the user stopped the server self.stop_q.put(True) @@ -331,8 +387,10 @@ class Web: # To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown # (We're putting the shutdown_password in the path as well to make routing simpler) if self.running: - requests.get('http://127.0.0.1:{}/{}/shutdown'.format(port, self.shutdown_password), - auth=requests.auth.HTTPBasicAuth('onionshare', self.password)) + requests.get( + "http://127.0.0.1:{}/{}/shutdown".format(port, self.shutdown_password), + auth=requests.auth.HTTPBasicAuth("onionshare", self.password), + ) # Reset any password that was in use self.password = None diff --git a/onionshare/web/website_mode.py b/onionshare/web/website_mode.py index 9dac8627..61b6d2c6 100644 --- a/onionshare/web/website_mode.py +++ b/onionshare/web/website_mode.py @@ -12,6 +12,7 @@ class WebsiteModeWeb(SendBaseModeWeb): """ All of the web logic for website mode """ + def init(self): pass @@ -19,38 +20,45 @@ class WebsiteModeWeb(SendBaseModeWeb): """ The web app routes for sharing a website """ - @self.web.app.route('/', defaults={'path': ''}) - @self.web.app.route('/') + + @self.web.app.route("/", defaults={"path": ""}) + @self.web.app.route("/") def path_public(path): return path_logic(path) - def path_logic(path=''): + def path_logic(path=""): """ Render the onionshare website. """ return self.render_logic(path) - def directory_listing_template(self, path, files, dirs, breadcrumbs, breadcrumbs_leaf): - return make_response(render_template('listing.html', - path=path, - files=files, - dirs=dirs, - breadcrumbs=breadcrumbs, - breadcrumbs_leaf=breadcrumbs_leaf, - static_url_path=self.web.static_url_path)) + def directory_listing_template( + self, path, files, dirs, breadcrumbs, breadcrumbs_leaf + ): + return make_response( + render_template( + "listing.html", + path=path, + files=files, + dirs=dirs, + breadcrumbs=breadcrumbs, + breadcrumbs_leaf=breadcrumbs_leaf, + static_url_path=self.web.static_url_path, + ) + ) def set_file_info_custom(self, filenames, processed_size_callback): self.common.log("WebsiteModeWeb", "set_file_info_custom") self.web.cancel_compression = True - def render_logic(self, path=''): + def render_logic(self, path=""): if path in self.files: filesystem_path = self.files[path] # If it's a directory if os.path.isdir(filesystem_path): # Is there an index.html? - index_path = os.path.join(path, 'index.html') + index_path = os.path.join(path, "index.html") if index_path in self.files: # Render it return self.stream_individual_file(self.files[index_path]) @@ -60,7 +68,7 @@ class WebsiteModeWeb(SendBaseModeWeb): filenames = [] for filename in os.listdir(filesystem_path): if os.path.isdir(os.path.join(filesystem_path, filename)): - filenames.append(filename + '/') + filenames.append(filename + "/") else: filenames.append(filename) filenames.sort() @@ -78,8 +86,8 @@ class WebsiteModeWeb(SendBaseModeWeb): else: # Special case loading / - if path == '': - index_path = 'index.html' + if path == "": + index_path = "index.html" if index_path in self.files: # Render it return self.stream_individual_file(self.files[index_path]) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 99c52937..23d8dd3d 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -32,22 +32,26 @@ from onionshare.onionshare import OnionShare from .onionshare_gui import OnionShareGui + class Application(QtWidgets.QApplication): """ This is Qt's QApplication class. It has been overridden to support threads and the quick keyboard shortcut. """ + def __init__(self, common): - if common.platform == 'Linux' or common.platform == 'BSD': + if common.platform == "Linux" or common.platform == "BSD": self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtWidgets.QApplication.__init__(self, sys.argv) self.installEventFilter(self) def eventFilter(self, obj, event): - if (event.type() == QtCore.QEvent.KeyPress and - event.key() == QtCore.Qt.Key_Q and - event.modifiers() == QtCore.Qt.ControlModifier): - self.quit() + if ( + event.type() == QtCore.QEvent.KeyPress + and event.key() == QtCore.Qt.Key_Q + and event.modifiers() == QtCore.Qt.ControlModifier + ): + self.quit() return False @@ -70,11 +74,34 @@ def main(): qtapp = Application(common) # Parse arguments - parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) - parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk") - parser.add_argument('--filenames', metavar='filenames', nargs='+', help="List of files or folders to share") - parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)") + parser = argparse.ArgumentParser( + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=48) + ) + parser.add_argument( + "--local-only", + action="store_true", + dest="local_only", + help="Don't use Tor (only for development)", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Log OnionShare errors to stdout, and web errors to disk", + ) + parser.add_argument( + "--filenames", + metavar="filenames", + nargs="+", + help="List of files or folders to share", + ) + parser.add_argument( + "--config", + metavar="config", + default=False, + help="Custom JSON config file location (optional)", + ) args = parser.parse_args() filenames = args.filenames @@ -118,10 +145,12 @@ def main(): def shutdown(): onion.cleanup() app.cleanup() + qtapp.aboutToQuit.connect(shutdown) # All done sys.exit(qtapp.exec_()) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 3ef285c4..04709dc2 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -29,10 +29,12 @@ from ..threads import OnionThread from ..threads import AutoStartTimer from ..widgets import Alert + class Mode(QtWidgets.QWidget): """ The class that all modes inherit from """ + start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() @@ -41,7 +43,17 @@ class Mode(QtWidgets.QWidget): starting_server_early = QtCore.pyqtSignal() set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): + def __init__( + self, + common, + qtapp, + app, + status_bar, + server_status_label, + system_tray, + filenames=None, + local_only=False, + ): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -65,7 +77,9 @@ class Mode(QtWidgets.QWidget): self.startup_thread = None # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) + self.server_status = ServerStatus( + self.common, self.qtapp, self.app, None, self.local_only + ) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -98,16 +112,26 @@ class Mode(QtWidgets.QWidget): """ Returns a human-friendly time delta from given seconds. """ - days = secs//86400 - hours = (secs - days*86400)//3600 - minutes = (secs - days*86400 - hours*3600)//60 - seconds = secs - days*86400 - hours*3600 - minutes*60 + days = secs // 86400 + hours = (secs - days * 86400) // 3600 + minutes = (secs - days * 86400 - hours * 3600) // 60 + seconds = secs - days * 86400 - hours * 3600 - minutes * 60 if not seconds: - seconds = '0' - result = ("{0}{1}, ".format(days, strings._('days_first_letter')) if days else "") + \ - ("{0}{1}, ".format(hours, strings._('hours_first_letter')) if hours else "") + \ - ("{0}{1}, ".format(minutes, strings._('minutes_first_letter')) if minutes else "") + \ - "{0}{1}".format(seconds, strings._('seconds_first_letter')) + seconds = "0" + result = ( + ("{0}{1}, ".format(days, strings._("days_first_letter")) if days else "") + + ( + "{0}{1}, ".format(hours, strings._("hours_first_letter")) + if hours + else "" + ) + + ( + "{0}{1}, ".format(minutes, strings._("minutes_first_letter")) + if minutes + else "" + ) + + "{0}{1}".format(seconds, strings._("seconds_first_letter")) + ) return result @@ -120,25 +144,45 @@ class Mode(QtWidgets.QWidget): if self.server_status.autostart_timer_datetime: now = QtCore.QDateTime.currentDateTime() if self.server_status.local_only: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_widget.dateTime()) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_widget.dateTime() + ) else: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_datetime.replace(second=0, microsecond=0)) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_datetime.replace( + second=0, microsecond=0 + ) + ) # Update the server button if seconds_remaining > 0: - self.server_status.server_button.setText(strings._('gui_waiting_to_start').format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + strings._("gui_waiting_to_start").format( + self.human_friendly_time(seconds_remaining) + ) + ) else: - self.server_status.server_button.setText(strings._('gui_please_wait')) + self.server_status.server_button.setText( + strings._("gui_please_wait") + ) # If the auto-stop timer has stopped, stop the server if self.server_status.status == ServerStatus.STATUS_STARTED: - if self.app.autostop_timer_thread and self.common.settings.get('autostop_timer'): + if self.app.autostop_timer_thread and self.common.settings.get( + "autostop_timer" + ): if self.autostop_timer_datetime_delta > 0: now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime) + seconds_remaining = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Update the server button server_button_text = self.get_stop_server_autostop_timer_text() - self.server_status.server_button.setText(server_button_text.format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + server_button_text.format( + self.human_friendly_time(seconds_remaining) + ) + ) self.status_bar.clearMessage() if not self.app.autostop_timer_thread.is_alive(): @@ -168,16 +212,16 @@ class Mode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('Mode', 'start_server') + self.common.log("Mode", "start_server") self.start_server_custom() self.set_server_active.emit(True) - self.app.set_stealth(self.common.settings.get('use_stealth')) + self.app.set_stealth(self.common.settings.get("use_stealth")) # Clear the status bar self.status_bar.clearMessage() - self.server_status_label.setText('') + self.server_status_label.setText("") # Ensure we always get a new random port each time we might launch an OnionThread self.app.port = None @@ -192,7 +236,7 @@ class Mode(QtWidgets.QWidget): # If scheduling a share, delay starting the real share if self.server_status.autostart_timer_datetime: - self.common.log('Mode', 'start_server', 'Starting auto-start timer') + self.common.log("Mode", "start_server", "Starting auto-start timer") self.startup_thread = AutoStartTimer(self) # Once the timer has finished, start the real share, with a WebThread self.startup_thread.success.connect(self.start_scheduled_service) @@ -201,7 +245,7 @@ class Mode(QtWidgets.QWidget): self.startup_thread.start() def start_onion_thread(self, obtain_onion_early=False): - self.common.log('Mode', 'start_server', 'Starting an onion thread') + self.common.log("Mode", "start_server", "Starting an onion thread") self.obtain_onion_early = obtain_onion_early self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) @@ -213,7 +257,7 @@ class Mode(QtWidgets.QWidget): # We start a new OnionThread with the saved scheduled key from settings self.common.settings.load() self.obtain_onion_early = obtain_onion_early - self.common.log('Mode', 'start_server', 'Starting a scheduled onion thread') + self.common.log("Mode", "start_server", "Starting a scheduled onion thread") self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) self.onion_thread.error.connect(self.starting_server_error.emit) @@ -237,7 +281,7 @@ class Mode(QtWidgets.QWidget): """ Step 2 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step2') + self.common.log("Mode", "start_server_step2") self.start_server_step2_custom() @@ -257,22 +301,28 @@ class Mode(QtWidgets.QWidget): """ Step 3 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step3') + self.common.log("Mode", "start_server_step3") self.start_server_step3_custom() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() - self.autostop_timer_datetime_delta = now.secsTo(self.server_status.autostop_timer_datetime) + self.autostop_timer_datetime_delta = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Start the auto-stop timer if self.autostop_timer_datetime_delta > 0: - self.app.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer_datetime_delta) + self.app.autostop_timer_thread = AutoStopTimer( + self.common, self.autostop_timer_datetime_delta + ) self.app.autostop_timer_thread.start() # The auto-stop timer has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() - self.start_server_error(strings._('gui_server_started_after_autostop_timer')) + self.start_server_error( + strings._("gui_server_started_after_autostop_timer") + ) def start_server_step3_custom(self): """ @@ -284,7 +334,7 @@ class Mode(QtWidgets.QWidget): """ If there's an error when trying to start the onion service """ - self.common.log('Mode', 'start_server_error') + self.common.log("Mode", "start_server_error") Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.set_server_active.emit(False) @@ -305,16 +355,16 @@ class Mode(QtWidgets.QWidget): """ self.cancel_server_custom() if self.startup_thread: - self.common.log('Mode', 'cancel_server: quitting startup thread') + self.common.log("Mode", "cancel_server: quitting startup thread") self.startup_thread.canceled = True self.app.onion.scheduled_key = None self.app.onion.scheduled_auth_cookie = None self.startup_thread.quit() if self.onion_thread: - self.common.log('Mode', 'cancel_server: quitting onion thread') + self.common.log("Mode", "cancel_server: quitting onion thread") self.onion_thread.quit() if self.web_thread: - self.common.log('Mode', 'cancel_server: quitting web thread') + self.common.log("Mode", "cancel_server: quitting web thread") self.web_thread.quit() self.stop_server() @@ -328,7 +378,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('Mode', 'stop_server') + self.common.log("Mode", "stop_server") if self.server_status.status != ServerStatus.STATUS_STOPPED: try: @@ -382,7 +432,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_RATE_LIMIT event. """ self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert( + self.common, strings._("error_rate_limit"), QtWidgets.QMessageBox.Critical + ) def handle_request_progress(self, event): """ diff --git a/onionshare_gui/mode/file_selection.py b/onionshare_gui/mode/file_selection.py index a7af61f8..c505dc03 100644 --- a/onionshare_gui/mode/file_selection.py +++ b/onionshare_gui/mode/file_selection.py @@ -24,11 +24,13 @@ from onionshare import strings from ..widgets import Alert, AddFileDialog + class DropHereLabel(QtWidgets.QLabel): """ When there are no files or folders in the FileList yet, display the 'drop files here' message and graphic. """ + def __init__(self, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) @@ -39,10 +41,16 @@ class DropHereLabel(QtWidgets.QLabel): self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + self.setPixmap( + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/logo_transparent.png") + ) + ) + ) else: - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"]) self.hide() @@ -57,6 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) @@ -65,8 +74,8 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_count_label"]) self.hide() def dragEnterEvent(self, event): @@ -78,6 +87,7 @@ class FileList(QtWidgets.QListWidget): """ The list of files and folders in the GUI. """ + files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() @@ -139,7 +149,7 @@ class FileList(QtWidgets.QListWidget): if self.count() > 0: # Add and delete an empty item, to force all items to get redrawn # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') + item = QtWidgets.QListWidgetItem("fake item") self.addItem(item) self.takeItem(self.row(item)) self.update() @@ -149,21 +159,27 @@ class FileList(QtWidgets.QListWidget): # and extend based on the overall width minus that amount. for index in range(self.count()): metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + elided = metrics.elidedText( + self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200 + ) self.item(index).setText(elided) - def dragEnterEvent(self, event): """ dragEnterEvent for dragging files and directories into the widget. """ if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + self.setStyleSheet(self.common.css["share_file_list_drag_enter"]) count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) + self.drop_count.setText("+{}".format(count)) size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.setGeometry( + self.width() - size_hint.width() - 10, + self.height() - size_hint.height() - 10, + size_hint.width(), + size_hint.height(), + ) self.drop_count.show() event.accept() else: @@ -173,7 +189,7 @@ class FileList(QtWidgets.QListWidget): """ dragLeaveEvent for dragging files and directories into the widget. """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() event.accept() self.update() @@ -201,7 +217,7 @@ class FileList(QtWidgets.QListWidget): else: event.ignore() - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() self.files_dropped.emit() @@ -238,12 +254,14 @@ class FileList(QtWidgets.QListWidget): # Item's filename attribute and size labels item.filename = filename item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + item_size.setStyleSheet(self.common.css["share_file_list_item_size"]) - item.basename = os.path.basename(filename.rstrip('/')) + item.basename = os.path.basename(filename.rstrip("/")) # Use the basename as the method with which to sort the list metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + elided = metrics.elidedText( + item.basename, QtCore.Qt.ElideRight, self.sizeHint().width() + ) item.setData(QtCore.Qt.DisplayRole, elided) # Item's delete button @@ -255,9 +273,13 @@ class FileList(QtWidgets.QListWidget): item.item_button = QtWidgets.QPushButton() item.item_button.setDefault(False) item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/file_delete.png")) + ) item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + item.item_button.setSizePolicy( + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed + ) # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() @@ -265,7 +287,7 @@ class FileList(QtWidgets.QListWidget): item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') + item_info.setObjectName("item-info") item_info.setLayout(item_info_layout) # Create the item's widget and layouts @@ -288,6 +310,7 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ + def __init__(self, common, parent): super(FileSelection, self).__init__() @@ -303,21 +326,21 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": # The macOS sandbox makes it so the Mac version needs separate add files # and folders buttons, in order to use native file selection dialogs - self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button = QtWidgets.QPushButton(strings._("gui_add_files")) self.add_files_button.clicked.connect(self.add_files) - self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button = QtWidgets.QPushButton(strings._("gui_add_folder")) self.add_folder_button.clicked.connect(self.add_folder) else: - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button = QtWidgets.QPushButton(strings._("gui_add")) self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) + self.delete_button = QtWidgets.QPushButton(strings._("gui_delete")) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": button_layout.addWidget(self.add_files_button) button_layout.addWidget(self.add_folder_button) else: @@ -336,14 +359,14 @@ class FileSelection(QtWidgets.QVBoxLayout): """ # All buttons should be hidden if the server is on if self.server_on: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.hide() self.add_folder_button.hide() else: self.add_button.hide() self.delete_button.hide() else: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.show() self.add_folder_button.show() else: @@ -362,7 +385,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) + file_dialog = AddFileDialog(self.common, caption=strings._("gui_choose_items")) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) @@ -374,7 +397,9 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add files button clicked. """ - files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + files = QtWidgets.QFileDialog.getOpenFileNames( + self.parent, caption=strings._("gui_choose_items") + ) filenames = files[0] for filename in filenames: self.file_list.add_file(filename) @@ -383,9 +408,11 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add folder button clicked. """ - filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, - caption=strings._('gui_choose_items'), - options=QtWidgets.QFileDialog.ShowDirsOnly) + filename = QtWidgets.QFileDialog.getExistingDirectory( + self.parent, + caption=strings._("gui_choose_items"), + options=QtWidgets.QFileDialog.ShowDirsOnly, + ) self.file_list.add_file(filename) def delete(self): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index b8baebd1..85eec7e4 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -31,6 +31,7 @@ class HistoryItem(QtWidgets.QWidget): """ The base history item """ + STATUS_STARTED = 0 STATUS_FINISHED = 1 STATUS_CANCELED = 2 @@ -49,34 +50,42 @@ class HistoryItem(QtWidgets.QWidget): When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started) + return self._get_label_text( + "gui_all_modes_transfer_finished", + "gui_all_modes_transfer_finished_range", + started, + ) def get_canceled_label_text(self, started): """ When an item is canceled, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started) + return self._get_label_text( + "gui_all_modes_transfer_canceled", + "gui_all_modes_transfer_canceled_range", + started, + ) def _get_label_text(self, string_name, string_range_name, started): """ Return a string that contains a date, or date range. """ ended = datetime.now() - if started.year == ended.year and started.month == ended.month and started.day == ended.day: + if ( + started.year == ended.year + and started.month == ended.month + and started.day == ended.day + ): if started.hour == ended.hour and started.minute == ended.minute: - text = strings._(string_name).format( - started.strftime("%b %d, %I:%M%p") - ) + text = strings._(string_name).format(started.strftime("%b %d, %I:%M%p")) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p") ) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%b %d, %I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p") ) return text @@ -85,6 +94,7 @@ class ShareHistoryItem(HistoryItem): """ Download history item, for share mode """ + def __init__(self, common, id, total_bytes): super(ShareHistoryItem, self).__init__() self.common = common @@ -97,7 +107,11 @@ class ShareHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -107,7 +121,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) self.progress_bar.total_bytes = total_bytes # Layout @@ -124,8 +140,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_all_modes_progress_complete').format( - self.common.format_seconds(time.time() - self.started)) + pb_fmt = strings._("gui_all_modes_progress_complete").format( + self.common.format_seconds(time.time() - self.started) + ) # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) @@ -137,24 +154,26 @@ class ShareHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class ReceiveHistoryItemFile(QtWidgets.QWidget): @@ -162,7 +181,9 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): super(ReceiveHistoryItemFile, self).__init__() self.common = common - self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + self.common.log( + "ReceiveHistoryItemFile", "__init__", "filename: {}".format(filename) + ) self.filename = filename self.dir = None @@ -174,11 +195,13 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): # File size label self.filesize_label = QtWidgets.QLabel() - self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) + self.filesize_label.setStyleSheet(self.common.css["receive_file_size"]) self.filesize_label.hide() # Folder button - folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) + folder_pixmap = QtGui.QPixmap.fromImage( + QtGui.QImage(self.common.get_resource_path("images/open_folder.png")) + ) folder_icon = QtGui.QIcon(folder_pixmap) self.folder_button = QtWidgets.QPushButton() self.folder_button.clicked.connect(self.open_folder) @@ -213,29 +236,36 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): """ Open the downloads folder, with the file selected, in a cross-platform manner """ - self.common.log('ReceiveHistoryItemFile', 'open_folder') + self.common.log("ReceiveHistoryItemFile", "open_folder") if not self.dir: - self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + self.common.log( + "ReceiveHistoryItemFile", + "open_folder", + "dir has not been set yet, can't open folder", + ) return abs_filename = os.path.join(self.dir, self.filename) # Linux - if self.common.platform == 'Linux' or self.common.platform == 'BSD': + if self.common.platform == "Linux" or self.common.platform == "BSD": try: # If nautilus is available, open it - subprocess.Popen(['nautilus', abs_filename]) + subprocess.Popen(["nautilus", abs_filename]) except: - Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) + Alert( + self.common, + strings._("gui_open_folder_error_nautilus").format(abs_filename), + ) # macOS - elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + elif self.common.platform == "Darwin": + subprocess.call(["open", "-R", abs_filename]) # Windows - elif self.common.platform == 'Windows': - subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) + elif self.common.platform == "Windows": + subprocess.Popen(["explorer", "/select,{}".format(abs_filename)]) class ReceiveHistoryItem(HistoryItem): @@ -248,7 +278,11 @@ class ReceiveHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -257,13 +291,15 @@ class ReceiveHistoryItem(HistoryItem): self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setMinimum(0) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # This layout contains file widgets self.files_layout = QtWidgets.QVBoxLayout() self.files_layout.setContentsMargins(0, 0, 0, 0) files_widget = QtWidgets.QWidget() - files_widget.setStyleSheet(self.common.css['receive_file']) + files_widget.setStyleSheet(self.common.css["receive_file"]) files_widget.setLayout(self.files_layout) # Layout @@ -282,10 +318,10 @@ class ReceiveHistoryItem(HistoryItem): Using the progress from Web, update the progress bar and file size labels for each file """ - if data['action'] == 'progress': + if data["action"] == "progress": total_uploaded_bytes = 0 - for filename in data['progress']: - total_uploaded_bytes += data['progress'][filename]['uploaded_bytes'] + for filename in data["progress"]: + total_uploaded_bytes += data["progress"][filename]["uploaded_bytes"] # Update the progress bar self.progress_bar.setMaximum(self.content_length) @@ -293,35 +329,39 @@ class ReceiveHistoryItem(HistoryItem): elapsed = datetime.now() - self.started if elapsed.seconds < 10: - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(total_uploaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(total_uploaded_bytes) + ) else: estimated_time_remaining = self.common.estimated_time_remaining( - total_uploaded_bytes, - self.content_length, - self.started.timestamp()) - pb_fmt = strings._('gui_all_modes_progress_eta').format( + total_uploaded_bytes, self.content_length, self.started.timestamp() + ) + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) + estimated_time_remaining, + ) # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" - for filename in list(data['progress']): + for filename in list(data["progress"]): # Add a new file if needed if filename not in self.files: self.files[filename] = ReceiveHistoryItemFile(self.common, filename) self.files_layout.addWidget(self.files[filename]) # Update the file - self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + self.files[filename].update( + data["progress"][filename]["uploaded_bytes"], + data["progress"][filename]["complete"], + ) - elif data['action'] == 'rename': - self.files[data['old_filename']].rename(data['new_filename']) - self.files[data['new_filename']] = self.files.pop(data['old_filename']) + elif data["action"] == "rename": + self.files[data["old_filename"]].rename(data["new_filename"]) + self.files[data["new_filename"]] = self.files.pop(data["old_filename"]) - elif data['action'] == 'set_dir': - self.files[data['filename']].set_dir(data['dir']) + elif data["action"] == "set_dir": + self.files[data["filename"]].set_dir(data["dir"]) - elif data['action'] == 'finished': + elif data["action"] == "finished": # Change the status self.status = HistoryItem.STATUS_FINISHED @@ -331,7 +371,7 @@ class ReceiveHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started)) - elif data['action'] == 'canceled': + elif data["action"] == "canceled": # Change the status self.status = HistoryItem.STATUS_CANCELED @@ -346,6 +386,7 @@ class IndividualFileHistoryItem(HistoryItem): """ Individual file history item, for share mode viewing of individual files """ + def __init__(self, common, data, path): super(IndividualFileHistoryItem, self).__init__() self.status = HistoryItem.STATUS_STARTED @@ -359,11 +400,15 @@ class IndividualFileHistoryItem(HistoryItem): self.started_dt = datetime.fromtimestamp(self.started) self.status = HistoryItem.STATUS_STARTED - self.directory_listing = 'directory_listing' in data + self.directory_listing = "directory_listing" in data # Labels - self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p")) - self.timestamp_label.setStyleSheet(self.common.css['history_individual_file_timestamp_label']) + self.timestamp_label = QtWidgets.QLabel( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + self.timestamp_label.setStyleSheet( + self.common.css["history_individual_file_timestamp_label"] + ) self.path_label = QtWidgets.QLabel("{}".format(self.path)) self.status_code_label = QtWidgets.QLabel() @@ -373,7 +418,9 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # Text layout labels_layout = QtWidgets.QHBoxLayout() @@ -389,21 +436,25 @@ class IndividualFileHistoryItem(HistoryItem): self.setLayout(layout) # Is a status code already sent? - if 'status_code' in data: - self.status_code_label.setText("{}".format(data['status_code'])) - if data['status_code'] >= 200 and data['status_code'] < 300: - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) - if data['status_code'] >= 400 and data['status_code'] < 500: - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_4xx']) + if "status_code" in data: + self.status_code_label.setText("{}".format(data["status_code"])) + if data["status_code"] >= 200 and data["status_code"] < 300: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + if data["status_code"] >= 400 and data["status_code"] < 500: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_4xx"] + ) self.status = HistoryItem.STATUS_FINISHED self.progress_bar.hide() return else: - self.total_bytes = data['filesize'] + self.total_bytes = data["filesize"] self.progress_bar.setMinimum(0) - self.progress_bar.setMaximum(data['filesize']) - self.progress_bar.total_bytes = data['filesize'] + self.progress_bar.setMaximum(data["filesize"]) + self.progress_bar.total_bytes = data["filesize"] # Start at 0 self.update(0) @@ -414,7 +465,9 @@ class IndividualFileHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: self.status_code_label.setText("200") - self.status_code_label.setStyleSheet(self.common.css['history_individual_file_status_code_label_2xx']) + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) self.progress_bar.hide() self.status = HistoryItem.STATUS_FINISHED @@ -424,30 +477,33 @@ class IndividualFileHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class HistoryItemList(QtWidgets.QScrollArea): """ List of items """ + def __init__(self, common): super(HistoryItemList, self).__init__() self.common = common @@ -511,12 +567,14 @@ class HistoryItemList(QtWidgets.QScrollArea): item.close() del self.items[key] + class History(QtWidgets.QWidget): """ A history of what's happened so far in this mode. This contains an internal object full of a scrollable list of items. """ - def __init__(self, common, empty_image, empty_text, header_text, mode=''): + + def __init__(self, common, empty_image, empty_text, header_text, mode=""): super(History, self).__init__() self.common = common self.mode = mode @@ -530,17 +588,19 @@ class History(QtWidgets.QWidget): # In progress, completed, and requests labels self.in_progress_label = QtWidgets.QLabel() - self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"]) self.completed_label = QtWidgets.QLabel() - self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label.setStyleSheet(self.common.css["mode_info_label"]) self.requests_label = QtWidgets.QLabel() - self.requests_label.setStyleSheet(self.common.css['mode_info_label']) + self.requests_label.setStyleSheet(self.common.css["mode_info_label"]) # Header self.header_label = QtWidgets.QLabel(header_text) - self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) - self.clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"]) + self.clear_button = QtWidgets.QPushButton( + strings._("gui_all_modes_clear_history") + ) + self.clear_button.setStyleSheet(self.common.css["downloads_uploads_clear"]) self.clear_button.setFlat(True) self.clear_button.clicked.connect(self.reset) header_layout = QtWidgets.QHBoxLayout() @@ -557,14 +617,14 @@ class History(QtWidgets.QWidget): self.empty_image.setPixmap(empty_image) self.empty_text = QtWidgets.QLabel(empty_text) self.empty_text.setAlignment(QtCore.Qt.AlignCenter) - self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + self.empty_text.setStyleSheet(self.common.css["downloads_uploads_empty_text"]) empty_layout = QtWidgets.QVBoxLayout() empty_layout.addStretch() empty_layout.addWidget(self.empty_image) empty_layout.addWidget(self.empty_text) empty_layout.addStretch() self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setStyleSheet(self.common.css["downloads_uploads_empty"]) self.empty.setLayout(empty_layout) # When there are items @@ -589,7 +649,7 @@ class History(QtWidgets.QWidget): """ Add a new item. """ - self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + self.common.log("History", "add", "id: {}, item: {}".format(id, item)) # Hide empty, show not empty self.empty.hide() @@ -636,35 +696,47 @@ class History(QtWidgets.QWidget): Update the 'completed' widget. """ if self.completed_count == 0: - image = self.common.get_resource_path('images/history_completed_none.png') + image = self.common.get_resource_path("images/history_completed_none.png") else: - image = self.common.get_resource_path('images/history_completed.png') - self.completed_label.setText(' {1:d}'.format(image, self.completed_count)) - self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + image = self.common.get_resource_path("images/history_completed.png") + self.completed_label.setText( + ' {1:d}'.format(image, self.completed_count) + ) + self.completed_label.setToolTip( + strings._("history_completed_tooltip").format(self.completed_count) + ) def update_in_progress(self): """ Update the 'in progress' widget. """ if self.in_progress_count == 0: - image = self.common.get_resource_path('images/history_in_progress_none.png') + image = self.common.get_resource_path("images/history_in_progress_none.png") else: - image = self.common.get_resource_path('images/history_in_progress.png') + image = self.common.get_resource_path("images/history_in_progress.png") - self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) - self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + self.in_progress_label.setText( + ' {1:d}'.format(image, self.in_progress_count) + ) + self.in_progress_label.setToolTip( + strings._("history_in_progress_tooltip").format(self.in_progress_count) + ) def update_requests(self): """ Update the 'web requests' widget. """ if self.requests_count == 0: - image = self.common.get_resource_path('images/history_requests_none.png') + image = self.common.get_resource_path("images/history_requests_none.png") else: - image = self.common.get_resource_path('images/history_requests.png') + image = self.common.get_resource_path("images/history_requests.png") - self.requests_label.setText(' {1:d}'.format(image, self.requests_count)) - self.requests_label.setToolTip(strings._('history_requests_tooltip').format(self.requests_count)) + self.requests_label.setText( + ' {1:d}'.format(image, self.requests_count) + ) + self.requests_label.setToolTip( + strings._("history_requests_tooltip").format(self.requests_count) + ) class ToggleHistory(QtWidgets.QPushButton): @@ -672,6 +744,7 @@ class ToggleHistory(QtWidgets.QPushButton): Widget for toggling showing or hiding the history, as well as keeping track of the indicator counter if it's hidden """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): super(ToggleHistory, self).__init__() self.common = common @@ -691,7 +764,9 @@ class ToggleHistory(QtWidgets.QPushButton): # Keep track of indicator self.indicator_count = 0 self.indicator_label = QtWidgets.QLabel(parent=self) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.indicator_label.setStyleSheet( + self.common.css["download_uploads_indicator"] + ) self.update_indicator() def update_indicator(self, increment=False): @@ -708,14 +783,16 @@ class ToggleHistory(QtWidgets.QPushButton): self.indicator_label.hide() else: size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.setGeometry( + 35 - size.width(), 0, size.width(), size.height() + ) self.indicator_label.show() def toggle_clicked(self): """ Toggle showing and hiding the history widget """ - self.common.log('ToggleHistory', 'toggle_clicked') + self.common.log("ToggleHistory", "toggle_clicked") if self.history_widget.isVisible(): self.history_widget.hide() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index ecbfa54a..a0507949 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -25,19 +25,21 @@ from onionshare.web import Web from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode + class ReceiveMode(Mode): """ Parts of the main window UI for receiving files. """ + def init(self): """ Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'receive') + self.web = Web(self.common, True, "receive") # Server status - self.server_status.set_mode('receive') + self.server_status.set_mode("receive") self.server_status.server_started_finished.connect(self.update_primary_action) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.update_primary_action) @@ -49,21 +51,31 @@ class ReceiveMode(Mode): # Upload history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))), - strings._('gui_receive_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/receive_icon_transparent.png") + ) + ), + strings._("gui_receive_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle.png") + ), + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle_selected.png") + ), ) # Receive mode warning - receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) + receive_warning = QtWidgets.QLabel(strings._("gui_receive_mode_warning")) receive_warning.setMinimumHeight(80) receive_warning.setWordWrap(True) @@ -90,20 +102,25 @@ class ReceiveMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_receive_stop_server_autostop_timer') + return strings._("gui_receive_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.cur_history_id == 0 or not self.web.receive_mode.uploads_in_progress: + if ( + self.web.receive_mode.cur_history_id == 0 + or not self.web.receive_mode.uploads_in_progress + ): self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # An upload is probably still running - hold off on stopping the share, but block new shares. else: - self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_receive_mode_autostop_timer_waiting") + ) self.web.receive_mode.can_upload = False return False @@ -136,56 +153,68 @@ class ReceiveMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) + self.system_tray.showMessage( + strings._("systray_page_loaded_title"), + strings._("systray_page_loaded_message"), + ) def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ - item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + item = ReceiveHistoryItem( + self.common, event["data"]["id"], event["data"]["content_length"] + ) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message')) + self.system_tray.showMessage( + strings._("systray_receive_started_title"), + strings._("systray_receive_started_message"), + ) def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. """ - self.history.update(event["data"]["id"], { - 'action': 'progress', - 'progress': event["data"]["progress"] - }) + self.history.update( + event["data"]["id"], + {"action": "progress", "progress": event["data"]["progress"]}, + ) def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - self.history.update(event["data"]["id"], { - 'action': 'rename', - 'old_filename': event["data"]["old_filename"], - 'new_filename': event["data"]["new_filename"] - }) + self.history.update( + event["data"]["id"], + { + "action": "rename", + "old_filename": event["data"]["old_filename"], + "new_filename": event["data"]["new_filename"], + }, + ) def handle_request_upload_set_dir(self, event): """ Handle REQUEST_UPLOAD_SET_DIR event. """ - self.history.update(event["data"]["id"], { - 'action': 'set_dir', - 'filename': event["data"]["filename"], - 'dir': event["data"]["dir"] - }) + self.history.update( + event["data"]["id"], + { + "action": "set_dir", + "filename": event["data"]["filename"], + "dir": event["data"]["dir"], + }, + ) def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. """ - self.history.update(event["data"]["id"], { - 'action': 'finished' - }) + self.history.update(event["data"]["id"], {"action": "finished"}) self.history.completed_count += 1 self.history.in_progress_count -= 1 self.history.update_completed() @@ -195,9 +224,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_CANCELED event. """ - self.history.update(event["data"]["id"], { - 'action': 'canceled' - }) + self.history.update(event["data"]["id"], {"action": "canceled"}) self.history.in_progress_count -= 1 self.history.update_in_progress() @@ -216,4 +243,4 @@ class ReceiveMode(Mode): self.toggle_history.update_indicator() def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') + self.common.log("ReceiveMode", "update_primary_action") diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 28b439af..d0cc6a04 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -36,6 +36,7 @@ class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ + def init(self): """ Custom initialization for ReceiveMode. @@ -44,7 +45,7 @@ class ShareMode(Mode): self.compress_thread = None # Create the Web object - self.web = Web(self.common, True, 'share') + self.web = Web(self.common, True, "share") # File selection self.file_selection = FileSelection(self.common, self) @@ -53,7 +54,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('share', self.file_selection) + self.server_status.set_mode("share", self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -68,15 +69,19 @@ class ShareMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_share_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_share_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() @@ -86,9 +91,13 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -125,7 +134,7 @@ class ShareMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ @@ -134,11 +143,13 @@ class ShareMode(Mode): # If there were no attempts to download the share, or all downloads are done, we can stop if self.web.share_mode.cur_history_id == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_share_mode_autostop_timer_waiting") + ) return False def start_server_custom(self): @@ -162,7 +173,9 @@ class ShareMode(Mode): for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size( + self.filenames + ) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread @@ -216,7 +229,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('ShareMode', 'cancel_server: quitting compress thread') + self.common.log("ShareMode", "cancel_server: quitting compress thread") self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -240,7 +253,10 @@ class ShareMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message')) + self.system_tray.showMessage( + strings._("systray_share_started_title"), + strings._("systray_share_started_message"), + ) def handle_request_progress(self, event): """ @@ -250,7 +266,10 @@ class ShareMode(Mode): # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message')) + self.system_tray.showMessage( + strings._("systray_share_completed_title"), + strings._("systray_share_completed_message"), + ) # Update completed and in progress labels self.history.completed_count += 1 @@ -259,10 +278,10 @@ class ShareMode(Mode): self.history.update_in_progress() # Close on finish? - if self.common.settings.get('close_after_first_download'): + if self.common.settings.get("close_after_first_download"): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically')) + self.server_status_label.setText(strings._("closing_automatically")) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.history.cancel(event["data"]["id"]) @@ -278,7 +297,10 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message')) + self.system_tray.showMessage( + strings._("systray_share_canceled_title"), + strings._("systray_share_canceled_message"), + ) def on_reload_settings(self): """ @@ -290,7 +312,7 @@ class ShareMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('ShareMode', 'update_primary_action') + self.common.log("ShareMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -306,9 +328,15 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() @@ -343,8 +371,8 @@ class ZipProgressBar(QtWidgets.QProgressBar): self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - self.setStyleSheet(self.common.css['share_zip_progess_bar']) + self.setFormat(strings._("zip_progress_bar_format")) + self.setStyleSheet(self.common.css["share_zip_progess_bar"]) self._total_files_size = total_files_size self._processed_size = 0 diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index fed362eb..414c7be1 100644 --- a/onionshare_gui/mode/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -24,13 +24,14 @@ class CompressThread(QtCore.QThread): """ Compresses files to be shared """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(CompressThread, self).__init__() self.mode = mode - self.mode.common.log('CompressThread', '__init__') + self.mode.common.log("CompressThread", "__init__") # prepare files to share def set_processed_size(self, x): @@ -38,17 +39,21 @@ class CompressThread(QtCore.QThread): self.mode._zip_progress_bar.update_processed_size_signal.emit(x) def run(self): - self.mode.common.log('CompressThread', 'run') + self.mode.common.log("CompressThread", "run") try: - self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size) + self.mode.web.share_mode.set_file_info( + self.mode.filenames, processed_size_callback=self.set_processed_size + ) self.success.emit() - self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames + self.mode.app.cleanup_filenames += ( + self.mode.web.share_mode.cleanup_filenames + ) except OSError as e: self.error.emit(e.strerror) def cancel(self): - self.mode.common.log('CompressThread', 'cancel') + self.mode.common.log("CompressThread", "cancel") # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py index 7382f5a7..8cd2eca6 100644 --- a/onionshare_gui/mode/website_mode/__init__.py +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -33,10 +33,12 @@ from .. import Mode from ..history import History, ToggleHistory from ...widgets import Alert + class WebsiteMode(Mode): """ Parts of the main window UI for sharing files. """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) @@ -45,7 +47,7 @@ class WebsiteMode(Mode): Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'website') + self.web = Web(self.common, True, "website") # File selection self.file_selection = FileSelection(self.common, self) @@ -54,7 +56,7 @@ class WebsiteMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('website', self.file_selection) + self.server_status.set_mode("website", self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -69,16 +71,20 @@ class WebsiteMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_website_mode_no_files'), - strings._('gui_all_modes_history'), - 'website' + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_website_mode_no_files"), + strings._("gui_all_modes_history"), + "website", ) self.history.in_progress_label.hide() self.history.completed_label.hide() @@ -90,9 +96,13 @@ class WebsiteMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -126,7 +136,7 @@ class WebsiteMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ @@ -134,10 +144,9 @@ class WebsiteMode(Mode): """ self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True - def start_server_custom(self): """ Starting the server. @@ -161,7 +170,6 @@ class WebsiteMode(Mode): self.starting_server_step3.emit() self.start_server_finished.emit() - def start_server_step3_custom(self): """ Step 3 in starting the server. Display large filesize @@ -191,8 +199,7 @@ class WebsiteMode(Mode): """ Log that the server has been cancelled """ - self.common.log('WebsiteMode', 'cancel_server') - + self.common.log("WebsiteMode", "cancel_server") def handle_tor_broke_custom(self): """ @@ -210,7 +217,7 @@ class WebsiteMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('WebsiteMode', 'update_primary_action') + self.common.log("WebsiteMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -226,9 +233,15 @@ class WebsiteMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 6406023f..4639ea13 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -33,20 +33,24 @@ from .widgets import Alert from .update_checker import UpdateThread from .server_status import ServerStatus + class OnionShareGui(QtWidgets.QMainWindow): """ OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' - MODE_WEBSITE = 'website' - def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False): + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" + + def __init__( + self, common, onion, qtapp, app, filenames, config=False, local_only=False + ): super(OnionShareGui, self).__init__() self.common = common - self.common.log('OnionShareGui', '__init__') + self.common.log("OnionShareGui", "__init__") self.setMinimumWidth(820) self.setMinimumHeight(660) @@ -57,8 +61,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_SHARE - self.setWindowTitle('OnionShare') - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle("OnionShare") + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) # Load settings, if a custom config was passed in self.config = config @@ -71,40 +77,52 @@ class OnionShareGui(QtWidgets.QMainWindow): # System tray menu = QtWidgets.QMenu() - self.settings_action = menu.addAction(strings._('gui_settings_window_title')) + self.settings_action = menu.addAction(strings._("gui_settings_window_title")) self.settings_action.triggered.connect(self.open_settings) - self.help_action = menu.addAction(strings._('gui_settings_button_help')) + self.help_action = menu.addAction(strings._("gui_settings_button_help")) self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self)) - exit_action = menu.addAction(strings._('systray_menu_exit')) + exit_action = menu.addAction(strings._("systray_menu_exit")) exit_action.triggered.connect(self.close) self.system_tray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale - if self.common.platform == 'Darwin': - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) + if self.common.platform == "Darwin": + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo_grayscale.png")) + ) else: - self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.system_tray.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system_tray.setContextMenu(menu) self.system_tray.show() # Mode switcher, to switch between share files and receive files - self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button')); + self.share_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_share_button") + ) self.share_mode_button.setFixedHeight(50) self.share_mode_button.clicked.connect(self.share_mode_clicked) - self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button')); + self.receive_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_receive_button") + ) self.receive_mode_button.setFixedHeight(50) self.receive_mode_button.clicked.connect(self.receive_mode_clicked) - self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button')); + self.website_mode_button = QtWidgets.QPushButton( + strings._("gui_mode_website_button") + ) self.website_mode_button.setFixedHeight(50) self.website_mode_button.clicked.connect(self.website_mode_clicked) self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFixedWidth(40) self.settings_button.setFixedHeight(50) - self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) + self.settings_button.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/settings.png")) + ) self.settings_button.clicked.connect(self.open_settings) - self.settings_button.setStyleSheet(self.common.css['settings_button']) - mode_switcher_layout = QtWidgets.QHBoxLayout(); + self.settings_button.setStyleSheet(self.common.css["settings_button"]) + mode_switcher_layout = QtWidgets.QHBoxLayout() mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) mode_switcher_layout.addWidget(self.receive_mode_button) @@ -112,13 +130,21 @@ class OnionShareGui(QtWidgets.QMainWindow): mode_switcher_layout.addWidget(self.settings_button) # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) + self.server_status_image_stopped = QtGui.QImage( + self.common.get_resource_path("images/server_stopped.png") + ) + self.server_status_image_working = QtGui.QImage( + self.common.get_resource_path("images/server_working.png") + ) + self.server_status_image_started = QtGui.QImage( + self.common.get_resource_path("images/server_started.png") + ) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) - self.server_status_label = QtWidgets.QLabel('') - self.server_status_label.setStyleSheet(self.common.css['server_status_indicator_label']) + self.server_status_label = QtWidgets.QLabel("") + self.server_status_label.setStyleSheet( + self.common.css["server_status_indicator_label"] + ) server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) @@ -128,17 +154,34 @@ class OnionShareGui(QtWidgets.QMainWindow): # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) - self.status_bar.setStyleSheet(self.common.css['status_bar']) + self.status_bar.setStyleSheet(self.common.css["status_bar"]) self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) # Share mode - self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only) + self.share_mode = ShareMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + self.local_only, + ) self.share_mode.init() - self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.share_mode.start_server_finished.connect(self.update_server_status_indicator) - self.share_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.share_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.share_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.share_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.share_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.share_mode.stop_server_finished.connect(self.stop_server_finished) self.share_mode.start_server_finished.connect(self.clear_message) self.share_mode.server_status.button_clicked.connect(self.clear_message) @@ -147,31 +190,68 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.set_server_active.connect(self.set_server_active) # Receive mode - self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) + self.receive_mode = ReceiveMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + None, + self.local_only, + ) self.receive_mode.init() - self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.receive_mode.start_server_finished.connect(self.update_server_status_indicator) - self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.receive_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.receive_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.receive_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.receive_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.receive_mode.stop_server_finished.connect(self.stop_server_finished) self.receive_mode.start_server_finished.connect(self.clear_message) self.receive_mode.server_status.button_clicked.connect(self.clear_message) self.receive_mode.server_status.url_copied.connect(self.copy_url) - self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.receive_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) self.receive_mode.set_server_active.connect(self.set_server_active) # Website mode - self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) + self.website_mode = WebsiteMode( + self.common, + qtapp, + app, + self.status_bar, + self.server_status_label, + self.system_tray, + filenames, + ) self.website_mode.init() - self.website_mode.server_status.server_started.connect(self.update_server_status_indicator) - self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator) - self.website_mode.start_server_finished.connect(self.update_server_status_indicator) - self.website_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.website_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.website_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.website_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.website_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) self.website_mode.stop_server_finished.connect(self.stop_server_finished) self.website_mode.start_server_finished.connect(self.clear_message) self.website_mode.server_status.button_clicked.connect(self.clear_message) self.website_mode.server_status.url_copied.connect(self.copy_url) - self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.website_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) self.website_mode.set_server_active.connect(self.set_server_active) self.update_mode_switcher() @@ -218,25 +298,43 @@ class OnionShareGui(QtWidgets.QMainWindow): # Based on the current mode, switch the mode switcher button styles, # and show and hide widgets to switch modes if self.mode == self.MODE_SHARE: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.receive_mode.hide() self.share_mode.show() self.website_mode.hide() elif self.mode == self.MODE_WEBSITE: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) self.receive_mode.hide() self.share_mode.hide() self.website_mode.show() else: - self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) - self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.share_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) + self.receive_mode_button.setStyleSheet( + self.common.css["mode_switcher_selected_style"] + ) + self.website_mode_button.setStyleSheet( + self.common.css["mode_switcher_unselected_style"] + ) self.share_mode.hide() self.receive_mode.show() @@ -246,19 +344,19 @@ class OnionShareGui(QtWidgets.QMainWindow): def share_mode_clicked(self): if self.mode != self.MODE_SHARE: - self.common.log('OnionShareGui', 'share_mode_clicked') + self.common.log("OnionShareGui", "share_mode_clicked") self.mode = self.MODE_SHARE self.update_mode_switcher() def receive_mode_clicked(self): if self.mode != self.MODE_RECEIVE: - self.common.log('OnionShareGui', 'receive_mode_clicked') + self.common.log("OnionShareGui", "receive_mode_clicked") self.mode = self.MODE_RECEIVE self.update_mode_switcher() def website_mode_clicked(self): if self.mode != self.MODE_WEBSITE: - self.common.log('OnionShareGui', 'website_mode_clicked') + self.common.log("OnionShareGui", "website_mode_clicked") self.mode = self.MODE_WEBSITE self.update_mode_switcher() @@ -267,42 +365,82 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: # Share mode if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_stopped") + ) elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.share_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_share_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_share_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_started") + ) elif self.mode == self.MODE_WEBSITE: # Website mode if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_share_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_stopped") + ) elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_share_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_share_started") + ) else: # Receive mode if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_stopped')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_stopped) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_stopped") + ) elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_working) + ) if self.receive_mode.server_status.autostart_timer_datetime: - self.server_status_label.setText(strings._('gui_status_indicator_receive_scheduled')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_scheduled") + ) else: - self.server_status_label.setText(strings._('gui_status_indicator_receive_working')) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_working") + ) elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_receive_started')) + self.server_status_image_label.setPixmap( + QtGui.QPixmap.fromImage(self.server_status_image_started) + ) + self.server_status_label.setText( + strings._("gui_status_indicator_receive_started") + ) def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service @@ -313,12 +451,22 @@ class OnionShareGui(QtWidgets.QMainWindow): If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ - self.common.log('OnionShareGui', '_tor_connection_canceled') + self.common.log("OnionShareGui", "_tor_connection_canceled") def ask(): - a = Alert(self.common, strings._('gui_tor_connection_ask'), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings')) - quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit')) + a = Alert( + self.common, + strings._("gui_tor_connection_ask"), + QtWidgets.QMessageBox.Question, + buttons=QtWidgets.QMessageBox.NoButton, + autostart=False, + ) + settings_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_open_settings") + ) + quit_button = QtWidgets.QPushButton( + strings._("gui_tor_connection_ask_quit") + ) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) @@ -326,12 +474,18 @@ class OnionShareGui(QtWidgets.QMainWindow): if a.clickedButton() == settings_button: # Open settings - self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') + self.common.log( + "OnionShareGui", + "_tor_connection_canceled", + "Settings button clicked", + ) self.open_settings() if a.clickedButton() == quit_button: # Quit - self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') + self.common.log( + "OnionShareGui", "_tor_connection_canceled", "Quit button clicked" + ) # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) @@ -343,7 +497,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ The TorConnectionDialog wants to open the Settings dialog """ - self.common.log('OnionShareGui', '_tor_connection_open_settings') + self.common.log("OnionShareGui", "_tor_connection_open_settings") # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) @@ -352,10 +506,12 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Open the SettingsDialog. """ - self.common.log('OnionShareGui', 'open_settings') + self.common.log("OnionShareGui", "open_settings") def reload_settings(): - self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') + self.common.log( + "OnionShareGui", "open_settings", "settings have changed, reloading" + ) self.common.settings.load() # We might've stopped the main requests timer if a Tor connection failed. @@ -371,12 +527,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.clearMessage() # If we switched off the auto-stop timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostop_timer'): + if not self.common.settings.get("autostop_timer"): self.share_mode.server_status.autostop_timer_container.hide() self.receive_mode.server_status.autostop_timer_container.hide() self.website_mode.server_status.autostop_timer_container.hide() # If we switched off the auto-start timer setting, ensure the widget is hidden. - if not self.common.settings.get('autostart_timer'): + if not self.common.settings.get("autostart_timer"): self.share_mode.server_status.autostart_timer_datetime = None self.receive_mode.server_status.autostart_timer_datetime = None self.website_mode.server_status.autostart_timer_datetime = None @@ -384,7 +540,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.autostart_timer_container.hide() self.website_mode.server_status.autostart_timer_container.hide() - d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) + d = SettingsDialog( + self.common, self.onion, self.qtapp, self.config, self.local_only + ) d.settings_saved.connect(reload_settings) d.exec_() @@ -397,10 +555,16 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Check for updates in a new thread, if enabled. """ - if self.common.platform == 'Windows' or self.common.platform == 'Darwin': - if self.common.settings.get('use_autoupdate'): + if self.common.platform == "Windows" or self.common.platform == "Darwin": + if self.common.settings.get("use_autoupdate"): + def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) self.update_thread = UpdateThread(self.common, self.onion, self.config) self.update_thread.update_available.connect(update_available) @@ -417,8 +581,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - self.status_bar.showMessage(strings._('gui_tor_connection_lost')) - self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings')) + self.status_bar.showMessage(strings._("gui_tor_connection_lost")) + self.system_tray.showMessage( + strings._("gui_tor_connection_lost"), + strings._("gui_tor_connection_error_settings"), + ) self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() @@ -480,14 +647,31 @@ class OnionShareGui(QtWidgets.QMainWindow): mode.handle_request_individual_file_canceled(event) if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) + Alert( + self.common, + strings._("error_cannot_create_data_dir").format( + event["data"]["receive_mode_dir"] + ), + ) if event["type"] == Web.REQUEST_OTHER: - if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_password): - self.status_bar.showMessage('{0:s}: {1:s}'.format(strings._('other_page_loaded'), event["path"])) + if event["path"] != "/favicon.ico" and event[ + "path" + ] != "/{}/shutdown".format(mode.web.shutdown_password): + self.status_bar.showMessage( + "{0:s}: {1:s}".format( + strings._("other_page_loaded"), event["path"] + ) + ) if event["type"] == Web.REQUEST_INVALID_PASSWORD: - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.invalid_passwords_count, strings._('incorrect_password'), event["data"])) + self.status_bar.showMessage( + "[#{0:d}] {1:s}: {2:s}".format( + mode.web.invalid_passwords_count, + strings._("incorrect_password"), + event["data"], + ) + ) mode.timer_callback() @@ -495,15 +679,20 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the URL gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_url') - self.system_tray.showMessage(strings._('gui_copied_url_title'), strings._('gui_copied_url')) + self.common.log("OnionShareGui", "copy_url") + self.system_tray.showMessage( + strings._("gui_copied_url_title"), strings._("gui_copied_url") + ) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ - self.common.log('OnionShareGui', 'copy_hidservauth') - self.system_tray.showMessage(strings._('gui_copied_hidservauth_title'), strings._('gui_copied_hidservauth')) + self.common.log("OnionShareGui", "copy_hidservauth") + self.system_tray.showMessage( + strings._("gui_copied_hidservauth_title"), + strings._("gui_copied_hidservauth"), + ) def clear_message(self): """ @@ -539,7 +728,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_action.setEnabled(not active) def closeEvent(self, e): - self.common.log('OnionShareGui', 'closeEvent') + self.common.log("OnionShareGui", "closeEvent") self.system_tray.hide() try: if self.mode == OnionShareGui.MODE_SHARE: @@ -549,16 +738,21 @@ class OnionShareGui(QtWidgets.QMainWindow): else: server_status = self.receive_mode.server_status if server_status.status != server_status.STATUS_STOPPED: - self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') + self.common.log("OnionShareGui", "closeEvent, opening warning dialog") dialog = QtWidgets.QMessageBox() - dialog.setWindowTitle(strings._('gui_quit_title')) + dialog.setWindowTitle(strings._("gui_quit_title")) if self.mode == OnionShareGui.MODE_SHARE: - dialog.setText(strings._('gui_share_quit_warning')) + dialog.setText(strings._("gui_share_quit_warning")) else: - dialog.setText(strings._('gui_receive_quit_warning')) + dialog.setText(strings._("gui_receive_quit_warning")) dialog.setIcon(QtWidgets.QMessageBox.Critical) - quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole) - dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole) + quit_button = dialog.addButton( + strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.YesRole + ) + dont_quit_button = dialog.addButton( + strings._("gui_quit_warning_dont_quit"), + QtWidgets.QMessageBox.NoRole, + ) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 85b1f230..4ce1f5d2 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -25,10 +25,12 @@ from onionshare import strings from .widgets import Alert + class ServerStatus(QtWidgets.QWidget): """ The server status chunk of the GUI. """ + server_started = QtCore.pyqtSignal() server_started_finished = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal() @@ -37,9 +39,9 @@ class ServerStatus(QtWidgets.QWidget): url_copied = QtCore.pyqtSignal() hidservauth_copied = QtCore.pyqtSignal() - MODE_SHARE = 'share' - MODE_RECEIVE = 'receive' - MODE_WEBSITE = 'website' + MODE_SHARE = "share" + MODE_RECEIVE = "receive" + MODE_WEBSITE = "website" STATUS_STOPPED = 0 STATUS_WORKING = 1 @@ -51,7 +53,7 @@ class ServerStatus(QtWidgets.QWidget): self.common = common self.status = self.STATUS_STOPPED - self.mode = None # Gets set in self.set_mode + self.mode = None # Gets set in self.set_mode self.qtapp = qtapp self.app = app @@ -63,19 +65,31 @@ class ServerStatus(QtWidgets.QWidget): self.resizeEvent(None) # Auto-start timer layout - self.autostart_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostart_timer')) + self.autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostart_timer") + ) self.autostart_timer_widget = QtWidgets.QDateTimeEdit() self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostart_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostart_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostart_timer_layout = QtWidgets.QHBoxLayout() autostart_timer_layout.addWidget(self.autostart_timer_label) autostart_timer_layout.addWidget(self.autostart_timer_widget) @@ -88,19 +102,31 @@ class ServerStatus(QtWidgets.QWidget): self.autostart_timer_container.hide() # Auto-stop timer layout - self.autostop_timer_label = QtWidgets.QLabel(strings._('gui_settings_autostop_timer')) + self.autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_autostop_timer") + ) self.autostop_timer_widget = QtWidgets.QDateTimeEdit() self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: # For testing - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(15) + ) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime() + ) else: # Set proposed timer to be 5 minutes into the future - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) - self.autostop_timer_widget.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) + self.autostop_timer_widget.setCurrentSection( + QtWidgets.QDateTimeEdit.MinuteSection + ) autostop_timer_layout = QtWidgets.QHBoxLayout() autostop_timer_layout.addWidget(self.autostop_timer_label) autostop_timer_layout.addWidget(self.autostop_timer_widget) @@ -125,16 +151,20 @@ class ServerStatus(QtWidgets.QWidget): self.url.setFont(url_font) self.url.setWordWrap(True) self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet(self.common.css['server_status_url']) + self.url.setStyleSheet(self.common.css["server_status_url"]) - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url')) + self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url")) self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_url_button.setStyleSheet(self.common.css["server_status_url_buttons"]) self.copy_url_button.setMinimumHeight(65) self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) + self.copy_hidservauth_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_hidservauth_button.setStyleSheet( + self.common.css["server_status_url_buttons"] + ) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) url_buttons_layout = QtWidgets.QHBoxLayout() url_buttons_layout.addWidget(self.copy_url_button) @@ -160,7 +190,9 @@ class ServerStatus(QtWidgets.QWidget): """ self.mode = share_mode - if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE): + if (self.mode == ServerStatus.MODE_SHARE) or ( + self.mode == ServerStatus.MODE_WEBSITE + ): self.file_selection = file_selection self.update() @@ -171,7 +203,7 @@ class ServerStatus(QtWidgets.QWidget): """ try: # Wrap the URL label - url_length=len(self.get_url()) + url_length = len(self.get_url()) if url_length > 60: width = self.frameGeometry().width() if width < 530: @@ -186,17 +218,25 @@ class ServerStatus(QtWidgets.QWidget): """ Reset the auto-start timer in the UI after stopping a share """ - self.autostart_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostart_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostart_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostart_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def autostop_timer_reset(self): """ Reset the auto-stop timer in the UI after stopping a share """ - self.autostop_timer_widget.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.autostop_timer_widget.setDateTime( + QtCore.QDateTime.currentDateTime().addSecs(300) + ) if not self.local_only: - self.autostop_timer_widget.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) + self.autostop_timer_widget.setMinimumDateTime( + QtCore.QDateTime.currentDateTime().addSecs(60) + ) def show_url(self): """ @@ -204,26 +244,38 @@ class ServerStatus(QtWidgets.QWidget): """ self.url_description.show() - info_image = self.common.get_resource_path('images/info.png') + info_image = self.common.get_resource_path("images/info.png") if self.mode == ServerStatus.MODE_SHARE: - self.url_description.setText(strings._('gui_share_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_share_url_description").format(info_image) + ) elif self.mode == ServerStatus.MODE_WEBSITE: - self.url_description.setText(strings._('gui_website_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_website_url_description").format(info_image) + ) else: - self.url_description.setText(strings._('gui_receive_url_description').format(info_image)) + self.url_description.setText( + strings._("gui_receive_url_description").format(info_image) + ) # Show a Tool Tip explaining the lifecycle of this URL - if self.common.settings.get('save_private_key'): - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent')) + if self.common.settings.get("save_private_key"): + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip( + strings._("gui_url_label_onetime_and_persistent") + ) else: - self.url_description.setToolTip(strings._('gui_url_label_persistent')) + self.url_description.setToolTip(strings._("gui_url_label_persistent")) else: - if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime')) + if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get( + "close_after_first_download" + ): + self.url_description.setToolTip(strings._("gui_url_label_onetime")) else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open')) + self.url_description.setToolTip(strings._("gui_url_label_stay_open")) self.url.setText(self.get_url()) self.url.show() @@ -245,15 +297,15 @@ class ServerStatus(QtWidgets.QWidget): self.common.settings.load() self.show_url() - if self.common.settings.get('save_private_key'): - if not self.common.settings.get('password'): - self.common.settings.set('password', self.web.password) + if self.common.settings.get("save_private_key"): + if not self.common.settings.get("password"): + self.common.settings.set("password", self.web.password) self.common.settings.save() - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: self.url_description.hide() @@ -262,59 +314,91 @@ class ServerStatus(QtWidgets.QWidget): self.copy_hidservauth_button.hide() # Button - if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: + if ( + self.mode == ServerStatus.MODE_SHARE + and self.file_selection.get_num_files() == 0 + ): self.server_button.hide() - elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0: + elif ( + self.mode == ServerStatus.MODE_WEBSITE + and self.file_selection.get_num_files() == 0 + ): self.server_button.hide() else: self.server_button.show() if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_stopped"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_start_server')) + self.server_button.setText(strings._("gui_share_start_server")) elif self.mode == ServerStatus.MODE_WEBSITE: - self.server_button.setText(strings._('gui_share_start_server')) + self.server_button.setText(strings._("gui_share_start_server")) else: - self.server_button.setText(strings._('gui_receive_start_server')) - self.server_button.setToolTip('') - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_start_server")) + self.server_button.setToolTip("") + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.show() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.show() elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(self.common.css['server_status_button_started']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_started"] + ) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: - self.server_button.setText(strings._('gui_share_stop_server')) + self.server_button.setText(strings._("gui_share_stop_server")) elif self.mode == ServerStatus.MODE_WEBSITE: - self.server_button.setText(strings._('gui_share_stop_server')) + self.server_button.setText(strings._("gui_share_stop_server")) else: - self.server_button.setText(strings._('gui_receive_stop_server')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_receive_stop_server")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_autostop_timer_tooltip').format(self.autostop_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_stop_server_autostop_timer_tooltip").format( + self.autostop_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(True) if self.autostart_timer_datetime: self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) else: - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostop_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() else: - self.server_button.setStyleSheet(self.common.css['server_status_button_working']) + self.server_button.setStyleSheet( + self.common.css["server_status_button_working"] + ) self.server_button.setEnabled(False) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('autostart_timer'): + self.server_button.setText(strings._("gui_please_wait")) + if self.common.settings.get("autostart_timer"): self.autostart_timer_container.hide() - self.server_button.setToolTip(strings._('gui_start_server_autostart_timer_tooltip').format(self.autostart_timer_widget.dateTime().toString("h:mm AP, MMMM dd, yyyy"))) - if self.common.settings.get('autostop_timer'): + self.server_button.setToolTip( + strings._("gui_start_server_autostart_timer_tooltip").format( + self.autostart_timer_widget.dateTime().toString( + "h:mm AP, MMMM dd, yyyy" + ) + ) + ) + if self.common.settings.get("autostop_timer"): self.autostop_timer_container.hide() def server_button_clicked(self): @@ -323,28 +407,60 @@ class ServerStatus(QtWidgets.QWidget): """ if self.status == self.STATUS_STOPPED: can_start = True - if self.common.settings.get('autostart_timer'): + if self.common.settings.get("autostart_timer"): if self.local_only: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime() + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime().toPyDateTime() + ) else: - self.autostart_timer_datetime = self.autostart_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostart_timer_datetime = ( + self.autostart_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostart_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostart_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostart_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostop_timer'): + Alert( + self.common, + strings._("gui_server_autostart_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostop_timer"): if self.local_only: - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime() + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime().toPyDateTime() + ) else: # Get the timer chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.autostop_timer_datetime = self.autostop_timer_widget.dateTime().toPyDateTime().replace(second=0, microsecond=0) + self.autostop_timer_datetime = ( + self.autostop_timer_widget.dateTime() + .toPyDateTime() + .replace(second=0, microsecond=0) + ) # If the timer has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.autostop_timer_datetime: + if ( + QtCore.QDateTime.currentDateTime().toPyDateTime() + > self.autostop_timer_datetime + ): can_start = False - Alert(self.common, strings._('gui_server_autostop_timer_expired'), QtWidgets.QMessageBox.Warning) - if self.common.settings.get('autostart_timer'): + Alert( + self.common, + strings._("gui_server_autostop_timer_expired"), + QtWidgets.QMessageBox.Warning, + ) + if self.common.settings.get("autostart_timer"): if self.autostop_timer_datetime <= self.autostart_timer_datetime: - Alert(self.common, strings._('gui_autostop_timer_cant_be_earlier_than_autostart_timer'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._( + "gui_autostop_timer_cant_be_earlier_than_autostart_timer" + ), + QtWidgets.QMessageBox.Warning, + ) can_start = False if can_start: self.start_server() @@ -385,7 +501,9 @@ class ServerStatus(QtWidgets.QWidget): """ Cancel the server. """ - self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.common.log( + "ServerStatus", "cancel_server", "Canceling the server mid-startup" + ) self.status = self.STATUS_WORKING self.autostart_timer_reset() self.autostop_timer_reset() @@ -421,8 +539,10 @@ class ServerStatus(QtWidgets.QWidget): """ Returns the OnionShare URL. """ - if self.common.settings.get('public_mode'): - url = 'http://{0:s}'.format(self.app.onion_host) + if self.common.settings.get("public_mode"): + url = "http://{0:s}".format(self.app.onion_host) else: - url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.password, self.app.onion_host) + url = "http://onionshare:{0:s}@{1:s}".format( + self.web.password, self.app.onion_host + ) return url diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index ec91a491..503e53a0 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -37,6 +37,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ + settings_saved = QtCore.pyqtSignal() def __init__(self, common, onion, qtapp, config=False, local_only=False): @@ -44,7 +45,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common = common - self.common.log('SettingsDialog', '__init__') + self.common.log("SettingsDialog", "__init__") self.onion = onion self.qtapp = qtapp @@ -52,22 +53,30 @@ class SettingsDialog(QtWidgets.QDialog): self.local_only = local_only self.setModal(True) - self.setWindowTitle(strings._('gui_settings_window_title')) - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowTitle(strings._("gui_settings_window_title")) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.system = platform.system() # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog - self.hide_tor_settings = os.environ.get('ONIONSHARE_HIDE_TOR_SETTINGS') == "1" + self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1" # General settings # Use a password or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) - public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) + self.public_mode_checkbox.setText( + strings._("gui_settings_public_mode_checkbox") + ) + public_mode_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Public-Mode" + ) + ) + public_mode_label.setStyleSheet(self.common.css["settings_whats_this"]) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) public_mode_label.setMinimumSize(public_mode_label.sizeHint()) @@ -75,16 +84,22 @@ class SettingsDialog(QtWidgets.QDialog): public_mode_layout.addWidget(self.public_mode_checkbox) public_mode_layout.addWidget(public_mode_label) public_mode_layout.addStretch() - public_mode_layout.setContentsMargins(0,0,0,0) + public_mode_layout.setContentsMargins(0, 0, 0, 0) self.public_mode_widget = QtWidgets.QWidget() self.public_mode_widget.setLayout(public_mode_layout) # Whether or not to use an auto-start timer self.autostart_timer_checkbox = QtWidgets.QCheckBox() self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostart_timer_checkbox.setText(strings._("gui_settings_autostart_timer_checkbox")) - autostart_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer")) - autostart_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostart_timer_checkbox.setText( + strings._("gui_settings_autostart_timer_checkbox") + ) + autostart_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Start-Timer" + ) + ) + autostart_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostart_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostart_timer_label.setOpenExternalLinks(True) autostart_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -92,16 +107,22 @@ class SettingsDialog(QtWidgets.QDialog): autostart_timer_layout.addWidget(self.autostart_timer_checkbox) autostart_timer_layout.addWidget(autostart_timer_label) autostart_timer_layout.addStretch() - autostart_timer_layout.setContentsMargins(0,0,0,0) + autostart_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostart_timer_widget = QtWidgets.QWidget() self.autostart_timer_widget.setLayout(autostart_timer_layout) # Whether or not to use an auto-stop timer self.autostop_timer_checkbox = QtWidgets.QCheckBox() self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) - self.autostop_timer_checkbox.setText(strings._("gui_settings_autostop_timer_checkbox")) - autostop_timer_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) - autostop_timer_label.setStyleSheet(self.common.css['settings_whats_this']) + self.autostop_timer_checkbox.setText( + strings._("gui_settings_autostop_timer_checkbox") + ) + autostop_timer_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer" + ) + ) + autostop_timer_label.setStyleSheet(self.common.css["settings_whats_this"]) autostop_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) autostop_timer_label.setOpenExternalLinks(True) autostop_timer_label.setMinimumSize(public_mode_label.sizeHint()) @@ -109,7 +130,7 @@ class SettingsDialog(QtWidgets.QDialog): autostop_timer_layout.addWidget(self.autostop_timer_checkbox) autostop_timer_layout.addWidget(autostop_timer_label) autostop_timer_layout.addStretch() - autostop_timer_layout.setContentsMargins(0,0,0,0) + autostop_timer_layout.setContentsMargins(0, 0, 0, 0) self.autostop_timer_widget = QtWidgets.QWidget() self.autostop_timer_widget.setLayout(autostop_timer_layout) @@ -124,39 +145,59 @@ class SettingsDialog(QtWidgets.QDialog): # Onion settings # Label telling user to connect to Tor for onion service settings - self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) - self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + self.connect_to_tor_label = QtWidgets.QLabel( + strings._("gui_connect_to_tor_for_onion_settings") + ) + self.connect_to_tor_label.setStyleSheet( + self.common.css["settings_connect_to_tor"] + ) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) - save_private_key_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL")) - save_private_key_label.setStyleSheet(self.common.css['settings_whats_this']) + self.save_private_key_checkbox.setText( + strings._("gui_save_private_key_checkbox") + ) + save_private_key_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Using-a-Persistent-URL" + ) + ) + save_private_key_label.setStyleSheet(self.common.css["settings_whats_this"]) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) save_private_key_layout = QtWidgets.QHBoxLayout() save_private_key_layout.addWidget(self.save_private_key_checkbox) save_private_key_layout.addWidget(save_private_key_label) save_private_key_layout.addStretch() - save_private_key_layout.setContentsMargins(0,0,0,0) + save_private_key_layout.setContentsMargins(0, 0, 0, 0) self.save_private_key_widget = QtWidgets.QWidget() self.save_private_key_widget.setLayout(save_private_key_layout) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setText(strings._("gui_use_legacy_v2_onions_checkbox")) - self.use_legacy_v2_onions_checkbox.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Legacy-Addresses")) - use_legacy_v2_onions_label.setStyleSheet(self.common.css['settings_whats_this']) - use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.use_legacy_v2_onions_checkbox.setText( + strings._("gui_use_legacy_v2_onions_checkbox") + ) + self.use_legacy_v2_onions_checkbox.clicked.connect( + self.use_legacy_v2_onions_checkbox_clicked + ) + use_legacy_v2_onions_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Legacy-Addresses" + ) + ) + use_legacy_v2_onions_label.setStyleSheet(self.common.css["settings_whats_this"]) + use_legacy_v2_onions_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() - use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) + use_legacy_v2_onions_layout.setContentsMargins(0, 0, 0, 0) self.use_legacy_v2_onions_widget = QtWidgets.QWidget() self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) @@ -165,8 +206,12 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option")) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) - use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) + use_stealth_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services" + ) + ) + use_stealth_label.setStyleSheet(self.common.css["settings_whats_this"]) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) @@ -174,17 +219,23 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() - use_stealth_layout.setContentsMargins(0,0,0,0) + use_stealth_layout.setContentsMargins(0, 0, 0, 0) self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget.setLayout(use_stealth_layout) - self.hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) + self.hidservauth_details = QtWidgets.QLabel( + strings._("gui_settings_stealth_hidservauth_string") + ) self.hidservauth_details.setWordWrap(True) self.hidservauth_details.setMinimumSize(self.hidservauth_details.sizeHint()) self.hidservauth_details.hide() - self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) - self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) + self.hidservauth_copy_button = QtWidgets.QPushButton( + strings._("gui_copy_hidservauth") + ) + self.hidservauth_copy_button.clicked.connect( + self.hidservauth_copy_button_clicked + ) self.hidservauth_copy_button.hide() # Onion settings widget @@ -205,14 +256,17 @@ class SettingsDialog(QtWidgets.QDialog): onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label")) onion_group.setLayout(onion_group_layout) - # Sharing options # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) - self.close_after_first_download_checkbox.setText(strings._("gui_settings_close_after_first_download_option")) - individual_downloads_label = QtWidgets.QLabel(strings._("gui_settings_individual_downloads_label")) + self.close_after_first_download_checkbox.setText( + strings._("gui_settings_close_after_first_download_option") + ) + individual_downloads_label = QtWidgets.QLabel( + strings._("gui_settings_individual_downloads_label") + ) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() @@ -222,10 +276,12 @@ class SettingsDialog(QtWidgets.QDialog): sharing_group.setLayout(sharing_group_layout) # OnionShare data dir - data_dir_label = QtWidgets.QLabel(strings._('gui_settings_data_dir_label')); + data_dir_label = QtWidgets.QLabel(strings._("gui_settings_data_dir_label")) self.data_dir_lineedit = QtWidgets.QLineEdit() self.data_dir_lineedit.setReadOnly(True) - data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button')) + data_dir_button = QtWidgets.QPushButton( + strings._("gui_settings_data_dir_browse_button") + ) data_dir_button.clicked.connect(self.data_dir_button_clicked) data_dir_layout = QtWidgets.QHBoxLayout() data_dir_layout.addWidget(data_dir_label) @@ -241,9 +297,15 @@ class SettingsDialog(QtWidgets.QDialog): # Option to disable Content Security Policy (for website sharing) self.csp_header_disabled_checkbox = QtWidgets.QCheckBox() self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.csp_header_disabled_checkbox.setText(strings._("gui_settings_csp_header_disabled_option")) - csp_header_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Content-Security-Policy")) - csp_header_label.setStyleSheet(self.common.css['settings_whats_this']) + self.csp_header_disabled_checkbox.setText( + strings._("gui_settings_csp_header_disabled_option") + ) + csp_header_label = QtWidgets.QLabel( + strings._("gui_settings_whats_this").format( + "https://github.com/micahflee/onionshare/wiki/Content-Security-Policy" + ) + ) + csp_header_label.setStyleSheet(self.common.css["settings_whats_this"]) csp_header_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) csp_header_label.setOpenExternalLinks(True) csp_header_label.setMinimumSize(csp_header_label.sizeHint()) @@ -251,7 +313,7 @@ class SettingsDialog(QtWidgets.QDialog): csp_header_layout.addWidget(self.csp_header_disabled_checkbox) csp_header_layout.addWidget(csp_header_label) csp_header_layout.addStretch() - csp_header_layout.setContentsMargins(0,0,0,0) + csp_header_layout.setContentsMargins(0, 0, 0, 0) self.csp_header_widget = QtWidgets.QWidget() self.csp_header_widget.setLayout(csp_header_layout) @@ -279,7 +341,9 @@ class SettingsDialog(QtWidgets.QDialog): self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button - self.check_for_updates_button = QtWidgets.QPushButton(strings._('gui_settings_autoupdate_check_button')) + self.check_for_updates_button = QtWidgets.QPushButton( + strings._("gui_settings_autoupdate_check_button") + ) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: @@ -290,18 +354,22 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) - autoupdate_group = QtWidgets.QGroupBox(strings._("gui_settings_autoupdate_label")) + autoupdate_group = QtWidgets.QGroupBox( + strings._("gui_settings_autoupdate_label") + ) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) - if self.system != 'Windows' and self.system != 'Darwin': + if self.system != "Windows" and self.system != "Darwin": autoupdate_group.hide() # Language settings language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages - language_names_to_locales = {v: k for k, v in self.common.settings.available_locales.items()} + language_names_to_locales = { + v: k for k, v in self.common.settings.available_locales.items() + } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: @@ -315,56 +383,106 @@ class SettingsDialog(QtWidgets.QDialog): # Connection type: either automatic, control port, or socket file # Bundled Tor - self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option')) - self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) + self.connection_type_bundled_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_bundled_option") + ) + self.connection_type_bundled_radio.toggled.connect( + self.connection_type_bundled_toggled + ) # Bundled Tor doesn't work on dev mode in Windows or Mac - if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if (self.system == "Windows" or self.system == "Darwin") and getattr( + sys, "onionshare_dev_mode", False + ): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio - self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_no_bridges_radio_option')) - self.tor_bridges_no_bridges_radio.toggled.connect(self.tor_bridges_no_bridges_radio_toggled) + self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_no_bridges_radio_option") + ) + self.tor_bridges_no_bridges_radio.toggled.connect( + self.tor_bridges_no_bridges_radio_toggled + ) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + ) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy')) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") + ) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option')) - self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) + self.tor_bridges_use_obfs4_radio.toggled.connect( + self.tor_bridges_use_obfs4_radio_toggled + ) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths() + ( + self.tor_path, + self.tor_geo_ip_file_path, + self.tor_geo_ipv6_file_path, + self.obfs4proxy_file_path, + ) = self.common.get_tor_paths() if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy')) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._( + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" + ) + ) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option')) - self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) + self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( + self.tor_bridges_use_meek_lite_azure_radio_toggled + ) # Custom bridges radio and textbox - self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_custom_radio_option')) - self.tor_bridges_use_custom_radio.toggled.connect(self.tor_bridges_use_custom_radio_toggled) - - self.tor_bridges_use_custom_label = QtWidgets.QLabel(strings._('gui_settings_tor_bridges_custom_label')) - self.tor_bridges_use_custom_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_custom_radio_option") + ) + self.tor_bridges_use_custom_radio.toggled.connect( + self.tor_bridges_use_custom_radio_toggled + ) + + self.tor_bridges_use_custom_label = QtWidgets.QLabel( + strings._("gui_settings_tor_bridges_custom_label") + ) + self.tor_bridges_use_custom_label.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) - self.tor_bridges_use_custom_textbox.setPlaceholderText('[address:port] [identifier]') + self.tor_bridges_use_custom_textbox.setPlaceholderText( + "[address:port] [identifier]" + ) tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_label) - tor_bridges_use_custom_textbox_options_layout.addWidget(self.tor_bridges_use_custom_textbox) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_label + ) + tor_bridges_use_custom_textbox_options_layout.addWidget( + self.tor_bridges_use_custom_textbox + ) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() - self.tor_bridges_use_custom_textbox_options.setLayout(tor_bridges_use_custom_textbox_options_layout) + self.tor_bridges_use_custom_textbox_options.setLayout( + tor_bridges_use_custom_textbox_options_layout + ) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget @@ -379,41 +497,73 @@ class SettingsDialog(QtWidgets.QDialog): self.bridges.setLayout(bridges_layout) # Automatic - self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option')) - self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled) + self.connection_type_automatic_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_automatic_option") + ) + self.connection_type_automatic_radio.toggled.connect( + self.connection_type_automatic_toggled + ) # Control port - self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option')) - self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled) - - connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label')) + self.connection_type_control_port_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_control_port_option") + ) + self.connection_type_control_port_radio.toggled.connect( + self.connection_type_control_port_toggled + ) + + connection_type_control_port_extras_label = QtWidgets.QLabel( + strings._("gui_settings_control_port_label") + ) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit() self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() - connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address) - connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port) + connection_type_control_port_extras_layout.addWidget( + connection_type_control_port_extras_label + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_address + ) + connection_type_control_port_extras_layout.addWidget( + self.connection_type_control_port_extras_port + ) self.connection_type_control_port_extras = QtWidgets.QWidget() - self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout) + self.connection_type_control_port_extras.setLayout( + connection_type_control_port_extras_layout + ) self.connection_type_control_port_extras.hide() # Socket file - self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option')) - self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled) - - connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label')) + self.connection_type_socket_file_radio = QtWidgets.QRadioButton( + strings._("gui_settings_connection_type_socket_file_option") + ) + self.connection_type_socket_file_radio.toggled.connect( + self.connection_type_socket_file_toggled + ) + + connection_type_socket_file_extras_label = QtWidgets.QLabel( + strings._("gui_settings_socket_file_label") + ) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() - connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) - connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path) + connection_type_socket_file_extras_layout.addWidget( + connection_type_socket_file_extras_label + ) + connection_type_socket_file_extras_layout.addWidget( + self.connection_type_socket_file_extras_path + ) self.connection_type_socket_file_extras = QtWidgets.QWidget() - self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout) + self.connection_type_socket_file_extras.setLayout( + connection_type_socket_file_extras_layout + ) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port - gui_settings_socks_label = QtWidgets.QLabel(strings._('gui_settings_socks_label')) + gui_settings_socks_label = QtWidgets.QLabel( + strings._("gui_settings_socks_label") + ) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() @@ -428,18 +578,32 @@ class SettingsDialog(QtWidgets.QDialog): # Authentication options # No authentication - self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option')) - self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled) + self.authenticate_no_auth_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_no_auth_option") + ) + self.authenticate_no_auth_radio.toggled.connect( + self.authenticate_no_auth_toggled + ) # Password - self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option')) - self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled) - - authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label')) - self.authenticate_password_extras_password = QtWidgets.QLineEdit('') + self.authenticate_password_radio = QtWidgets.QRadioButton( + strings._("gui_settings_authenticate_password_option") + ) + self.authenticate_password_radio.toggled.connect( + self.authenticate_password_toggled + ) + + authenticate_password_extras_label = QtWidgets.QLabel( + strings._("gui_settings_password_label") + ) + self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() - authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) - authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password) + authenticate_password_extras_layout.addWidget( + authenticate_password_extras_label + ) + authenticate_password_extras_layout.addWidget( + self.authenticate_password_extras_password + ) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) @@ -450,27 +614,43 @@ class SettingsDialog(QtWidgets.QDialog): authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) - self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label")) + self.authenticate_group = QtWidgets.QGroupBox( + strings._("gui_settings_authenticate_label") + ) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_automatic_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_control_port_radio) - connection_type_radio_group_layout.addWidget(self.connection_type_socket_file_radio) - connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label")) + connection_type_radio_group_layout.addWidget( + self.connection_type_automatic_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_control_port_radio + ) + connection_type_radio_group_layout.addWidget( + self.connection_type_socket_file_radio + ) + connection_type_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_connection_type_label") + ) connection_type_radio_group.setLayout(connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) - self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_tor_bridges")) - self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) + self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( + strings._("gui_settings_tor_bridges") + ) + self.connection_type_bridges_radio_group.setLayout( + connection_type_bridges_radio_group_layout + ) self.connection_type_bridges_radio_group.hide() # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button')) + self.connection_type_test_button = QtWidgets.QPushButton( + strings._("gui_settings_connection_type_test_button") + ) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget(self.connection_type_test_button) @@ -486,13 +666,15 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons - self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save')) + self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) - self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel')) + self.cancel_button = QtWidgets.QPushButton( + strings._("gui_settings_button_cancel") + ) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) - version_label.setStyleSheet(self.common.css['settings_version']) - self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help')) + version_label = QtWidgets.QLabel("OnionShare {0:s}".format(self.common.version)) + version_label.setStyleSheet(self.common.css["settings_version"]) + self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help")) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) @@ -503,7 +685,7 @@ class SettingsDialog(QtWidgets.QDialog): # Tor network connection status self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet(self.common.css['settings_tor_status']) + self.tor_status.setStyleSheet(self.common.css["settings_tor_status"]) self.tor_status.hide() # Layout @@ -542,37 +724,37 @@ class SettingsDialog(QtWidgets.QDialog): self.old_settings = Settings(self.common, self.config) self.old_settings.load() - close_after_first_download = self.old_settings.get('close_after_first_download') + close_after_first_download = self.old_settings.get("close_after_first_download") if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - csp_header_disabled = self.old_settings.get('csp_header_disabled') + csp_header_disabled = self.old_settings.get("csp_header_disabled") if csp_header_disabled: self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Checked) else: self.csp_header_disabled_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostart_timer = self.old_settings.get('autostart_timer') + autostart_timer = self.old_settings.get("autostart_timer") if autostart_timer: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - autostop_timer = self.old_settings.get('autostop_timer') + autostop_timer = self.old_settings.get("autostop_timer") if autostop_timer: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Unchecked) - save_private_key = self.old_settings.get('save_private_key') + save_private_key = self.old_settings.get("save_private_key") if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + use_legacy_v2_onions = self.old_settings.get("use_legacy_v2_onions") if use_legacy_v2_onions: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) @@ -580,84 +762,102 @@ class SettingsDialog(QtWidgets.QDialog): else: self.use_stealth_widget.hide() - data_dir = self.old_settings.get('data_dir') + data_dir = self.old_settings.get("data_dir") self.data_dir_lineedit.setText(data_dir) - public_mode = self.old_settings.get('public_mode') + public_mode = self.old_settings.get("public_mode") if public_mode: self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked) else: self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_stealth = self.old_settings.get('use_stealth') + use_stealth = self.old_settings.get("use_stealth") if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) - if save_private_key and self.old_settings.get('hidservauth_string') != "": + if save_private_key and self.old_settings.get("hidservauth_string") != "": self.hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_autoupdate = self.old_settings.get('use_autoupdate') + use_autoupdate = self.old_settings.get("use_autoupdate") if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') + autoupdate_timestamp = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) - locale = self.old_settings.get('locale') + locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(QtCore.QVariant(locale)) self.language_combobox.setCurrentIndex(locale_index) - connection_type = self.old_settings.get('connection_type') - if connection_type == 'bundled': + connection_type = self.old_settings.get("connection_type") + if connection_type == "bundled": if self.connection_type_bundled_radio.isEnabled(): self.connection_type_bundled_radio.setChecked(True) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'automatic': + elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) - elif connection_type == 'control_port': + elif connection_type == "control_port": self.connection_type_control_port_radio.setChecked(True) - elif connection_type == 'socket_file': + elif connection_type == "socket_file": self.connection_type_socket_file_radio.setChecked(True) - self.connection_type_control_port_extras_address.setText(self.old_settings.get('control_port_address')) - self.connection_type_control_port_extras_port.setText(str(self.old_settings.get('control_port_port'))) - self.connection_type_socket_file_extras_path.setText(self.old_settings.get('socket_file_path')) - self.connection_type_socks_address.setText(self.old_settings.get('socks_address')) - self.connection_type_socks_port.setText(str(self.old_settings.get('socks_port'))) - auth_type = self.old_settings.get('auth_type') - if auth_type == 'no_auth': + self.connection_type_control_port_extras_address.setText( + self.old_settings.get("control_port_address") + ) + self.connection_type_control_port_extras_port.setText( + str(self.old_settings.get("control_port_port")) + ) + self.connection_type_socket_file_extras_path.setText( + self.old_settings.get("socket_file_path") + ) + self.connection_type_socks_address.setText( + self.old_settings.get("socks_address") + ) + self.connection_type_socks_port.setText( + str(self.old_settings.get("socks_port")) + ) + auth_type = self.old_settings.get("auth_type") + if auth_type == "no_auth": self.authenticate_no_auth_radio.setChecked(True) - elif auth_type == 'password': + elif auth_type == "password": self.authenticate_password_radio.setChecked(True) - self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) + self.authenticate_password_extras_password.setText( + self.old_settings.get("auth_password") + ) - if self.old_settings.get('no_bridges'): + if self.old_settings.get("no_bridges"): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) - self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4')) - self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure')) - - if self.old_settings.get('tor_bridges_use_custom_bridges'): + self.tor_bridges_use_obfs4_radio.setChecked( + self.old_settings.get("tor_bridges_use_obfs4") + ) + self.tor_bridges_use_meek_lite_azure_radio.setChecked( + self.old_settings.get("tor_bridges_use_meek_lite_azure") + ) + + if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] - bridges = self.old_settings.get('tor_bridges_use_custom_bridges').split('Bridge ') + bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split( + "Bridge " + ) for bridge in bridges: new_bridges.append(bridge) - new_bridges = ''.join(new_bridges) + new_bridges = "".join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) # If we're connected to Tor, show onion service settings, show label if not @@ -667,10 +867,12 @@ class SettingsDialog(QtWidgets.QDialog): # If v3 onion services are supported, allow using legacy mode if self.onion.supports_v3_onions: - self.common.log('SettingsDialog', '__init__', 'v3 onions are supported') + self.common.log("SettingsDialog", "__init__", "v3 onions are supported") self.use_legacy_v2_onions_checkbox.show() else: - self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported') + self.common.log( + "SettingsDialog", "__init__", "v3 onions are not supported" + ) self.use_legacy_v2_onions_widget.hide() self.use_legacy_v2_onions_checkbox_clicked(True) else: @@ -681,7 +883,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type bundled was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_bundled_toggled') + self.common.log("SettingsDialog", "connection_type_bundled_toggled") if self.hide_tor_settings: return if checked: @@ -716,8 +918,12 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on - if not self.old_settings.get('tor_bridges_use_meek_lite_azure'): - Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning'), QtWidgets.QMessageBox.Warning) + if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): + Alert( + self.common, + strings._("gui_settings_meek_lite_expensive_warning"), + QtWidgets.QMessageBox.Warning, + ) def tor_bridges_use_custom_radio_toggled(self, checked): """ @@ -732,7 +938,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - self.common.log('SettingsDialog', 'connection_type_automatic_toggled') + self.common.log("SettingsDialog", "connection_type_automatic_toggled") if self.hide_tor_settings: return if checked: @@ -745,7 +951,7 @@ class SettingsDialog(QtWidgets.QDialog): Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'connection_type_control_port_toggled') + self.common.log("SettingsDialog", "connection_type_control_port_toggled") if self.hide_tor_settings: return if checked: @@ -756,13 +962,12 @@ class SettingsDialog(QtWidgets.QDialog): else: self.connection_type_control_port_extras.hide() - def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') + self.common.log("SettingsDialog", "connection_type_socket_file_toggled") if self.hide_tor_settings: return if checked: @@ -777,14 +982,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - self.common.log('SettingsDialog', 'authenticate_no_auth_toggled') + self.common.log("SettingsDialog", "authenticate_no_auth_toggled") def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ - self.common.log('SettingsDialog', 'authenticate_password_toggled') + self.common.log("SettingsDialog", "authenticate_password_toggled") if checked: self.authenticate_password_extras.show() else: @@ -795,9 +1000,13 @@ class SettingsDialog(QtWidgets.QDialog): Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ - self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') + self.common.log( + "SettingsDialog", + "hidservauth_copy_button_clicked", + "HidServAuth was copied to clipboard", + ) clipboard = self.qtapp.clipboard() - clipboard.setText(self.old_settings.get('hidservauth_string')) + clipboard.setText(self.old_settings.get("hidservauth_string")) def use_legacy_v2_onions_checkbox_clicked(self, checked): """ @@ -823,11 +1032,16 @@ class SettingsDialog(QtWidgets.QDialog): Browse for a new OnionShare data directory """ data_dir = self.data_dir_lineedit.text() - selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_data_dir_label'), data_dir) + selected_dir = QtWidgets.QFileDialog.getExistingDirectory( + self, strings._("gui_settings_data_dir_label"), data_dir + ) if selected_dir: - self.common.log('SettingsDialog', 'data_dir_button_clicked', 'selected dir: {}'.format(selected_dir)) + self.common.log( + "SettingsDialog", + "data_dir_button_clicked", + "selected dir: {}".format(selected_dir), + ) self.data_dir_lineedit.setText(selected_dir) def test_tor_clicked(self): @@ -835,33 +1049,57 @@ class SettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - self.common.log('SettingsDialog', 'test_tor_clicked') + self.common.log("SettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True + else: tor_status_update_func = None onion = Onion(self.common) - onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) + onion.connect( + custom_settings=settings, + config=self.config, + tor_status_update_func=tor_status_update_func, + ) # If an exception hasn't been raised yet, the Tor settings work - Alert(self.common, strings._('settings_test_success').format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions)) + Alert( + self.common, + strings._("settings_test_success").format( + onion.tor_version, + onion.supports_ephemeral, + onion.supports_stealth, + onion.supports_v3_onions, + ), + ) # Clean up onion.cleanup() - except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorNotSupported, + BundledTorTimeout, + ) as e: Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) - if settings.get('connection_type') == 'bundled': + if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() @@ -869,7 +1107,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - self.common.log('SettingsDialog', 'check_for_updates') + self.common.log("SettingsDialog", "check_for_updates") # Disable buttons self._disable_buttons() self.qtapp.processEvents() @@ -878,7 +1116,7 @@ class SettingsDialog(QtWidgets.QDialog): # Update the last checked label settings = Settings(self.common, self.config) settings.load() - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): @@ -890,22 +1128,37 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(self.common, strings._("update_available").format(update_url, installed_version, latest_version)) + Alert( + self.common, + strings._("update_available").format( + update_url, installed_version, latest_version + ), + ) close_forced_update_thread() def update_not_available(): - Alert(self.common, strings._('update_not_available')) + Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): - Alert(self.common, strings._('update_error_check_error'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_check_error"), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() def update_invalid_version(latest_version): - Alert(self.common, strings._('update_error_invalid_latest_version').format(latest_version), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("update_error_invalid_latest_version").format(latest_version), + QtWidgets.QMessageBox.Warning, + ) close_forced_update_thread() - forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) + forced_update_thread = UpdateThread( + self.common, self.onion, self.config, force=True + ) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -916,7 +1169,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - self.common.log('SettingsDialog', 'save_clicked') + self.common.log("SettingsDialog", "save_clicked") def changed(s1, s2, keys): """ @@ -931,13 +1184,19 @@ class SettingsDialog(QtWidgets.QDialog): settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare - if changed(settings, self.old_settings, ['locale']): + if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale - new_locale = settings.get('locale') - if new_locale in strings.translations and 'gui_settings_language_changed_notice' in strings.translations[new_locale]: - notice = strings.translations[new_locale]['gui_settings_language_changed_notice'] + new_locale = settings.get("locale") + if ( + new_locale in strings.translations + and "gui_settings_language_changed_notice" + in strings.translations[new_locale] + ): + notice = strings.translations[new_locale][ + "gui_settings_language_changed_notice" + ] else: - notice = strings._('gui_settings_language_changed_notice') + notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings @@ -948,33 +1207,58 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = False if not self.local_only: if self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - - if changed(settings, self.old_settings, [ - 'connection_type', 'control_port_address', - 'control_port_port', 'socks_address', 'socks_port', - 'socket_file_path', 'auth_type', 'auth_password', - 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_azure', - 'tor_bridges_use_custom_bridges']): + self.common.log( + "SettingsDialog", "save_clicked", "Connected to Tor" + ) + + if changed( + settings, + self.old_settings, + [ + "connection_type", + "control_port_address", + "control_port_port", + "socks_address", + "socks_port", + "socket_file_path", + "auth_type", + "auth_password", + "no_bridges", + "tor_bridges_use_obfs4", + "tor_bridges_use_meek_lite_azure", + "tor_bridges_use_custom_bridges", + ], + ): reboot_onion = True else: - self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + self.common.log( + "SettingsDialog", "save_clicked", "Not connected to Tor" + ) # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object - self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log( + "SettingsDialog", "save_clicked", "rebooting the Onion" + ) self.onion.cleanup() - tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) + tor_con = TorConnectionDialog( + self.common, self.qtapp, self.onion, settings + ) tor_con.start() - self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + self.common.log( + "SettingsDialog", + "save_clicked", + "Onion done rebooting, connected to Tor: {}".format( + self.onion.connected_to_tor + ), + ) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() @@ -991,9 +1275,13 @@ class SettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - self.common.log('SettingsDialog', 'cancel_clicked') + self.common.log("SettingsDialog", "cancel_clicked") if not self.local_only and not self.onion.is_authenticated(): - Alert(self.common, strings._('gui_tor_connection_canceled'), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + strings._("gui_tor_connection_canceled"), + QtWidgets.QMessageBox.Warning, + ) sys.exit() else: self.close() @@ -1002,26 +1290,31 @@ class SettingsDialog(QtWidgets.QDialog): """ Help button clicked. """ - self.common.log('SettingsDialog', 'help_clicked') + self.common.log("SettingsDialog", "help_clicked") SettingsDialog.open_help() @staticmethod def open_help(): - help_url = 'https://github.com/micahflee/onionshare/wiki' + help_url = "https://github.com/micahflee/onionshare/wiki" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ - self.common.log('SettingsDialog', 'settings_from_fields') + self.common.log("SettingsDialog", "settings_from_fields") settings = Settings(self.common, self.config) - settings.load() # To get the last update timestamp - - settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) - settings.set('csp_header_disabled', self.csp_header_disabled_checkbox.isChecked()) - settings.set('autostart_timer', self.autostart_timer_checkbox.isChecked()) - settings.set('autostop_timer', self.autostop_timer_checkbox.isChecked()) + settings.load() # To get the last update timestamp + + settings.set( + "close_after_first_download", + self.close_after_first_download_checkbox.isChecked(), + ) + settings.set( + "csp_header_disabled", self.csp_header_disabled_checkbox.isChecked() + ) + settings.set("autostart_timer", self.autostart_timer_checkbox.isChecked()) + settings.set("autostop_timer", self.autostop_timer_checkbox.isChecked()) # Complicated logic here to force v2 onion mode on or off depending on other settings if self.use_legacy_v2_onions_checkbox.isChecked(): @@ -1030,142 +1323,167 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = False if self.save_private_key_checkbox.isChecked(): - settings.set('save_private_key', True) - settings.set('private_key', self.old_settings.get('private_key')) - settings.set('password', self.old_settings.get('password')) - settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) + settings.set("save_private_key", True) + settings.set("private_key", self.old_settings.get("private_key")) + settings.set("password", self.old_settings.get("password")) + settings.set( + "hidservauth_string", self.old_settings.get("hidservauth_string") + ) else: - settings.set('save_private_key', False) - settings.set('private_key', '') - settings.set('password', '') + settings.set("save_private_key", False) + settings.set("private_key", "") + settings.set("password", "") # Also unset the HidServAuth if we are removing our reusable private key - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") if use_legacy_v2_onions: - settings.set('use_legacy_v2_onions', True) + settings.set("use_legacy_v2_onions", True) else: - settings.set('use_legacy_v2_onions', False) + settings.set("use_legacy_v2_onions", False) - settings.set('data_dir', self.data_dir_lineedit.text()) - settings.set('public_mode', self.public_mode_checkbox.isChecked()) - settings.set('use_stealth', self.stealth_checkbox.isChecked()) + settings.set("data_dir", self.data_dir_lineedit.text()) + settings.set("public_mode", self.public_mode_checkbox.isChecked()) + settings.set("use_stealth", self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): - settings.set('hidservauth_string', '') + settings.set("hidservauth_string", "") # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) - settings.set('locale', locale) + settings.set("locale", locale) # Tor connection if self.connection_type_bundled_radio.isChecked(): - settings.set('connection_type', 'bundled') + settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): - settings.set('connection_type', 'automatic') + settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): - settings.set('connection_type', 'control_port') + settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): - settings.set('connection_type', 'socket_file') + settings.set("connection_type", "socket_file") if self.autoupdate_checkbox.isChecked(): - settings.set('use_autoupdate', True) + settings.set("use_autoupdate", True) else: - settings.set('use_autoupdate', False) - - settings.set('control_port_address', self.connection_type_control_port_extras_address.text()) - settings.set('control_port_port', self.connection_type_control_port_extras_port.text()) - settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text()) - - settings.set('socks_address', self.connection_type_socks_address.text()) - settings.set('socks_port', self.connection_type_socks_port.text()) + settings.set("use_autoupdate", False) + + settings.set( + "control_port_address", + self.connection_type_control_port_extras_address.text(), + ) + settings.set( + "control_port_port", self.connection_type_control_port_extras_port.text() + ) + settings.set( + "socket_file_path", self.connection_type_socket_file_extras_path.text() + ) + + settings.set("socks_address", self.connection_type_socks_address.text()) + settings.set("socks_port", self.connection_type_socks_port.text()) if self.authenticate_no_auth_radio.isChecked(): - settings.set('auth_type', 'no_auth') + settings.set("auth_type", "no_auth") if self.authenticate_password_radio.isChecked(): - settings.set('auth_type', 'password') + settings.set("auth_type", "password") - settings.set('auth_password', self.authenticate_password_extras_password.text()) + settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set('no_bridges', True) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", True) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', True) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", True) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', True) - settings.set('tor_bridges_use_custom_bridges', '') + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', False) + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] - bridges = self.tor_bridges_use_custom_textbox.toPlainText().split('\n') + bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n") bridges_valid = False for bridge in bridges: - if bridge != '': + if bridge != "": # Check the syntax of the custom bridge to make sure it looks legitimate - ipv4_pattern = re.compile("(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$") - ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$") - meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)") - if ipv4_pattern.match(bridge) or \ - ipv6_pattern.match(bridge) or \ - meek_lite_pattern.match(bridge): - new_bridges.append(''.join(['Bridge ', bridge, '\n'])) + ipv4_pattern = re.compile( + "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" + ) + ipv6_pattern = re.compile( + "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" + ) + meek_lite_pattern = re.compile( + "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" + ) + if ( + ipv4_pattern.match(bridge) + or ipv6_pattern.match(bridge) + or meek_lite_pattern.match(bridge) + ): + new_bridges.append("".join(["Bridge ", bridge, "\n"])) bridges_valid = True if bridges_valid: - new_bridges = ''.join(new_bridges) - settings.set('tor_bridges_use_custom_bridges', new_bridges) + new_bridges = "".join(new_bridges) + settings.set("tor_bridges_use_custom_bridges", new_bridges) else: - Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) - settings.set('no_bridges', True) + Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) + settings.set("no_bridges", True) return False return settings def closeEvent(self, e): - self.common.log('SettingsDialog', 'closeEvent') + self.common.log("SettingsDialog", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.local_only: if not self.onion.is_authenticated(): - self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + self.common.log( + "SettingsDialog", "closeEvent", "Closing while not connected to Tor" + ) # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): - self.common.log('SettingsDialog', '_update_autoupdate_timestamp') + self.common.log("SettingsDialog", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) - last_checked = dt.strftime('%B %d, %Y %H:%M') + last_checked = dt.strftime("%B %d, %Y %H:%M") else: - last_checked = strings._('gui_settings_autoupdate_timestamp_never') - self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked)) + last_checked = strings._("gui_settings_autoupdate_timestamp_never") + self.autoupdate_timestamp.setText( + strings._("gui_settings_autoupdate_timestamp").format(last_checked) + ) def _tor_status_update(self, progress, summary): - self.tor_status.setText('{}
    {}% {}'.format(strings._('connecting_to_tor'), progress, summary)) + self.tor_status.setText( + "{}
    {}% {}".format( + strings._("connecting_to_tor"), progress, summary + ) + ) self.qtapp.processEvents() - if 'Done' in summary: + if "Done" in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): - self.common.log('SettingsDialog', '_disable_buttons') + self.common.log("SettingsDialog", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) @@ -1173,7 +1491,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setEnabled(False) def _enable_buttons(self): - self.common.log('SettingsDialog', '_enable_buttons') + self.common.log("SettingsDialog", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 57e0f0af..090574c1 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -27,6 +27,7 @@ class OnionThread(QtCore.QThread): """ Starts the onion service, and waits for it to finish """ + success = QtCore.pyqtSignal() success_early = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) @@ -34,28 +35,34 @@ class OnionThread(QtCore.QThread): def __init__(self, mode): super(OnionThread, self).__init__() self.mode = mode - self.mode.common.log('OnionThread', '__init__') + self.mode.common.log("OnionThread", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): - self.mode.common.log('OnionThread', 'run') + self.mode.common.log("OnionThread", "run") # Make a new static URL path for each new share self.mode.web.generate_static_url_path() # Choose port and password early, because we need them to exist in advance for scheduled shares - self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') + self.mode.app.stay_open = not self.mode.common.settings.get( + "close_after_first_download" + ) if not self.mode.app.port: self.mode.app.choose_port() - if not self.mode.common.settings.get('public_mode'): + if not self.mode.common.settings.get("public_mode"): if not self.mode.web.password: - self.mode.web.generate_password(self.mode.common.settings.get('password')) + self.mode.web.generate_password( + self.mode.common.settings.get("password") + ) try: if self.mode.obtain_onion_early: - self.mode.app.start_onion_service(await_publication=False, save_scheduled_key=True) + self.mode.app.start_onion_service( + await_publication=False, save_scheduled_key=True + ) # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) self.success_early.emit() @@ -70,7 +77,19 @@ class OnionThread(QtCore.QThread): self.mode.web_thread.start() self.success.emit() - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + except ( + TorTooOld, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + OSError, + ) as e: self.error.emit(e.args[0]) return @@ -79,17 +98,23 @@ class WebThread(QtCore.QThread): """ Starts the web service """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(WebThread, self).__init__() self.mode = mode - self.mode.common.log('WebThread', '__init__') + self.mode.common.log("WebThread", "__init__") def run(self): - self.mode.common.log('WebThread', 'run') - self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.web.password) + self.mode.common.log("WebThread", "run") + self.mode.web.start( + self.mode.app.port, + self.mode.app.stay_open, + self.mode.common.settings.get("public_mode"), + self.mode.web.password, + ) self.success.emit() @@ -97,30 +122,40 @@ class AutoStartTimer(QtCore.QThread): """ Waits for a prescribed time before allowing a share to start """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) + def __init__(self, mode, canceled=False): super(AutoStartTimer, self).__init__() self.mode = mode self.canceled = canceled - self.mode.common.log('AutoStartTimer', '__init__') + self.mode.common.log("AutoStartTimer", "__init__") # allow this thread to be terminated self.setTerminationEnabled() def run(self): now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) try: # Sleep until scheduled time while autostart_timer_datetime_delta > 0 and self.canceled == False: time.sleep(0.1) now = QtCore.QDateTime.currentDateTime() - autostart_timer_datetime_delta = now.secsTo(self.mode.server_status.autostart_timer_datetime) + autostart_timer_datetime_delta = now.secsTo( + self.mode.server_status.autostart_timer_datetime + ) # Timer has now finished if self.canceled == False: - self.mode.server_status.server_button.setText(strings._('gui_please_wait')) - self.mode.server_status_label.setText(strings._('gui_status_indicator_share_working')) + self.mode.server_status.server_button.setText( + strings._("gui_please_wait") + ) + self.mode.server_status_label.setText( + strings._("gui_status_indicator_share_working") + ) self.success.emit() except ValueError as e: self.error.emit(e.args[0]) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 2bcbf1a6..58fc01d0 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -24,10 +24,12 @@ from onionshare.onion import * from .widgets import Alert + class TorConnectionDialog(QtWidgets.QProgressDialog): """ Connecting to Tor dialog. """ + open_settings = QtCore.pyqtSignal() def __init__(self, common, qtapp, onion, custom_settings=False): @@ -40,18 +42,20 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): else: self.settings = self.common.settings - self.common.log('TorConnectionDialog', '__init__') + self.common.log("TorConnectionDialog", "__init__") self.qtapp = qtapp self.onion = onion self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.setModal(True) self.setFixedSize(400, 150) # Label - self.setLabelText(strings._('connecting_to_tor')) + self.setLabelText(strings._("connecting_to_tor")) # Progress bar ticks from 0 to 100 self.setRange(0, 100) @@ -59,10 +63,10 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setMinimumDuration(100) # Start displaying the status at 0 - self._tor_status_update(0, '') + self._tor_status_update(0, "") def start(self): - self.common.log('TorConnectionDialog', 'start') + self.common.log("TorConnectionDialog", "start") t = TorConnectionThread(self.common, self.settings, self, self.onion) t.tor_status_update.connect(self._tor_status_update) @@ -81,17 +85,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _tor_status_update(self, progress, summary): self.setValue(int(progress)) - self.setLabelText("{}
    {}".format(strings._('connecting_to_tor'), summary)) + self.setLabelText( + "{}
    {}".format(strings._("connecting_to_tor"), summary) + ) def _connected_to_tor(self): - self.common.log('TorConnectionDialog', '_connected_to_tor') + self.common.log("TorConnectionDialog", "_connected_to_tor") self.active = False # Close the dialog after connecting self.setValue(self.maximum()) def _canceled_connecting_to_tor(self): - self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor') + self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor") self.active = False self.onion.cleanup() @@ -99,12 +105,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): - self.common.log('TorConnectionDialog', '_error_connecting_to_tor') + self.common.log("TorConnectionDialog", "_error_connecting_to_tor") self.active = False def alert_and_open_settings(): # Display the exception in an alert box - Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning) + Alert( + self.common, + "{}\n\n{}".format(msg, strings._("gui_tor_connection_error_settings")), + QtWidgets.QMessageBox.Warning, + ) # Open settings self.open_settings.emit() @@ -114,6 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): # Cancel connecting to Tor QtCore.QTimer.singleShot(1, self.cancel) + class TorConnectionThread(QtCore.QThread): tor_status_update = QtCore.pyqtSignal(str, str) connected_to_tor = QtCore.pyqtSignal() @@ -125,7 +136,7 @@ class TorConnectionThread(QtCore.QThread): self.common = common - self.common.log('TorConnectionThread', '__init__') + self.common.log("TorConnectionThread", "__init__") self.settings = settings @@ -133,7 +144,7 @@ class TorConnectionThread(QtCore.QThread): self.onion = onion def run(self): - self.common.log('TorConnectionThread', 'run') + self.common.log("TorConnectionThread", "run") # Connect to the Onion try: @@ -144,11 +155,15 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: - self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') + self.common.log( + "TorConnectionThread", "run", "caught exception: BundledTorCanceled" + ) self.canceled_connecting_to_tor.emit() except Exception as e: - self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) + self.common.log( + "TorConnectionThread", "run", "caught exception: {}".format(e.args[0]) + ) self.error_connecting_to_tor.emit(str(e.args[0])) def _tor_status_update(self, progress, summary): diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 1e37b73a..a8bd7979 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -27,21 +27,26 @@ from onionshare.onion import Onion from onionshare import strings + class UpdateCheckerCheckError(Exception): """ Error checking for updates because of some Tor connection issue, or because the OnionShare website is down. """ + pass + class UpdateCheckerInvalidLatestVersion(Exception): """ Successfully downloaded the latest version, but it doesn't appear to be a valid version string. """ + def __init__(self, latest_version): self.latest_version = latest_version + class UpdateChecker(QtCore.QObject): """ Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest @@ -50,6 +55,7 @@ class UpdateChecker(QtCore.QObject): Only check at most once per day, unless force is True. """ + update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal() @@ -60,12 +66,12 @@ class UpdateChecker(QtCore.QObject): self.common = common - self.common.log('UpdateChecker', '__init__') + self.common.log("UpdateChecker", "__init__") self.onion = onion self.config = config def check(self, force=False, config=False): - self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) + self.common.log("UpdateChecker", "check", "force={}".format(force)) # Load the settings settings = Settings(self.common, config) settings.load() @@ -77,7 +83,7 @@ class UpdateChecker(QtCore.QObject): check_for_updates = False # See if it's been 1 day since the last check - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = settings.get("autoupdate_timestamp") if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp) now = datetime.datetime.now() @@ -90,45 +96,61 @@ class UpdateChecker(QtCore.QObject): # Check for updates if check_for_updates: - self.common.log('UpdateChecker', 'check', 'checking for updates') + self.common.log("UpdateChecker", "check", "checking for updates") # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform - user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform) + user_agent = "OnionShare {}, {}".format( + self.common.version, self.common.platform + ) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users - path = '/latest-version.txt' + path = "/latest-version.txt" if force: - path += '?force=1' + path += "?force=1" - if Version(self.onion.tor_version) >= Version('0.3.2.9'): - onion_domain = 'lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion' + if Version(self.onion.tor_version) >= Version("0.3.2.9"): + onion_domain = ( + "lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion" + ) else: - onion_domain = 'elx57ue5uyfplgva.onion' + onion_domain = "elx57ue5uyfplgva.onion" - self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) + self.common.log( + "UpdateChecker", + "check", + "loading http://{}{}".format(onion_domain, path), + ) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() - s.settimeout(15) # 15 second timeout + s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(onion_domain) - http_request += 'User-Agent: {}\r\n'.format(user_agent) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) + http_request = "GET {} HTTP/1.0\r\n".format(path) + http_request += "Host: {}\r\n".format(onion_domain) + http_request += "User-Agent: {}\r\n".format(user_agent) + http_request += "\r\n" + s.sendall(http_request.encode("utf-8")) http_response = s.recv(1024) - latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') - - self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) + latest_version = ( + http_response[http_response.find(b"\r\n\r\n") :] + .strip() + .decode("utf-8") + ) + + self.common.log( + "UpdateChecker", + "check", + "latest OnionShare version: {}".format(latest_version), + ) except Exception as e: - self.common.log('UpdateChecker', 'check', '{}'.format(e)) + self.common.log("UpdateChecker", "check", "{}".format(e)) self.update_error.emit() raise UpdateCheckerCheckError @@ -140,22 +162,32 @@ class UpdateChecker(QtCore.QObject): raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) - timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp() + timestamp = ( + datetime.datetime.now() + .replace(microsecond=0) + .replace(second=0) + .timestamp() + ) # Re-load the settings first before saving, just in case they've changed since we started our thread settings.load() - settings.set('autoupdate_timestamp', timestamp) + settings.set("autoupdate_timestamp", timestamp) settings.save() # Do we need to update? - update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) + update_url = "https://github.com/micahflee/onionshare/releases/tag/v{}".format( + latest_version + ) installed_version = self.common.version if installed_version < latest_version: - self.update_available.emit(update_url, installed_version, latest_version) + self.update_available.emit( + update_url, installed_version, latest_version + ) return # No updates are available self.update_not_available.emit() + class UpdateThread(QtCore.QThread): update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() @@ -167,13 +199,13 @@ class UpdateThread(QtCore.QThread): self.common = common - self.common.log('UpdateThread', '__init__') + self.common.log("UpdateThread", "__init__") self.onion = onion self.config = config self.force = force def run(self): - self.common.log('UpdateThread', 'run') + self.common.log("UpdateThread", "run") u = UpdateChecker(self.common, self.onion, self.config) u.update_available.connect(self._update_available) @@ -182,28 +214,28 @@ class UpdateThread(QtCore.QThread): u.update_invalid_version.connect(self._update_invalid_version) try: - u.check(config=self.config,force=self.force) + u.check(config=self.config, force=self.force) except Exception as e: # If update check fails, silently ignore - self.common.log('UpdateThread', 'run', '{}'.format(e)) + self.common.log("UpdateThread", "run", "{}".format(e)) pass def _update_available(self, update_url, installed_version, latest_version): - self.common.log('UpdateThread', '_update_available') + self.common.log("UpdateThread", "_update_available") self.active = False self.update_available.emit(update_url, installed_version, latest_version) def _update_not_available(self): - self.common.log('UpdateThread', '_update_not_available') + self.common.log("UpdateThread", "_update_not_available") self.active = False self.update_not_available.emit() def _update_error(self): - self.common.log('UpdateThread', '_update_error') + self.common.log("UpdateThread", "_update_error") self.active = False self.update_error.emit() def _update_invalid_version(self, latest_version): - self.common.log('UpdateThread', '_update_invalid_version') + self.common.log("UpdateThread", "_update_invalid_version") self.active = False self.update_invalid_version.emit(latest_version) diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py index 600165aa..d16485fe 100644 --- a/onionshare_gui/widgets.py +++ b/onionshare_gui/widgets.py @@ -19,19 +19,30 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui + class Alert(QtWidgets.QMessageBox): """ An alert box dialog. """ - def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): + + def __init__( + self, + common, + message, + icon=QtWidgets.QMessageBox.NoIcon, + buttons=QtWidgets.QMessageBox.Ok, + autostart=True, + ): super(Alert, self).__init__(None) self.common = common - self.common.log('Alert', '__init__') + self.common.log("Alert", "__init__") self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setWindowIcon( + QtGui.QIcon(self.common.get_resource_path("images/logo.png")) + ) self.setText(message) self.setIcon(icon) self.setStandardButtons(buttons) @@ -49,11 +60,12 @@ class AddFileDialog(QtWidgets.QFileDialog): This is because the macOS sandbox requires native dialogs, and this is a Qt5 dialog. """ + def __init__(self, common, *args, **kwargs): QtWidgets.QFileDialog.__init__(self, *args, **kwargs) self.common = common - self.common.log('AddFileDialog', '__init__') + self.common.log("AddFileDialog", "__init__") self.setOption(self.DontUseNativeDialog, True) self.setOption(self.ReadOnly, True) @@ -65,5 +77,5 @@ class AddFileDialog(QtWidgets.QFileDialog): list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def accept(self): - self.common.log('AddFileDialog', 'accept') + self.common.log("AddFileDialog", "accept") QtWidgets.QDialog.accept(self) diff --git a/setup.py b/setup.py index 608d4088..9af72fc1 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ along with this program. If not, see . import os, sys, platform, tempfile from distutils.core import setup + def file_list(path): files = [] for filename in os.listdir(path): @@ -29,7 +30,8 @@ def file_list(path): files.append(os.path.join(path, filename)) return files -version = open('share/version.txt').read().strip() + +version = open("share/version.txt").read().strip() description = ( """OnionShare lets you securely and anonymously send and receive files. It """ """works by starting a web server, making it accessible as a Tor onion """ @@ -37,61 +39,99 @@ description = ( """files from you, or upload files to you. It does _not_ require setting up """ """a separate server or using a third party file-sharing service.""" ) -long_description = description + "\n\n" + ( - """If you want to send files to someone, OnionShare hosts them on your own """ - """computer and uses a Tor onion service to make them temporarily accessible """ - """over the internet. The receiving user just needs to open the web address """ - """in Tor Browser to download the files. If you want to receive files, """ - """OnionShare hosts an anonymous dropbox directly on your computer and uses """ - """a Tor onion service to make it temporarily accessible over the internet. """ - """Other users can upload files to you from by loading the web address in """ - """Tor Browser.""" +long_description = ( + description + + "\n\n" + + ( + """If you want to send files to someone, OnionShare hosts them on your own """ + """computer and uses a Tor onion service to make them temporarily accessible """ + """over the internet. The receiving user just needs to open the web address """ + """in Tor Browser to download the files. If you want to receive files, """ + """OnionShare hosts an anonymous dropbox directly on your computer and uses """ + """a Tor onion service to make it temporarily accessible over the internet. """ + """Other users can upload files to you from by loading the web address in """ + """Tor Browser.""" + ) ) -author = 'Micah Lee' -author_email = 'micah@micahflee.com' -url = 'https://github.com/micahflee/onionshare' -license = 'GPL v3' -keywords = 'onion, share, onionshare, tor, anonymous, web server' +author = "Micah Lee" +author_email = "micah@micahflee.com" +url = "https://github.com/micahflee/onionshare" +license = "GPL v3" +keywords = "onion, share, onionshare, tor, anonymous, web server" classifiers = [ - "Programming Language :: Python :: 3", - "Framework :: Flask", - "Topic :: Communications :: File Sharing", - "Topic :: Security :: Cryptography", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Intended Audience :: End Users/Desktop", - "Operating System :: OS Independent", - "Environment :: Web Environment" - ] -data_files=[ - (os.path.join(sys.prefix, 'share/applications'), ['install/org.onionshare.OnionShare.desktop']), - (os.path.join(sys.prefix, 'share/icons/hicolor/scalable/apps'), ['install/org.onionshare.OnionShare.svg']), - (os.path.join(sys.prefix, 'share/metainfo'), ['install/org.onionshare.OnionShare.appdata.xml']), - (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), - (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), - (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')), - (os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')), - (os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')), - (os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')), - (os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js')) - ] -if not platform.system().endswith('BSD') and platform.system() != 'DragonFly': - data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])) + "Programming Language :: Python :: 3", + "Framework :: Flask", + "Topic :: Communications :: File Sharing", + "Topic :: Security :: Cryptography", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Intended Audience :: End Users/Desktop", + "Operating System :: OS Independent", + "Environment :: Web Environment", +] +data_files = [ + ( + os.path.join(sys.prefix, "share/applications"), + ["install/org.onionshare.OnionShare.desktop"], + ), + ( + os.path.join(sys.prefix, "share/icons/hicolor/scalable/apps"), + ["install/org.onionshare.OnionShare.svg"], + ), + ( + os.path.join(sys.prefix, "share/metainfo"), + ["install/org.onionshare.OnionShare.appdata.xml"], + ), + (os.path.join(sys.prefix, "share/onionshare"), file_list("share")), + (os.path.join(sys.prefix, "share/onionshare/images"), file_list("share/images")), + (os.path.join(sys.prefix, "share/onionshare/locale"), file_list("share/locale")), + ( + os.path.join(sys.prefix, "share/onionshare/templates"), + file_list("share/templates"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/css"), + file_list("share/static/css"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/img"), + file_list("share/static/img"), + ), + ( + os.path.join(sys.prefix, "share/onionshare/static/js"), + file_list("share/static/js"), + ), +] +if not platform.system().endswith("BSD") and platform.system() != "DragonFly": + data_files.append( + ( + "/usr/share/nautilus-python/extensions/", + ["install/scripts/onionshare-nautilus.py"], + ) + ) setup( - name='onionshare', version=version, - description=description, long_description=long_description, - author=author, author_email=author_email, maintainer=author, maintainer_email=author_email, - url=url, license=license, keywords=keywords, classifiers=classifiers, + name="onionshare", + version=version, + description=description, + long_description=long_description, + author=author, + author_email=author_email, + maintainer=author, + maintainer_email=author_email, + url=url, + license=license, + keywords=keywords, + classifiers=classifiers, packages=[ - 'onionshare', - 'onionshare.web', - 'onionshare_gui', - 'onionshare_gui.mode', - 'onionshare_gui.mode.share_mode', - 'onionshare_gui.mode.receive_mode', - 'onionshare_gui.mode.website_mode' + "onionshare", + "onionshare.web", + "onionshare_gui", + "onionshare_gui.mode", + "onionshare_gui.mode.share_mode", + "onionshare_gui.mode.receive_mode", + "onionshare_gui.mode.website_mode", ], include_package_data=True, - scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'], - data_files=data_files + scripts=["install/scripts/onionshare", "install/scripts/onionshare-gui"], + data_files=data_files, ) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 3e82769a..6d6340d1 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -20,17 +20,17 @@ from onionshare_gui.mode.website_mode import WebsiteMode class GuiBaseTest(object): @staticmethod def set_up(test_settings): - '''Create GUI with given settings''' + """Create GUI with given settings""" # Create our test file - testfile = open('/tmp/test.txt', 'w') - testfile.write('onionshare') + testfile = open("/tmp/test.txt", "w") + testfile.write("onionshare") testfile.close() # Create a test dir and files - if not os.path.exists('/tmp/testdir'): - testdir = os.mkdir('/tmp/testdir') - testfile = open('/tmp/testdir/test', 'w') - testfile.write('onionshare') + if not os.path.exists("/tmp/testdir"): + testdir = os.mkdir("/tmp/testdir") + testfile = open("/tmp/testdir/test", "w") + testfile.write("onionshare") testfile.close() common = Common() @@ -39,7 +39,7 @@ class GuiBaseTest(object): strings.load_strings(common) # Get all of the settings in test_settings - test_settings['data_dir'] = '/tmp/OnionShare' + test_settings["data_dir"] = "/tmp/OnionShare" for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val @@ -51,53 +51,55 @@ class GuiBaseTest(object): app = OnionShare(common, testonion, True, 0) web = Web(common, False, True) - open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) - - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', True) + open("/tmp/settings.json", "w").write(json.dumps(test_settings)) + + gui = OnionShareGui( + common, + testonion, + qtapp, + app, + ["/tmp/test.txt", "/tmp/testdir"], + "/tmp/settings.json", + True, + ) return gui @staticmethod def tear_down(): - '''Clean up after tests''' + """Clean up after tests""" try: - os.remove('/tmp/test.txt') - os.remove('/tmp/settings.json') - os.remove('/tmp/large_file') - os.remove('/tmp/download.zip') - os.remove('/tmp/webpage') - shutil.rmtree('/tmp/testdir') - shutil.rmtree('/tmp/OnionShare') + os.remove("/tmp/test.txt") + os.remove("/tmp/settings.json") + os.remove("/tmp/large_file") + os.remove("/tmp/download.zip") + os.remove("/tmp/webpage") + shutil.rmtree("/tmp/testdir") + shutil.rmtree("/tmp/OnionShare") except: pass - def gui_loaded(self): - '''Test that the GUI actually is shown''' + """Test that the GUI actually is shown""" self.assertTrue(self.gui.show) - def windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - + """Test that the window title is OnionShare""" + self.assertEqual(self.gui.windowTitle(), "OnionShare") def settings_button_is_visible(self): - '''Test that the settings button is visible''' + """Test that the settings button is visible""" self.assertTrue(self.gui.settings_button.isVisible()) - def settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' + """Test that the settings button is hidden when the server starts""" self.assertFalse(self.gui.settings_button.isVisible()) - def server_status_bar_is_visible(self): - '''Test that the status bar is visible''' + """Test that the status bar is visible""" self.assertTrue(self.gui.status_bar.isVisible()) - def click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' + """Test that we can switch Mode by clicking the button""" if type(mode) == ReceiveMode: QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) @@ -108,16 +110,14 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(self.gui.website_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_WEBSITE) - def click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' + """Test that we can toggle Download or Upload history by clicking the toggle button""" currently_visible = mode.history.isVisible() QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertEqual(mode.history.isVisible(), not currently_visible) - def history_indicator(self, mode, public_mode, indicator_count="1"): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + """Test that we can make sure the history is toggled off, do an action, and the indiciator works""" # Make sure history is toggled off if mode.history.isVisible(): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) @@ -128,12 +128,16 @@ class GuiBaseTest(object): if type(mode) == ReceiveMode: # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + files = {"file[]": open("/tmp/test.txt", "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password), + ) QtTest.QTest.qWait(2000) if type(mode) == ShareMode: @@ -142,7 +146,10 @@ class GuiBaseTest(object): if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password), + ) QtTest.QTest.qWait(2000) # Indicator should be visible, have a value of "1" @@ -153,19 +160,16 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - def history_is_not_visible(self, mode): - '''Test that the History section is not visible''' + """Test that the History section is not visible""" self.assertFalse(mode.history.isVisible()) - def history_is_visible(self, mode): - '''Test that the History section is visible''' + """Test that the History section is visible""" self.assertTrue(mode.history.isVisible()) - def server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' + """Test we can start the service""" # Should be in SERVER_WORKING state QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 1) @@ -175,115 +179,143 @@ class GuiBaseTest(object): self.assertFalse(mode.toggle_history.indicator_label.isVisible()) def server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + """Test that the Server Status indicator shows we are Starting""" + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_working"), + ) def server_status_indicator_says_scheduled(self, mode): - '''Test that the Server Status indicator shows we are Scheduled''' - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_scheduled')) + """Test that the Server Status indicator shows we are Scheduled""" + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_scheduled"), + ) def server_is_started(self, mode, startup_time=2000): - '''Test that the server has started''' + """Test that the server has started""" QtTest.QTest.qWait(startup_time) # Should now be in SERVER_STARTED state self.assertEqual(mode.server_status.status, 2) - def web_server_is_running(self): - '''Test that the web server has started''' + """Test that the web server has started""" try: - r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port)) self.assertTrue(True) except requests.exceptions.ConnectionError: self.assertTrue(False) - def have_a_password(self, mode, public_mode): - '''Test that we have a valid password''' + """Test that we have a valid password""" if not public_mode: - self.assertRegex(mode.server_status.web.password, r'(\w+)-(\w+)') + self.assertRegex(mode.server_status.web.password, r"(\w+)-(\w+)") else: - self.assertIsNone(mode.server_status.web.password, r'(\w+)-(\w+)') + self.assertIsNone(mode.server_status.web.password, r"(\w+)-(\w+)") def add_button_visible(self, mode): - '''Test that the add button should be visible''' + """Test that the add button should be visible""" self.assertTrue(mode.server_status.file_selection.add_button.isVisible()) def url_description_shown(self, mode): - '''Test that the URL label is showing''' + """Test that the URL label is showing""" self.assertTrue(mode.server_status.url_description.isVisible()) - def have_copy_url_button(self, mode, public_mode): - '''Test that the Copy URL button is shown and that the clipboard is correct''' + """Test that the Copy URL button is shown and that the clipboard is correct""" self.assertTrue(mode.server_status.copy_url_button.isVisible()) - QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + mode.server_status.copy_url_button, QtCore.Qt.LeftButton + ) clipboard = self.gui.qtapp.clipboard() if public_mode: - self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port)) + self.assertEqual( + clipboard.text(), "http://127.0.0.1:{}".format(self.gui.app.port) + ) else: - self.assertEqual(clipboard.text(), 'http://onionshare:{}@127.0.0.1:{}'.format(mode.server_status.web.password, self.gui.app.port)) - + self.assertEqual( + clipboard.text(), + "http://onionshare:{}@127.0.0.1:{}".format( + mode.server_status.web.password, self.gui.app.port + ), + ) def server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' + """Test that the Server Status indicator shows we are started""" if type(mode) == ReceiveMode: - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_receive_started"), + ) if type(mode) == ShareMode: - self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - + self.assertEqual( + mode.server_status_label.text(), + strings._("gui_status_indicator_share_started"), + ) def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' + """Test that the web page contains a string""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', mode.web.password)) + r = requests.get( + url, auth=requests.auth.HTTPBasicAuth("onionshare", mode.web.password) + ) self.assertTrue(string in r.text) - def history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' + """Test that the relevant widgets are present in the history view after activity has taken place""" self.assertFalse(mode.history.empty.isVisible()) self.assertTrue(mode.history.not_empty.isVisible()) - def counter_incremented(self, mode, count): - '''Test that the counter has incremented''' + """Test that the counter has incremented""" self.assertEqual(mode.history.completed_count, count) - def server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open) or (type(mode) == WebsiteMode): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + """Test that the server stops when we click Stop""" + if ( + type(mode) == ReceiveMode + or (type(mode) == ShareMode and stay_open) + or (type(mode) == WebsiteMode) + ): + QtTest.QTest.mouseClick( + mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(mode.server_status.status, 0) - def web_server_is_stopped(self): - '''Test that the web server also stopped''' + """Test that the web server also stopped""" QtTest.QTest.qWait(2000) try: - r = requests.get('http://127.0.0.1:{}/'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/".format(self.gui.app.port)) self.assertTrue(False) except requests.exceptions.ConnectionError: self.assertTrue(True) - def server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' + """Test that the Server Status indicator shows we closed""" if type(mode) == ReceiveMode: - self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + self.assertEqual( + self.gui.receive_mode.server_status_label.text(), + strings._("gui_status_indicator_receive_stopped"), + ) if type(mode) == ShareMode: if stay_open: - self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + self.assertEqual( + self.gui.share_mode.server_status_label.text(), + strings._("gui_status_indicator_share_stopped"), + ) else: - self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + self.assertEqual( + self.gui.share_mode.server_status_label.text(), + strings._("closing_automatically"), + ) def clear_all_history_items(self, mode, count): if count == 0: @@ -292,41 +324,40 @@ class GuiBaseTest(object): # Auto-stop timer tests def set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' + """Test that the timeout can be set""" timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) mode.server_status.autostop_timer_widget.setDateTime(timer) self.assertTrue(mode.server_status.autostop_timer_widget.dateTime(), timer) def autostop_timer_widget_hidden(self, mode): - '''Test that the auto-stop timer widget is hidden when share has started''' + """Test that the auto-stop timer widget is hidden when share has started""" self.assertFalse(mode.server_status.autostop_timer_container.isVisible()) - def server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' + """Test that the server has timed out after the timer ran out""" QtTest.QTest.qWait(wait) # We should have timed out now self.assertEqual(mode.server_status.status, 0) # Auto-start timer tests def set_autostart_timer(self, mode, timer): - '''Test that the timer can be set''' + """Test that the timer can be set""" schedule = QtCore.QDateTime.currentDateTime().addSecs(timer) mode.server_status.autostart_timer_widget.setDateTime(schedule) self.assertTrue(mode.server_status.autostart_timer_widget.dateTime(), schedule) def autostart_timer_widget_hidden(self, mode): - '''Test that the auto-start timer widget is hidden when share has started''' + """Test that the auto-start timer widget is hidden when share has started""" self.assertFalse(mode.server_status.autostart_timer_container.isVisible()) def scheduled_service_started(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' + """Test that the server has timed out after the timer ran out""" QtTest.QTest.qWait(wait) # We should have started now self.assertEqual(mode.server_status.status, 2) def cancel_the_share(self, mode): - '''Test that we can cancel a share before it's started up ''' + """Test that we can cancel a share before it's started up """ self.server_working_on_start_button_pressed(mode) self.server_status_indicator_says_scheduled(mode) self.add_delete_buttons_hidden() @@ -334,7 +365,9 @@ class GuiBaseTest(object): self.set_autostart_timer(mode, 10) QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) QtTest.QTest.qWait(2000) - QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseRelease( + mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.assertEqual(mode.server_status.status, 0) self.server_is_stopped(mode, False) self.web_server_is_stopped() diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 80e05250..34db1a94 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -4,25 +4,44 @@ from datetime import datetime, timedelta from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest + class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, file_to_upload, expected_basename, identical_files_at_once=False): - '''Test that we can upload the file''' + def upload_file( + self, + public_mode, + file_to_upload, + expected_basename, + identical_files_at_once=False, + ): + """Test that we can upload the file""" # Wait 2 seconds to make sure the filename, based on timestamp, isn't accidentally reused QtTest.QTest.qWait(2000) - files = {'file[]': open(file_to_upload, 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + files = {"file[]": open(file_to_upload, "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) if identical_files_at_once: # Send a duplicate upload to test for collisions r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) if identical_files_at_once: # Send a duplicate upload to test for collisions - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) QtTest.QTest.qWait(2000) @@ -35,7 +54,9 @@ class GuiReceiveTest(GuiBaseTest): time_dir = now.strftime("%H.%M.%S-1") else: time_dir = now.strftime("%H.%M.%S") - receive_mode_dir = os.path.join(self.gui.common.settings.get('data_dir'), date_dir, time_dir) + receive_mode_dir = os.path.join( + self.gui.common.settings.get("data_dir"), date_dir, time_dir + ) expected_filename = os.path.join(receive_mode_dir, expected_basename) if os.path.exists(expected_filename): exists = True @@ -45,31 +66,37 @@ class GuiReceiveTest(GuiBaseTest): self.assertTrue(exists) def upload_file_should_fail(self, public_mode): - '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - url = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + """Test that we can't upload the file when permissions are wrong, and expected content is shown""" + files = {"file[]": open("/tmp/test.txt", "rb")} + url = "http://127.0.0.1:{}/upload".format(self.gui.app.port) if public_mode: r = requests.post(url, files=files) else: - r = requests.post(url, files=files, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.receive_mode.web.password)) + r = requests.post( + url, + files=files, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.receive_mode.web.password + ), + ) QtCore.QTimer.singleShot(1000, self.accept_dialog) - self.assertTrue('Error uploading, please inform the OnionShare user' in r.text) + self.assertTrue("Error uploading, please inform the OnionShare user" in r.text) def upload_dir_permissions(self, mode=0o755): - '''Manipulate the permissions on the upload dir in between tests''' - os.chmod('/tmp/OnionShare', mode) + """Manipulate the permissions on the upload dir in between tests""" + os.chmod("/tmp/OnionShare", mode) def try_without_auth_in_non_public_mode(self): - r = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) + r = requests.post("http://127.0.0.1:{}/upload".format(self.gui.app.port)) self.assertEqual(r.status_code, 401) - r = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) + r = requests.get("http://127.0.0.1:{}/close".format(self.gui.app.port)) self.assertEqual(r.status_code, 401) # 'Grouped' tests follow from here def run_all_receive_mode_setup_tests(self, public_mode): - '''Set up a share in Receive mode and start it''' + """Set up a share in Receive mode and start it""" self.click_mode(self.gui.receive_mode) self.history_is_not_visible(self.gui.receive_mode) self.click_toggle_history(self.gui.receive_mode) @@ -83,24 +110,28 @@ class GuiReceiveTest(GuiBaseTest): self.url_description_shown(self.gui.receive_mode) self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.web_page( + self.gui.receive_mode, + "Select the files you want to send, then click", + public_mode, + ) def run_all_receive_mode_tests(self, public_mode): - '''Upload files in receive mode and stop the share''' + """Upload files in receive mode and stop the share""" self.run_all_receive_mode_setup_tests(public_mode) if not public_mode: self.try_without_auth_in_non_public_mode() - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.counter_incremented(self.gui.receive_mode, 2) - self.upload_file(public_mode, '/tmp/testdir/test', 'test') + self.upload_file(public_mode, "/tmp/testdir/test", "test") self.counter_incremented(self.gui.receive_mode, 3) - self.upload_file(public_mode, '/tmp/testdir/test', 'test') + self.upload_file(public_mode, "/tmp/testdir/test", "test") self.counter_incremented(self.gui.receive_mode, 4) # Test uploading the same file twice at the same time, and make sure no collisions - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt', True) + self.upload_file(public_mode, "/tmp/test.txt", "test.txt", True) self.counter_incremented(self.gui.receive_mode, 6) self.history_indicator(self.gui.receive_mode, public_mode, "2") self.server_is_stopped(self.gui.receive_mode, False) @@ -111,7 +142,7 @@ class GuiReceiveTest(GuiBaseTest): self.history_indicator(self.gui.receive_mode, public_mode, "2") def run_all_receive_mode_unwritable_dir_tests(self, public_mode): - '''Attempt to upload (unwritable) files in receive mode and stop the share''' + """Attempt to upload (unwritable) files in receive mode and stop the share""" self.run_all_receive_mode_setup_tests(public_mode) self.upload_dir_permissions(0o400) self.upload_file_should_fail(public_mode) @@ -131,8 +162,8 @@ class GuiReceiveTest(GuiBaseTest): def run_all_clear_all_button_tests(self, public_mode): """Test the Clear All history button""" self.run_all_receive_mode_setup_tests(public_mode) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.history_widgets_present(self.gui.receive_mode) self.clear_all_history_items(self.gui.receive_mode, 0) - self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') + self.upload_file(public_mode, "/tmp/test.txt", "test.txt") self.clear_all_history_items(self.gui.receive_mode, 2) diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 6925defa..630d0562 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -6,89 +6,137 @@ import tempfile from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest + class GuiShareTest(GuiBaseTest): # Persistence tests def have_same_password(self, password): - '''Test that we have the same password''' + """Test that we have the same password""" self.assertEqual(self.gui.share_mode.server_status.web.password, password) # Share-specific tests def file_selection_widget_has_files(self, num=2): - '''Test that the number of items in the list is as expected''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num) - + """Test that the number of items in the list is as expected""" + self.assertEqual( + self.gui.share_mode.server_status.file_selection.get_num_files(), num + ) def deleting_all_files_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + """Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button""" + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect( + self.gui.share_mode.server_status.file_selection.file_list.item(0) + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.viewport(), + QtCore.Qt.LeftButton, + pos=rect.center(), + ) # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + self.assertTrue( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) # Click delete, delete button should still be visible since we have one more file - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.delete_button, + QtCore.Qt.LeftButton, + ) + + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect( + self.gui.share_mode.server_status.file_selection.file_list.item(0) + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.viewport(), + QtCore.Qt.LeftButton, + pos=rect.center(), + ) + self.assertTrue( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.delete_button, + QtCore.Qt.LeftButton, + ) # No more files, the delete button should be hidden - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + self.assertFalse( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) def add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + """Test that we can also delete a file by clicking on its [X] widget""" + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/etc/hosts" + ) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.file_selection.file_list.item( + 0 + ).item_button, + QtCore.Qt.LeftButton, + ) self.file_selection_widget_has_files(0) - def file_selection_widget_read_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + """Re-add some files to the list so we can share""" + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/etc/hosts" + ) + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/test.txt" + ) self.file_selection_widget_has_files(2) - def add_large_file(self): - '''Add a large file to the share''' - size = 1024*1024*155 - with open('/tmp/large_file', 'wb') as fout: + """Add a large file to the share""" + size = 1024 * 1024 * 155 + with open("/tmp/large_file", "wb") as fout: fout.write(os.urandom(size)) - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file') - + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/large_file" + ) def add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - + """Test that the add and delete buttons are hidden when the server starts""" + self.assertFalse( + self.gui.share_mode.server_status.file_selection.add_button.isVisible() + ) + self.assertFalse( + self.gui.share_mode.server_status.file_selection.delete_button.isVisible() + ) def download_share(self, public_mode): - '''Test that we can download the share''' + """Test that we can download the share""" url = "http://127.0.0.1:{}/download".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) tmp_file = tempfile.NamedTemporaryFile() - with open(tmp_file.name, 'wb') as f: + with open(tmp_file.name, "wb") as f: f.write(r.content) zip = zipfile.ZipFile(tmp_file.name) QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + self.assertEqual("onionshare", zip.read("test.txt").decode("utf-8")) def individual_file_is_viewable_or_not(self, public_mode, stay_open): - '''Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)''' + """Test whether an individual file is viewable (when in stay_open mode) and that it isn't (when not in stay_open mode)""" url = "http://127.0.0.1:{}".format(self.gui.app.port) download_file_url = "http://127.0.0.1:{}/test.txt".format(self.gui.app.port) if public_mode: r = requests.get(url) else: - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) if stay_open: self.assertTrue('a href="test.txt"' in r.text) @@ -96,32 +144,44 @@ class GuiShareTest(GuiBaseTest): if public_mode: r = requests.get(download_file_url) else: - r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + download_file_url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) tmp_file = tempfile.NamedTemporaryFile() - with open(tmp_file.name, 'wb') as f: + with open(tmp_file.name, "wb") as f: f.write(r.content) - with open(tmp_file.name, 'r') as f: - self.assertEqual('onionshare', f.read()) + with open(tmp_file.name, "r") as f: + self.assertEqual("onionshare", f.read()) else: self.assertFalse('a href="/test.txt"' in r.text) if public_mode: r = requests.get(download_file_url) else: - r = requests.get(download_file_url, auth=requests.auth.HTTPBasicAuth('onionshare', self.gui.share_mode.server_status.web.password)) + r = requests.get( + download_file_url, + auth=requests.auth.HTTPBasicAuth( + "onionshare", self.gui.share_mode.server_status.web.password + ), + ) self.assertEqual(r.status_code, 404) self.download_share(public_mode) QtTest.QTest.qWait(2000) def hit_401(self, public_mode): - '''Test that the server stops after too many 401s, or doesn't when in public_mode''' + """Test that the server stops after too many 401s, or doesn't when in public_mode""" url = "http://127.0.0.1:{}/".format(self.gui.app.port) for _ in range(20): password_guess = self.gui.common.build_password() - r = requests.get(url, auth=requests.auth.HTTPBasicAuth('onionshare', password_guess)) + r = requests.get( + url, auth=requests.auth.HTTPBasicAuth("onionshare", password_guess) + ) # A nasty hack to avoid the Alert dialog that blocks the rest of the test if not public_mode: @@ -134,7 +194,6 @@ class GuiShareTest(GuiBaseTest): else: self.web_server_is_stopped() - # 'Grouped' tests follow from here def run_all_share_mode_setup_tests(self): @@ -148,7 +207,6 @@ class GuiShareTest(GuiBaseTest): self.add_a_file_and_delete_using_its_delete_widget() self.file_selection_widget_read_files() - def run_all_share_mode_started_tests(self, public_mode, startup_time=2000): """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) @@ -162,10 +220,9 @@ class GuiShareTest(GuiBaseTest): self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) - def run_all_share_mode_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.web_page(self.gui.share_mode, "Total size", public_mode) self.download_share(public_mode) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) @@ -179,7 +236,7 @@ class GuiShareTest(GuiBaseTest): def run_all_share_mode_individual_file_download_tests(self, public_mode, stay_open): """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.web_page(self.gui.share_mode, "Total size", public_mode) self.individual_file_is_viewable_or_not(public_mode, stay_open) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) @@ -222,7 +279,6 @@ class GuiShareTest(GuiBaseTest): self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): """Same as end-to-end share tests but also test the password is the same on multiple shared""" self.run_all_share_mode_setup_tests() @@ -231,7 +287,6 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_download_tests(public_mode, stay_open) self.have_same_password(password) - def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() @@ -258,12 +313,16 @@ class GuiShareTest(GuiBaseTest): self.set_autostart_timer(self.gui.share_mode, 15) self.set_timeout(self.gui.share_mode, 5) QtCore.QTimer.singleShot(4000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.mouseClick( + self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton + ) self.server_is_stopped(self.gui.share_mode, False) def run_all_share_mode_unreadable_file_tests(self): - '''Attempt to share an unreadable file''' + """Attempt to share an unreadable file""" self.run_all_share_mode_setup_tests() QtCore.QTimer.singleShot(1000, self.accept_dialog) - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/nonexistent.txt') + self.gui.share_mode.server_status.file_selection.file_list.add_file( + "/tmp/nonexistent.txt" + ) self.file_selection_widget_has_files(2) diff --git a/tests/GuiWebsiteTest.py b/tests/GuiWebsiteTest.py index 798c619a..f6b67112 100644 --- a/tests/GuiWebsiteTest.py +++ b/tests/GuiWebsiteTest.py @@ -13,13 +13,16 @@ from onionshare.web import Web from onionshare_gui import Application, OnionShare, OnionShareGui from .GuiShareTest import GuiShareTest + class GuiWebsiteTest(GuiShareTest): @staticmethod def set_up(test_settings): - '''Create GUI with given settings''' + """Create GUI with given settings""" # Create our test file - testfile = open('/tmp/index.html', 'w') - testfile.write('

    This is a test website hosted by OnionShare

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

    This is a test website hosted by OnionShare

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

    إصدارتك {} و الأحدث {}.", - "update_error_check_error": "تعذّر التماس إصدارة أحدث: موقع 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_canceled": "تعذّر الاتصال بتور.\n\nتحقّق من اتصالك بالإنترنت ثم أعد تشغيل OnionShare و اضبط اتصاله بتور.", "gui_tor_connection_lost": "غير متصل بشبكة تور.", "gui_server_started_after_autostop_timer": "بلغ مؤقِّت الإيقاف أجله قبل اشتغال الخادوم. أنشئ مشاركة جديدة.", - "gui_server_autostop_timer_expired": "بلغ مؤقّت الإيقاف أجله بالفعل. حدّثه للبدء بالمشاركة.", - "share_via_onionshare": "شاركه باستعمال OnionShare", + "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 هذا سيكون بوسعه تنزيل تلك الملفات باستعمال متصفّح تور: ", @@ -181,7 +181,7 @@ "gui_download_in_progress": "", "gui_open_folder_error_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": "أضف دليلا", @@ -209,8 +209,8 @@ "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_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: {}", @@ -225,5 +225,13 @@ "days_first_letter": "يوم", "hours_first_letter": "ساعة", "minutes_first_letter": "دقيقة", - "seconds_first_letter": "ثانية" + "seconds_first_letter": "ثانية", + "gui_website_url_description": "أى شخص معه مسار OnionsShare هذا سيكون بامكانه زيارة موقعك باستخدام متصفح تور: ", + "gui_mode_website_button": "قم بإصدار الموقع", + "gui_website_mode_no_files": "لا يوجد موقع تمت مشاركته حتى الآن", + "incorrect_password": "كلمة السر غير صحيحة", + "gui_settings_individual_downloads_label": "ازل لتسمح بتحميل الملفات فرادى", + "history_requests_tooltip": "{} طلب من الويب", + "gui_settings_csp_header_disabled_option": "أوقف حماية Content Security Policy", + "gui_settings_website_label": "اعدادات الموقع" } diff --git a/share/locale/hi.json b/share/locale/hi.json index 8cc4612c..9cfc310d 100644 --- a/share/locale/hi.json +++ b/share/locale/hi.json @@ -1,18 +1,18 @@ { "config_onion_service": "", - "preparing_files": "", + "preparing_files": "फाइल कंप्रेस हो रहा है।", "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": "", - "close_on_autostop_timer": "", - "closing_automatically": "", - "large_filesize": "", + "not_a_readable_file": "{0:s} रीड होने योग्य फाइल नहीं है।", + "no_available_port": "अनियन सेवा शुरू करने के लिए कोई उपलब्ध पोर्ट नहीं सका", + "other_page_loaded": "पता लोड हो गया", + "close_on_autostop_timer": "ऑटो-स्टॉप टाइमर बंद होने के कारण बंद हो गया", + "closing_automatically": "स्थानांतरण पूरा होने के कारण रुक गया", + "large_filesize": "चेतावनी: बड़े आकार की फाइल साझा करने में घंटों लग सकते हैं", "help_local_only": "", "help_stay_open": "", "help_autostop_timer": "", @@ -21,52 +21,52 @@ "help_verbose": "", "help_filename": "", "help_config": "", - "gui_drag_and_drop": "", + "gui_drag_and_drop": "साझा शुरू करने के लिए\nफाइलों एवं फोल्डरों को ड्रैग और ड्रॉप करें", "gui_add": "जोड़ें", - "gui_add_files": "", - "gui_add_folder": "", + "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_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_start_server": "रिसीव मोड चालू करें", + "gui_receive_stop_server": "रिसीव मोड बंद करें", + "gui_receive_stop_server_autostop_timer": "रिसीव मोड बंद करें ({} remaining)", "gui_receive_stop_server_autostop_timer_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_canceled": "Canceled", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_copy_url": "पता कॉपी करें", + "gui_copy_hidservauth": "HidServAuth कॉपी करें", + "gui_canceled": "रद्द हो गया", + "gui_copied_url_title": "OnionShare पता कॉपी हो गया", + "gui_copied_url": "OnionShare पता क्लिपबोर्ड में कॉपी हो गया", + "gui_copied_hidservauth_title": "HidServAuth कॉपी हो गया", + "gui_copied_hidservauth": "HidServAuth लाइन क्लिपबोर्ड में कॉपी हो गया", + "gui_please_wait": "शुरू हो रहा है... रद्द करने के लिए क्लिक करें।", "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "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": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "सेटिंग", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", + "gui_quit_warning_dont_quit": "रद्द करें", + "error_rate_limit": "किसी ने आपके पासवर्ड का अंदाज़ा लगाने के लिए कई सारे गलत प्रयास किए हैं, इसीलिए OnionShare ने सर्वर रोक दिया है। साझा पुनः शुरू करें और साझा करने के लिए भेजनेवाले व्यक्ति को एक नया पता साझा करें।", + "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_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": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "अंतिम जाँच %1", - "gui_settings_autoupdate_timestamp_never": "कभी नहीँ", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "जनरल सेटिंग्स", - "gui_settings_onion_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", + "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_onion_label": "Onion सेटिंग्स", + "gui_settings_sharing_label": "साझा सेटिंग्स", + "gui_settings_close_after_first_download_option": "इस फाइल को भेजने के बाद साझा बंद कर दें", + "gui_settings_connection_type_label": "OnionShare को Tor से कैसे जुड़ना चाहिए?", "gui_settings_connection_type_bundled_option": "", "gui_settings_connection_type_automatic_option": "", "gui_settings_connection_type_control_port_option": "", @@ -180,5 +180,11 @@ "gui_share_mode_no_files": "", "gui_share_mode_autostop_timer_waiting": "", "gui_receive_mode_no_files": "", - "gui_receive_mode_autostop_timer_waiting": "" + "gui_receive_mode_autostop_timer_waiting": "", + "gui_stop_server_autostop_timer_tooltip": "ऑटो-स्टॉप टाइमर {} पर बंद होगा", + "gui_start_server_autostart_timer_tooltip": "ऑटो-स्टार्ट टाइमर {} पर बंद होगा", + "gui_waiting_to_start": "{} में शुरू होने के लिए शेडयूल है। रद्द करने के लिए क्लिक करें।", + "incorrect_password": "पासवर्ड गलत है", + "gui_settings_individual_downloads_label": "विशिष्ट फाइलों के डाउनलोड को मंजूरी देने के लिए अचिन्हित करें", + "gui_settings_csp_header_disabled_option": "सामग्री सुरक्षा नियम हेडर को अक्षम करें" } diff --git a/share/locale/it.json b/share/locale/it.json index ad8bc508..7692ae6c 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "Stai ricevendo dei file, vuoi davvero terminare OnionShare?", "gui_quit_warning_quit": "Esci", "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_rate_limit": "Qualcuno ha tentato troppe volte di indovinare la tua password. 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", @@ -71,7 +71,7 @@ "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi cliccare per copiare il tuo HidServAuth.", "gui_settings_autoupdate_label": "Controlla se vi sono nuove versioni", - "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione", + "gui_settings_autoupdate_option": "Avvisami quando è disponibile una nuova versione", "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}", "gui_settings_autoupdate_timestamp_never": "Mai", "gui_settings_autoupdate_check_button": "Controlla se esiste una nuova versione", @@ -137,8 +137,8 @@ "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nAssicurati di essere connesso a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.", "gui_tor_connection_lost": "Disconnesso da Tor.", "gui_server_started_after_autostop_timer": "Il timer ad arresto automatico si è fermato prima dell'avvio del server. Si prega di fare una nuova condivisione.", - "gui_server_autostop_timer_expired": "Il timer di arresto automatico è già scaduto. Si prega di aggiornarlo per iniziare la condivisione.", - "share_via_onionshare": "Usa OnionShare", + "gui_server_autostop_timer_expired": "Il timer di arresto automatico è già scaduto. Si prega di modificarlo per iniziare la condivisione.", + "share_via_onionshare": "Condividi via OnionShare", "gui_connect_to_tor_for_onion_settings": "Connetti a Tor per vedere le impostazioni del servizio onion", "gui_use_legacy_v2_onions_checkbox": "Usa gli indirizzi legacy", "gui_save_private_key_checkbox": "Usa un indirizzo persistente", @@ -218,8 +218,8 @@ "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.", - "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Il tempo di arresto automatico non può essere uguale o precedente all'ora di avvio automatico. Si prega di aggiornarlo per iniziare la condivisione.", + "gui_server_autostart_timer_expired": "L'ora pianificata è già passata. Si prega di modificarla per iniziare la condivisione.", + "gui_autostop_timer_cant_be_earlier_than_autostart_timer": "Il tempo di arresto automatico non può essere uguale o precedente all'ora di avvio automatico. Si prega di modificarlo per iniziare la condivisione.", "gui_status_indicator_share_scheduled": "In programma…", "gui_status_indicator_receive_scheduled": "In programma…", "days_first_letter": "d", @@ -227,5 +227,11 @@ "minutes_first_letter": "m", "seconds_first_letter": "s", "incorrect_password": "Password non corretta", - "gui_settings_individual_downloads_label": "Disabilita per consentire il download di file singoli" + "gui_settings_individual_downloads_label": "Disabilita per consentire il download di file singoli", + "gui_website_url_description": "Chiunque, con questo indirizzo di OnionShare, può visitare il tuo sito web utilizzando il Browser Tor: ", + "gui_mode_website_button": "Pubblica sito web", + "gui_website_mode_no_files": "Nessun sito web condiviso al momento", + "history_requests_tooltip": "{} richieste web", + "gui_settings_csp_header_disabled_option": "Disabilita header dei criteri di sicurezza dei contenuti", + "gui_settings_website_label": "Impostazioni sito web" } diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index 3cf4be4e..40ace5a8 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -227,5 +227,6 @@ "days_first_letter": "d", "hours_first_letter": "h", "minutes_first_letter": "m", - "seconds_first_letter": "s" + "seconds_first_letter": "s", + "incorrect_password": "Senha incorreta" } -- cgit v1.2.3-54-g00ecf From 6b1f3af3b76c40ac33f85c050e53c407c62e98f7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 12 Oct 2019 21:50:33 -0700 Subject: Added new languages (Arabic, Dutch, Persian, Romanian, Serbian) and removed one (Finnish) --- onionshare/settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index a44e0c37..a28e3b04 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -64,13 +64,15 @@ class Settings(object): # Dictionary of available languages in this version of OnionShare, # mapped to the language name, in that language self.available_locales = { + 'ar': 'العربية', # Arabic #'bn': 'বাংলা', # Bengali (commented out because not at 90% translation) "ca": "Català", # Catalan "zh_Hant": "正體中文 (繁體)", # Traditional Chinese "zh_Hans": "中文 (简体)", # Simplified Chinese "da": "Dansk", # Danish + "nl": "Nederlands", # Dutch "en": "English", # English - "fi": "Suomi", # Finnish + # "fi": "Suomi", # Finnish (commented out because not at 90% translation) "fr": "Français", # French "de": "Deutsch", # German "el": "Ελληνικά", # Greek @@ -79,11 +81,13 @@ class Settings(object): "it": "Italiano", # Italian "ja": "日本語", # Japanese "nb": "Norsk Bokmål", # Norwegian Bokmål - #'fa': 'فارسی', # Persian (commented out because not at 90% translation) + 'fa': 'فارسی', # Persian "pl": "Polski", # Polish "pt_BR": "Português (Brasil)", # Portuguese Brazil "pt_PT": "Português (Portugal)", # Portuguese Portugal + "ro": "Română", # Romanian "ru": "Русский", # Russian + "sr_Latn": "Српски", # Serbian (latin) "es": "Español", # Spanish "sv": "Svenska", # Swedish "te": "తెలుగు", # Telugu -- cgit v1.2.3-54-g00ecf From aac23dd295dec68e42951b26115537abaeda7398 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 13 Oct 2019 09:38:39 -0700 Subject: Gitignore .vscode --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c7ad70de..1fcbaa99 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,6 @@ tags # virtualenv venv + +# other +.vscode \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 402c6e7e208456ee87828ae71a3ba5574b6186e7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 13 Oct 2019 09:39:17 -0700 Subject: Fix how Serbian (latin) is written --- onionshare/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index a28e3b04..25a28350 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -64,7 +64,7 @@ class Settings(object): # Dictionary of available languages in this version of OnionShare, # mapped to the language name, in that language self.available_locales = { - 'ar': 'العربية', # Arabic + "ar": "العربية", # Arabic #'bn': 'বাংলা', # Bengali (commented out because not at 90% translation) "ca": "Català", # Catalan "zh_Hant": "正體中文 (繁體)", # Traditional Chinese @@ -81,13 +81,13 @@ class Settings(object): "it": "Italiano", # Italian "ja": "日本語", # Japanese "nb": "Norsk Bokmål", # Norwegian Bokmål - 'fa': 'فارسی', # Persian + "fa": "فارسی", # Persian "pl": "Polski", # Polish "pt_BR": "Português (Brasil)", # Portuguese Brazil "pt_PT": "Português (Portugal)", # Portuguese Portugal "ro": "Română", # Romanian "ru": "Русский", # Russian - "sr_Latn": "Српски", # Serbian (latin) + "sr_Latn": "Srpska (latinica)", # Serbian (latin) "es": "Español", # Spanish "sv": "Svenska", # Swedish "te": "తెలుగు", # Telugu -- cgit v1.2.3-54-g00ecf From 8f1e09875be594b5713908bb9d92894a663b2210 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 13 Oct 2019 09:55:36 -0700 Subject: Initialize the flask app with a random static path, to avoid issue where /static doesn't work in website mode --- onionshare/web/web.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 4c4207e6..b5b805ec 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -68,6 +68,9 @@ class Web: self.app = Flask( __name__, static_folder=self.common.get_resource_path("static"), + static_url_path="/static_".format( + self.common.random_string(16) + ), # randomize static_url_path to avoid making /static unusable template_folder=self.common.get_resource_path("templates"), ) self.app.secret_key = self.common.random_string(8) -- cgit v1.2.3-54-g00ecf From 7d24bf9ffcd1e21e087431c21bfc658803a61ab6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 13 Oct 2019 10:06:39 -0700 Subject: Version bump to 2.2, and added translation changes to changelog --- CHANGELOG.md | 8 ++++++++ install/onionshare.nsi | 4 ++-- share/version.txt | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ca10992..da5f0c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ * Updated various dependencies, including Flask, Werkzeug, urllib3, requests, and PyQt5 * Updated Tor to 0.4.1.5 * Other minor bug fixes +* New translations: + * Arabic (العربية) + * Dutch (Nederlands) + * Persian (فارسی) + * Romanian (Română) + * Serbian latin (Srpska (latinica)) +* Removed translations with fewer than 90% of strings translated: + * Finnish (Suomi) ## 2.1 diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 52e9b526..42bd268b 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -3,10 +3,10 @@ !define ABOUTURL "https:\\onionshare.org\" # change these with each release -!define INSTALLSIZE 115186 +!define INSTALLSIZE 132423 !define VERSIONMAJOR 2 !define VERSIONMINOR 2 -!define VERSIONSTRING "2.2.dev1" +!define VERSIONSTRING "2.2" RequestExecutionLevel admin diff --git a/share/version.txt b/share/version.txt index 4ec9ad89..61618788 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.2.dev1 \ No newline at end of file +2.2 \ No newline at end of file -- cgit v1.2.3-54-g00ecf
    Filename