summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2019-10-13 14:35:40 -0400
committerGitHub <noreply@github.com>2019-10-13 14:35:40 -0400
commit896659c6963c14c4e047cff7f3d421977060ed23 (patch)
tree660468dd7478fb001ced211a4a33ae28a1b5f9a4
parentbbf6c02da645fcd4fc50db18dd341178ad4fa2e6 (diff)
parent7d24bf9ffcd1e21e087431c21bfc658803a61ab6 (diff)
downloadonionshare-2.2.tar.gz
onionshare-2.2.zip
Merge pull request #1048 from micahflee/developv2.2
Version 2.2
-rw-r--r--.circleci/config.yml2
-rw-r--r--.gitignore3
-rw-r--r--BUILD.md93
-rw-r--r--CHANGELOG.md64
-rw-r--r--MANIFEST.in6
-rw-r--r--README.md2
-rwxr-xr-xinstall/build_rpm.sh2
-rwxr-xr-xinstall/check_lacked_trans.py68
-rw-r--r--install/get-tor-osx.py83
-rw-r--r--install/get-tor-windows.py50
-rw-r--r--install/onionshare.nsi6
-rw-r--r--install/org.onionshare.OnionShare.appdata.xml (renamed from install/onionshare.appdata.xml)33
-rw-r--r--install/org.onionshare.OnionShare.desktop (renamed from install/onionshare.desktop)2
-rw-r--r--install/org.onionshare.OnionShare.svg2154
-rw-r--r--install/pyinstaller.spec2
-rw-r--r--install/requirements-tests.txt10
-rw-r--r--install/requirements.txt20
-rw-r--r--install/scripts/onionshare-nautilus.py34
-rw-r--r--onionshare/__init__.py247
-rw-r--r--onionshare/common.py256
-rw-r--r--onionshare/onion.py442
-rw-r--r--onionshare/onionshare.py20
-rw-r--r--onionshare/settings.py162
-rw-r--r--onionshare/strings.py9
-rw-r--r--onionshare/web/receive_mode.py365
-rw-r--r--onionshare/web/send_base_mode.py305
-rw-r--r--onionshare/web/share_mode.py324
-rw-r--r--onionshare/web/web.py343
-rw-r--r--onionshare/web/website_mode.py104
-rw-r--r--onionshare_gui/__init__.py51
-rw-r--r--onionshare_gui/mode/__init__.py161
-rw-r--r--onionshare_gui/mode/file_selection.py (renamed from onionshare_gui/mode/share_mode/file_selection.py)93
-rw-r--r--onionshare_gui/mode/history.py371
-rw-r--r--onionshare_gui/mode/receive_mode/__init__.py107
-rw-r--r--onionshare_gui/mode/share_mode/__init__.py92
-rw-r--r--onionshare_gui/mode/share_mode/threads.py21
-rw-r--r--onionshare_gui/mode/website_mode/__init__.py266
-rw-r--r--onionshare_gui/onionshare_gui.py444
-rw-r--r--onionshare_gui/server_status.py290
-rw-r--r--onionshare_gui/settings_dialog.py918
-rw-r--r--onionshare_gui/threads.py72
-rw-r--r--onionshare_gui/tor_connection_dialog.py43
-rw-r--r--onionshare_gui/update_checker.py98
-rw-r--r--onionshare_gui/widgets.py22
-rw-r--r--screenshots/onionshare-website-server.pngbin0 -> 148709 bytes
-rw-r--r--setup.py141
-rw-r--r--share/images/history_completed.png (renamed from share/images/share_completed.png)bin646 -> 646 bytes
-rw-r--r--share/images/history_completed_none.png (renamed from share/images/share_completed_none.png)bin437 -> 437 bytes
-rw-r--r--share/images/history_in_progress.png (renamed from share/images/share_in_progress.png)bin638 -> 638 bytes
-rw-r--r--share/images/history_in_progress_none.png (renamed from share/images/share_in_progress_none.png)bin412 -> 412 bytes
-rw-r--r--share/images/history_requests.pngbin0 -> 738 bytes
-rw-r--r--share/images/history_requests_none.pngbin0 -> 754 bytes
-rw-r--r--share/locale/ar.json288
-rw-r--r--share/locale/ca.json160
-rw-r--r--share/locale/cs.json46
-rw-r--r--share/locale/da.json29
-rw-r--r--share/locale/de.json44
-rw-r--r--share/locale/el.json168
-rw-r--r--share/locale/en.json24
-rw-r--r--share/locale/es.json176
-rw-r--r--share/locale/fa.json162
-rw-r--r--share/locale/fr.json57
-rw-r--r--share/locale/ga.json23
-rw-r--r--share/locale/hi.json98
-rw-r--r--share/locale/hu.json33
-rw-r--r--share/locale/id.json3
-rw-r--r--share/locale/is.json27
-rw-r--r--share/locale/it.json74
-rw-r--r--share/locale/ja.json20
-rw-r--r--share/locale/ms.json34
-rw-r--r--share/locale/nb.json29
-rw-r--r--share/locale/nl.json89
-rw-r--r--share/locale/pt_BR.json82
-rw-r--r--share/locale/pt_PT.json3
-rw-r--r--share/locale/ro.json275
-rw-r--r--share/locale/sl.json36
-rw-r--r--share/locale/sr_Latn.json183
-rw-r--r--share/locale/sv.json55
-rw-r--r--share/locale/sw.json175
-rw-r--r--share/locale/te.json2
-rw-r--r--share/locale/tr.json207
-rw-r--r--share/locale/uk.json22
-rw-r--r--share/locale/zh_Hans.json28
-rw-r--r--share/locale/zh_Hant.json20
-rw-r--r--share/static/css/style.css48
-rw-r--r--share/static/img/warning.pngbin804 -> 0 bytes
-rw-r--r--share/static/js/receive-noscript.js2
-rw-r--r--share/static/js/receive.js2
-rw-r--r--share/templates/401.html19
-rw-r--r--share/templates/403.html6
-rw-r--r--share/templates/404.html6
-rw-r--r--share/templates/405.html19
-rw-r--r--share/templates/denied.html2
-rw-r--r--share/templates/listing.html53
-rw-r--r--share/templates/receive.html28
-rw-r--r--share/templates/receive_noscript_xss.html35
-rw-r--r--share/templates/send.html41
-rw-r--r--share/templates/thankyou.html8
-rw-r--r--share/version.txt2
-rw-r--r--stdeb.cfg4
-rw-r--r--tests/GuiBaseTest.py293
-rw-r--r--tests/GuiReceiveTest.py155
-rw-r--r--tests/GuiShareTest.py273
-rw-r--r--tests/GuiWebsiteTest.py136
-rw-r--r--tests/SettingsGuiBaseTest.py158
-rw-r--r--tests/TorGuiBaseTest.py114
-rw-r--r--tests/TorGuiReceiveTest.py34
-rw-r--r--tests/TorGuiShareTest.py40
-rw-r--r--tests/conftest.py53
-rw-r--r--tests/local_onionshare_401_public_mode_skips_ratelimit_test.py (renamed from tests/local_onionshare_404_public_mode_skips_ratelimit_test.py)13
-rw-r--r--tests/local_onionshare_401_triggers_ratelimit_test.py (renamed from tests/local_onionshare_404_triggers_ratelimit_test.py)12
-rw-r--r--tests/local_onionshare_quitting_during_share_prompts_warning_test.py8
-rw-r--r--tests/local_onionshare_receive_mode_clear_all_button_test.py26
-rw-r--r--tests/local_onionshare_receive_mode_timer_test.py9
-rw-r--r--tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py10
-rw-r--r--tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py11
-rw-r--r--tests/local_onionshare_receive_mode_upload_public_mode_test.py11
-rw-r--r--tests/local_onionshare_receive_mode_upload_test.py10
-rw-r--r--tests/local_onionshare_settings_dialog_legacy_tor_test.py2
-rw-r--r--tests/local_onionshare_settings_dialog_no_tor_test.py2
-rw-r--r--tests/local_onionshare_settings_dialog_v3_tor_test.py2
-rw-r--r--tests/local_onionshare_share_mode_autostart_and_autostop_timer_mismatch_test.py4
-rw-r--r--tests/local_onionshare_share_mode_autostart_timer_test.py9
-rw-r--r--tests/local_onionshare_share_mode_autostart_timer_too_short_test.py13
-rw-r--r--tests/local_onionshare_share_mode_cancel_share_test.py8
-rw-r--r--tests/local_onionshare_share_mode_clear_all_button_test.py26
-rw-r--r--tests/local_onionshare_share_mode_download_public_mode_test.py8
-rw-r--r--tests/local_onionshare_share_mode_download_stay_open_test.py8
-rw-r--r--tests/local_onionshare_share_mode_download_test.py7
-rw-r--r--tests/local_onionshare_share_mode_individual_file_view_stay_open_test.py26
-rw-r--r--tests/local_onionshare_share_mode_individual_file_view_test.py26
-rw-r--r--tests/local_onionshare_share_mode_large_download_test.py7
-rw-r--r--tests/local_onionshare_share_mode_password_persistent_test.py (renamed from tests/local_onionshare_share_mode_slug_persistent_test.py)8
-rw-r--r--tests/local_onionshare_share_mode_timer_test.py9
-rw-r--r--tests/local_onionshare_share_mode_timer_too_short_test.py13
-rw-r--r--tests/local_onionshare_share_mode_unreadable_file_test.py7
-rw-r--r--tests/local_onionshare_website_mode_csp_enabled_test.py26
-rw-r--r--tests/local_onionshare_website_mode_test.py26
-rw-r--r--tests/onionshare_790_cancel_on_second_share_test.py7
-rw-r--r--tests/onionshare_receive_mode_upload_public_mode_test.py9
-rw-r--r--tests/onionshare_receive_mode_upload_test.py8
-rw-r--r--tests/onionshare_share_mode_cancel_share_test.py8
-rw-r--r--tests/onionshare_share_mode_download_public_mode_test.py8
-rw-r--r--tests/onionshare_share_mode_download_stay_open_test.py8
-rw-r--r--tests/onionshare_share_mode_download_test.py7
-rw-r--r--tests/onionshare_share_mode_persistent_test.py8
-rw-r--r--tests/onionshare_share_mode_stealth_test.py9
-rw-r--r--tests/onionshare_share_mode_timer_test.py9
-rw-r--r--tests/onionshare_share_mode_tor_connection_killed_test.py6
-rw-r--r--tests/onionshare_share_mode_v2_onion_test.py8
-rw-r--r--tests/test_helpers.py2
-rw-r--r--tests/test_onionshare.py13
-rw-r--r--tests/test_onionshare_common.py299
-rw-r--r--tests/test_onionshare_settings.py180
-rw-r--r--tests/test_onionshare_strings.py19
-rw-r--r--tests/test_onionshare_web.py150
156 files changed, 9938 insertions, 3771 deletions
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
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
diff --git a/BUILD.md b/BUILD.md
index c4a5c5e3..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-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 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:
@@ -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.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:
@@ -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 <h.goebel@goebel-consult.de>`. 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.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:
@@ -155,33 +113,31 @@ 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:
```
-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
```
@@ -198,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 <h.goebel@goebel-consult.de>`. 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:
@@ -346,7 +287,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 +341,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..da5f0c42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,34 @@
# OnionShare Changelog
+## 2.2
+
+* 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
+* 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
+* 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
* 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,15 +36,15 @@
* 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 (فارسی),
+ * Persian (فارسی)
## 2.0
@@ -35,31 +57,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 +89,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 +167,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 +182,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 +220,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
diff --git a/MANIFEST.in b/MANIFEST.in
index 71af3740..7dd8a881 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,8 +6,8 @@ include share/images/*
include share/locale/*
include share/templates/*
include share/static/*
-include install/onionshare.desktop
-include install/onionshare.appdata.xml
-include install/onionshare80.xpm
+include install/org.onionshare.OnionShare.desktop
+include install/org.onionshare.OnionShare.appdata.xml
+include install/org.onionshare.OnionShare.svg
include install/scripts/onionshare-nautilus.py
include tests/*.py
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/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/install/check_lacked_trans.py b/install/check_lacked_trans.py
index 010cdb7a..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,26 +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, '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:
@@ -81,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())
@@ -96,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 861dc935..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.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()))))
- 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 0f4ad8c3..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.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')
+ 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/onionshare.nsi b/install/onionshare.nsi
index 68213528..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 1
-!define VERSIONSTRING "2.1"
+!define VERSIONMINOR 2
+!define VERSIONSTRING "2.2"
RequestExecutionLevel admin
diff --git a/install/onionshare.appdata.xml b/install/org.onionshare.OnionShare.appdata.xml
index 2302a2e8..6ae1b5b6 100644
--- a/install/onionshare.appdata.xml
+++ b/install/org.onionshare.OnionShare.appdata.xml
@@ -1,27 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2018 Micah Lee <micah@micahflee.com> -->
-<component type="desktop">
- <id>onionshare.desktop</id>
+<component type="desktop-application">
+ <id>org.onionshare.OnionShare</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license>
<name>OnionShare</name>
<summary>Securely and anonymously share a file of any size</summary>
<description>
<p>
- 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 <em>not</em>
- require setting up a separate server or using a third party file-sharing service.
+ OnionShare lets you securely and anonymously send and receive files. It works by starting a web server,
+ making it accessible as a Tor onion service, and generating an unguessable web address so others can
+ download files from you, or upload files to you. It does <em>not</em> require setting up a separate server
+ or using a third party file-sharing service.
</p>
<p>
- If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor
- onion service to make them temporarily accessible over the internet. The receiving user just
- needs to open the web address in Tor Browser to download the files. If you want to receive files,
- OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to
- make it temporarily accessible over the internet. Other users can upload files to you from by
- loading the web address in Tor Browser.
+ If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor onion
+ service to make them temporarily accessible over the internet. The receiving user just needs to open the web
+ address in Tor Browser to download the files. If you want to receive files, OnionShare hosts an anonymous
+ dropbox directly on your computer and uses a Tor onion service to make it temporarily accessible over the
+ internet. Other users can upload files to you from by loading the web address in Tor Browser.
</p>
</description>
+ <launchable type="desktop-id">org.onionshare.OnionShare.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-server.png</image>
@@ -40,6 +40,13 @@
<caption>Uploading files to OnionShare user using Tor Browser</caption>
</screenshot>
</screenshots>
+ <url type="bugtracker">https://github.com/micahflee/onionshare/issues/</url>
+ <url type="help">https://github.com/micahflee/onionshare/wiki/</url>
<url type="homepage">https://onionshare.org/</url>
- <updatecontact>micah@micahflee.com</updatecontact>
+ <developer_name>Micah Lee</developer_name>
+ <update_contact>micah@micahflee.com</update_contact>
+ <content_rating type="oars-1.1" />
+ <releases>
+ <release type="stable" date="2019-05-07" version="2.1" />
+ </releases>
</component>
diff --git a/install/onionshare.desktop b/install/org.onionshare.OnionShare.desktop
index 697668db..536d73c6 100644
--- a/install/onionshare.desktop
+++ b/install/org.onionshare.OnionShare.desktop
@@ -7,7 +7,7 @@ Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk
Exec=/usr/bin/onionshare-gui
Terminal=false
Type=Application
-Icon=onionshare80
+Icon=org.onionshare.OnionShare
Categories=Network;FileTransfer;
Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting;
Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting;
diff --git a/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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+]>
+<svg version="1.1" id="OnionShare" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1024 1024"
+ style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#FFFFFF;}
+ .st1{fill:#4E0D4E;}
+</style>
+<switch>
+ <foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1">
+ <i:pgfRef xlink:href="#adobe_illustrator_pgf">
+ </i:pgfRef>
+ </foreignObject>
+ <g i:extraneous="self">
+ <g>
+ <path class="st0" d="M896,512c0-212.08-171.92-384-384-384c-112.14,0-213.06,48.08-283.26,124.74l130.35,130.35
+ C395.77,339.61,450.66,312,512,312c110.46,0,200,89.54,200,200h-88l176,176l176-176H896z"/>
+ <circle class="st1" cx="512" cy="512" r="512"/>
+ <path class="st0" d="M896,512c0-212.08-171.92-384-384-384c-112.14,0-213.06,48.08-283.26,124.74l130.35,130.35
+ C395.77,339.61,450.66,312,512,312c110.46,0,200,89.54,200,200h-88l176,176l176-176H896z"/>
+ <path class="st0" d="M128,512c0,212.08,171.92,384,384,384c112.14,0,213.06-48.08,283.26-124.74L664.91,640.91
+ C628.23,684.39,573.34,712,512,712c-110.46,0-200-89.54-200-200h88L224,336L48,512H128z"/>
+ </g>
+ </g>
+</switch>
+<i:pgf id="adobe_illustrator_pgf">
+ <![CDATA[
+ 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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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ fHpOYLVO0UqMky0+GJNnI+safSj4T7kCTYwe3JIqLCiwfr2yNIohcq8TNbXsWwam1EheVzh7KOcE
+xQeruOOyToEQf9JCRWVoivtockynn0DMAXqUrQ0kACUL9tRnZ0RzRzw5BrKoJpRnIXT1fi70GxI7
+8p3v/lQlYw3s263aVbSYUGrIwUoLsmYFWSML8L2wpD7izlkuGAcGybMCCihFjwDIh9/rBlfGyxwi
+D2IGpIsHDQcn/kSrGLwTjL1tvGF5YbDxXEECeuRLc6GTOduXHpeuykGw02qXX3NmzcbtV0ZeZhM7
+P+uKLN7A6Zw2BDxwQa88BdVK9kzSa7WsjIKaGH5OSGVWXeNiQ2oOlU9rhTCxKcHKYbCvyoxmCCyO
+VzMr5XPJ0qJ4M5RaU2vnfbQoHHuE9xA5hhk/JJ8iyrAA3hsJ3+6XewCcW8WloqSOjiEtkYuKPaB3
+iFx7Ps9PXFT8brra/QY5rfc6+9rYqNOZqFgzD4KagnUMIq0E2Og2bcUJyc5j1zVJShtt/53DnIQV
+/SeO2XsB5AuDXwiEbRswuv3prGHZcN0Q6JE+hHb/laV4vnlLA+HWvaQdljSKMmDdAdvUMQZGKhW8
+uU4GDDyMkn+X9LmReb3TocdeiJJCVcbxLOjNqDMzewlgvmlx9gBrfh3xoxAGZDFC/RHcwxsVQQns
+hypQ5uexdNtwRij2mZuA9HbPo/Zqv+kIC3mU38qPXNebcEb7I0ER3PWwFvFGwems3u2ImlCLHtGJ
+uYFDbpHcZHb8hNrjQnxW8u/aWSR1A9ArKGyOxXrPM6KyJjdh3T+HeC6qsLoMQQu6YdLdXy2pfuV3
+TXxNL3CO52uiDcjHjbc+MYWeEt3DIQjCq7ATpmWukAtwy6ITF9yW4QXiMEWUW+4jQhKc0gYFB9zk
+gCpHbu9ldS1xnI/BJz0bMQQPtQ5yPQ6edrededElJ8cyskFWDJSRupi8IToFRo24B1MH6vKy0miw
+dvhgOpceqWY/KGf1ryRvQ4GFChridlKlxk8Y0O3iQWkngweFTWR1BT8jBHqm7TgQH4AH6FVyK7w/
+CxsdyvV3SO0El+cca+qXwuX0zYNnea91Fzq1sOa6/fjWLnVFuoMSga9tg/mjAgjMNOutc45pgkOz
+qcVbnvfUCdGwqaXD+Z92rv5psw/JbPxG/mtNXsQTbDaUUpKNU1p3sb8ePv9+j5b7+M6hIgh8xb34
+HI3zKfcVV6dKCMyjRbjlxDXn3RcDCZeDBRQW4qNqx0+4bG8KOylJsxiSk/rp5NMS4KQo2MkHuwWT
+lWT6zLV9NaFEY5+veAJ5UyTxnCFlfqCY1e5MofFovgcUarI9TgXxZaZtCWhv6hlKa0ZezQ0PDxQL
+KBxplhd6NEG+zIJEkrBWlAJafVvh1LP6W9hZN55+LrhpYwSr/Rt90F9//Ur+ldPgP+/5/pfn0/96
+0scqk5KsTSoQff7RQvffb9kjtcKTQCkXBSKAw6Sj+P+6nVRmDdyct8aJd3DmoMtrQw+vQJFeZHB6
+9Z7jEcssBKha/b0op2IajHNpTxU5/G4XGimjYOmpvkyOFk6RM/uQlsOH+rX1FWKdFjTV85yyIhFU
+6/ZzLl+kx9rDxzMVkNOrven08bq1KUCLRfZskZjDxYgReJt4HAzkDUisd80IGSWNosG1weJCFSHB
+SZuIFIdWM3LgJMlQixxBx4qfosXXOdke7M1A76wbEPiDgf/Q4x+kA+3qmJ17orfvLxZrhqW4Dr1o
+64u6LxgM1mueQ68LoyNskMCiIZd4NthnfVBSKu74ikDmk4r3RKaw4wxCpRSuzRZ5hi7gcMTZfcHo
+A8Ebtn4WFEgakOocK2F9vx8NIqkDiNY1dn00D9O3Ug17eTbmyuECnU2nyNuamYgnyqNt6g102m/n
+BispZi5wfa8WLJyaR9Wkf530XppAAS2PQsFjYCCLwbi7PP3ywzA75nPSCF+JknbnUzrGP/0chUYW
+ANsbuNihOoELTJUgHJCtOAtVGTunCAUMpLAg+fzbfSCIQcHQOufm6yiIzEy1Lma/rLRMIRryOOFa
+AWiAa2VKeW65Wgo6T5mUkhG04pFuFDbvXEQFgvLl2XlrvNGh1CgjQV2hCWTQH4sQhRp0k4iYUj/s
+l+rTetOKJb2mDQbCjjBDWdtp6JnpJBnwib+EpF4+2BeNAJTvRRyBDyFgUKrkLSOhoQQAYcz26wbd
+5jglgTXpT9CGIGefKzMEUoJOtz+9Rw1xozXABw+yL3yAzhIiYNDEgUZQaeQD6a5Nw5b761BkUxD1
+wgN1ZK+6xHZV9npPQ6pBzhJL8e6WNAdHe/Is1A7ybWj0s7T7SnHghC0LvTQahtbTkHaHnIxoJG09
+RCPJXu8MqQGcLJTIVuh4jVMZiTG9QCmtVNrHxYBNFUNdfUuJRAQ+8SXqhq7Txt3MlDmwBDdBHTU1
+TXTnMToiRRzqa/IUQFqS4lxLnuU+wWy87ErAyDSKEIOV+DZqrFKBMjada7qSbXiSN1LEE0Ksy+mh
+d1I0pqfAplXzQL0HJ9DJHgVWEnQcHs4Ik+EA2qQkQgG/8Qu5pqkkmfvS0SXK8O7N7e7N1a/nobhQ
+FHE6C2U/FyVJh+bpV+T65X5QelU8uxCzwZNAxgDur9a44ER48A3S1mVtL10ypx8itoY+qCqUvBKl
+vBYiO1AA5uX0YyBNSJV3Xq4pXtnDNB+wqXV2iMMr7WhbRij3um9MOZk6Vzt5HRFs754WXCYZC8+s
+QuOvP9uCdhtIDvf4H3tlAgwKzlTQ336N3af6l8S5emdpr8kH7MJQs9+Lfo1u94rx6b6qmWjno2Vc
+9IlfIZOfAMAaIiXtHrsUr1LYMu+zpENYY01vLn7CAeIfDcnmZU+iIQV7EslECdUyDtGR0FRXodr8
+NFi6I8RMLZTxRVVYFaY3d421HNs1XXLg6IDRtWJQlm776uC7+Wu1Dmiy4u6ZXy7CA8C0m4hWXTt/
+Tgd12QsufqALL02RfG4ihJBPX/n6zIwSntzfstNd8ekRqAmGwRzMlJCbTsEzvpVn8dkKdPfWSa/6
+odJMKKudUAEPPCxD/SCa2M8Tq+zNy9ddlQYW+c26NiXE6D2EUV88cR2EPNB/RB68/O2k54ziu1Bj
+kvu9w+iHeP7RztejdE3N4YnCvK7baBrsaNrYpAZ9P+GfV+KH8M8vYPs8EuK7KJNCnoNPgPgqGy9H
+lG5OuXIkkTl9pqR2qmrUJDhzrrYQKHJJ5SqwVe0ZuwLgYmRYKmBkqJkBRPcEPTswMOcs3ZbHgpBi
+qKvtboQU/S8USXf2hbyMM63dWX/yZqupcNhZHQtU/oqegOoSlJ37+4Fu+sXNUWNR+7XfGPS58HGq
+vTpzPWIroktAmxxdAsE8uIFR8Y7dZgxlcraCBsgegabao9SRtvIFcbUK+4cjnj2D4ABE3DkWqcBl
+yg3Y8F0HOQiaOMiBUglmsCxt+lTig5kNmjTU2aJTyk+8aD2uqc5xjoGigKIwZExBl6DB8/D1CqRw
+hEwlVgY1IDf7h8BznrMQsBaL+ACs668P/VzOIUU9xKNjQIaKUjPqCspN2BC211q9EDugkQAu580I
+pdYBsSFdzwhS+1co65ddc1tKLrRuGNnENRN4cOKSyoK5UirsafJpWZWIlbuW+zW4JGJUBBAiNz6x
+jFJ4u0HyJ986kSc4KCJwfqYjdCEiYqqQs5Gfow2AROeqoW8LCDsj2rwyF3xPKyY5J2cv+cUJrk4u
+9F0JTBwjkD30zmXnsEH/gGHLdZQ4o7g6j1jVjQpm7aYMWnmQVmjUCD+eQu350StJx5LH+hoTOgvU
+kzthSZ1XvFBQUDM14V4ZtdK3rDUOERMh9dn9YPO2cZiaN31K/XIzv5cjll7xXKF+FzjLaZtkdRrb
+DoK8UwmmADXjFxhKMAWT4QgZ7VVX4OWIC5liC6jXN2hNfYPGV9S7MCSegxi5s+sUHSdApSkcsK4F
+LG3PFpKAhHbtajNHbexijrne9vO1lhpBwUH6092R+gvGk7IYSRLptoDLi5AJv5ceBjodz+2Lkh5/
+E49zgYn31IDy9uODxlsnE9j+AD6LyiWS7062Fno+6hoQb1h7LbS2bt68f1OQ9cyyofEcjVxarJXj
+QiIVXDFABmOxZFRELVO1Fexxg+lNsUAoVDRoBYdQrt2fRgWldhz4cBGCIq7SHYJ0NcrTF7EGvezs
++ZQZ0bBboqDwnO1hbdrjpdRFFPmH7/gft9pifwOVIadYKXcb4bihKyk4pF1wSImHorbGlShQqlsL
+MprvoBbTbBxiKCksovxcyU7W5NVHCNjaOFNZgXiM9ihYQsH8vBDtElQCD6CHdUSUf2qWEPGGn22h
+Sh7nn/ih6Dmcw7U6B89qAB/3KunJYUJ/h8OkBfNJ1LecgKvcyM02E+fa8719sFey58bJ74ha6GKq
+Ib/q7Wpc8zxwhgR8GrMp3nGCVuwjPsBOUYDvSYviTDxN2IrNNNl+8OpjfcX5WxERvchLIfEThFWU
+yN1hK9n+V6+ipSVm+izIvWOH5KF6PtiSqc8Xd/3lewJXqOMlIiElr7F161j4hANQxKd6ffB/KIoS
+dxotrGIAJdEAOyKJ+fW95MTFLDEeAYWRNfoGMECt7PV1AauNoRh+mnxgw3elL8K/92hiURNMeJoj
+gXC33nQBJxqCVJxoHq8Jwk9jr86fh5MtbAd38JYRsDNIgq4+KuVpevWayBhBgMinAhdrvWqJ2072
+QK4wA/SPsVJwraakwZ4IokOCIByVW832XL/SOd68W+1BaKfGs7apq3Sk9cExBASRcyfiJQ8DJGcL
+HUTHTnf1RdbYHSuHaaFqdy+k2pwih2d5L2e233OWKLbigRggfwDEQEO0VYJFatALSenmcyFxSnnl
+lSXJLeyG60e6JFSLc5iaUAdS+mhMKVq+PWmFilUj1cHvKFlYFbYSMj8x/nCU3kQmkPpbMedXiMVw
+jRix9SUCS0nZ1qjmXuksk+5cUqmUh6WYHHLKn1LSQCmas70MDQIoDUx0idsMyknAWeVUKCQN06qC
+pA40/t5LHRV9Jp/3nPSCi4DvvsVR7KJ+T1Cr58xViBz0a3dET1A4ejoQtAz03wFBy+baUIX7Igmr
+C3Q7nzWE6fU3ezVEDbncAx9PIpLHqlssaYiBERjqQvCoGj7Cp6eu9TSKvWtMHT5ZFwoehcjnPEBl
+uwQy4761pX/9CkiPK6HKwtPjmNBdByLVExCwNAS6NNMrwVydjsBv+ofrsEZsAk/uVxyFGASQgiHB
+EPufsmPjRX7t91DxbtgQcCXa+7MJpEbHxBF5Lhuj5fE7fQMEEUkI2z73pE4Eqgu93TCm200+J8FI
+DUU1uvM/7RZGiWhMsp90mAhenCNLVWDrMfR9AOWyEzEgh+PCrfl1BDbXZwTlhJ8QSBQSvgSdogji
+ewAjm9DwODsGWqyLpwMUnsQOTeQvslbF5sBDoZ8BeUp90GX5EUoQbjIpptGIwNMVYA==
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ H5N92WJDij42i1BZC4MRQL8RDV8LsCNWD/uOin3bd8nPFBlnzkVCaBeaZVL/QvE0HNdEkNjQUAP1
+D8uf2QleW67cRlRZE2ZhZCWRggdGPr4sQrXKcC4KWkl/FrsxcX0gSogWArEKoBwI/wDM9oK3YHGQ
+CQRISEtFRXXYi0JZqAcmPBKJ5ariEfYJZWdBNMyhEzAZR4/XPwpsEo8dSA2ictZLwsuk8qPO3QOt
+9JyRhhlhTNEBztHgpcJRqIm2L8DjEOFzwGixAW1al6RA0lCLG8mRoLNFGYSww9Z6cXHIgE/RjfRw
+jUArKPyCOGrQBlTOIipi33iyvwwMkHfZObbXxJnxzyoIfRVDlkOoE/I08hBMLnXKFqUBFMXkaqvE
+taWIBShQ4dL5/b0FbApuTHMzN6Zz5V8iUw8AeGKOJXt8T1DWSUDA0SkSiFJFZD1+Azv+KRv0tQGx
+gLAWB/cFFE8OSNSSwdTFoU/SH85omC2EeJBVwJkhQ1AL0QeYzYN/ySOIctlFDKpQx7NIp+nldMSZ
+E22PUo+RgN4UaUghD5z7NIqkChmr1yJH1AsOUnydYRAEqN8WpZqIcRFkp1wvnGh6b/9VL4ZzoRLa
+poQUArnpzGrF4QpbTogkIL/KtdS4bdzjGFXBrphkGoUthbkqkhVxjw8jahzQEnBHZ8bPOVp8KeIt
+avUgwQSoSGN0S6M8D85kggIuDLVWpTb4bAZH7Q8Smz3ZXsU9e6gQpDEQVx+lkQLRDWHywVuR64Nm
+xkkuqSaFsYEqQ99r59j3OIM8xoF/kBhHHDwyrOM1kbk2JgcpgtHXCAZOAxwtnQcupFng8gvZAUNF
+3yQ2RlFik/iD3DYrs1dHEKuqGyIGUXOuAKpRymxGAXSkD9Xfw1ihNGCGDJ5DhgxcGgVRIOxCMQT4
+lhYCgBkGoDqSogoiEswhj1zZxDGYLs9W9sAGhlLW0a7Zw+xk9toKxQcHGeEtVuni7oDce4OZHwQr
+zqGvDOtG4HBeelVcJhAgciRk1oIPNH5nVp7ZCiIyaYFQ8nCWOAm1FLBxE3zgQihBWeo9CV36FXSB
+ysGq3lq5UUJaar2gpqCVNsogIW8asC/T36l1kykgRyBatFvXbY9yjunRKS0fhSV5vqPALalLrFSP
+Tm03iLWu92JaJcs3yrWByRYjHbYVsdZpTYH7h7ZI9oezzWUJSMwYVCB57jylhKTJmt8kMjjqWsKY
+x9FWOJlk/ZK+Be6IBxt7jFitXsE4JXJ4QG+/c3Luc0HcG774jWXXptZ04roT2hChbBBLo8pmml5V
+13ZuibaWvJ+ueAC878k8hR1FgezhGr53WI9+R6/nXmthgtaD3YBzYdzZE202k8wE6gAoHyQBzdd5
+04+eQoptB5kKPaUd6EWyciIRyE4/Fzh0GMD6G9wiVXJgJCTXuldD/GLAvIxODHCgshudQLLIIxHJ
+wcaWGVKo1VxYi0kERvJFCYcRhkZHHIrS/d3+E7tJEfakYimUUMhpDKfS4Awnyh0LDSSBXioBIBve
+nujRzuiQgqERm5Zc0/CADzLT2ROwEqH1EsykPHd8knCOIqRtvTNKYPGazttoW9BATAkwomYn20QJ
+JIeUEfpxfF/yoSKu6NZB9il1Uo2Ll0A1FaGkA0Hbo5RefSGUpFgBjLAvOqBbi1pQZ0Acsx9B1vG3
+yyQSv4FTOSsKSs2Vat2LYDJEFS10v+ITKcIIIKDI2dSQwu0kpZJ88FQTmHDGEUoFYElMHaAfATnK
+chYqrIJY/gCvDWP9qEi5rasOVqmaSpsB7rX3b7oR2Rz7qIiTOwGvo3IjQUpinws6gHLoXhUBGB/F
+q1m8nICSQPHe4O4VS4cYDGGn6i9hvai2JXttCyEEdV94QhEYRgaI1dCr3I+E/goTSHb+4OUT0i3F
+eQnvLvx2Jddi8qFW1mV3W7SARdOxFjlIjWcn7jMOEwg/GyKkNf2SEne8lq1gLbIz4YhSCgDvUhOx
+BYCCzTVUXojOeSHg+dR6I8dN+dbJw69kqgmRGrTBouvOu5a1dFQ0KsumrXNDA5S9IJKKFYK/STgZ
+DWYs9d5KrspMtCNViB8Vn5IQ4fcBOG9wIA/3wcuIaFOtrhWxrPNaqF7DnxQcSVmsBwgQaWWImUwR
+RtZ9HD1WMNfZW4B/HojbqPhCU0yKLNn23sLLuE2rHPkxBDlERQYewy54iroyM+ESEewChpBKK5IA
+rfG+O091ghJv8Ffp68sqiYTDFVPChlQYEWOXUBEVfBDUIsqFigwJfKjOv3HfncoRW52chWioyX4i
+3zX3cvCMGoIG1GeYeSV6EcGAEi3Af9MS0QP0PXgtFbRzRKIbufmFq0DpEBm7+EnwpIDCjOeFgItn
+REYv5W4BSE1OTSpvKnTDBNgFAtyBfIGcbzw/A+QvHJs9fmctxRaJRBALqqbO6U9V6BEEZJm8SgC3
+bQu1qhzGbPkoEtREoYdUD1k7mELBU0Uq2bEQgoLIItoiDliAQ1QsCXFi4xp7R/4eEX0CV7QoEpyB
+4O91PHQ5pJF5OKS7scoexb/oJXLnpb4VOyjgr5Vmk2uxAHG9JtYTpa0nCn4GXlbEDhSkNe6N7USN
+TdXnWMJNjFTY/4TVBi9b69COTD1xiDJyP3MF1o7iAMsOkQ3TbhRAE5qpikTgfA7u6RSMAt7WEWIB
+rU9Z2iSWaymYMabsJ9tcAmAqvePTRfLVg5Q1gYTAUh2qS0eNSH6IhPmSCjYVHIGaE7VBfBXKxMeD
+JK664BiZdhgHufxpofz1QdyOsnoEDG76atOPwFECnRWkLLnZzpgmKHMYLDXZOMhV3NNR8iwnoPdb
+CGK4gCG27ZLAY3CNeQvOIXKYqIr2kSni5iTqG+TwRkst5DeLgbFzz0RUdrq9APJF/G564xFZSwBx
+rDxnd6vA/I0KTGpmAmo2Ylx+DfYRfg0OEiCTxNNQEpUaSgsUwCKZVI0KnGCCGPRFM+6G3IjhCWiF
+xt2dvaNXsyVc1I+Y7FHUlpUhDzFDVCz5ZEFwKVqaRsR1hCn7ILuoVA2MRqriGODZc74+1SijfsTK
+cAKkB9i3QJC0QpIlL4g8DcKixU1JpVw0iN6ughgSsKNEwKe8qe5Zjs1ah6wEHSrUQOt03MbecRtV
+s0b2eZGvnMKPqoLh2FcwHRjXpEaavsY+eri8J03S4VapcqeFg1GAN9equCpGKlhKXKUpY97kJDoN
+uCUo2PbFRI1xdIwt5di3FE6x5UslwSZIHzr3kBF6wcwDDEt4DFTREatG/aoPAvmsEW6EfibXMipM
+I6y8VNFgs/zzAtvPYrXBoS//VUMd4BL8EHh2nVkIQW+lagJqNVUHynlFPTZHOWBbauFvBYBw9Hzu
+4lHzroRcgTIAv6Sup/IkKvM/KrEfu6h4+CALLQNyh1jzlWR4mm3YOpGlAt6m8TcsDFe9OG06ude5
+n9efD4IWJKJdS0WJEZPvKDfyUPcbeL3MMBm7ve8kodkQym3jqHiGinAKC3Xwn8scaEDHYr3iqQfn
+hkwDnSMk6orpGx4yB+pR1o74jtuucnaqVJ8fxLqgqT3G644DzymPvRQ0eQJL66QGrcrXSUWF57tm
+JWrMHeIRz3Il9MU9CPSyIBpt/Q3UsZI5md0fI3y1QYkhvaMQEWxAlhHygnVYypeo5cdZK147T6VZ
+EgcyGUktjUykDZ6sTqYFv+5FHAKiBAJGMFLogEGkNvL4Ub7YAzXWLimGajVwC/Yl2s4g0B2YmcOy
+FqUWz7Kmivek9S8UzW4hzxVFNzAUQV3EU18fYJPRy+MOc2kQOzJF7z1KhuraYFbqlKoV3LbjiQJK
+6KgmT0uVH94ULJ4ctJ+Cn6zYmXICYTnhPDQDgFig5IOiog3oa9lznBTjwfjSYVxqzh6q/5K/qifB
+RqgXndg2VSPkL5xbYaKgBkU1iG10NmgWjJxxSI4f622klilojwk4DELpYX6zoP7FmCLQBpNoJjGY
+3CaoRSc1BWNR2WrUmMWkBmXE7evHOjLiV20rz7Ao1YGfiPjsSaqOMkJL3zZOKEzOEr5jlfNj7Q6d
++5RHyxV7X3YVXJkwpRNyBszNfiRPo8ouWscsIjykFiBQ9MIHVIZXW7PZBETvqzZRTwlsa9uO+CZX
+8sCcl7urvNzFMyoc8APGlN6XsHeRnIucKvKRci4YwqSpcC5mslliu3ySXMdAFuMyY7viHcPB2nUK
+5Q0eVIvkpNTVBYc3K0OwG14bKtoU4T5E0VSyBsl3orSyrzjPaG2CjIDGghYCiYXXPnSOTQGXBo6M
+QbXobKNBJWTo+aO7AxspKu+vFWsxVjtICXgqoXtQJkAifo0Q7xnjLLoWs4DIDw9q4b6dlnSOTi3I
+81GwoRsxGhRBUYUy2WhRcQyq07HhbPqSJ5wlKnNgvCAC1fQiyiAgFzw/ovfoPXcAqfjaPTx1xwNX
+eM+ICSkyIlJI5yLtnYs01+CHkN8gOcieaqTcdv1U0i6KxURLbuRYppXilbjnSCDTdqE+Bb6lQHiI
+ZGmOIkx5mW0cFZgrJoiz4Lxt+YO3yGoZQsWHKcSPCPi3ozI0jKgPtpzlNqNCrvVSqiAPPLEzyTIQ
+tHpBZwVZWax64gMR6LgKYuPM16R+VwUbz51Xvdo6I8qNOwY/A7BvHLJMihAko2CfFJymdJejRJ6D
+MffKC7vIGWiKAsuEnkQLWwPNrpRHuUSUFSEbMAYVwenBLmVbxWFWSVgK68I+jNKpI7n34HnASCWj
+WbAH1GcVx+ZyvRp+iMYhO6JPqfwQ0qOUSWp6lD1wfFJwgHrKjwcpg50XrjTkqUeHJHKThvNLKDWg
+uVGBIqbzqrXg51S1dXIicfggoBW0hej5sIPASWUcKArWlFxGWCuCw1QOt+K5x3zQmVFYnWNib6op
+Wn11ppdKHmZSCvgN+eE8PcuGk2g4LURr1GQlrnmxA2cj6arFAX9RUuVPbfCmJIfSUyIDxyzQM62Q
+IsgCMdUmDc616+XvDn6AJix4BJNj3TL/hJoOJZ0hp6SsRs9SAIeD4A8tCD1JyW4FyjBgy1Q4GoxX
+W67K30OD92M2pDH+DIQeeAMUxWSSA4XVpPt0civ6e9I+gJUna7XzbDMYmIocjp7hWJxsi+/lDSCJ
+NC2z57paXgOJX3agI8WKgH5sW59A4twEjYIbjhHXbl0XAh13O1j05PB34N0cpOYDxMYJ4dap6tJx
+ITT1vpHIc5S7a5CDR/EzlD5+4mmU4AULxJTyjejM7Z4bKPwc1dIAuRNXCEDkDebOdaScnLvW7D40
+bgB4HX1KGcRoTeQeiu29VQvy5IsnRysgRDltcXcPJhfO/SaP5VExOn+lSbq2qxBFEsmQIypLuk+y
+kZWuhxaMj7kVtiFZze5ki2POJOoL2x6gm5rYKm6aoHJL77WKd0kUBOhYJRuBBQ9CEsuNEhvVgeBs
+j+7mE7sdIAXgS41VJ3JlBdEjd2pFWhS3NSGfFUoxJU8IXj3ZLbpPTLpPkVkSshv2GA==
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+OHhWgu0m4WXzxsIxgspBsTiymKSzEnMKXiEu/LVgGwWp888aofCEHYg7u5r4EyXqXMUEiKDzOnjt
+INjVcWcpAiJXo8n0gs3bwoVI1k/XUTFfQxQEOMilsXGLNewrqACvemsV6hBEaCbnAQOIrom2As9n
+cXbNCKpTJrBKTwA1l+sDOdCPB1nvqqidda0gXFsn3LIvOnmPuW/BT4rRnzznAPoCEewovIFrSbtR
+riwPSYj9HRDMUVuFnrHuyCTGK6IQSThsQjjSBCLywHdFw4vy/gsmLoqQ67bY3HujmR+EEC1qcQUv
+AtziNUzu4oFYniNEi7bbBxgAkl6uOSgXhRbZVRb4Ii3RloPqv3UgOKO4Nox8LqLLIaM0LPVuUmbR
+UYMqi0F1hnImAHalNN7RrwgDLL4beDndABfptCyVrFR2u0XjqYRChgwCI66rrLR1DRFR7b2+XkR4
+pBMCooAdH3vCfYROmhFg1AE6O3qEsSMZqcK7xl3nSvsInQub0VRpwE3KNelCCPmCkw3yahIzq3p5
+UxPK4BNg28EEB8PunXNyn+vg3oCh761m9uZffHX05ujg4ujlN/aIW2prb3mDtPjtn04v1m/HA8/O
+dcuLY5p+87ef3xz5XW9tzT8H354c+Wu/ODv85n8f/ey/yc3it18dHZzc8KvfHb/+5suj88Oj04u1
+9rc948vzox+Pj36yR5y8ndj0q7Of3t46bmsvYW99fPR29Q6kOEOaBukssd9be/C3Y+vzfx6/vPi+
+PoakKtX7BIzE8v4f/s+j4+++v7jrBV8cvbr4xlbEH87PTu9s/LezN1faXlkJv3/2q8//1H7z+9OX
+9Zd8znz+5i9np1/aUr6w1by355efH31nd1v74tlf3vBN7988P3/39vvxPr/+y9FPi/phMTS/edYs
+Prf//8dPz5TT8e5ZWHz+5pkKsvW///jZPvwv++P/2aWfFmnx58X/+b/N4iW/+erZHkHUhf7zevkB
+TlFg315whQ9U9dx62X7yYv0+L56d8iq8w1fPAIyE5ybZQU38j2JhcuNJMU7CEiJfhOATuXMgEqlw
+fF+kVLFLKo3LtGnXUdjNWFUy4HriyaLZr5XnNfmkXfzj85t74F1VGXu+1LGvr7QfPIHuavt6ubZf
+fRG7fRksry/fZrx87f5QoZLAeOX+4+Vr9xcVeLx2//Ey7f/u68YW2qVVM2kxhSmLKbx3MVGhTIKv
+3hqU5uW71suEIAlXv6g9y7dcG3/+4ua7bt8iS/vYlGu9UcejaLKHq51sVXqd1i57z+GL9AVQTK1Z
+rIajVdotJvWlIcprawrORm4oc7+/9vh6WY9Hh8R0W13mnVilmXeK2bNQL10ETjwJDO3S5dWEXHkP
+yrMp3r3yHuNlfw98nsN6a+5NWcdAVi96C7Rgly5efo/l5dvfA+V+3DNr71Ev+3tk9P31yz5I2uX6
+C6qdyxcvvcfq8pX3iCsxub4fVpfH27y4chmGyKBlEWXjp/oeQWO01g+/9sWVTvvVDxAIfz89PXh9
+9HKhq4iGRXtdKtx6uoTm/ccLZmJRxSm7ae3wuPbFeAEkiH7tgLnt+urg8Re0f/5rO4SDCUkt1Z4s
+c4AYMinYywuZIGpX9wll430IHTytpbveqK6q6/fKAiZzWNR0270uNxp3ytVm7DpKMZ1+8vWNz7vc
+5ta38sLpDpLe2N32Vpcb3fpWUEoVnghq0G2vdbkR92JFvm+BkaVUzF5/fXWFjV+c3Lxum0vzcnOj
+a3f6/tl/2hr9/56NWuIWLlMBF1AjAstF/55leqlRnS6KvUlmGCdR9QfFmhEmvu1eVw==
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ OSkbxNGlyZcDN1QwiOarUf9apAhByPGj2Wvjd8zWwduj9Gat124vIln9SCVfqaQjLEv+8cdhEENA
+V/97coL7X5MTyP7wOOgvlBP/CXt0Mq5QlmHoXJM+9f3+qW5QRBP5B0v+KixhLY/5vwxJxL/UQ/bv
+3WN5klv8P+e56b9VXlqtp53Sv8pp81dbSBNy2kfgKSO6o34dNbMRqdlApUVTK2bMf+5sZPkQa/+F
+LuS/ARDkzlpVVUQBTGKeJ84oRsUaMwxPnFbkZl9VlUUFnesyhzd+MzSUKNn/h1rCL/W3/68xu3yn
+1Y6V3iu11u8/3O6H242uu4wKoDEIkrouHBg8GX+Tkygi+ZtYyTDjySg0RB4r/NGuAM/Iaq+tjha7
+1Dpd3Xz/J4cku9VGx+Gt+E3r9PTgOVlitdupOoLp/a52WjrMNvrkPd5470X/wezovdX5P+KpwNRu
+w/HWrpDORNN9Uat0Ph3dtyv1jn38l0az9rebI38jz0YU/qo3K5ivHct89yv/Ks79E1z8x7tx8c5W
+huUZmRcEAe+4w8gHixdCIZMRJJHeZcGkOFeWN5PiMX5hRQxRgWPtyeFB4UNO/N9zCf+EDv+boUO8
+cBulMonP8+RCBFEUeFEFeiI3ROoE5CIOJiWY9yogbaEFpDooKiiuMgoB/UQGPSKDksirP5HBvyEy
++L8oAn6igj/xHk+TcELxHlb4iQr+YIkNSwxm7HPJDcvpYWQ3uuDVbP90hPmJEEbkCpwtb2riTOHf
+Cxee+YmcjuyYKoOO+BnLNvpa7Fj7p18Q+A+wEnlBUAURSy6LokQu5GMVAQsJKDLDcaJqsF3JbRLK
+9J49Rrce0cui2G/c4wZUbBYQgR3tvr0I9P53AE+QRQaL8/FgGzMqsR14rEalipLMChJPri4EYEgy
+j3UnOI6jIsyCJF7xjp4tuxMLbzNUHNcb8r6w5P8zsCS1fCS8ppslhV8BNWH9KgMQYADAquHvE9yI
+KNEDy7p7AmHuOMjMSL7AE/4zwJNUQVEYEQDCq3gZO55vlXlWZGUsB4B1PijweDtWkYC3jYTRAYSE
+rTrR0w96Ykr8z8BPViWOYYAPAuYwWEgL6w+oQITwA4/eMh1+rIPnIZxFO/Q4JF05xFdmwE+eDPQm
+bkD8HfBXZF4SRQ7vcKVcEpRoBmQSQJXjWIYxnLWwLy7GOCiIfPH1l2Drr/VS8/8u2/O/ijrKL2J1
+vxZ5OPEHe/4J2APw/hcizy/FnZ/zRj82u2Wz38GvlX6j92Cz1kv1r3bDtNapd8URv8HjReeTPrMU
+cEO8GwysfmbpFHhez7Y2Urmg0KxZdQtCix+cVhpar6eRFZ6+THhNiTt7JaCHRQLB6/+bcv8+dVod
+BHHi6r3e0+izSZ8Oo9cTGOOmVJXnFAlPdzA8x1LHF2u2Pd/Jxs61mj4TRmIVMLJFUQbFXSWVjFIw
+I5gCVg5QQffmYobzzHj7Rms0Wr/rHUicJCosq3IMK0kMF7M8bNYLOx1Na+rtRVZhYEE8lhwGm4ra
+ApyINSBVgcFS6rwJOOP93J8V43VF4RVBQSOWlVSGGFkS9MSIKi+BpFGMQBRjex1dWPrrAGBYMSew
+MG2ZnmcBIIGdjPJIFK2pW28fVd60Zq+id8BKWM6SF2Xdu6BiVT1eZRhWESRS81nlZEUUQegpHJbK
+RccFJvNznAL7CbMmc7ZZhTAwgsBmZoPFQwwk59kbMp8NVuVjOxvwSnZDEI09ZDgsAyoyHCnLhwjA
+yRIimCyLjEKnyTEywAjmDvOUqTdFAauM43GXcd9g4vYZcPROYpeNywoOF4E+KQ6s3Z0NToFJ8ZKJ
+hphlzTOAFzwPw5MREKcZAesa8qyqzwK0A0YmxX8FJAR1oBwFPwga71mAhbizoTK2WWD/HFYml3hG
+AhOUWJmYAM4ruOsMOdKkAq1gjVQRyEzgWJ0EHB44fG/gKBTr3CFzFggLAD1OgzGBwQtY9RDWTuGO
+mAc2LvQgsSq8LFN3lmpV3UBdCLmAw6eAuhJnbwFalT6siIuH/mBYUTVWj9XbWQXfBdzQiRtYgqIo
+SDQcsBp9WIefh7Ry/I/QqBM7jGE53HmebD1v0IiiyKLAqOiLw+3RVwZtgUKQo8l0Km7PHMLEBgDO
+f1SW4Bsn2EeFjQWmwki8KgH3EghysQBhQZR4DhkL5YyMwALkVVnhKAf1ctsMbDXn8EMY02AFBmGu
+4uIl3mTtEkwB+CHLCLwOXhV4FAObT4pv0iosDKOyoiiwMKRI3J0uRxuW37QTH6I958IFOguR4JvC
+wiRkweCSkgA0RYvxSgpPuQnsIycoEnAaidSxTDE8rEgFhijCHFjdoTrgluHsS2cNfCMrFxDbJAPb
+VIQ/sGZBYURgcIj0PEgRQD30pIkMTzFNhNUIEjpyCQPlEC6MCggDaMEp1EBg3Hjg8KMxgn0SWNZy
+QzR4IfQB81QFGBaeUPiDlOUYTpIw+UShP7ED+D0IbreZQseEPTXAzbLmqCguBZSgDJa8JPkwjMwD
+68OyrFhlHbsDJsiClONYjsgpwpwQXQA+AnGVefqvJf+lSygGWFE2RLEq4/W9IEUV4KWUUQLCARsG
+k4whegZd/EB5H9cWewQgyKAC5TG4dmAeBq7JApC7Cu15Qbf7ANbA54HuRIXnjaA/QMDB3LwiH7a1
+sTyuTVEM5AKSAgNTVVgkLv0gncjLQMiYYESQhOGBtkHgqDA4cBkCYUHFO3NEFL2EjbOK40Qe/qK6
+YwhuGtAXL8G0BAJxga6daEUp5OJ4cpYXWZagKt4gAgqBIqgAEEJ7zjt3cF5u/ObdnmLK5wibQ4GG
+yeAGfwG2AcsBCSVjkU3CWkF9U3ATGZbePwJdYEVg+C+IWspwQP8A2gMK5YmwSQkeKgaGDpxyT2d0
+DHJ5gBau3cB5IDJQxICceaBmosOA2iEqAkhVELY8T9YJ+M6iAwBgodA8MSBLmBnwAx5lh6dcHZD3
+ohNhDe4rG5oQDKFPCsQsCFrsgEMpgyOCnJdIqSyFxJjIfsDieAUd+sCeOEJjroOb+J7b+UxHBbra
+2cAtUU1xBxgkYbwAJgfijQwKHEeU0O/BImpQVAThBqgLAp+h0RbFJci90MIFCkP8cQZeqJaGocqg
+saO4h2XyZEgOGDyPUTNQdwTqePdS8lxqDMbi7KxPdSh5PMFGjjX0aQArqLmKLImkgC+KCww1AZ8H
+8PLEkIHvwNxh0iB8OImyOXmADCWHqkl+MVVelbAdhTA7c6dV4GxYOx6UNqqhwTRULHiGOyiQ8mci
+fMJDurALoBWQXQBBDOiBaAKKJjnJS9vZlQDCrF23ZOmbj8o3MBTC/GVT8QCCl4H7U3mK/fEgfWGC
+KhIiOUiP0ThQ/oD8QDQRJsOBdQaCGA8Iw0K85A2DBUBcehnvqfmCIELNF0lBMWclw2pgQggAnkIY
+VB+YKyuAyFGIeJB4GewWbAT4SnRtvPFLBZIGVGLpJUn8AGy4AeHsaGFwcQVhRWSFJJrYIol4/lmA
+f0VackvBFAO8wEwGrYfsB9AdFpWGhUoiS5kIFpHGgsggYHnOU0EbjFwK7pJ1Oi4h+QKACU81NBfg
+RrgPgBsq2sGEfrH2NwMwVCSiRgDfxcvY0Mkp02g0bC8W3gN8kySi7UgD+ye4pChC04PNI9OASRF1
+VuGMOYGWxAPzgJ+BhVJOKYAqAfircAoJ4gIHATEAEhaIDzRcoqiCNAJo4l4Ad5Y81Vt+wMhwK592
+9ZZot6aKAcJX4jGfBCiaxPJSoGmKHNYZB5YqEaGuUh0HyAH0XgI7nrgXeIHCQfSgL1Ru3fTvbeWx
+RPcndo5p5QFeA/1xsD0KgJkoFqj7gyogAjZx1CaGhbGw5TBx3QIe3OPBdJfBE/2MJ3NUcQcpVhlI
+BcY3WnrQnwrcSCHaDkIXAAY7S6PrIJtgtSxoigqdAWpuKDUEGI4jbbgBSLltIrTznIoVnRPBKp6o
+UZRd00NSxA+1EqMuI5uXyutwlLOx1UexUtNi5VYspzXRVUr9O2F9eb5k9Xna77QbMNZJp9J800J7
+czXHfoZJ0DfbUkUusdOp/NnFQ/2nb7/AP0d1WhSdphoH5IM59JICpglHb6MDtswixoA+odLb6ID2
+QaGXVbx9jkhNkFyICsAfwaYiqowDQSP9ootzxA4OMdbU5CVV4DFYA0yON5gxDIiWDDoMiGoJ2Api
+C1oA6olEpIK0EEVGFlmYJEdtWfNf3UJFs0FESSAaegOmZIgisE6FoxftiaC4w2pl+BHko0pVUQ6o
+GOwy1B3JKRYJA0yo1ajwkbDTgSMD4b/QOcmEi6CTRjYlJh6YYdFykmUQijgFFu0JdFSCpUE1CrDT
+wHaUgd2IMpH1HAP0CGIUfXvUcBfMf+3aM/5FmWPwd5BtYNGASYSWAjETgN0DIYMqDfxdkb1/ATgL
+OEkFYU24+eCpifBf9HlxRBYStx38NdgWSBDcHGwNthXROWCaMmEtYF+RsrIiD2YFOh3BmNcdGqjo
+8Oh2lImSIJn/OlQnkXcqcUADIjoEgakqVAzgEBJDnAii/ossg30J9o3KSKKuPg4eFQn9RZ+HTJVJ
+YjbIBq8G0pbQAQhbwlC1kEVbCpQiUI8Yyr2B8nkAhCiAGOPoYb6UYv6rExUjEM0cJTn8NdxwCl6U
+gQ5OGUUOUUNZBvQ/sJuBIImww7K8ighQFcHCUHQn5MCawn8xdHWGuOWQ43CGFxLsY5mRQXagi4Da
+iZwAo4KAAgqj2+j6AQcY0B/CfzE8ocRMERDv4a/BaY9aL/WGFsu1Gi0UAq1+22S7IqjHyOHQfBao
+cSIhUQJqYI1WiaNuqwF9UBzw5kge9956eXOAURNHCqEBAytBAVXAmAGNRfdS40Um8BPMCrmTcQJM
+GNBYBjRj93UnBuHJhmrMmZYUei9VUBXQUgVRQ6gMhAieigSVS3deAotUYQx0n7F+DuPB20g93YaG
+w5igKl35adYMsBlxtGHKih/Wuz17BNJ588LgeWKv1PfBm7YdNzgNhATJmKOURGcZ+qj059dLq4Hd
+/H9TiUyn0/o9dqi94rCn760eqAeJy3pNa5FK8N16FX+v9LugbyRKvVYbvzYqf5KnGNxLXLZgRVrs
+kCxY/4JrwfZapVN9X9QHh+nah/Zfgg7kfKsKfTV7+UqvMhV/ShvfY6vkmy02Dd8Td0dard7/ip1r
+XZiDHq006yjABNiY2UFJ6/XbJN+upzVBLzvtaJiEidFjjEXHn1jrMgeYTuxI677Hzitd0OHq/0ci
+nrZh6Bsg0+xvnPR77X4v5B0zRc5jcoeg4vUrb1rstNXut2n7NGxW5U/P5Sc5ngPdWkTzScJYZowD
+xgfWGAuqNip0eo7eaQsAfa41yq3zk04dgAzvgh7a6taxN/KUo4MlMUJp61KMASGxI/XI6kA1AHSa
+Oae/JE6a0AaopYP489YazH60tsTKXCT5kEf6HRvxJ3RwA9MEaSZzPJr9AmhlGHMF1Q==
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ YM7iBJibvENJzXOLhk+UMPq07lSjh5k9K7G6GVWEqqyhG+/FG4VAwCGa+mOaX1KX8Xa5U//C0qVX
+0fMcfXAlnO/ktW6v3tSvmRya5djePo6aDGe8e06TTv+0XhyOkA7NvNag9emIiFlEbrYatIGnNA8f
+d2HwNjTXC0FXHgbRiEsEWrgVKDdxmEpPI9fENCt197VrAYRTHrjJ3eclAriLrkaAULZlUAeLV1yP
+U7qynllkRvtS/wV3tNXsnSMGRdMZPPWUQAIeuCIoCF18ZKgdbbxT44I0KjfD8czAi36ndRSxatM7
+sXwr7GCu0qZFXOtB2YJuFhSh6Skwtz0rYTWIho07gKM2zTqze32nAKzENtlgfk7pDXhI1EnYBXb5
+HcAbw0rUvXctpl//EOsal0f9/q41Y11651SlGbMr/4hJsUoXf7aScoxL3lPk1q4e6dzZ2Z+tfqwN
+ex8DuaXRjSRD0+7eKvUm3mljG2glBoOZrzZh/rFeC7uoarE6uQCnEmtU/sRCwZU2vZQQZWK3X33H
+6e1hUKn+1rS6oaM1AUR9mF3r1Rq+3o31m59NQPNUZGlR7dTbwfcAmEwLCOBKe8GbqMK3lV6LXzdy
+0YKauu4hDTWssoEZ0cbSgLe221ow/xYIDPS7aKJiXzQpV+pVmjXzei+wWeFHqiTlTHPS7MV1/dJR
+C/H1Cbjku+8VTXix8+AlQu4uutqb7RJNez1u13VZ7UrTliftk3jXtN2ZYF5aJAYl3Rkjli53UKpQ
++dKrD5Qwh+eEBsmVwXhnqHN/4XG+nHcmocJvhSbgtwcQoLN+r3Wgddz1ueFJWQP+P3jpLDy5rrQ9
+J10erOQCT07fXgfmA/tbrQ+mpMMTcs2qoyNzDV8vwNCIaBkcBC+xOmkOXqmrz8x5j+zaoLdjDY8a
+qE+FZs0sP1HpoY8Y8PkpC9peE0cwnk0RPLf/4pk+uLwpPsvLW5cvaSa9fJRc3nrv8fiJE9bPVnnz
+wZn5iTxY47fKvWz+Vd353J0936jkX5mbTfMpt7xxLr1PL/K7G9PJ9ML5VHx6efNzfXrx+FadXnmv
+w6Pn19T0cn+1NL1ydJ2fTjJHHJPeuEmQ4cXp3OKZ0OW6RzC5/KewdfK8yWcVXpFupa/b9eRzsUWi
+ptZTZvdJy03FO53NjZfMSvt4f/tA7W4qu+tXqWLrVrgsdO5vmfxt8aZc3MhsVNmljNxk0ifaBZ6x
+4Zj90/Mcs/ssprnn6d0TdmXh/dI+EWHlHD9l4bX7DIFY/nNT3Z37cE2gO/2wkOcWNmfyriapdFfZ
+4bZmdx/h606Dqc3f5A14HnY7nbXuZee+oRwwaaFEQUDSOo1ulZ34Nb99lkjAi2wTp3JiQbnzkFva
+TvGHSn95c2d63gIbGVRonZeafoM+AsQent5L1rCOQTeexe+lNuM56KN0e+o36O5s4yV9aw0KELMN
+uz591529vDn1HvRsY2FzNXe37zXo8lpVWPcZVHyfii/NbQlH3msVbq6ZIrN05DnoTLEmzcnni8de
+gzLF8lXeGhT2xT6sNBs/LWUyfoM+Mzuztxfeg+4ktxf2XlLXXoPCvtx/VCR92NOFBdeu8mu9Ro0M
+Cij5UnDu6k3ngds/xkEXB/c0dSdsHOWWYVChNRUfQKXH9aLvoGLjZKbnN2il8zgfv/QadCoO7xar
+UnNB4smw7kG7mQfeb9BdoXV30/IedH0m0V2Q5ztkUMQxx7Cd/jMbX0xs3T14Dbq8vnbut1Jpdva7
+fyt7DYocRri5Y4oH66eeAJ4pfqlx4Th/5jUoU2zV930HnT860XbIoFPxgbUKNxqzczZ7672rx1dM
+/DN9WYJB5bZr0O7CzpMB3ptkwhp0Kk6GFb8/S+d0rYX7z6Jj0NtN5nBP5XHQpYGV7n5+y0J2S/Aa
+lDn8etXIoMgtHcOSQZWjwuOL36CPzEnjpOQ96MHC7UEq1eq4BoVRyLClI172WisZ9HBHOBB8Br0T
+mPJeadFn0H6vdLhzJ0/FPdd6yfTqvoOWteP0u9+ge8zl48Kma1AYhQ57qC5cJqaPtzwHvUpezvsO
+epVJrM36DVpnblc2gPN7r/V4T/uYri4lPAd9eJk58h30c7WxsO8aFEehw95vMY+PGcF70JPlmfYS
+sHfPQZ/564TvoDM3j0tpIpE91ro+Pd3pFE8/cdDlAaI54baXZ5T1Kgy6+u3mSX22eakP+qkukUF1
+uU+G/X6SvzpkUJD2iV0HgBdPl1dbvQIOujJIqeV0/KR+dA6DbnfdKy0ctxmgSjpsb2vZxQrjzNwT
+JRruobe672QPZ0yhcFHEQVODjDA+vaAl5BsYtNgng4IUs1jhRjp5RQfdYg+SrkFnyvtlyh74rYvD
+Q/ugXK85zeV6VRyUGVjpJX8/Ff+4zq8vwbD7024AdzqFZUOqnn65nk5z6t6r/1O+upe0ng5IseXN
+jVbD921gvfMd36dMYb22YjwtNQc5zOH27p3x/HKAwR+el58Cntaeq/5Pj6Ybb+buez0X5lL+T0/6
+nx/+T0uXqmo9HYAYU3ovZP3fLp+3TnyfdnornCHUdm48ePLlhfxtPL93Expz+Zbr+z+9mjudC3gq
+3ScsiHk8333P+z+9Fe+W/Z8+fCZOrKeDEHtMCNf+bz++PWq+T0G4b256PdUhxgqXyYr/2xup1wv/
+p1lVEPyfHm/yrQCIsSffq2u+T1fn260n36fT88s50Xj61BmA2PTc0ean8fzFzfumOSb/5Xzadllg
+yGbOTSN0XreSNtuL623gT0dNnflUzrP6p+f9LdM62N0o5z/ZXDa9f5Wf1fZL+a3lUllJTs/34dPO
+6Xa6t5ArXt8Xa5b1Bh3MLlhSzGYAz6brGy+LsI0zBeDoW+cO3teZ4RY2TpNU90I7x7bSzVl+Afre
+/yKsFe2cG7s+lj5SmgtgBV/3UYwger2uew0KHH2V9R2U2Dk+g0qzU3G0dB6sYR2D3tz7DgqqbZv3
+HxTtHAcmO4dFS+fNGHSnYR90fXrRPqhQmrOD91TkbIPW5udnrUHBskD93xyWdwwqvqP23/AeVFh8
+8B90plhJOfQx57BE+/cZFOxB0P6ffQa9efIdFNYyszMn+a6VaP8+g4JqADpFxW/Qc2tQqvU5AHx8
+cOk/KOoUTlSaw6cr5qekri4tbaTdu+/Tks8xEXpknpdWM8HtdG5JlC2LX6ATSaDvDHquFgh0LLfM
+prpzneMWtpg9BAvvdnhtrhSIfwz+Q//kkks501wHrsTPneFv5xY1AVDFxeVsq3tM5wCf8ug3KJCR
+XYwJhr84ha/zcaLz95eMAagSrA9gm8/pfNtoUtq2+56A6TH7Yjxu/dlsJ2plXWMmvdh9StAcJrr9
+TRs5/Hpkytn0u5aP459ZEzpLXp45WMFdnq3MLeyaAOQsbw9OebMQ1/+sHLW8JuWYUrEbOKVZduWC
+XcE/d7rOr/tc9Jmd2IC+lSgcBANd/1O5zltWtWt9IF9whfzc2sGhtULv9eGfsP1bmPHaP9x9xw4i
+fV7ZbRqP9aHyOtT+6R4Srx1ktA/tchhg+SMDXoHQ/bwK6ywSsrfYwkN3xwvuU/FhMWstFYFyXHC3
+QcwJ+afOZCiHec2lDEweAVgu1lO431xysp6CxXoM2jfXP+Ru3Kx0HAA0J+wAIO7+aWqeKmWDsCug
+Nb1ne9s9m6f+AvD2oxUTdp7xAEKVjwXuoZ898GbcnlRJ/XEeS0vOeC1tgCpDlra1enYUsDRKQ4vz
+hIasiTi55c1K26SxoFXtHMzru++B7IX7fNK1IDvnj7wgNOfseG6SoQPP5/HPuS5fBpH8scC8sjM3
+wwPGBRZLSlPfhVNOZxM62mwcpY2lU6eOd2fZW3XfR+TrXU3F/Ttz0V1lZmfZSXdFt8j3o7qpUJHx
+wvWD6S5dn11dIX90HkgiIw600DEZJvo0E76hcbqhNu/3AG4UB3iguS/k+Taj9S7SAzN74Rc9EbYy
+c8T5LjL93lresHQwC2IBW0I1RceW7IRpYQ7WQ/VkT+YDzWs77FL2o+itEJjKoq/G6JBin0ovWDBF
+2F/SGCb/2PcSSzCKh/4UqD3tuPn0ILCW7GJXj4t5Tkqb8ZOV2+ZsIk0JFTpfWXlybjKAEOFB9i9Y
+oQvV+W3NP9X4iPvnjFhRYF0mJ4YMl1ThIdbr+J2lg+c1NVRnzCQhxk4OYtwkIcaPBzFdLOuItjJo
+uL7tMrWFSiHYKpmKoh1z2euaJ+fwUmj9+Fhvazqygu1NlW+7Tkt8HKrsbc0NaSiTeKwnjmWve/Fx
+oIOzmXfNxlsfC4YOo1WeLiKaCue+a3kKoeRIE3EYeKhdhE7FYyIhhOuaiJduCVNx6ZYjTcRGqXqM
+L8Q2rMz0djxE1C5xXUdAFdQtdUxf9jhmpPe4lch3gMTvcrbZPnTfelFxkeownm4NgM7Hnq9x7W1a
+GwTiAiCs5aH77Svjvexh/yn5MgDT3o/IAEBXCBHLXgzAR4d56CXmJ7M+fuuyfET3ZSygA8jfQyWy
+G2t9dPk9EtQbcX3201Bkhczrwvf1EP4MH5sccL9nuRUNy2J0YAkRkGEqIrAikbg3MgCBO+JiW+x+
+10Xi/Jza7ocbaSHeJeIh+dp3kvgIvgR+bjUx5zubqfgwRsW+2yXr7QgYcMl6cf6vfacGP9rShIS1
+NAdV+jk4vCbiFsuB7g1zLS50Z/f7TpNypAWp37xLg/X20gX6ffZJcDeC32cqHgKY19XEbQQfSLC/
+BnUYgE6Ax8aLFTg8DU5VOj2oSrcPEGJ2ZTp0AB9Vmt+6WJ4NoZdwpGofBMSkQsWgw6O4tXq2EMFv
+66UJDy5tdXx6aR8QGRgVz70lMkwlzkZZVRCeH1hyz4TYSAtyizw/BjAV9/dwwjZNO8NoozAAAIs0
+FY8AmHBd9mBA0Plh8mwUXRaDVc7zG2f0t0Cqm4pMd8iEl0fECJvfEnMAhGDeF1XaYVer3Sj0EsHf
+ip1t9MbmydelEYMUrl3DKM8QQs+3H3ek0NnLVDxyPxEp0KsX42wP7WfsSAXpZVDumTkjQ0g+vbNc
+6i6qECVyP8AtjmHehEvfxN9SwZTjclThWnz8XtgZMxlxgzj2oS2FCUJ3jM8XlKWmFyg99sWugAZy
+tMvWIEeD3zw4moljQ2gSSBvuKF4wR/OMVucGDlWMztGgq8P+VDycDUXhaPDgdHpsTen6YhyOZtE+
+7NsEOBr2MsjRvHAstJ+hOZqpKbn6GZ+jYS8GRzP9lvbQy5kVx/HWBZwbFmRB08Ch6Rv31lLadlJ5
+XlweVOivLyMEYCOeudq5aY9jRhunCGBDQzhj1OACduUOl/rE98PZLHbGR2UzxmlbL3Iu3N/7Gz4R
+iflS1y4m0Y/TpTXYy1TEfoY8AuHlhyH9RDKpQ2fjffDIFuENUsndnQ3nyFp2nh9zS8P770FpCL8N
+bVV7W3zIx9LjWnw5YCPX/RCLL7o0DD9NMRVdGlZmXkYlH0u+XF9NQr+HXfOQhcNLMQ==
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ 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
+ ]]>
+ <![CDATA[
+ mCq1sdu6NWwcXMKyjIqwaV/hMD3YlZo/8hZuZBIQQNmJM3AiU7smwzgr2A/mkw7Igfixs/nkeDaL
+A63J+RI/QXG5jNds18nAXEYh80Z8oWOw8vzdathxVo55Z6XQlR1nhWAQYB4JBoGYsism8gRVPcbW
+QNw9J8mz6Bw23erGCG/PLECjHdRSlq2NH42YCmgndkis9yxJ49G4vVsibDuzyLbPqpSsRLFbhtwW
+78chy5dIyAnKsYsEj3Lu7m054n6eGQjd6P5p+7Gc+1MKVFyBdM7szc4vUAzNL2jNiJFifpt5xSuj
+eFbecQekMj0NmoXbht71hfYrs2DvoNE5JQuELrArv2DW7/ZsD+lOcuBdmWt4bldzROWK6Wcxd5Yl
+X2+9Stpif4XonOiWfNvuCtuW6oquXxqRK8m5khmJg60XsqqlYVG8b9OObzirHhrIucJ874sP4p3j
+9DQkrqIPuX7Qxm6fFceXUJm6QpGgjsRBJXQQNyIwrNFlMMat9LXG/nnM+rYvZ84sO2etvgSzzrnY
+0kbkhDD1Fn7qV8Qc32LnSuTLHXVlck92awNj367PWf7hsp7NxfRIPn9wdKnbGNqLgsxWSqWuhxTc
+3n4gfl/js8Uw1gLMDHVVnD1oNtTLo2S72mZqYyTKB1eZt0vQltFczFDkbVtvvu5CxtFhbtY8PYGM
+nd28ETzvZG8P14vpt+BL2V44QFaJe/kxq/ZiFrSl5ces2ouZ633Hj1m1F+MLefkxq/ZifCEvP2Zh
+qd/88twNftHiMnbLT23oXBumNs5q5JVht17IrSXJK8RuvZBbik2vFLv1Qm59oVVjt17IrR3XXR12
+64Xc+kKrxm69kFva+yvFbr2ii+Qtq8VuvZBbEqNeLXbrhdwS7GC12O13sIOfY7deyC2PHawGu/0i
+dvBL7NYLuf02dvApdvs5drAK7NYrYg7zyhVjtx+i+SvDbr2Q2x/K2AfY7Vdl7HfYrRdy+30Z+wy7
+/YKMrQC7fQ+fWi12+7GMrQq79eLDF/Gpb2C376H5q8VuvYwIw6ZXid16Ibfvofk/x269RsaHaP6P
+sFsv5PZDi/wj7PY7a9J+jt16Ibfz2nIV2O0XteUvsVsv5NbTT/4VduuF3DoaZmXYrRdyO6+TV4Hd
+eiG3tp+8OuzWAsrmkFtfaNXYrRdyy1aJ/xy7ddGkhXXeLvqY3olxOxFueWQpnXUB1VseD0uXknzI
+4J1NNAtbaOAt/8QmmoUtNEQn/wObaBa20HAcW+UmmoUtNKT3/4FNNAtbaFzUeKWbaBa20JB++Qc2
+0SxsoXFG5Wo30SxsobHR/BVvolmoDbTl+5toyCTc3fRAY0GWzrLA74gd5DwdOXhurR+zt/vdTZim
+6q2dbjiaamIrF4F9Mtfaq6bIERb/ZFE/JRXDxeAzUXrFuBsshScuEdNjdGvxHjnzVaAIOC7cCeQU
+vGLynWMTisgBFza4ozfSyh+2PsddkkIXpFhrbBZXo7B7VSww3MjZpTKn0crIjlEuIkiqbO9SidLt
+OdY+FGj6zb7V8aDH2D0GkxHtRXUWMOuZhVKLiTxm4JFL20WQnG2k/0n7wLIQiPb+YNjh4VlfKAR3
+KuZ09koKKPcZs9sblpt/m2Mf9rM/BH/kVzP8WNT9oqJAQiF3yy1f+GTYGw0rT82xGfGXhz7k3z/w
+he6T++NprteeQl5z/Lc/RW7VjsrVUs6f8s89s+UPQ8XQPTwAuRECD99jfzIzGg1ITiV/5T/49+to
+PPXTWvkvRv5spfJ5ucvepNcamHPlgQn3pIbwf+0vcmH6ZvDPiQ/5kdVM8ldr+pDVbij4NyQO4eIZ
+bv3lx8h/5L++Rf4OIXLuS0gqEnRJkURRU1VF1v2SiA1B1rGINUM2VNH/4lVIlrCgKLqKkSJqmuxP
+QKagKZphYFGUFCihImmOTEKT5QUii0XavoSuqIIBSV3TkaJi7EHHUDRBN0SkK7KKoQZ+SVEUQYaK
+GEjEigiVwUjUBWRoIpBTNKxBfTVd0KDSOtJlBEWy8CpDg1uGouqSJiJSSJJ1QTIUIKEYpFAZCqlY
+QCqwBMmyKEkiFDKwoKu6LMoGENP8CR2pAtYkWdGgbXABL1vgoEfTF4tA01VJFiQNioGEYg0pHnSU
+JTqSaAiGpENlsPFOkcX+zJJXiZ8UgqbL0BXzhZAsLRYS9c/Ep/wVGSv7HmG87r/6kKBBFxoaVkUo
+D6wlgiwA00XVQBrWgb+GTu4ouqhgCYEgytB95A5SdB1ETzRkjDUN7iAFQRfBoxj6WJU8ysBISYif
+8uLFo5BoSCpUFcuiCoKHgPGyKoKkKIqEQDoMBftFDWmfiMFSERADkF1NAKJAW1VlrHjQweJc1yge
+tVkqstQq8qqlQgoSBeArAsbJmij6l2sjaTDyPxHuxSLwqiXuLNNZ7oil2nzeV20QJG/1/UkakwGf
+PDeboI6bvVTqYDDovU7MVKpo9rpPU0thv1foqteZPrEyUFNQjRgUmAgCKOme5bNgu8xx3TEDpeF0
+oURp2Jv2moOzWbMzbg6t9yNPYuejaZM0ZX/YHZgLlmX+rYNRu/9Xb2J+RO20Z1amzfGUo6ZhA1Sc
+KquaBjpQVj5oUu0T0mCtOcJbrEC59y8TzOkrKdIcN18mLJd/OOJPVoc9sgCrMh33ht2lB4vNYWdg
+jo+bL+/Qnjehr8s28/+m8P+m8H/EFCYweKofq+8Xr0KIWArQOWA7NSwbREBAhrAIb5IQaF3NL0L5
+z4zSYpE245JqYKyABtBkXfWgo8Aw0DSQMqgEFiVSRJdgYEBFiFwZBjGRMFI0SVIMXVMMUQK7BeyA
+lkE3g2WGItAhCggVmGykgQ+AEWG1gQRFJW8H4wFlgIsqNFwF8YXHwCtQNdIfGki5jDAyMAixPwGv
+FGTwI3Qdgf2Ad0noc1FcLEJHoSKIhgj/GToGX8KDjrFEB2ohwKiBIaka7xTxEEV9Sco8BAijOYFV
+yChUlgqBSvhYfMpfkbHye8Y0LAkYnAFDArVs6+NMZr8NkzLbGHHqtuoz/OGIv3blK2fYLA2sAJ3l
+JBIwgTttds2LcbMH2tvXnTT/ZfqbwyGhYr5Cjr87NifT0dj0T55Gf5E78IhdHGZ/J3nffwFO02W8
+ ]]>
+</i:pgf>
+</svg>
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,
diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt
index 599b9808..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==5.0.0
-pluggy==0.9.0
+more-itertools==7.2.0
+pluggy==0.13.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 \ No newline at end of file
diff --git a/install/requirements.txt b/install/requirements.txt
index 0abd773f..36b9fa4f 100644
--- a/install/requirements.txt
+++ b/install/requirements.txt
@@ -1,8 +1,9 @@
altgraph==0.16.1
-certifi==2019.3.9
+certifi==2019.9.11
chardet==3.0.4
Click==7.0
-Flask==1.0.2
+Flask==1.1.1
+Flask-HTTPAuth==3.3.0
future==0.17.1
idna==2.8
itsdangerous==1.1.0
@@ -10,11 +11,12 @@ 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
+PyInstaller==3.5
+PyQt5==5.13.1
+PyQt5-sip==4.19.19
+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.6
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 620ada98..108ffd0b 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
@@ -38,22 +47,84 @@ def main(cwd=None):
print("OnionShare {0:s} | https://onionshare.org/".format(common.version))
# OnionShare CLI in OSX needs to change current working directory (#132)
- if common.platform == 'Darwin':
+ if common.platform == "Darwin":
if cwd:
os.chdir(cwd)
# Parse arguments
- parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=28))
- parser.add_argument('--local-only', action='store_true', dest='local_only', help="Don't use Tor (only for development)")
- parser.add_argument('--stay-open', action='store_true', dest='stay_open', help="Continue sharing after files have been sent")
- parser.add_argument('--auto-start-timer', metavar='<int>', dest='autostart_timer', default=0, help="Schedule this share to start N seconds from now")
- parser.add_argument('--auto-stop-timer', metavar='<int>', dest='autostop_timer', default=0, help="Stop sharing after a given amount of seconds")
- parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
- parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
- parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
- parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
- parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
- parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
+ parser = argparse.ArgumentParser(
+ formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=28)
+ )
+ parser.add_argument(
+ "--local-only",
+ action="store_true",
+ dest="local_only",
+ help="Don't use Tor (only for development)",
+ )
+ parser.add_argument(
+ "--stay-open",
+ action="store_true",
+ dest="stay_open",
+ help="Continue sharing after files have been sent",
+ )
+ parser.add_argument(
+ "--auto-start-timer",
+ metavar="<int>",
+ dest="autostart_timer",
+ default=0,
+ help="Schedule this share to start N seconds from now",
+ )
+ parser.add_argument(
+ "--auto-stop-timer",
+ metavar="<int>",
+ dest="autostop_timer",
+ default=0,
+ help="Stop sharing after a given amount of seconds",
+ )
+ parser.add_argument(
+ "--connect-timeout",
+ metavar="<int>",
+ dest="connect_timeout",
+ default=120,
+ help="Give up connecting to Tor after a given amount of seconds (default: 120)",
+ )
+ parser.add_argument(
+ "--stealth",
+ action="store_true",
+ dest="stealth",
+ help="Use client authorization (advanced)",
+ )
+ parser.add_argument(
+ "--receive",
+ action="store_true",
+ dest="receive",
+ help="Receive shares instead of sending them",
+ )
+ parser.add_argument(
+ "--website",
+ action="store_true",
+ dest="website",
+ help="Publish a static website",
+ )
+ parser.add_argument(
+ "--config",
+ metavar="config",
+ default=False,
+ help="Custom JSON config file location (optional)",
+ )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ dest="verbose",
+ help="Log OnionShare errors to stdout, and web errors to disk",
+ )
+ parser.add_argument(
+ "filename",
+ metavar="filename",
+ nargs="*",
+ help="List of files or folders to share",
+ )
args = parser.parse_args()
filenames = args.filename
@@ -68,20 +139,24 @@ 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'
+ mode = "receive"
+ elif website:
+ mode = "website"
else:
- mode = 'share'
+ 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):
@@ -96,6 +171,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
@@ -106,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()
@@ -116,44 +195,66 @@ def main(cwd=None):
# Start the onionshare app
try:
common.settings.load()
- if not common.settings.get('public_mode'):
- web.generate_slug(common.settings.get('slug'))
+ if not common.settings.get("public_mode"):
+ 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()
+
# 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
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)
- 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)
+ 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)
@@ -168,7 +269,15 @@ def main(cwd=None):
print(e.args[0])
sys.exit()
- if mode == 'share':
+ if mode == "website":
+ # Prepare files to share
+ 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.")
try:
@@ -180,44 +289,50 @@ 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.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
- if common.settings.get('save_private_key'):
- if not common.settings.get('slug'):
- common.settings.set('slug', web.slug)
+ # 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)
common.settings.save()
# Build the URL
- 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)
+ 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:")
@@ -234,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
@@ -242,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':
+ 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 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
@@ -265,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 325f11d4..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'):
- 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,85 +74,118 @@ 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
- def build_slug(self):
+ def build_password(self):
"""
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 ShareMode and ReceiveMode and their child widgets
- 'mode_info_label': """
+ # Common styles between modes and their child widgets
+ "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,9 +327,20 @@ class Common(object):
background-color: #4e064f;
width: 10px;
}""",
-
+ "history_individual_file_timestamp_label": """
+ QLabel {
+ color: #666666;
+ }""",
+ "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': """
+ "share_zip_progess_bar": """
QProgressBar {
border: 1px solid #4e064f;
background-color: #ffffff !important;
@@ -323,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;
@@ -345,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
@@ -408,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]
@@ -420,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):
@@ -445,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):
@@ -489,6 +506,7 @@ class AutoStopTimer(threading.Thread):
"""
Background thread sleeps t hours and returns.
"""
+
def __init__(self, common, time):
threading.Thread.__init__(self)
@@ -498,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 2f4ddffd..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,37 +579,40 @@ 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()
+
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:
@@ -479,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
@@ -487,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:
@@ -525,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
@@ -551,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
@@ -604,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 16b64a05..25a28350 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:
@@ -59,62 +64,67 @@ 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
- '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
+ "ca": "Català", # Catalan
+ "zh_Hant": "正體中文 (繁體)", # Traditional Chinese
+ "zh_Hans": "中文 (简体)", # Simplified Chinese
+ "da": "Dansk", # Danish
+ "nl": "Nederlands", # Dutch
+ "en": "English", # English
+ # "fi": "Suomi", # Finnish (commented out because not at 90% translation)
+ "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
+ "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": "Srpska (latinica)", # Serbian (latin)
+ "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,
- 'slug': '',
- 'hidservauth_string': '',
- 'data_dir': self.build_default_data_dir(),
- '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()
@@ -129,14 +139,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:
@@ -144,14 +154,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):
"""
@@ -162,26 +172,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:
@@ -189,7 +201,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
@@ -197,22 +209,24 @@ class Settings(object):
"""
Save settings to file.
"""
- self.common.log('Settings', 'save')
- open(self.filename, 'w').write(json.dumps(self._settings))
- 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 bc805445..90f000b9 100644
--- a/onionshare/web/receive_mode.py
+++ b/onionshare/web/receive_mode.py
@@ -8,97 +8,107 @@ from werkzeug.utils import secure_filename
from .. import strings
-class ReceiveModeWeb(object):
+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
self.can_upload = True
- self.upload_count = 0
self.uploads_in_progress = []
+ # This tracks the history id
+ self.cur_history_id = 0
+
self.define_routes()
def define_routes(self):
"""
The web app routes for receiving files
"""
- def index_logic():
- 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.slug)
-
- r = make_response(render_template(
- 'receive.html',
- upload_action=upload_action))
- return self.web.add_security_headers(r)
-
- @self.web.app.route("/<slug_candidate>")
- 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 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
+ )
+ )
+ return self.web.add_security_headers(r)
- 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.
"""
- 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.upload_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')
-
- if self.common.settings.get('public_mode'):
- return redirect('/')
- else:
- return redirect('/{}'.format(slug_candidate))
+ 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
@@ -106,67 +116,49 @@ class ReceiveModeWeb(object):
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:
- if self.common.settings.get('public_mode'):
- path = '/'
- else:
- path = '/{}'.format(slug_candidate)
- return redirect('{}'.format(path))
+ 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("/<slug_candidate>/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("/<slug_candidate>/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'])
+ @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):
@@ -174,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)
@@ -190,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
@@ -197,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))
@@ -256,25 +270,22 @@ 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.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.slug) or self.path == '/{}/upload-ajax'.format(self.web.slug):
- self.upload_request = True
+ if self.method == "POST":
+ if self.path == "/upload" or self.path == "/upload-ajax":
+ self.upload_request = True
if self.upload_request:
# No errors yet
@@ -284,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:
@@ -296,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
@@ -306,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
@@ -327,28 +354,33 @@ 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
+ # 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.
@@ -356,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.upload_id,
- 'content_length': self.content_length
- })
- self.web.receive_mode.uploads_in_progress.append(self.upload_id)
+ 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
@@ -388,23 +422,26 @@ 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:
- upload_id = self.upload_id
+ 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': upload_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': upload_id
- })
- self.web.receive_mode.uploads_in_progress.remove(upload_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:
pass
@@ -417,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.upload_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
new file mode 100644
index 00000000..86d34016
--- /dev/null
+++ b/onionshare/web/send_base_mode.py
@@ -0,0 +1,305 @@
+import os
+import sys
+import tempfile
+import mimetypes
+import gzip
+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)
+ """
+
+ def __init__(self, common, web):
+ super(SendBaseModeWeb, self).__init__()
+ self.common = common
+ self.web = web
+
+ # Information about the file to be shared
+ self.is_zipped = False
+ self.download_filename = None
+ self.download_filesize = None
+ self.gzip_filename = None
+ self.gzip_filesize = None
+ self.zip_writer = None
+
+ # If "Stop After First Download" is checked (stay_open == False), only allow
+ # 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()
+
+ 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.cur_history_id = 0
+ 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("/"))
+
+ # If it's a filename, add it
+ if os.path.isfile(filename):
+ self.files[basename] = filename
+ self.root_files[basename] = filename
+
+ # If it's a directory, add it recursively
+ elif os.path.isdir(filename):
+ self.root_files[basename + "/"] = filename
+
+ for root, _, nested_filenames in os.walk(filename):
+ # Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
+ # and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
+ # The normalized_root should be "some_folder/foobar"
+ normalized_root = os.path.join(
+ basename, root[len(filename) :].lstrip("/")
+ ).rstrip("/")
+
+ # Add the dir itself
+ self.files[normalized_root + "/"] = root
+
+ # Add the files in this dir
+ for nested_filename in nested_filenames:
+ self.files[
+ os.path.join(normalized_root, nested_filename)
+ ] = os.path.join(root, nested_filename)
+
+ self.set_file_info_custom(filenames, processed_size_callback)
+
+ def directory_listing(self, filenames, path="", filesystem_path=None):
+ # 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]
+ 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, breadcrumbs, breadcrumbs_leaf
+ )
+ 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):
+ """
+ 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)
+
+ path = request.path
+
+ # 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},
+ )
+
+ # 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
+ while not done:
+ chunk = fp.read(chunk_size)
+ if chunk == b"":
+ done = True
+ else:
+ try:
+ yield chunk
+
+ # 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_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},
+ )
+
+ 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()
+
+ 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
diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py
index 560a8ba4..21dea639 100644
--- a/onionshare/web/share_mode.py
+++ b/onionshare/web/share_mode.py
@@ -3,65 +3,46 @@ import sys
import tempfile
import zipfile
import mimetypes
-import gzip
from flask import Response, request, render_template, make_response
+from .send_base_mode import SendBaseModeWeb
from .. import strings
-class ShareModeWeb(object):
+class ShareModeWeb(SendBaseModeWeb):
"""
All of the web logic for share mode
"""
- def __init__(self, common, web):
- self.common = common
- 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
+ def init(self):
+ self.common.log("ShareModeWeb", "init")
- self.define_routes()
+ # 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):
"""
The web app routes for sharing files
"""
- @self.web.app.route("/<slug_candidate>")
- 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=''):
+
+ @self.web.app.route("/", defaults={"path": ""})
+ @self.web.app.route("/<path:path>")
+ def index(path):
"""
Render the template for the onionshare landing page.
"""
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:
- 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,38 +51,10 @@ class ShareModeWeb(object):
else:
self.filesize = self.download_filesize
- if self.web.slug:
- r = make_response(render_template(
- 'send.html',
- slug=self.web.slug,
- 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))
- return self.web.add_security_headers(r)
-
- @self.web.app.route("/<slug_candidate>/download")
- def download(slug_candidate):
- self.web.check_slug_candidate(slug_candidate)
- return download_logic()
+ return self.render_logic(path)
@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.
"""
@@ -109,16 +62,16 @@ 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
- 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')
+ 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,
@@ -133,10 +86,11 @@ class ShareModeWeb(object):
self.filesize = self.download_filesize
# Tell GUI the download started
- self.web.add_request(self.web.REQUEST_STARTED, path, {
- 'id': download_id,
- 'use_gzip': use_gzip
- })
+ 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}
+ )
basename = os.path.basename(self.download_filename)
@@ -147,19 +101,19 @@ class ShareModeWeb(object):
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': download_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:
@@ -170,15 +124,26 @@ class ShareModeWeb(object):
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': download_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
@@ -186,13 +151,13 @@ class ShareModeWeb(object):
canceled = True
# tell the GUI the download has canceled
- self.web.add_request(self.web.REQUEST_CANCELED, path, {
- 'id': download_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
@@ -205,60 +170,127 @@ class ShareModeWeb(object):
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 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")
+ 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=""):
+ 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, filesystem_path)
+
+ # If it's a file
+ elif os.path.isfile(filesystem_path):
+ if self.download_individual_files:
+ return self.stream_individual_file(filesystem_path)
+ else:
+ 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:
+ history_id = self.cur_history_id
+ self.cur_history_id += 1
+ return self.web.error404(history_id)
+ else:
+ # Special case loading /
+
+ if path == "":
+ # Root directory listing
+ filenames = list(self.root_files)
+ filenames.sort()
+ return self.directory_listing(filenames, path)
- self.cleanup_filenames = []
+ else:
+ # If the path isn't found, throw a 404
+ history_id = self.cur_history_id
+ self.cur_history_id += 1
+ return self.web.error404(history_id)
- # build file info list
- self.file_info = {'files': [], 'dirs': []}
+ def build_zipfile_list(self, filenames, processed_size_callback=None):
+ 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(object):
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()
@@ -291,33 +325,6 @@ class ShareModeWeb(object):
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):
"""
@@ -325,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
@@ -332,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
@@ -353,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?
@@ -362,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 edaf75f1..b5b805ec 100644
--- a/onionshare/web/web.py
+++ b/onionshare/web/web.py
@@ -5,54 +5,78 @@ import queue
import socket
import sys
import tempfile
+import requests
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
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
def stubbed_show_server_banner(env, debug, app_import_path, eager_loading):
pass
+
try:
flask.cli.show_server_banner = stubbed_show_server_banner
except:
pass
-class Web(object):
+class Web:
"""
The Web object is the OnionShare web server, powered by flask
"""
+
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
-
- def __init__(self, common, is_gui, mode='share'):
+ 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
- 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"),
+ 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)
+ self.generate_static_url_path()
+ self.auth = HTTPBasicAuth()
+ self.auth.error_handler(self.error401)
# Verbose mode?
if self.common.verbose:
@@ -68,7 +92,7 @@ class Web(object):
# 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
@@ -78,27 +102,27 @@ class Web(object):
# 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 = [
- ('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')
+ ("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.slug = None
- self.error404_count = 0
+ self.password = None
+
+ 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
@@ -109,59 +133,143 @@ class Web(object):
# Create the mode web object, which defines its own routes
self.share_mode = None
self.receive_mode = None
- if self.mode == 'receive':
- self.receive_mode = ReceiveModeWeb(self.common, self)
- elif self.mode == 'share':
+ 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)
+
+ 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
+ # 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 + "/<path:filename>",
+ endpoint="static",
+ view_func=self.app.send_static_file,
+ )
def define_common_routes(self):
"""
- Common web app routes between sending and receiving
+ Common web app routes between all modes.
"""
- @self.app.errorhandler(404)
- def page_not_found(e):
- """
- 404 error page.
- """
- return self.error404()
- @self.app.route("/<slug_candidate>/shutdown")
- def shutdown(slug_candidate):
- """
- Stop the flask web server, from the context of an http request.
- """
- self.check_shutdown_slug_candidate(slug_candidate)
- self.force_shutdown()
- return ""
+ @self.auth.get_password
+ def get_pw(username):
+ if username == "onionshare":
+ return self.password
+ else:
+ return None
+
+ @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():
+ return None
+
+ return _check_login()
- @self.app.route("/noscript-xss-instructions")
- def noscript_xss_instructions():
+ @self.app.errorhandler(404)
+ def not_found(e):
+ mode = self.get_mode()
+ history_id = mode.cur_history_id
+ mode.cur_history_id += 1
+ return self.error404(history_id)
+
+ @self.app.route("/<password_candidate>/shutdown")
+ def shutdown(password_candidate):
"""
- Display instructions for disabling Tor Browser's NoScript XSS setting
+ Stop the flask web server, from the context of an http request.
"""
- 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
+ if password_candidate == self.shutdown_password:
+ self.force_shutdown()
+ return ""
+ abort(404)
- # 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)
+ 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:
+ 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 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('404.html'), 404)
+ r = make_response(
+ render_template("401.html", static_url_path=self.static_url_path), 401
+ )
return self.add_security_headers(r)
def error403(self):
self.add_request(Web.REQUEST_OTHER, request.path)
+ r = make_response(
+ render_template("403.html", static_url_path=self.static_url_path), 403
+ )
+ return self.add_security_headers(r)
+
+ def error404(self, history_id):
+ self.add_request(
+ self.REQUEST_INDIVIDUAL_FILE_STARTED,
+ "{}".format(request.path),
+ {"id": history_id, "status_code": 404},
+ )
- r = make_response(render_template('403.html'), 403)
+ 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):
+ 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):
@@ -170,52 +278,61 @@ class Web(object):
"""
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:;",
+ )
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, data=None):
+ 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
- })
-
- 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))
+ 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.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):
"""
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)
- 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_passwords(self):
+ self.invalid_passwords_count = 0
+ self.invalid_passwords = []
def force_shutdown(self):
"""
@@ -223,19 +340,25 @@ class Web(object):
"""
# 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
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
@@ -247,10 +370,10 @@ class Web(object):
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)
@@ -259,22 +382,18 @@ class Web(object):
"""
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)
- # Reset any slug that was in use
- self.slug = None
-
- # To stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
+ # 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:
- 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_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
new file mode 100644
index 00000000..61b6d2c6
--- /dev/null
+++ b/onionshare/web/website_mode.py
@@ -0,0 +1,104 @@
+import os
+import sys
+import tempfile
+import mimetypes
+from flask import Response, request, render_template, make_response
+
+from .send_base_mode import SendBaseModeWeb
+from .. import strings
+
+
+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
+ """
+
+ @self.web.app.route("/", defaults={"path": ""})
+ @self.web.app.route("/<path:path>")
+ def path_public(path):
+ return 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 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
+ return self.stream_individual_file(self.files[index_path])
+
+ 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):
+ return self.stream_individual_file(filesystem_path)
+
+ # If it's not a directory or file, throw a 404
+ else:
+ history_id = self.cur_history_id
+ self.cur_history_id += 1
+ return self.web.error404(history_id)
+ else:
+ # Special case loading /
+
+ if path == "":
+ index_path = "index.html"
+ if index_path in self.files:
+ # Render it
+ return self.stream_individual_file(self.files[index_path])
+ 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
+ history_id = self.cur_history_id
+ self.cur_history_id += 1
+ return self.web.error404(history_id)
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 8f5ff32b..04709dc2 100644
--- a/onionshare_gui/mode/__init__.py
+++ b/onionshare_gui/mode/__init__.py
@@ -22,15 +22,19 @@ 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
+from ..threads import AutoStartTimer
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()
starting_server_step2 = QtCore.pyqtSignal()
@@ -39,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
@@ -63,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)
@@ -96,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
@@ -118,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():
@@ -166,22 +212,22 @@ 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
# 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)
@@ -190,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)
@@ -199,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)
@@ -211,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)
@@ -235,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()
@@ -255,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):
"""
@@ -282,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)
@@ -303,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()
@@ -326,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:
@@ -380,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):
"""
@@ -417,3 +471,32 @@ 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.
+ """
+ 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)
+
+ 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"])
+
+ if self.server_status.status == self.server_status.STATUS_STOPPED:
+ self.history.cancel(event["data"]["id"])
+
+ 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"])
diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/file_selection.py
index 0d4229fe..c505dc03 100644
--- a/onionshare_gui/mode/share_mode/file_selection.py
+++ b/onionshare_gui/mode/file_selection.py
@@ -22,13 +22,15 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
-from ...widgets import Alert, AddFileDialog
+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 1546cb68..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,37 @@ 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):
def __init__(self, common, id, content_length):
@@ -247,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()
@@ -256,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
@@ -281,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)
@@ -292,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
@@ -330,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
@@ -341,10 +382,128 @@ 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, data, path):
+ super(IndividualFileHistoryItem, self).__init__()
+ self.status = HistoryItem.STATUS_STARTED
+ self.common = common
+
+ self.id = id
+ self.path = path
+ self.total_bytes = 0
+ self.downloaded_bytes = 0
+ 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.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()
+
+ # 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.setValue(0)
+ self.progress_bar.setStyleSheet(
+ self.common.css["downloads_uploads_progress_bar"]
+ )
+
+ # Text layout
+ labels_layout = QtWidgets.QHBoxLayout()
+ labels_layout.addWidget(self.timestamp_label)
+ labels_layout.addWidget(self.path_label)
+ labels_layout.addWidget(self.status_code_label)
+ labels_layout.addStretch()
+
+ # Layout
+ layout = QtWidgets.QVBoxLayout()
+ layout.addLayout(labels_layout)
+ layout.addWidget(self.progress_bar)
+ 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"]
+ )
+ self.status = HistoryItem.STATUS_FINISHED
+ self.progress_bar.hide()
+ return
+
+ else:
+ 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
+
+ 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
+
+ else:
+ elapsed = time.time() - self.started
+ if elapsed < 10:
+ # Wait a couple of seconds for the download rate to stabilize.
+ # This prevents a "Windows copy dialog"-esque experience at
+ # the beginning of the download.
+ pb_fmt = strings._("gui_all_modes_progress_starting").format(
+ self.common.human_readable_filesize(downloaded_bytes)
+ )
+ else:
+ pb_fmt = strings._("gui_all_modes_progress_eta").format(
+ self.common.human_readable_filesize(downloaded_bytes),
+ self.estimated_time_remaining,
+ )
+
+ self.progress_bar.setFormat(pb_fmt)
+
+ def cancel(self):
+ self.progress_bar.setFormat(strings._("gui_canceled"))
+ self.status = HistoryItem.STATUS_CANCELED
+
+ @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
"""
+
def __init__(self, common):
super(HistoryItemList, self).__init__()
self.common = common
@@ -404,45 +563,53 @@ 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)
# 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.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"])
# 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.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()
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(clear_button)
+ header_layout.addWidget(self.requests_label)
+ header_layout.addWidget(self.clear_button)
# When there are no items
self.empty_image = QtWidgets.QLabel()
@@ -450,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
@@ -482,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()
@@ -520,27 +687,56 @@ 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.
"""
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')
- self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count))
- self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count))
+ image = self.common.get_resource_path("images/history_completed.png")
+ self.completed_label.setText(
+ '<img src="{0:s}" /> {1:d}'.format(image, self.completed_count)
+ )
+ self.completed_label.setToolTip(
+ strings._("history_completed_tooltip").format(self.completed_count)
+ )
def update_in_progress(self):
"""
Update the 'in progress' widget.
"""
if self.in_progress_count == 0:
- image = self.common.get_resource_path('images/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')
- self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
- self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
+ image = self.common.get_resource_path("images/history_in_progress.png")
+
+ self.in_progress_label.setText(
+ '<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)
+ )
+ self.in_progress_label.setToolTip(
+ strings._("history_in_progress_tooltip").format(self.in_progress_count)
+ )
+
+ 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")
+ else:
+ image = self.common.get_resource_path("images/history_requests.png")
+
+ self.requests_label.setText(
+ '<img src="{0:s}" /> {1:d}'.format(image, self.requests_count)
+ )
+ self.requests_label.setToolTip(
+ strings._("history_requests_tooltip").format(self.requests_count)
+ )
class ToggleHistory(QtWidgets.QPushButton):
@@ -548,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
@@ -567,13 +764,15 @@ 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):
"""
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
@@ -584,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 4c0b49ba..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.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'))
+ 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
@@ -112,8 +129,8 @@ class ReceiveMode(Mode):
Starting the server.
"""
# Reset web counters
- self.web.receive_mode.upload_count = 0
- self.web.error404_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
self.reset_info_counters()
@@ -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()
@@ -212,6 +239,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')
+ 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 6cb50b2b..d0cc6a04 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
@@ -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,20 +134,22 @@ 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):
"""
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'))
+ 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):
@@ -146,8 +157,8 @@ class ShareMode(Mode):
Starting the server.
"""
# Reset web counters
- self.web.share_mode.download_count = 0
- self.web.error404_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
self.reset_info_counters()
@@ -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):
@@ -225,12 +238,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'))
-
def handle_request_started(self, event):
"""
Handle REQUEST_STARTED event.
@@ -246,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):
"""
@@ -256,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
@@ -265,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"])
@@ -284,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):
"""
@@ -296,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()
@@ -312,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()
@@ -325,6 +347,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):
@@ -347,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 24e2c242..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,21 +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:
- 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.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames
+ 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)
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
new file mode 100644
index 00000000..8cd2eca6
--- /dev/null
+++ b/onionshare_gui/mode/website_mode/__init__.py
@@ -0,0 +1,266 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import os
+import random
+import string
+
+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
+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.
+ """
+ # 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/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()
+ 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/share_icon_toggle.png")),
+ QtGui.QIcon(
+ self.common.get_resource_path("images/share_icon_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, stretch=1)
+ self.setLayout(self.wrapper_layout)
+
+ # Always start with focus on file selection
+ self.file_selection.setFocus()
+
+ def get_stop_server_autostop_timer_text(self):
+ """
+ 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")
+
+ def autostop_timer_finished_should_stop_server(self):
+ """
+ The auto-stop timer expired, should we stop the server? Returns a bool
+ """
+
+ self.server_status.stop_server()
+ self.server_status_label.setText(strings._("close_on_autostop_timer"))
+ return True
+
+ def start_server_custom(self):
+ """
+ Starting the server.
+ """
+ # Reset web counters
+ self.web.website_mode.visit_count = 0
+ self.web.reset_invalid_passwords()
+
+ # 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.
+ """
+ self.web.website_mode.set_file_info(self.filenames)
+ self.success.emit()
+
+ 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.completed_count = 0
+ self.file_selection.file_list.adjustSize()
+
+ def cancel_server_custom(self):
+ """
+ Log that the server has been cancelled
+ """
+ self.common.log("WebsiteMode", "cancel_server")
+
+ def handle_tor_broke_custom(self):
+ """
+ Connection to Tor broke.
+ """
+ self.primary_action.hide()
+
+ 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()
+ self.toggle_history.indicator_count = 0
+ self.toggle_history.update_indicator()
+
+ @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..4639ea13 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
@@ -32,19 +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'
- 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)
@@ -55,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
@@ -69,50 +77,74 @@ 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.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)
+ mode_switcher_layout.addWidget(self.website_mode_button)
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)
@@ -122,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)
@@ -141,19 +190,70 @@ 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.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 +262,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)
@@ -197,62 +298,149 @@ 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.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.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()
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.mode = self.MODE_WEBSITE
+ self.update_mode_switcher()
+
def update_server_status_indicator(self):
# Set the status image
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")
+ )
+ 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:
- 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
@@ -263,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)
@@ -276,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)
@@ -293,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)
@@ -302,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.
@@ -317,35 +523,48 @@ 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.
- 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
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 = SettingsDialog(
+ self.common, self.onion, self.qtapp, self.config, self.local_only
+ )
d.settings_saved.connect(reload_settings)
d.exec_()
# 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):
"""
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)
@@ -362,15 +581,21 @@ 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()
+ 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
@@ -412,12 +637,41 @@ 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"]))
+ 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):
- self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, 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"],
+ )
+ )
mode.timer_callback()
@@ -425,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):
"""
@@ -450,35 +709,50 @@ 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)
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:
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:
- 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 0c51119e..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,8 +39,9 @@ class ServerStatus(QtWidgets.QWidget):
url_copied = QtCore.pyqtSignal()
hidservauth_copied = QtCore.pyqtSignal()
- MODE_SHARE = 'share'
- MODE_RECEIVE = 'receive'
+ MODE_SHARE = "share"
+ MODE_RECEIVE = "receive"
+ MODE_WEBSITE = "website"
STATUS_STOPPED = 0
STATUS_WORKING = 1
@@ -50,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
@@ -62,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)
@@ -87,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)
@@ -124,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)
@@ -159,7 +190,9 @@ 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()
@@ -170,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:
@@ -185,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):
"""
@@ -203,24 +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)
+ )
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()
@@ -237,17 +292,20 @@ 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'):
- if not self.common.settings.get('slug'):
- self.common.settings.set('slug', self.web.slug)
+ 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()
@@ -256,53 +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
+ ):
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"))
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"))
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):
@@ -311,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()
@@ -373,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()
@@ -409,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://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
+ 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..503e53a0 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 <http://www.gnu.org/licenses/>.
"""
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,10 +32,12 @@ from .widgets import Alert
from .update_checker import *
from .tor_connection_dialog import TorConnectionDialog
+
class SettingsDialog(QtWidgets.QDialog):
"""
Settings dialog.
"""
+
settings_saved = QtCore.pyqtSignal()
def __init__(self, common, onion, qtapp, config=False, local_only=False):
@@ -39,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
@@ -47,19 +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"
+
# 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"))
- 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())
@@ -67,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())
@@ -84,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())
@@ -101,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)
@@ -116,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)
@@ -157,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())
@@ -166,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
@@ -197,25 +256,32 @@ 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"))
+ 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)
# 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)
@@ -228,6 +294,42 @@ 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
@@ -239,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:
@@ -250,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:
@@ -275,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
@@ -339,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()
@@ -388,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)
@@ -410,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)
@@ -446,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)
@@ -463,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
@@ -472,6 +694,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()
@@ -484,7 +707,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)
@@ -500,31 +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)
- autostart_timer = self.old_settings.get('autostart_timer')
+ 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")
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)
@@ -532,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
@@ -619,22 +867,25 @@ 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:
self.connect_to_tor_label.show()
self.onion_settings_widget.hide()
-
def connection_type_bundled_toggled(self, checked):
"""
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:
self.authenticate_group.hide()
self.connection_type_socks.hide()
@@ -644,6 +895,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 +904,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,16 +913,24 @@ 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
- 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):
"""
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()
@@ -675,7 +938,9 @@ 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:
self.authenticate_group.hide()
self.connection_type_socks.hide()
@@ -686,7 +951,9 @@ 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:
self.authenticate_group.show()
self.connection_type_control_port_extras.show()
@@ -695,13 +962,14 @@ 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:
self.authenticate_group.show()
self.connection_type_socket_file_extras.show()
@@ -714,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:
@@ -732,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):
"""
@@ -760,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):
@@ -772,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()
@@ -806,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()
@@ -815,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():
@@ -827,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)
@@ -853,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):
"""
@@ -868,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
@@ -885,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()
@@ -928,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()
@@ -939,25 +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('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():
@@ -966,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('slug', self.old_settings.get('slug'))
- 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('slug', '')
+ 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('<strong>{}</strong><br>{}% {}'.format(strings._('connecting_to_tor'), progress, summary))
+ self.tor_status.setText(
+ "<strong>{}</strong><br>{}% {}".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)
@@ -1109,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 26a9ee6b..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,25 +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 slug 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')
+ # 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.common.settings.get("public_mode"):
+ if not self.mode.web.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()
@@ -67,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
@@ -76,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.slug)
+ 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()
@@ -94,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("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor'), summary))
+ self.setLabelText(
+ "<strong>{}</strong><br>{}".format(strings._("connecting_to_tor"), summary)
+ )
def _connected_to_tor(self):
- self.common.log('TorConnectionDialog', '_connected_to_tor')
+ self.common.log("TorConnectionDialog", "_connected_to_tor")
self.active = False
# Close the dialog after connecting
self.setValue(self.maximum())
def _canceled_connecting_to_tor(self):
- self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
+ self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
self.active = False
self.onion.cleanup()
@@ -99,12 +105,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
QtCore.QTimer.singleShot(1, self.cancel)
def _error_connecting_to_tor(self, msg):
- self.common.log('TorConnectionDialog', '_error_connecting_to_tor')
+ self.common.log("TorConnectionDialog", "_error_connecting_to_tor")
self.active = False
def alert_and_open_settings():
# Display the exception in an alert box
- Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings')), QtWidgets.QMessageBox.Warning)
+ Alert(
+ self.common,
+ "{}\n\n{}".format(msg, strings._("gui_tor_connection_error_settings")),
+ QtWidgets.QMessageBox.Warning,
+ )
# Open settings
self.open_settings.emit()
@@ -114,6 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
# Cancel connecting to Tor
QtCore.QTimer.singleShot(1, self.cancel)
+
class TorConnectionThread(QtCore.QThread):
tor_status_update = QtCore.pyqtSignal(str, str)
connected_to_tor = QtCore.pyqtSignal()
@@ -125,7 +136,7 @@ class TorConnectionThread(QtCore.QThread):
self.common = common
- self.common.log('TorConnectionThread', '__init__')
+ self.common.log("TorConnectionThread", "__init__")
self.settings = settings
@@ -133,7 +144,7 @@ class TorConnectionThread(QtCore.QThread):
self.onion = onion
def run(self):
- self.common.log('TorConnectionThread', 'run')
+ self.common.log("TorConnectionThread", "run")
# Connect to the Onion
try:
@@ -144,11 +155,15 @@ class TorConnectionThread(QtCore.QThread):
self.canceled_connecting_to_tor.emit()
except BundledTorCanceled as e:
- self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
+ self.common.log(
+ "TorConnectionThread", "run", "caught exception: BundledTorCanceled"
+ )
self.canceled_connecting_to_tor.emit()
except Exception as e:
- self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
+ self.common.log(
+ "TorConnectionThread", "run", "caught exception: {}".format(e.args[0])
+ )
self.error_connecting_to_tor.emit(str(e.args[0]))
def _tor_status_update(self, progress, summary):
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 <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
+
class Alert(QtWidgets.QMessageBox):
"""
An alert box dialog.
"""
- def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
+
+ def __init__(
+ self,
+ common,
+ message,
+ icon=QtWidgets.QMessageBox.NoIcon,
+ buttons=QtWidgets.QMessageBox.Ok,
+ autostart=True,
+ ):
super(Alert, self).__init__(None)
self.common = common
- self.common.log('Alert', '__init__')
+ self.common.log("Alert", "__init__")
self.setWindowTitle("OnionShare")
- self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
+ self.setWindowIcon(
+ QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
+ )
self.setText(message)
self.setIcon(icon)
self.setStandardButtons(buttons)
@@ -49,11 +60,12 @@ class AddFileDialog(QtWidgets.QFileDialog):
This is because the macOS sandbox requires native dialogs, and this is a Qt5
dialog.
"""
+
def __init__(self, common, *args, **kwargs):
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
self.common = common
- self.common.log('AddFileDialog', '__init__')
+ self.common.log("AddFileDialog", "__init__")
self.setOption(self.DontUseNativeDialog, True)
self.setOption(self.ReadOnly, True)
@@ -65,5 +77,5 @@ class AddFileDialog(QtWidgets.QFileDialog):
list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def accept(self):
- self.common.log('AddFileDialog', 'accept')
+ self.common.log("AddFileDialog", "accept")
QtWidgets.QDialog.accept(self)
diff --git a/screenshots/onionshare-website-server.png b/screenshots/onionshare-website-server.png
new file mode 100644
index 00000000..55e4fa27
--- /dev/null
+++ b/screenshots/onionshare-website-server.png
Binary files differ
diff --git a/setup.py b/setup.py
index f482abb6..9af72fc1 100644
--- a/setup.py
+++ b/setup.py
@@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, sys, platform, tempfile
from distutils.core import setup
+
def file_list(path):
files = []
for filename in os.listdir(path):
@@ -29,7 +30,8 @@ def file_list(path):
files.append(os.path.join(path, filename))
return files
-version = open('share/version.txt').read().strip()
+
+version = open("share/version.txt").read().strip()
description = (
"""OnionShare lets you securely and anonymously send and receive files. It """
"""works by starting a web server, making it accessible as a Tor onion """
@@ -37,60 +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/onionshare.desktop']),
- (os.path.join(sys.prefix, 'share/metainfo'), ['install/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')),
- (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 platform.system() != 'OpenBSD':
- 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",
+ "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/share/images/share_completed.png b/share/images/history_completed.png
index e68fe5a2..e68fe5a2 100644
--- a/share/images/share_completed.png
+++ b/share/images/history_completed.png
Binary files differ
diff --git a/share/images/share_completed_none.png b/share/images/history_completed_none.png
index 8dbd6939..8dbd6939 100644
--- a/share/images/share_completed_none.png
+++ b/share/images/history_completed_none.png
Binary files differ
diff --git a/share/images/share_in_progress.png b/share/images/history_in_progress.png
index 19694659..19694659 100644
--- a/share/images/share_in_progress.png
+++ b/share/images/history_in_progress.png
Binary files differ
diff --git a/share/images/share_in_progress_none.png b/share/images/history_in_progress_none.png
index 2d61dba4..2d61dba4 100644
--- a/share/images/share_in_progress_none.png
+++ b/share/images/history_in_progress_none.png
Binary files differ
diff --git a/share/images/history_requests.png b/share/images/history_requests.png
new file mode 100644
index 00000000..4965744d
--- /dev/null
+++ b/share/images/history_requests.png
Binary files differ
diff --git a/share/images/history_requests_none.png b/share/images/history_requests_none.png
new file mode 100644
index 00000000..93a71ef3
--- /dev/null
+++ b/share/images/history_requests_none.png
Binary files differ
diff --git a/share/locale/ar.json b/share/locale/ar.json
index d125e5fd..f1451cc7 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": "<a href='{0:s}'>ما هذا؟</a>",
- "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": "يمكنكم الحصول على جسور مِن <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
- "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 يكلّف مشروع تور للغاية..<br><br>استخدمها عند الضرورة فقط لتعذّر الاتّصال بتور مباشرة، أو عبر وسائل نقل obfs4 أو الجسور الاعتيادية.",
+ "gui_settings_tor_bridges_custom_radio_option": "استخدام جسورًا مطوّعة",
+ "gui_settings_tor_bridges_custom_label": "يمكنكم الحصول على عناوين جسور مِن <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "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. <a href='{}'>يمكنك تنزيلها الآن</a>.<br><br> إصدارتك {} و الأحدث {}.",
+ "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": "<b>أيّ شخص</b> لديه مسار OnionShare هذا سيكون بوسعه <b>تنزيل</b> تلك الملفات باستعمال <b>متصفّح تور</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>أيّ شخص</b> لديه مسار OnionShare هذا سيكون بوسعه <b>رفع</b> ملفات إلى حاسوبك باستعمال <b>متصفّح تور</b>: <img src='{}' />",
+ "gui_url_label_persistent": "هذه المشاركة لن توقف تلقائيًّا.<br><br>كل مشاركة لاحقة ستستخدم العنوان نفسه. لاستعمال عناوين لمرة واحدة عطّل خيار \"استخدم عنوانًا دائمًا\" في الإعدادات.",
+ "gui_url_label_stay_open": "هذه المشاركة لن تتوقف تلقائيا.",
+ "gui_url_label_onetime": "هذه المشاركة ستتوقف تلقائيًّا بعد تمام أوّل تنزيلة.",
+ "gui_url_label_onetime_and_persistent": "هذه المشاركة لن توقف تلقائيًّا.<br><br>كل مشاركة لاحقة ستستخدم العنوان نفسه. لاستعمال عناوين لمرة واحدة عطّل خيار \"استخدم عنوانًا دائمًا\" في الإعدادات.",
"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": "طور التلقّي يسمح للآخرين برفع ملفات إلى حاسوبك.<br><br><b>بعض الملفات قد تكون قادرة على السيطرة على نظامك إذا ما فتحتها. لا تفتح ملفات إلا من أشخاص تثق بهم، أو إنْ كنت واثقًا ممّا تفعل.</b>",
+ "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,59 @@
"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": "ثانية",
+ "gui_website_url_description": "<b>أى شخص</b> معه مسار OnionsShare هذا سيكون بامكانه <b>زيارة</b> موقعك باستخدام <b>متصفح تور</b>: <img src='{}' />",
+ "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/ca.json b/share/locale/ca.json
index b88dcead..a414834c 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": "Deixa de compartir (queden {})",
"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,38 +47,38 @@
"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 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 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": "<a href='{0:s}'>Què és això?</a>",
"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_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",
@@ -88,79 +88,79 @@
"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.<br><br>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 .<br><br>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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
- "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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "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": "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: {}",
"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.<a href='{}'>Feu clic aquí</a> per obtenir-la.<br><br>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?",
+ "connecting_to_tor": "S'està connectant a la xarxa Tor",
+ "update_available": "Hi ha una nova versió d'OnionShare.<a href='{}'>Feu clic aquí</a> per a obtenir-la.<br><br>Esteu usant {} i la més recent és {}.",
+ "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": "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 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.\nTorna 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_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. 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",
- "gui_share_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>descarregar</b> fitxers teus fent servir el <b>Navegador de Tor</b>: <img src='{}' />",
- "gui_receive_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>pujar</b> fitxers al teu ordinador fent servir el <b>Navegador de Tor</b>: <img src='{}' />",
- "gui_url_label_persistent": "Aquesta sessió no es tancarà.<br><br>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_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.<br><br>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_status_indicator_share_stopped": "A punt per compartir",
+ "gui_share_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>baixar</b> els vostres fitxers fent servir el <b>Navegador Tor</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>pujar</b> fitxers al vostre ordinador fent servir el <b>Navegador Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Aquest recurs no es tancarà ell sol.<br><br>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.<br><br>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.<br><br><b>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.</b>",
- "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.<br><br><b>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.</b>",
+ "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,50 +181,66 @@
"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": "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",
- "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",
"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",
"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_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",
"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": "<b>Qualsevol persona</b> amb aquesta adreça d'OnionShare pot <b>visitar</b> el vostre lloc web fent servir el <b>Navegador Tor</b>: <img src='{}' />",
+ "gui_mode_website_button": "Publica el lloc web",
+ "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 {}",
+ "incorrect_password": "La contrasenya no és correcta",
+ "history_requests_tooltip": "{} peticions web",
+ "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"
}
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": "<a href='{0:s}'>Co je to?</a>",
+ "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/da.json b/share/locale/da.json
index b3a2234a..1eac766b 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.",
@@ -95,11 +95,11 @@
"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. <a href='{}'>Klik her</a> for at hente den.<br><br>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,10 +171,10 @@
"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.<br><br>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": "<b>Alle</b> med OnionShare-adressen kan <b>downloade</b> dine filer med <b>Tor Browser</b>: <img src='{}' />",
- "gui_receive_url_description": "<b>Alle</b> med OnionShare-adressen kan <b>uploade</b> filer til din computer med <b>Tor Browser</b>: <img src='{}' />",
+ "gui_share_url_description": "<b>Alle</b> med OnionShare-adressen kan <b>downloade</b> dine filer, med <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Alle</b> med OnionShare-adressen kan <b>uploade</b> filer til din computer, med <b>Tor Browser</b>: <img src='{}' />",
"history_in_progress_tooltip": "{} igangværende",
"history_completed_tooltip": "{} færdige",
"info_in_progress_uploads_tooltip": "{} igangværende upload(s)",
@@ -222,12 +222,21 @@
"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",
"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": "<b>Alle</b> men OnionShare-adressen kan <b>besøge</b> dit websted med <b>Tor Browser</b>: <img src='{}' />",
+ "gui_mode_website_button": "Udgiv websted",
+ "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",
+ "gui_settings_csp_header_disabled_option": "Deaktivér indholdets header om sikkerhedspolitik",
+ "gui_settings_website_label": "Webstedsindstillinger"
}
diff --git a/share/locale/de.json b/share/locale/de.json
index 44839231..c50e3bfc 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 Verteildienst zu starten",
+ "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)",
@@ -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",
@@ -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.",
@@ -120,18 +120,18 @@
"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. <a href='{}'>Klicke hier</a> um sie herunterzuladen.<br><br>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?",
"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": "<b>Jeder</b> mit dieser OnionShareAdresse kann deine Dateien mit dem <b>Tor Browser</b> <b>herunterladen</b>: <img src='{}' />",
- "gui_receive_url_description": "<b>Jeder</b> mit dieser OnionShareAdresse kann mit dem <b>Tor Browser</b> Dateien auf deinen Computer <b>hochladen</b>: <img src='{}' />",
+ "gui_share_url_description": "<b>Jeder</b> mit dieser OnionShare-Adresse kann deine Dateien mit dem <b>Tor Browser</b> <b>herunterladen</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Jeder</b> mit dieser OnionShare-Adresse kann mit dem <b>Tor Browser</b> Dateien auf deinen Computer <b>hochladen</b>: <img src='{}' />",
"gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.<br><br>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.",
@@ -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. 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",
@@ -218,13 +218,27 @@
"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_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_start_server_autostart_timer_tooltip": "Automatischer Stoptimer endet um {}",
+ "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",
"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": "<b>Jeder</b> mit dieser OnionShare-Adresse kann deine Webseite mit dem <b>Tor Browser</b> <b>ansehen</b>: <img src='{}' />",
+ "gui_mode_website_button": "Webseite veröffentlichen",
+ "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",
+ "gui_settings_individual_downloads_label": "Abwählen, um den Download einzelner Dateien zu erlauben",
+ "history_requests_tooltip": "{} Web-Anfragen",
+ "systray_individual_file_downloaded_message": "Individuelle Datei {} betrachtet"
}
diff --git a/share/locale/el.json b/share/locale/el.json
index 00a063c9..0fa50def 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": "<a href='{0:s}'> Τί είναι αυτό? </a>",
- "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη",
- "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα να επιλέξετε την αντιγραφή του HidServAuth σας.",
+ "gui_settings_whats_this": "<a href='{0:s}'>Τί είναι αυτό;</a>",
+ "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 στη λειτουργία. <br><br> Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο 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 στη λειτουργία. <br><br> Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλων κανονικών bridges.",
+ "gui_settings_tor_bridges_custom_radio_option": "Χρήση παραμετροποιημένων bridges",
"gui_settings_tor_bridges_custom_label": "Αποκτήστε bridges στο <a href=\"https://bridges.torproject.org/options\"> https://bridges.torproject.org</a>",
- "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. <a href='{}'>Κάντε κλικ εδώ</a> για να το λάβετε.<br><br>Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.",
- "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": "<b>Οποιοσδήποτε</b> με αυτήν την διεύθυνση OnionShare, μπορεί να <b>κατεβάσει</b> τα αρχεία σας με χρήση <b> Φυλλομετρητη Tor</b>: <img src='{}' />",
- "gui_receive_url_description": "<b>Οποιοσδήποτε</b> με αυτήν την διεύθυνση OnionShare, μπορεί να <b>ανεβάσει</b> αρχεία στον υπολογιστή σας με χρήση του <b>Φυλλομετρητή Tor</b>: <img src='{}' />",
- "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.<br><br>Οποιοσδήποτε μετέπειτα διαμοιρασμός κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
- "gui_url_label_stay_open": "Αυτος ο διαμοιρασμός δεν έχει auto-stop.",
- "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει με την πρώτη λήψη.",
- "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.<br><br>Οποιοσδήποτε μετέπειτα διαμοιρασμός θα κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
+ "gui_share_url_description": "<b>Οποιοσδήποτε</b> με αυτή τη διεύθυνση OnionShare μπορεί να <b>κατεβάσει</b> τα αρχεία σας χρησιμοποιώντας το <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Οποιοσδήποτε</b> με αυτή τη διεύθυνση OnionShare, μπορεί να <b>ανεβάσει</b> αρχεία στον υπολογιστή σας χρησιμοποιώντας το <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν θα λήξει αυτόματα.<br><br>Οποιοσδήποτε επακόλουθος διαμοιρασμός θα επαναχρησιμοποιήσει αυτή τη διεύθυνση. (Για να χρησιμοποιήσετε διευθύνσεις μιας χρήσης, απενεργοποιήστε τη λειτουργία \"Χρήση μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
+ "gui_url_label_stay_open": "Αυτός ο διαμοιρασμός δε λήγει αυτόματα.",
+ "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει μετά την πρώτη λήψη.",
+ "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν θα λήξει αυτόματα.<br><br>Οποιοσδήποτε επακόλουθος διαμοιρασμός θα επαναχρησιμοποιήσει αυτή τη διεύθυνση. (Για να χρησιμοποιήσετε διευθύνσεις μιας χρήσης, απενεργοποιήστε τη λειτουργία \"Χρήση μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)",
"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": "Η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας.<br><br><b> Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.</b>",
- "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει",
+ "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει σε τρίτους/ες να ανεβάζουν αρχεία στον υπολογιστή σας.<br><br><b> Μερικά αρχεία μπορούν δυνητικά να αποκτήσουν έλεγχο του υπολογιστή σας εάν τα ανοίξετε. Να ανοίγετε αρχεία μόνο από άτομα που εμπιστεύεστε ή εάν ξέρετε τι κάνετε.</b>",
+ "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,58 @@
"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_autostop_timer_cant_be_earlier_than_autostart_timer": "Η ώρα αυτόματης διακοπής δεν μπορεί να είναι ίδια ή νωρίτερα από την ώρα αυτόματης έναρξης.Παρακαλείστε να την ανανεώσετε για να ξεκινήσετε τον διαμοιρασμό.",
+ "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": "δ"
+ "seconds_first_letter": "δ",
+ "gui_website_url_description": "<b>Οποιοσδήποτε</b> με αυτή τη διεύθυνση OnionShare μπορεί <b>να επισκεφτεί</b> την ιστοσελία χρησιμοποιώντας τον <b>Tor Browser</b>: <img src='{}' />",
+ "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/en.json b/share/locale/en.json
index 40e3a1d4..badb83ca 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",
+ "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",
@@ -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.",
@@ -51,6 +52,8 @@
"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_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",
"gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser",
@@ -96,24 +99,25 @@
"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. <a href='{}'>Click here</a> to get it.<br><br>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",
"gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
+ "gui_website_url_description": "<b>Anyone</b> with this OnionShare address can <b>visit</b> your website using the <b>Tor Browser</b>: <img src='{}' />",
"gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
"gui_url_label_persistent": "This share will not auto-stop.<br><br>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.",
@@ -131,17 +135,20 @@
"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.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
"gui_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_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",
"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",
@@ -165,6 +172,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",
"receive_mode_upload_starting": "Upload of total size {} is starting",
diff --git a/share/locale/es.json b/share/locale/es.json
index b5110f6f..3c5c07f1 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. <a href='{}'>Haz clic aquí</a> para obtenerlo.<br><br>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. <a href='{}'>Pulse aquí</a> para descargarla.<br><br>Utiliza la versión {}; la más reciente es la {}.",
+ "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?",
"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 una nueva conexión compartida.",
- "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ó.\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",
"gui_share_url_description": "<b>Cualquier persona </b> con esta dirección de OnionShare puede <b>descargar</b> tus archivos usando el <b>Navegador Tor</b>: <img src='{}' />",
- "gui_receive_url_description": "Con esta dirección de OnionShare, <b>cualquier persona</b> puede <b>subir</b> archivos a tu ordenador usando el <b>Navegador Tor</b>: <img src='{}' />",
- "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.<br><br>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": "<b>Cualquiera</b> que tenga esta dirección de OnionShare puede <b>cargar</b> archivos a su equipo mediante el <b>navegador Tor</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.<br><br>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.<br><br>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.<br><br>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": "<a href='{0:s}'>¿Qué es esto?</a>",
"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. <br><br>Ú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.<br><br>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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "gui_settings_tor_bridges_custom_label": "Puede obtener puentes en <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
"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.<br><br><b>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.</b>",
- "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 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",
- "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,33 @@
"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. 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",
"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": "<b>Cualquiera</b> que tenga esta dirección de OnionShare puede <b>visitar</b> su sitio web mediante el <b>navegador Tor</b>: <img src='{}' />",
+ "gui_mode_website_button": "Publicar sitio web",
+ "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 {}",
+ "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"
}
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": "<a href='{0:s}'>این چیست؟</a>",
"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 بسیار هزینه بر هستند.<br><br> فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق obfs4، یا دیگر بریج ها از آن استفاده کنید.",
- "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم",
- "gui_settings_tor_bridges_custom_label": "میتوانید از <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a> بریج دریافت کنید",
- "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.",
+ "gui_settings_meek_lite_expensive_warning": "هشدار: پل‌های meek_lite برای پروژه Tor بسیار هزینه بر هستند.<br><br> فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق obfs4، یا دیگر پل‌ها از آن استفاده کنید.",
+ "gui_settings_tor_bridges_custom_radio_option": "استفاده از پل‌های کاستوم",
+ "gui_settings_tor_bridges_custom_label": "می‌توانید از <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a> پل دریافت کنید",
+ "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 وجود دارد. <a href='{}'> اینجا کلیک کنید</a> تا آن را دریافت کنید.<br><br> شما در حال استفاده از {} می باشید و آخرین نسخه {} می باشد.",
- "update_error_check_error": "ناتوانی در بررسی برای نسخه های جدید: سایت OnionShare میگوید که آخرین نسخه '{}' ناشناس می باشد…",
- "update_error_invalid_latest_version": "ناتوانی در بررسی نسخه جدید: شاید شما به Tor متصل نیستید، یا سایت OnionShare کار نمی کند؟",
- "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.",
+ "update_available": "نسخه جدید OnionShare وجود دارد. <a href='{}'> اینجا کلیک کنید</a> تا آن را دریافت کنید.<br><br> شما در حال استفاده از {} هستید و آخرین نسخه {} است.",
+ "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": "<b>هرکس</b> با این آدرس OnionShare میتواند روی کامپیوتر شما فایل <b>دانلود</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
- "gui_receive_url_description": "<b>هرکس</b> با این آدرس OnionShare میتواند روی کامپیوتر شما فایل <b>آپلود</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
- "gui_url_label_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.<br><br>هر اشتراک بعدی از آدرس دوباره استفاده می کند. ( برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)",
- "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.",
- "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.",
- "gui_url_label_onetime_and_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.<br><br> هر اشتراک بعدی از آدرس دوباره استفاده میکند. (برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)",
- "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری",
+ "gui_share_url_description": "<b>هرکس</b> با این آدرس OnionShare می‌تواند روی کامپیوتر شما پرونده <b>بارگیری</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>هرکس</b> با این آدرس OnionShare می‌تواند روی کامپیوتر شما پرونده <b>بارگذاری</b> کند از طریق <b>مرورگر تور</b>: <img src='{}' />",
+ "gui_url_label_persistent": "این هم‌رسانی به صورت خودکار متوقف نمی‌شود.<br><br>هم‌رسانی‌های بعدی هم از همین نشانی استفاده می‌کنند. (برای استفاده از نشانی‌های یک‌بارمصرف، گزینه «استفاده از آدرس پایا» را در تنظیمات غیرفعال کنید.)",
+ "gui_url_label_stay_open": "این هم‌رسانی به صورت خودکار متوقف خواهد شد.",
+ "gui_url_label_onetime": "این هم‌رسانی پس از اولین تکمیل متوقف خواهد شد.",
+ "gui_url_label_onetime_and_persistent": "این هم‌رسانی به صورت خودکار متوقف نخواهد شد.<br><br> هم‌رسانی‌های بعدی نیز از همین نشانی استفاده خواهند کرد. (برای استفاده از نشانی‌های یک‌بارمصرف، گزینه «استفاده از آدرس پایا» را در تنظیمات غیرفعال کنید.)",
+ "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": "حالت دریافت به سایر افراد اجازه می دهد تا روی کامپیوتر شما فایل آپلود کنند.<br><br><b>برخی فایل ها را اگر باز کنید پتانسیل این را دارند که کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی را باز کنید که از کسانی دریافت کرده اید که به آن ها اعتماد دارید، یا میدانید دارید چه کار میکنید.</b>",
- "receive_mode_upload_starting": "آپلود حجم کلی {} در حال آغاز می باشد",
+ "gui_receive_mode_warning": "حالت دریافت به سایر افراد اجازه می‌دهد تا روی کامپیوتر شما پرونده بارگذاری کنند.<br><br><b>برخی پرونده‌ها را اگر باز کنید پتانسیل این را دارند که کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی را باز کنید که از کسانی دریافت کرده اید که به آن‌ها اعتماد دارید، یا می‌دانید دارید چه کار میکنید.</b>",
+ "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": "<b>هرکسی</b> با این نشانی OnionShare می‌تواند <b>با استفاده از مرورگر تور</b> سایت شما را <b>بازدید</b> کند: <img src='{}' />",
+ "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/fr.json b/share/locale/fr.json
index d066ba3d..c0baa245 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 :",
@@ -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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
"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": "<b>Quiconque</b> possède cette adresse OnionShare peut <b>télécharger</b> vos fichiers en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
+ "gui_share_url_description": "<b>Quiconque</b> possède cette adresse OnionShare peut <b>télécharger</b> vos fichiers en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
"gui_receive_url_description": "<b>Quiconque</b> possède cette adresse OnionShare peut <b>téléverser</b> des fichiers vers votre ordinateur en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
"gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.<br><br>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.",
@@ -152,15 +152,15 @@
"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_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_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",
"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,10 +178,10 @@
"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.<br><br>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.<br><br>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",
@@ -226,5 +226,20 @@
"days_first_letter": "j",
"hours_first_letter": "h",
"minutes_first_letter": "min",
- "seconds_first_letter": "s"
+ "seconds_first_letter": "s",
+ "gui_website_url_description": "<b>Quiconque</b> aura cette adresse OnionShare pourra <b>visiter</b> votre site Web en utilisant le <b>Navigateur Tor</b> : <img src='{}' />",
+ "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é",
+ "invalid_password_guess": "La tentative de mot de passe est invalide",
+ "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"
}
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. <a href='{}'>Cliceáil anseo</a> lena íoslódáil.<br><br>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á <b>aon duine</b> a bhfuil an seoladh OnionShare aige/aici in ann do chuid comhad a <b>íoslódáil</b> le <b>Brabhsálaí Tor</b>: <img src='{}' />",
@@ -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/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": "<a href='{0:s}'>यह क्या है</a>",
+ "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/hu.json b/share/locale/hu.json
index e84d0e64..3a6bba0c 100644
--- a/share/locale/hu.json
+++ b/share/locale/hu.json
@@ -1,17 +1,17 @@
{
"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": "",
- "no_available_port": "",
- "other_page_loaded": "",
- "close_on_autostop_timer": "",
- "closing_automatically": "",
+ "not_a_readable_file": "{0:s} nem egy olvasható fájl.",
+ "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/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/is.json b/share/locale/is.json
index 2c4f7182..d90213e1 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.",
@@ -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. <a href='{}'>Smelltu hér</a> til að ná í hana.<br><br>Þú 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": "<b>Hver sem er</b> með þetta OnionShare vistfang getur <b>sótt</b> skrárnar þínar með því að nota <b>Tor-vafrann</b>: <img src='{}' />",
@@ -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,12 +218,21 @@
"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",
"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": "<b>Hver sem er</b> með þetta OnionShare vistfang getur <b>skoðað</b> vefsvæðið þitt með því að nota <b>Tor-vafrann</b>: <img src='{}' />",
+ "gui_mode_website_button": "Birta vefsvæði",
+ "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",
+ "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 8779cf2e..7692ae6c 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 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,12 +71,12 @@
"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 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.<br><br>Da usare solo se impossibile connettersi a Tor direttamente, con obfs4, o altri bridge normali.",
@@ -127,18 +127,18 @@
"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. <a href='{}'>Clicca qui</a> per scaricarla.<br><br>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.",
- "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",
@@ -213,17 +213,25 @@
"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.",
- "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",
"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",
+ "gui_website_url_description": "<b>Chiunque</b>, con questo indirizzo di OnionShare, può <b>visitare</b> il tuo sito web utilizzando il <b>Browser Tor</b>: <img src='{}' />",
+ "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/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": "<b>誰でも</b> このOnionShareアドレスを知る限り、<b>Torブラウザで</b> サイトを<b>訪れることができます</b>: <img src='{}' />",
+ "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/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/nb.json b/share/locale/nb.json
index 8041db76..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:",
@@ -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.",
@@ -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. <a href='{}'>Klikk her</a> for å laste den ned.<br><br>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?",
@@ -133,8 +133,8 @@
"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.",
- "share_via_onionshare": "OnionShare det",
+ "gui_server_autostop_timer_expired": "Tidsavbruddsuret har gått ut allerede. Juster det for å starte deling.",
+ "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": "<b>Alle</b> som har denne OnionShare-adressen kan <b>Laste ned</b> filene dine ved bruk av <b>Tor-Browser</b>: <img src='{}' />",
@@ -223,12 +223,25 @@
"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",
"hours_first_letter": "t",
"minutes_first_letter": "m",
- "seconds_first_letter": "s"
+ "seconds_first_letter": "s",
+ "gui_website_url_description": "<b>Hvem som helst</b> med denne OnionShare-adressen kan <b>besøke</b> din nettside ved bruk av <b>Tor-nettleseren</b>: <img src='{}' />",
+ "gui_mode_website_button": "Publiser nettside",
+ "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",
+ "invalid_password_guess": "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"
}
diff --git a/share/locale/nl.json b/share/locale/nl.json
index 6ca041e5..0d414bc6 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",
@@ -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",
@@ -85,20 +85,20 @@
"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. <a href='{}'>Klik hier</a> om hem te krijgen.<br><br>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_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_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",
"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",
@@ -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": "<a href='{0:s}'>1Wat is dit?</a>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",
@@ -142,8 +142,8 @@
"gui_receive_url_description": "<b>1Iedereen</b>2 met dit OnionShare adres kan bestanden op je computer <b>3plaatsen</b>4 met de <b>5Tor Browser</b>6: <img src='{}' />7",
"gui_url_label_persistent": "Deze share stopt niet vanzelf. <br><br>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. <br><br>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. <br><br>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. <br><br><b>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.</b>",
+ "gui_receive_mode_warning": "Ontvangstmodus laat anderen bestanden op je computer zetten. <br><br><b>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.</b>",
"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,63 @@
"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": "Surf",
+ "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": "<b>1Iedereen</b>2 met dit OnionShare-adres kan je bestanden <b>3bezoeken</b>4 met de <b>5Tor Browser</b>6: <img src='{}' />",
+ "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",
+ "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..13c10b6c 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": "<a href='{0:s}'>O que é isso?</a>",
@@ -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.<br><br>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.<br><br>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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
- "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. <a href='{}'>Clique aqui</a> para obtê-la.<br><br>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": "<b>Qualquer pessoa</b> com este endereço do OnionShare pode <b>baixar</b> seus arquivos usando o <b>Tor Browser</b>: <img src='{}' />",
@@ -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.<br><br><b>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.</b>",
+ "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.<br><br><b>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.</b>",
"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": "<b>Qualquer um</b> com este endereço OnionShare pode <b>visitar</b> seu site usando o <b>navegador Tor</b>: <img src='{}' />",
+ "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 Website"
+} \ No newline at end of file
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"
}
diff --git a/share/locale/ro.json b/share/locale/ro.json
index 36daf7dc..c6b9c4d0 100644
--- a/share/locale/ro.json
+++ b/share/locale/ro.json
@@ -1,19 +1,19 @@
{
"config_onion_service": "",
- "preparing_files": "",
+ "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": "",
- "no_available_port": "",
- "other_page_loaded": "",
- "close_on_autostop_timer": "",
- "closing_automatically": "",
+ "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": "",
+ "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": "<a href='{0:s}'>Ce este asta?</a>",
+ "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. <br> <br> 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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "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. <a href='{}'>Clic aici</a> pentru a o obține.<br><br>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": "<b>Oricine</b> are această adresă OnionShare poate <b>descărca</b> fișierele dvs. folosind <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Oricine</b> are această adresă OnionShare poate <b>încărca</b> fișiere pe computerul dvs. folosind <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Această partajare nu se va opri automat. <br> <br> 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. <br> <br> 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. <br> <br> <br> <b> 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. </b>",
+ "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": "<b>Oricine</b> are această adresă OnionShare poate <b>vizita</b> website-ul dvs. folosind <b>Tor Browser</b>: <img src='{}' />",
+ "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/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"
}
diff --git a/share/locale/sr_Latn.json b/share/locale/sr_Latn.json
new file mode 100644
index 00000000..7fc22ce2
--- /dev/null
+++ b/share/locale/sr_Latn.json
@@ -0,0 +1,183 @@
+{
+ "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": "<a href='{0:s}'>Šta je ovo?</a>",
+ "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.<br><br>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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
+ "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. <a href='{}'>Klikni ovde</a> da bi ga dobio.<br><br>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": "<b>Svako</b> sa ovom OnionShare sdresom može <b>preuzeti</b> tvoje datoteke koristeći <b>Tor Browser</b>: <img src='{}' />",
+ "gui_website_url_description": "<b>Svako</b> sa ovom OnionShare adresom može <b>posetiti</b> tvoju veb-stranicu koristeći <b>Tor Browser</b>: <img src='{}' />",
+ "gui_receive_url_description": "<b>Svako</b> sa ovom OnionShare adresom može <b>poslati</b> datoteke na tvoj računar koristeći <b>Tor Browser</b>: <img src='{}' />",
+ "gui_url_label_persistent": "Ovo deljenje neće se automatski zaustaviti. <br> <br>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. <br> <br>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. <br> <br> <b>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š. </b>",
+ "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/sv.json b/share/locale/sv.json
index 34a718db..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",
@@ -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.",
@@ -78,13 +78,13 @@
"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",
- "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": "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",
@@ -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)",
@@ -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?",
@@ -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. <a href='{}'>Klicka här</a> för att få den.<br><br>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-timern 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_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. 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": "<b>Alla</b> med denna OnionShare-adress kan <b>hämta</b> dina filer med hjälp av <b>Tor Browser</b>: <img src='{}' />",
@@ -158,12 +158,12 @@
"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.<br><br><b>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.</h>",
+ "gui_receive_mode_warning": "Mottagarläge låter personer skicka filer till din dator.<br><br><b>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.</h>",
"receive_mode_upload_starting": "Sändning av total storlek {} börjar",
"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,14 +216,29 @@
"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.",
+ "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",
"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": "<b>Någon</b> med denna OnionShare-adress kan <b>besöka</b> din webbplats med hjälp av <b>Tor Browser</b>: <img src='{}' />",
+ "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 {}",
+ "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"
}
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..7eeb7b1f 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,167 +34,176 @@
"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 ş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",
"gui_settings_whats_this": "<a href='{0:s}'>Bu nedir?</a>",
- "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.<br><br>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.<br><br>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 <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a> 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 <a href='{}'>buraya tıklayın</a>.<br><br>{} 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 <a href='{}'>buraya tıklayın</a>.<br><br>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ş. 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'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 <b>herkes</b> <b>Tor Tarayıcıyı</b> kullanarak dosyalarınızı <b>indirebilir</b>: <img src='{}' />",
"gui_receive_url_description": "Bu OnionShare adresine sahip olan <b>herkes</b> <b>Tor Tarayıcıyı</b> kullanarak dosyaları <b>yükleyebilir</b>: <img src='{}' />",
- "gui_url_label_persistent": "Bu paylaşım otomatik olarak durmayacak.<br><br>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.<br><br>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.<br><br>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.<br><br>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.<br><br><b>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.</b>",
- "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.<br><br><b>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.</b>",
+ "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 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",
- "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 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",
+ "gui_receive_mode_autostop_timer_waiting": "Alma işleminin bitmesi bekleniyor",
"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 <b>herkes</b>, <b>Tor Browser</b>'ı kullanarak web sitenizi <b>ziyaret</b> edebilir: <img src='{}' />",
+ "gui_mode_website_button": "Web Sitesini Yayınla",
+ "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/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 вийшов. <a href='{}'>Натисніть тут</a> щоб його отримати.<br><br>Ваша версія {}, а остання {}.",
- "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": "<b>Будь-хто</b> з цією адресою OnionShare може <b>відвідати</b> ваш веб-сайт за допомогою <b>Tor Browser</b>: <img src='{}' />",
+ "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": "<b>任何人</b>只要有这个 OnionShare 地址,都可以用<b> Tor Browser </b>来<b>下载</b>您的文件:<img src='{}' />",
@@ -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": "<b>任何</b>使用此 OnionShare 地址的人可以使用 <b>Tor 浏览器</b>来<b>访问</b>你的网站:<img src='{}' />",
+ "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 1cee99c7..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設定",
@@ -218,8 +218,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": "預定…",
"gui_share_mode_autostop_timer_waiting": "等待完成傳送",
@@ -227,5 +227,13 @@
"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標頭",
+ "gui_website_url_description": "有OnionShare位址的<b>任何人</b>都可以使用<b>Tor瀏覽器</b>來<b>造訪</b>你的網站: : <img src='{}' />",
+ "gui_mode_website_button": "發佈網站",
+ "gui_website_mode_no_files": "尚未分享網站",
+ "gui_settings_individual_downloads_label": "取消選取以允許下載個別的檔案",
+ "history_requests_tooltip": "{}個網頁請求",
+ "gui_settings_website_label": "網站設定"
}
diff --git a/share/static/css/style.css b/share/static/css/style.css
index e445e5de..af41b155 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;
@@ -70,6 +74,30 @@ header .right ul li {
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;
@@ -223,19 +251,11 @@ li.info {
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;
+a {
+ text-decoration: none;
+ color: #1c1ca0;
}
-.disable-noscript-xss-wrapper {
- max-width: 900px;
- margin: 0 auto;
-}
+a:visited {
+ color: #601ca0;
+} \ No newline at end of file
diff --git a/share/static/img/warning.png b/share/static/img/warning.png
deleted file mode 100644
index 9be8cbaf..00000000
--- a/share/static/img/warning.png
+++ /dev/null
Binary files 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/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/401.html b/share/templates/401.html
new file mode 100644
index 00000000..dc50f534
--- /dev/null
+++ b/share/templates/401.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>OnionShare: 401 Unauthorized Access</title>
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
+</head>
+
+<body>
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">401 Unauthorized Access</p>
+ </div>
+ </div>
+</body>
+
+</html>
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 @@
<head>
<title>OnionShare: 403 Forbidden</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<div class="info-wrapper">
<div class="info">
- <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="info-header">You are not allowed to perform that action at this time.</p>
</div>
</div>
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 @@
<head>
<title>OnionShare: 404 Not Found</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<div class="info-wrapper">
<div class="info">
- <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="info-header">404 Not Found</p>
</div>
</div>
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 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>OnionShare: 405 Method Not Allowed</title>
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
+</head>
+
+<body>
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">405 Method Not Allowed</p>
+ </div>
+ </div>
+</body>
+
+</html>
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 @@
<head>
<title>OnionShare</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
</head>
<body>
diff --git a/share/templates/listing.html b/share/templates/listing.html
new file mode 100644
index 00000000..2f70dbf0
--- /dev/null
+++ b/share/templates/listing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OnionShare</title>
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon" />
+ <link href="{{ static_url_path }}/css/style.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body>
+
+ <header class="clearfix">
+ <img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
+ <h1>OnionShare</h1>
+ </header>
+
+ {% if breadcrumbs %}
+ <ul class="breadcrumbs">
+ {% for breadcrumb in breadcrumbs %}<li><a href="{{ breadcrumb[1] }}">{{ breadcrumb[0] }}</a> <span class="sep">&#8227;</span></li>{% endfor %}<li>{{ breadcrumbs_leaf }}</li>
+ </ul>
+ {% endif %}
+
+ <table class="file-list" id="file-list">
+ <tr>
+ <th id="filename-header">Filename</th>
+ <th id="size-header">Size</th>
+ <th></th>
+ </tr>
+
+ {% for info in dirs %}
+ <tr>
+ <td>
+ <img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_folder.png" />
+ <a href="{{ info.basename }}">
+ {{ info.basename }}
+ </a>
+ </td>
+ <td>&mdash;</td>
+ </tr>
+ {% endfor %}
+
+ {% for info in files %}
+ <tr>
+ <td>
+ <img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_file.png" />
+ <a href="{{ info.basename }}">
+ {{ info.basename }}
+ </a>
+ </td>
+ <td>{{ info.size_human }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </body>
+</html>
diff --git a/share/templates/receive.html b/share/templates/receive.html
index 4f207a03..59a3ebe4 100644
--- a/share/templates/receive.html
+++ b/share/templates/receive.html
@@ -2,31 +2,18 @@
<html>
<head>
<title>OnionShare</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<header class="clearfix">
- <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<div class="upload-wrapper">
- <!--
- We are not using a <noscript> tag because it only works when the security slider is set to
- Safest, not Safer: https://trac.torproject.org/projects/tor/ticket/29506
- -->
- <div id="noscript">
- <p>
- <img src="/static/img/warning.png" title="Warning" /><strong>Warning:</strong> Due to a bug in Tor Browser and Firefox, uploads
- sometimes never finish. To upload reliably, either set your Tor Browser
- <a rel="noreferrer" target="_blank" href="https://tb-manual.torproject.org/en-US/security-slider/">security slider</a>
- to Standard or
- <a target="_blank" href="/noscript-xss-instructions">turn off your Tor Browser's NoScript XSS setting</a>.</p>
- </div>
-
- <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="upload-header">Send Files</p>
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
@@ -45,14 +32,13 @@
</ul>
</div>
- <form id="send" method="post" enctype="multipart/form-data" action="{{ upload_action }}">
+ <form id="send" method="post" enctype="multipart/form-data" action="/upload">
<p><input type="file" id="file-select" name="file[]" multiple /></p>
<p><button type="submit" id="send-button" class="button">Send Files</button></p>
</form>
</div>
- <script src="/static/js/receive-noscript.js"></script>
- <script src="/static/js/jquery-3.4.0.min.js"></script>
- <script async src="/static/js/receive.js"></script>
+ <script src="{{ static_url_path }}/js/jquery-3.4.0.min.js"></script>
+ <script async src="{{ static_url_path }}/js/receive.js"></script>
</body>
</html>
diff --git a/share/templates/receive_noscript_xss.html b/share/templates/receive_noscript_xss.html
deleted file mode 100644
index bce78524..00000000
--- a/share/templates/receive_noscript_xss.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>OnionShare</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
- </head>
- <body>
-
- <header class="clearfix">
- <img class="logo" src="/static/img/logo.png" title="OnionShare">
- <h1>OnionShare</h1>
- </header>
-
- <div class="disable-noscript-xss-wrapper">
- <h3>Disable your Tor Browser's NoScript XSS setting</h3>
-
- <p>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.</p>
-
- <p>Here is how to disable this setting:</p>
-
- <ol>
- <li>Click the menu icon in the top-right of Tor Browser and open "Add-ons"</li>
- <li>Next to the NoScript add-on, click the "Preferences" button</li>
- <li>Switch to the "Advanced" tab</li>
- <li>Uncheck "Sanitize cross-site suspicious requests"</li>
- </ol>
-
- <p>If you'd like to learn technical details about this issue, check
- <a rel="noreferrer" href="https://github.com/micahflee/onionshare/issues/899">this issue</a>
- on GitHub.</p>
- </div>
- </body>
-</html>
diff --git a/share/templates/send.html b/share/templates/send.html
index 2a56829a..941c4130 100644
--- a/share/templates/send.html
+++ b/share/templates/send.html
@@ -3,8 +3,8 @@
<head>
<title>OnionShare</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
<meta name="onionshare-filename" content="{{ filename }}">
<meta name="onionshare-filesize" content="{{ filesize }}">
</head>
@@ -15,45 +15,54 @@
<div class="right">
<ul>
<li>Total size: <strong>{{ filesize_human }}</strong> {% if is_zipped %} (compressed){% endif %}</li>
- {% if slug %}
- <li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
- {% else %}
<li><a class="button" href='/download'>Download Files</a></li>
- {% endif %}
</ul>
</div>
- <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
+ {% if breadcrumbs %}
+ <ul class="breadcrumbs">
+ {% for breadcrumb in breadcrumbs %}<li><a href="{{ breadcrumb[1] }}">{{ breadcrumb[0] }}</a> <span class="sep">&#8227;</span></li>{% endfor %}<li>{{ breadcrumbs_leaf }}</li>
+ </ul>
+ {% endif %}
+
<table class="file-list" id="file-list">
<tr>
<th id="filename-header">Filename</th>
<th id="size-header">Size</th>
<th></th>
</tr>
- {% for info in file_info.dirs %}
+ {% for info in dirs %}
<tr>
<td>
- <img width="30" height="30" title="" alt="" src="/static/img/web_folder.png" />
- {{ info.basename }}
+ <img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_folder.png" />
+ <a href="{{ info.basename }}">
+ {{ info.basename }}
+ </a>
</td>
- <td>{{ info.size_human }}</td>
- <td></td>
+ <td>&mdash;</td>
</tr>
{% endfor %}
- {% for info in file_info.files %}
+
+ {% for info in files %}
<tr>
<td>
- <img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
+ <img width="30" height="30" title="" alt="" src="{{ static_url_path }}/img/web_file.png" />
+ {% if download_individual_files %}
+ <a href="{{ info.basename }}">
+ {{ info.basename }}
+ </a>
+ {% else %}
{{ info.basename }}
+ {% endif %}
</td>
<td>{{ info.size_human }}</td>
- <td></td>
</tr>
{% endfor %}
</table>
- <script async src="/static/js/send.js" charset="utf-8"></script>
+ <script async src="{{ static_url_path }}/js/send.js" charset="utf-8"></script>
</body>
</html>
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 @@
<head>
<title>OnionShare is closed</title>
- <link href="/static/img/favicon.ico" rel="icon" type="image/x-icon">
- <link rel="stylesheet" rel="subresource" type="text/css" href="/static/css/style.css" media="all">
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<header class="clearfix">
- <img class="logo" src="/static/img/logo.png" title="OnionShare">
+ <img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<div class="info-wrapper">
<div class="info">
- <p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="info-header">Thank you for using OnionShare</p>
<p class="info-description">You may now close this window.</p>
</div>
diff --git a/share/version.txt b/share/version.txt
index 879b416e..61618788 100644
--- a/share/version.txt
+++ b/share/version.txt
@@ -1 +1 @@
-2.1
+2.2 \ No newline at end of file
diff --git a/stdeb.cfg b/stdeb.cfg
index 0adbac43..8c7ef6f5 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, python3-distutils, python-nautilus, tor, obfs4proxy
+Build-Depends: python3, python3-all, python3-pytest, python3-requests
Suite: cosmic
X-Python3-Version: >= 3.5.3
diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py
index d3fc9945..6d6340d1 100644
--- a/tests/GuiBaseTest.py
+++ b/tests/GuiBaseTest.py
@@ -2,8 +2,7 @@ import json
import os
import requests
import shutil
-import socket
-import socks
+import base64
from PyQt5 import QtCore, QtTest
@@ -15,22 +14,23 @@ 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):
@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,70 +51,73 @@ 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)
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):
- '''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):
- '''Test that we can make sure the history is toggled off, do an action, and the indiciator works'''
+ 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():
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
@@ -125,208 +128,236 @@ 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.slug)
+ 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:
- 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),
+ )
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.slug)
- 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"
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)
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)
+ 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'''
- 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'''
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
- self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0)
-
+ """Test that the web server has started"""
+ 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_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 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'''
+ """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://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug))
-
+ 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'''
- s = socks.socksocket()
- s.settimeout(60)
- s.connect(('127.0.0.1', self.gui.app.port))
+ """Test that the web page contains a string"""
- if not public_mode:
- path = '/{}'.format(mode.server_status.web.slug)
+ url = "http://127.0.0.1:{}/".format(self.gui.app.port)
+ if public_mode:
+ r = requests.get(url)
else:
- path = '/'
-
- 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/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()
+ 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):
- 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)
- 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):
- '''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:
+ 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):
- '''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 40c3de95..34db1a94 100644
--- a/tests/GuiReceiveTest.py
+++ b/tests/GuiReceiveTest.py
@@ -4,21 +4,48 @@ 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'''
- 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)
+ 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 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:
- path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port)
- response = requests.post(path, files=files)
- if identical_files_at_once:
- # Send a duplicate upload to test for collisions
- response = requests.post(path, files=files)
+ 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
+ ),
+ )
+
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):
@@ -27,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
@@ -37,53 +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')}
- if not public_mode:
- path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug)
+ """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:
- 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", 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):
- response = 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))
- self.assertEqual(response.status_code, 404)
+ """Manipulate the permissions on the upload dir in between tests"""
+ os.chmod("/tmp/OnionShare", mode)
- 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)
- else:
- path = '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
- response = requests.post(path, files={})
- response = requests.post(path, files={})
- response = requests.post(path, files={})
-
- # 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)
+ 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(r.status_code, 401)
+ 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)
@@ -93,40 +106,43 @@ 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)
- 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):
- '''Upload files in receive mode and stop the share'''
+ 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"""
self.run_all_receive_mode_setup_tests(public_mode)
if not public_mode:
- self.try_public_paths_in_non_public_mode()
- self.upload_file(public_mode, '/tmp/test.txt', 'test.txt')
+ 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)
- 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.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, receive_allow_receiver_shutdown):
- '''Attempt to upload (unwritable) files in receive mode and stop the share'''
+ 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)
self.upload_file_should_fail(public_mode)
@@ -142,3 +158,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 29661712..630d0562 100644
--- a/tests/GuiShareTest.py
+++ b/tests/GuiShareTest.py
@@ -2,103 +2,186 @@ import os
import requests
import socks
import zipfile
+import tempfile
from PyQt5 import QtCore, QtTest
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
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_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')
- self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt')
+ 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"
+ )
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'''
- s = socks.socksocket()
- s.settimeout(60)
- s.connect(('127.0.0.1', self.gui.app.port))
+ """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
+ ),
+ )
+
+ 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"))
+ 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:
- path = '/download'
+ r = requests.get(url)
else:
- path = '{}/download'.format(self.gui.share_mode.web.slug)
-
- 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
+ ),
+ )
+
+ 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)
+ 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.download_share(public_mode)
+
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_guess)
+ )
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
if not public_mode:
@@ -111,12 +194,6 @@ class GuiShareTest(GuiBaseTest):
else:
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):
@@ -128,9 +205,8 @@ 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):
"""Tests in share mode after starting a share"""
self.server_working_on_start_button_pressed(self.gui.share_mode)
@@ -139,32 +215,59 @@ 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)
+ 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)
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.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)
+
+ 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.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)
-
def run_all_share_mode_tests(self, public_mode, stay_open):
"""End-to-end share tests"""
self.run_all_share_mode_setup_tests()
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"""
+ 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"""
@@ -176,15 +279,13 @@ 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 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):
"""Auto-stop timer tests in share mode"""
@@ -212,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
new file mode 100644
index 00000000..f6b67112
--- /dev/null
+++ b/tests/GuiWebsiteTest.py
@@ -0,0 +1,136 @@
+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 .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(
+ "<html><body><p>This is a test website hosted by OnionShare</p></body></html>"
+ )
+ 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 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:
+ 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_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"""
+ 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):
+ """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.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 8bd963bd..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.slug)
+ 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.slug)
+ 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.slug)
+ 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.slug))
-
+ 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 a21dd4fc..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.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)
+ 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)
@@ -35,19 +36,23 @@ 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)
- 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)
@@ -56,4 +61,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..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.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)
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()
@@ -53,37 +55,34 @@ 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)
-
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)
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)
-
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 +91,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/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_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_401_public_mode_skips_ratelimit_test.py
index 4fad5532..388a424b 100644
--- a/tests/local_onionshare_404_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 Local404PublicModeRateLimitTest(unittest.TestCase, 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 Local404PublicModeRateLimitTest(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_404(True)
+ self.hit_401(True)
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_401_triggers_ratelimit_test.py
index 49be0f5b..cdeb34db 100644
--- a/tests/local_onionshare_404_triggers_ratelimit_test.py
+++ b/tests/local_onionshare_401_triggers_ratelimit_test.py
@@ -4,12 +4,11 @@ import unittest
from .GuiShareTest import GuiShareTest
-class Local404RateLimitTest(unittest.TestCase, 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 Local404RateLimitTest(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_404(False)
+ 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
new file mode 100644
index 00000000..d69c3e59
--- /dev/null
+++ b/tests/local_onionshare_receive_mode_clear_all_button_test.py
@@ -0,0 +1,26 @@
+#!/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_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 5737bae3..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, 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..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, 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..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, 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..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, True)
+ 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
new file mode 100644
index 00000000..1c11fe81
--- /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()
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
new file mode 100644
index 00000000..18b3283a
--- /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..d41b2010
--- /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()
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_slug_persistent_test.py b/tests/local_onionshare_share_mode_password_persistent_test.py
index 58e1cfeb..067815f7 100644
--- a/tests/local_onionshare_share_mode_slug_persistent_test.py
+++ b/tests/local_onionshare_share_mode_password_persistent_test.py
@@ -4,12 +4,13 @@ import unittest
from .GuiShareTest import GuiShareTest
-class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest):
+
+class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
@classmethod
def setUpClass(cls):
test_settings = {
"public_mode": False,
- "slug": "",
+ "password": "",
"save_private_key": True,
"close_after_first_download": False,
}
@@ -20,10 +21,11 @@ class LocalShareModePersistentSlugTest(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
new file mode 100644
index 00000000..f9fdb983
--- /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_disabled": False}
+ 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
new file mode 100644
index 00000000..ba00e780
--- /dev/null
+++ b/tests/local_onionshare_website_mode_test.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import pytest
+import unittest
+
+from .GuiWebsiteTest import GuiWebsiteTest
+
+
+class LocalWebsiteModeTest(unittest.TestCase, GuiWebsiteTest):
+ @classmethod
+ def setUpClass(cls):
+ test_settings = {"csp_header_disabled": 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/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 d0fb5b22..c5d44733 100644
--- a/tests/onionshare_share_mode_persistent_test.py
+++ b/tests/onionshare_share_mode_persistent_test.py
@@ -4,13 +4,14 @@ 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,
}
@@ -22,10 +23,11 @@ class ShareModePersistentSlugTest(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 f975dce7..1f230295 100644
--- a/tests/test_onionshare_common.py
+++ b/tests/test_onionshare_common.py
@@ -29,39 +29,42 @@ import zipfile
import pytest
-LOG_MSG_REGEX = re.compile(r"""
+LOG_MSG_REGEX = re.compile(
+ r"""
^\[Jun\ 06\ 2013\ 11:05:00\]
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
- \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
-SLUG_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 TestBuildSlug:
- @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_slug_regex(self, test_input, expected):
- """ Test that `SLUG_REGEX` accounts for the following patterns
+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),
+ ),
+ )
+ 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 +72,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:
@@ -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.<function TestLog.test_output.<locals>.dummy_func'
- ' at 0xdeadbeef>'),
- ('[Jun 06 2013 11:05:00]'
- ' TestModule.<function TestLog.test_output.<locals>.dummy_func'
- ' at 0xdeadbeef>: TEST_MSG')
- ))
+ @pytest.mark.parametrize(
+ "test_input",
+ (
+ (
+ "[Jun 06 2013 11:05:00]"
+ " TestModule.<function TestLog.test_output.<locals>.dummy_func"
+ " at 0xdeadbeef>"
+ ),
+ (
+ "[Jun 06 2013 11:05:00]"
+ " TestModule.<function TestLog.test_output.<locals>.dummy_func"
+ " at 0xdeadbeef>: TEST_MSG"
+ ),
+ ),
+ )
def test_log_msg_regex(self, test_input):
assert bool(LOG_MSG_REGEX.match(test_input))
@@ -272,10 +303,10 @@ class TestLog:
# From: https://stackoverflow.com/questions/1218933
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
- common_obj.log('TestModule', dummy_func)
- common_obj.log('TestModule', dummy_func, 'TEST_MSG')
+ common_obj.log("TestModule", dummy_func)
+ common_obj.log("TestModule", dummy_func, "TEST_MSG")
output = buf.getvalue()
- line_one, line_two, _ = output.split('\n')
+ line_one, line_two, _ = output.split("\n")
assert LOG_MSG_REGEX.match(line_one)
assert LOG_MSG_REGEX.match(line_two)
diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py
index bcc2f7cb..0bce2f94 100644
--- a/tests/test_onionshare_settings.py
+++ b/tests/test_onionshare_settings.py
@@ -28,85 +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': '',
- 'slug': '',
- 'hidservauth_string': '',
- 'data_dir': os.path.expanduser('~/OnionShare'),
- 'public_mode': 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
@@ -115,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 0c29859b..b971b31a 100644
--- a/tests/test_onionshare_web.py
+++ b/tests/test_onionshare_web.py
@@ -27,16 +27,18 @@ 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
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):
@@ -44,19 +46,19 @@ 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
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
@@ -68,114 +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 404 pages
- res = c.get('/')
+ # Load / without auth
+ res = c.get("/")
res.get_data()
- assert res.status_code == 404
+ assert res.status_code == 401
- res = c.get('/invalidslug'.format(web.slug))
+ # 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.slug))
+ # 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.slug))
+ 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'.format(web.slug))
+ 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'.format(web.slug))
+ 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 404 pages
- res = c.get('/')
+ # Load / without auth
+ res = c.get("/")
res.get_data()
- assert res.status_code == 404
+ assert res.status_code == 401
- res = c.get('/invalidslug'.format(web.slug))
+ # 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.slug))
+ # Load / with valid auth
+ 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:
- # Upload page should be accessible from /
- res = c.get('/')
+ # Loading / should work without auth
+ res = c.get("/")
data1 = res.get_data()
assert res.status_code == 200
- # /[slug] should be a 404
- res = c.get('/{}'.format(web.slug))
- 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)
+ web = web_obj(common_obj, "receive")
+ common_obj.settings.set("public_mode", False)
with web.app.test_client() as c:
- # / should be a 404
- res = c.get('/')
- data1 = res.get_data()
- assert res.status_code == 404
+ # Load / without auth
+ 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.get_data()
+ assert res.status_code == 200
- # Upload page should be accessible from /[slug]
- res = c.get('/{}'.format(web.slug))
- 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', (
- '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))
@@ -190,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
@@ -210,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))
@@ -223,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"