mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-28 08:49:30 -05:00
Merge branch 'develop' into 910_flatpak
This commit is contained in:
commit
4fdf53f493
@ -1,7 +1,8 @@
|
||||
# Python CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
||||
#
|
||||
# To run the tests, CircleCI needs these environment variables:
|
||||
# QT_EMAIL - email address for a Qt account
|
||||
# QT_PASSWORD - password for a Qt account
|
||||
# (Unfortunately you can't install Qt without logging in.)
|
||||
|
||||
version: 2
|
||||
workflows:
|
||||
version: 2
|
||||
@ -9,11 +10,12 @@ workflows:
|
||||
jobs:
|
||||
- test-3.6
|
||||
- test-3.7
|
||||
- test-3.8
|
||||
|
||||
jobs:
|
||||
test-3.6: &test-template
|
||||
docker:
|
||||
- image: circleci/python:3.6.6
|
||||
- image: circleci/python:3.6-buster
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
@ -21,17 +23,25 @@ jobs:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
name: Install Qt5 binaries
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy
|
||||
sudo pip3 install -r install/requirements.txt
|
||||
sudo pip3 install -r install/requirements-tests.txt
|
||||
sudo pip3 install pytest-cov flake8
|
||||
sudo apt-get install xvfb libdbus-1-3 libxkbcommon-x11-0 libxkbcommon-x11-dev
|
||||
cd ~/
|
||||
wget https://download.qt.io/official_releases/qt/5.14/5.14.0/qt-opensource-linux-x64-5.14.0.run
|
||||
chmod +x qt-opensource-linux-x64-5.14.0.run
|
||||
xvfb-run ./qt-opensource-linux-x64-5.14.0.run --script ~/repo/.circleci/qt-installer-script.js --platform minimal --verbose
|
||||
|
||||
# run tests!
|
||||
- run:
|
||||
name: run flake tests
|
||||
name: Install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-pip xvfb
|
||||
sudo pip3 install poetry flake8
|
||||
poetry install
|
||||
|
||||
- run:
|
||||
name: Run flake tests
|
||||
command: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
@ -39,11 +49,16 @@ jobs:
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- run:
|
||||
name: run tests
|
||||
name: Run unit tests
|
||||
command: |
|
||||
xvfb-run -s "-screen 0 1280x1024x24" pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv --no-qt-log tests/
|
||||
xvfb-run -s "-screen 0 1280x1024x24" poetry run ./tests/run.sh --rungui
|
||||
|
||||
test-3.7:
|
||||
<<: *test-template
|
||||
docker:
|
||||
- image: circleci/python:3.7.1
|
||||
- image: circleci/python:3.7-buster
|
||||
|
||||
test-3.8:
|
||||
<<: *test-template
|
||||
docker:
|
||||
- image: circleci/python:3.8-buster
|
||||
|
75
.circleci/qt-installer-script.js
Normal file
75
.circleci/qt-installer-script.js
Normal file
@ -0,0 +1,75 @@
|
||||
function Controller() {
|
||||
installer.installationFinished.connect(proceed)
|
||||
}
|
||||
|
||||
function logCurrentPage() {
|
||||
var pageName = page().objectName
|
||||
var pagePrettyTitle = page().title
|
||||
console.log("At page: " + pageName + " ('" + pagePrettyTitle + "')")
|
||||
}
|
||||
|
||||
function page() {
|
||||
return gui.currentPageWidget()
|
||||
}
|
||||
|
||||
function proceed(button, delay) {
|
||||
gui.clickButton(button || buttons.NextButton, delay)
|
||||
}
|
||||
|
||||
Controller.prototype.WelcomePageCallback = function() {
|
||||
logCurrentPage()
|
||||
proceed(buttons.NextButton, 2000)
|
||||
}
|
||||
|
||||
Controller.prototype.CredentialsPageCallback = function() {
|
||||
logCurrentPage()
|
||||
page().loginWidget.EmailLineEdit.text = installer.environmentVariable("QT_EMAIL");
|
||||
page().loginWidget.PasswordLineEdit.text = installer.environmentVariable("QT_PASSWORD");
|
||||
proceed()
|
||||
}
|
||||
|
||||
Controller.prototype.IntroductionPageCallback = function() {
|
||||
logCurrentPage()
|
||||
proceed()
|
||||
}
|
||||
|
||||
Controller.prototype.TargetDirectoryPageCallback = function() {
|
||||
logCurrentPage()
|
||||
proceed()
|
||||
}
|
||||
|
||||
Controller.prototype.ComponentSelectionPageCallback = function() {
|
||||
logCurrentPage()
|
||||
page().deselectAll()
|
||||
page().selectComponent("qt.qt5.5140.gcc_64")
|
||||
proceed()
|
||||
}
|
||||
|
||||
Controller.prototype.LicenseAgreementPageCallback = function() {
|
||||
logCurrentPage()
|
||||
page().AcceptLicenseRadioButton.checked = true
|
||||
gui.clickButton(buttons.NextButton)
|
||||
}
|
||||
|
||||
Controller.prototype.ReadyForInstallationPageCallback = function() {
|
||||
logCurrentPage()
|
||||
proceed()
|
||||
}
|
||||
|
||||
Controller.prototype.PerformInstallationPageCallback = function() {
|
||||
logCurrentPage()
|
||||
}
|
||||
|
||||
Controller.prototype.FinishedPageCallback = function() {
|
||||
logCurrentPage()
|
||||
page().LaunchQtCreatorCheckBoxForm.launchQtCreatorCheckBox.checked = false
|
||||
proceed(buttons.FinishButton)
|
||||
}
|
||||
|
||||
Controller.prototype.DynamicTelemetryPluginFormCallback = function() {
|
||||
logCurrentPage()
|
||||
console.log(Object.keys(page().TelemetryPluginForm.statisticGroupBox))
|
||||
var radioButtons = page().TelemetryPluginForm.statisticGroupBox
|
||||
radioButtons.disableStatisticRadioButton.checked = true
|
||||
proceed()
|
||||
}
|
151
BUILD.md
151
BUILD.md
@ -1,3 +1,22 @@
|
||||
# Index
|
||||
* [Building OnionShare](#building-onionshare)
|
||||
* [Linux](#linux)
|
||||
* [Use newest software](#use-newest-software)
|
||||
* [Use package managers](#use-package-managers)
|
||||
* [macOS](#macos)
|
||||
* [Windows](#windows)
|
||||
* [Setting up your dev environment](#setting-up-your-dev-environment)
|
||||
* [To make a .exe](#to-make-a-exe)
|
||||
* [To build the installer](#to-build-the-installer)
|
||||
* [Running tests](#running-tests)
|
||||
* [Making releases](#making-releases)
|
||||
* [Changelog, version, and signed git tag](#changelog-version-and-signed-git-tag)
|
||||
* [Linux release](#linux-release)
|
||||
* [macOS release](#macos-release)
|
||||
* [Windows release](#windows-release)
|
||||
* [Source package](#source-package)
|
||||
* [Publishing the release](#publishing-the-release)
|
||||
|
||||
# Building OnionShare
|
||||
|
||||
Start by getting the source code:
|
||||
@ -9,18 +28,64 @@ cd onionshare
|
||||
|
||||
## Linux
|
||||
|
||||
### Use newest software
|
||||
|
||||
The recommended way to develop OnionShare is to use the latest versions of all dependencies.
|
||||
|
||||
First, install `tor` from either the [official Debian repository](https://support.torproject.org/apt/tor-deb-repo/), or from your package manager.
|
||||
|
||||
Then download Qt 5.14.0 for Linux:
|
||||
|
||||
```sh
|
||||
cd ~/Downloads
|
||||
wget https://download.qt.io/official_releases/qt/5.14/5.14.0/qt-opensource-linux-x64-5.14.0.run
|
||||
```
|
||||
|
||||
If you'd like to check to make sure you have the exact installer I have, here is the sha256 checksum:
|
||||
|
||||
```sh
|
||||
sha256sum qt-opensource-linux-x64-5.14.0.run
|
||||
4379f147c6793ec7e7349d2f9ee7d53b8ab6ea4e4edf8ee0574a75586a6a6e0e qt-opensource-linux-x64-5.14.0.run
|
||||
```
|
||||
|
||||
Then make it executable and install Qt:
|
||||
|
||||
```sh
|
||||
chmod +x qt-opensource-linux-x64-5.14.0.run
|
||||
./qt-opensource-linux-x64-5.14.0.run
|
||||
```
|
||||
|
||||
You have to create a Qt account and login to install Qt. Choose the default installation folder in your home directory. The only component you need is `Qt 5.14.0` > `Desktop gcc 64-bit`.
|
||||
|
||||
Install [poetry](https://python-poetry.org/docs/) from your package manager, or by doing `pip install --user poetry`. Then install dependencies:
|
||||
|
||||
```sh
|
||||
poetry install
|
||||
```
|
||||
|
||||
You can run the CLI and the GUI versions of OnionShare like this:
|
||||
|
||||
```sh
|
||||
poetry run ./dev_scripts/onionshare
|
||||
poetry run ./dev_scripts/onionshare-gui
|
||||
```
|
||||
|
||||
### Use package managers
|
||||
|
||||
Alternatively, you can install dependencies from package managers.
|
||||
|
||||
Install the needed dependencies:
|
||||
|
||||
For Debian-like distros:
|
||||
**For Debian-like distros:**
|
||||
|
||||
```
|
||||
apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth python3-distutils
|
||||
apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest python3-pytestqt build-essential fakeroot python3-all python3-stdeb dh-python python3-flask-httpauth python3-distutils python3-psutil python3-watchdog
|
||||
```
|
||||
|
||||
For Fedora-like distros:
|
||||
**For Fedora-like distros:**
|
||||
|
||||
```
|
||||
dnf install -y python3-flask python3-flask-httpauth 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 python3-psutil python3-watchdog
|
||||
```
|
||||
|
||||
After that you can try both the CLI and the GUI version of OnionShare:
|
||||
@ -36,7 +101,7 @@ Create a .deb on Debian-like distros: `./install/build_deb.sh`
|
||||
|
||||
Create a .rpm on Fedora-like distros: `./install/build_rpm.sh`
|
||||
|
||||
For OpenSuSE: There are instructions for building [in the wiki](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support#opensuse-leap-150).
|
||||
For openSUSE: There are instructions for building [in the wiki](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support#opensuse-leap-150).
|
||||
|
||||
For ArchLinux: There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare.
|
||||
|
||||
@ -52,24 +117,17 @@ You may also need to run the command `/Applications/Python\ 3.7/Install\ Certifi
|
||||
|
||||
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:
|
||||
If you don't have it already, install poetry (`pip3 install --user poetry`). Then install dependencies:
|
||||
|
||||
```sh
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
```
|
||||
|
||||
Then install the dependencies:
|
||||
|
||||
```sh
|
||||
pip3 install -r install/requirements.txt
|
||||
poetry install
|
||||
```
|
||||
|
||||
#### You can run both the CLI and GUI versions of OnionShare without building an bundle
|
||||
|
||||
```sh
|
||||
./dev_scripts/onionshare
|
||||
./dev_scripts/onionshare-gui
|
||||
poetry run ./dev_scripts/onionshare
|
||||
poetry run ./dev_scripts/onionshare-gui
|
||||
```
|
||||
|
||||
#### To build the app bundle
|
||||
@ -94,19 +152,25 @@ Now you should have `dist/OnionShare.pkg`.
|
||||
|
||||
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:
|
||||
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`.
|
||||
|
||||
```cmd
|
||||
pip install -r install\requirements.txt
|
||||
Install [poetry](https://python-poetry.org/). Open PowerShell, and run:
|
||||
|
||||
```
|
||||
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python
|
||||
```
|
||||
|
||||
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`.
|
||||
And add `%USERPROFILE%\.poetry\bin` to your path. Then open a command prompt and cd to the `dangerzone` folder, and install the poetry dependencies:
|
||||
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
After that you can try both the CLI and the GUI version of OnionShare:
|
||||
|
||||
```
|
||||
python dev_scripts\onionshare
|
||||
python dev_scripts\onionshare-gui
|
||||
poetry run python dev_scripts\onionshare
|
||||
poetry run python dev_scripts\onionshare-gui
|
||||
```
|
||||
|
||||
#### If you want to build a .exe
|
||||
@ -141,19 +205,6 @@ cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\
|
||||
vcvars32.bat
|
||||
```
|
||||
|
||||
Make sure you have a new enough `setuptools`:
|
||||
|
||||
```
|
||||
pip install --upgrade setuptools
|
||||
```
|
||||
|
||||
Now make sure you don't have PyInstaller installed from pip:
|
||||
|
||||
```
|
||||
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 and checkout the `v3.5` tag:
|
||||
|
||||
```
|
||||
@ -164,6 +215,14 @@ git tag -v v3.5
|
||||
|
||||
(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).)
|
||||
|
||||
The next step is to compile the bootloader. We should do this all in dangerzone's poetry shell:
|
||||
|
||||
```
|
||||
cd onionshare
|
||||
poetry shell
|
||||
cd ..\pyinstaller
|
||||
```
|
||||
|
||||
And compile the bootloader, following [these instructions](https://pythonhosted.org/PyInstaller/bootloader-building.html). To compile, run this:
|
||||
|
||||
```
|
||||
@ -171,11 +230,12 @@ cd bootloader
|
||||
python waf distclean all --target-arch=32bit --msvc_targets=x86
|
||||
```
|
||||
|
||||
Finally, install the PyInstaller module into your local site-packages:
|
||||
Finally, install the PyInstaller module into your poetry environment:
|
||||
|
||||
```
|
||||
cd ..
|
||||
python setup.py install
|
||||
exit
|
||||
```
|
||||
|
||||
Now the next time you use PyInstaller to build OnionShare, the `.exe` file should not be flagged as malicious by anti-virus.
|
||||
@ -204,28 +264,22 @@ This will prompt you to codesign three binaries and execute one unsigned binary.
|
||||
|
||||
# Running tests
|
||||
|
||||
OnionShare includes PyTest unit tests. To run the tests, first install some dependencies:
|
||||
OnionShare includes PyTest unit tests. To run tests, you can run `pytest` against the `tests/` directory.
|
||||
|
||||
```sh
|
||||
pip3 install -r install/requirements-tests.txt
|
||||
```
|
||||
|
||||
Then you can run `pytest` against the `tests/` directory.
|
||||
|
||||
```sh
|
||||
pytest tests/
|
||||
poetry run ./tests/run.sh
|
||||
```
|
||||
|
||||
You can run GUI tests like this:
|
||||
|
||||
```sh
|
||||
pytest --rungui tests/
|
||||
poetry run ./tests/run.sh --rungui
|
||||
```
|
||||
|
||||
If you would like to also run the GUI unit tests in 'tor' mode, start Tor Browser in the background, then run:
|
||||
|
||||
```sh
|
||||
pytest --rungui --runtor tests/
|
||||
poetry run ./tests/run.sh --rungui --runtor
|
||||
```
|
||||
|
||||
Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive.
|
||||
@ -233,7 +287,7 @@ Keep in mind that the Tor tests take a lot longer to run than local mode, but th
|
||||
You can also choose to wrap the tests in `xvfb-run` so that a ton of OnionShare windows don't pop up on your desktop (you may need to install the `xorg-x11-server-Xvfb` package), like this:
|
||||
|
||||
```sh
|
||||
xvfb-run pytest --rungui tests/
|
||||
xvfb-run poetry run ./tests/run.sh --rungui
|
||||
```
|
||||
|
||||
# Making releases
|
||||
@ -245,8 +299,9 @@ This section documents the release process. Unless you're a core OnionShare deve
|
||||
Before making a release, all of these should be complete:
|
||||
|
||||
* `share/version.txt` should have the correct version
|
||||
* `pyproject.toml` should have the correct version
|
||||
* `install/org.onionshare.OnionShare.appdata.xml` should have the correct version
|
||||
* `install/onionshare.nsi` should have the correct version
|
||||
* `install/onionshare.nsi` should have the correct version, for the Windows installer
|
||||
* `CHANGELOG.md` should be updated to include a list of all major changes since the last release
|
||||
* There must be a PGP-signed git tag for the version, e.g. for OnionShare 2.1, the tag must be `v2.1`
|
||||
|
||||
|
@ -4,6 +4,7 @@ GenericName=OnionShare Client
|
||||
Comment=Share a file securely and anonymously over Tor
|
||||
Comment[da]=Del en fil sikkert og anonymt over Tor
|
||||
Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk
|
||||
Comment[hr]=Dijeli datoteku sigurno i anonimno preko Tora
|
||||
Exec=/usr/bin/onionshare-gui
|
||||
Terminal=false
|
||||
Type=Application
|
||||
@ -12,5 +13,6 @@ Categories=Network;FileTransfer;
|
||||
Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting;
|
||||
Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting;
|
||||
Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting;
|
||||
Keywords[hr]=tor;anonimnost;privatnost;Onion usluga;dijeljenje datoteka;hosting datoteka;
|
||||
StartupNotify=true
|
||||
StartupWMClass=onionshare
|
||||
|
@ -1,10 +0,0 @@
|
||||
atomicwrites==1.3.0
|
||||
attrs==19.1.0
|
||||
more-itertools==7.2.0
|
||||
pluggy==0.13.0
|
||||
py==1.8.0
|
||||
pytest==5.1.2
|
||||
pytest-faulthandler==2.0.1
|
||||
pytest-qt==3.2.2
|
||||
six==1.12.0
|
||||
urllib3==1.25.3
|
@ -1,22 +0,0 @@
|
||||
altgraph==0.16.1
|
||||
certifi==2019.9.11
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
Flask==1.1.1
|
||||
Flask-HTTPAuth==3.3.0
|
||||
future==0.17.1
|
||||
idna==2.8
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.10.1
|
||||
macholib==1.11
|
||||
MarkupSafe==1.1.1
|
||||
pefile==2019.4.18
|
||||
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.25.3
|
||||
Werkzeug==0.15.6
|
@ -3,7 +3,10 @@ import sys
|
||||
import json
|
||||
import locale
|
||||
import subprocess
|
||||
import urllib
|
||||
try:
|
||||
import urllib.request
|
||||
except:
|
||||
import urllib
|
||||
import gi
|
||||
|
||||
gi.require_version("Nautilus", "3.0")
|
||||
@ -67,7 +70,10 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||
def url2path(self, url):
|
||||
file_uri = url.get_activation_uri()
|
||||
arg_uri = file_uri[7:]
|
||||
path = urllib.url2pathname(arg_uri)
|
||||
try:
|
||||
path = urllib.request.url2pathname(arg_uri)
|
||||
except:
|
||||
path = urllib.url2pathname(arg_uri)
|
||||
return path
|
||||
|
||||
def exec_onionshare(self, filenames):
|
||||
|
@ -26,11 +26,12 @@ from .common import Common
|
||||
from .web import Web
|
||||
from .onion import *
|
||||
from .onionshare import OnionShare
|
||||
from .mode_settings import ModeSettings
|
||||
|
||||
|
||||
def build_url(common, app, web):
|
||||
def build_url(mode_settings, app, web):
|
||||
# Build the URL
|
||||
if common.settings.get("public_mode"):
|
||||
if mode_settings.get("general", "public"):
|
||||
return f"http://{app.onion_host}"
|
||||
else:
|
||||
return f"http://onionshare:{web.password}@{app.onion_host}"
|
||||
@ -45,6 +46,30 @@ def main(cwd=None):
|
||||
|
||||
# Display OnionShare banner
|
||||
print(f"OnionShare {common.version} | https://onionshare.org/")
|
||||
reset='\033[0m'
|
||||
purple='\33[95m'
|
||||
print(purple)
|
||||
print(" @@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ___ _ ")
|
||||
print(" @@@@@@ @@@@@@@@@@@@@ / _ \ (_) ")
|
||||
print(" @@@@ @ @@@@@@@@@@@ | | | |_ __ _ ___ _ __ ")
|
||||
print(" @@@@@@@@ @@@@@@@@@@ | | | | '_ \| |/ _ \| '_ \ ")
|
||||
print(" @@@@@@@@@@@@ @@@@@@@@@@ \ \_/ / | | | | (_) | | | | ")
|
||||
print(" @@@@@@@@@@@@@@@@ @@@@@@@@@ \___/|_| |_|_|\___/|_| |_| ")
|
||||
print(" @@@@@@@@@ @@@@@@@@@@@@@@@@ _____ _ ")
|
||||
print(" @@@@@@@@@@ @@@@@@@@@@@@ / ___| | ")
|
||||
print(" @@@@@@@@@@ @@@@@@@@ \ `--.| |__ __ _ _ __ ___ ")
|
||||
print(" @@@@@@@@@@@ @ @@@@ `--. \ '_ \ / _` | '__/ _ \\")
|
||||
print(" @@@@@@@@@@@@@ @@@@@@ /\__/ / | | | (_| | | | __/")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \____/|_| |_|\__,_|_| \___|")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@@@@@@@@@@@ ")
|
||||
print(" @@@@@@@@@ ")
|
||||
print(reset)
|
||||
|
||||
# OnionShare CLI in OSX needs to change current working directory (#132)
|
||||
if common.platform == "Darwin":
|
||||
@ -55,63 +80,101 @@ def main(cwd=None):
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=28)
|
||||
)
|
||||
# Select modes
|
||||
parser.add_argument(
|
||||
"--receive", action="store_true", dest="receive", help="Receive files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--website", action="store_true", dest="website", help="Publish website"
|
||||
)
|
||||
# Tor connection-related args
|
||||
parser.add_argument(
|
||||
"--local-only",
|
||||
action="store_true",
|
||||
dest="local_only",
|
||||
default=False,
|
||||
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>",
|
||||
metavar="SECONDS",
|
||||
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)",
|
||||
metavar="FILENAME",
|
||||
default=None,
|
||||
help="Filename of custom global settings",
|
||||
)
|
||||
# Persistent file
|
||||
parser.add_argument(
|
||||
"--persistent",
|
||||
metavar="FILENAME",
|
||||
default=None,
|
||||
help="Filename of persistent session",
|
||||
)
|
||||
# General args
|
||||
parser.add_argument(
|
||||
"--public",
|
||||
action="store_true",
|
||||
dest="public",
|
||||
default=False,
|
||||
help="Don't use a password",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-start-timer",
|
||||
metavar="SECONDS",
|
||||
dest="autostart_timer",
|
||||
default=0,
|
||||
help="Start onion service at scheduled time (N seconds from now)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-stop-timer",
|
||||
metavar="SECONDS",
|
||||
dest="autostop_timer",
|
||||
default=0,
|
||||
help="Stop onion service at schedule time (N seconds from now)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--legacy",
|
||||
action="store_true",
|
||||
dest="legacy",
|
||||
default=False,
|
||||
help="Use legacy address (v2 onion service, not recommended)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--client-auth",
|
||||
action="store_true",
|
||||
dest="client_auth",
|
||||
default=False,
|
||||
help="Use client authorization (requires --legacy)",
|
||||
)
|
||||
# Share args
|
||||
parser.add_argument(
|
||||
"--autostop-sharing",
|
||||
action="store_true",
|
||||
dest="autostop_sharing",
|
||||
default=True,
|
||||
help="Share files: Stop sharing after files have been sent",
|
||||
)
|
||||
# Receive args
|
||||
parser.add_argument(
|
||||
"--data-dir",
|
||||
metavar="data_dir",
|
||||
default=None,
|
||||
help="Receive files: Save files received to this directory",
|
||||
)
|
||||
# Website args
|
||||
parser.add_argument(
|
||||
"--disable_csp",
|
||||
action="store_true",
|
||||
dest="disable_csp",
|
||||
default=False,
|
||||
help="Publish website: Disable Content Security Policy header (allows your website to use third-party resources)",
|
||||
)
|
||||
# Other
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
@ -131,16 +194,21 @@ def main(cwd=None):
|
||||
for i in range(len(filenames)):
|
||||
filenames[i] = os.path.abspath(filenames[i])
|
||||
|
||||
local_only = bool(args.local_only)
|
||||
verbose = bool(args.verbose)
|
||||
stay_open = bool(args.stay_open)
|
||||
autostart_timer = int(args.autostart_timer)
|
||||
autostop_timer = int(args.autostop_timer)
|
||||
connect_timeout = int(args.connect_timeout)
|
||||
stealth = bool(args.stealth)
|
||||
receive = bool(args.receive)
|
||||
website = bool(args.website)
|
||||
config = args.config
|
||||
local_only = bool(args.local_only)
|
||||
connect_timeout = int(args.connect_timeout)
|
||||
config_filename = args.config
|
||||
persistent_filename = args.persistent
|
||||
public = bool(args.public)
|
||||
autostart_timer = int(args.autostart_timer)
|
||||
autostop_timer = int(args.autostop_timer)
|
||||
legacy = bool(args.legacy)
|
||||
client_auth = bool(args.client_auth)
|
||||
autostop_sharing = bool(args.autostop_sharing)
|
||||
data_dir = args.data_dir
|
||||
disable_csp = bool(args.disable_csp)
|
||||
verbose = bool(args.verbose)
|
||||
|
||||
if receive:
|
||||
mode = "receive"
|
||||
@ -149,42 +217,86 @@ def main(cwd=None):
|
||||
else:
|
||||
mode = "share"
|
||||
|
||||
# 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
|
||||
valid = True
|
||||
for filename in filenames:
|
||||
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
||||
print(f"{filename} is not a valid file.")
|
||||
valid = False
|
||||
if not os.access(filename, os.R_OK):
|
||||
print(f"{filename} is not a readable file.")
|
||||
valid = False
|
||||
if not valid:
|
||||
sys.exit()
|
||||
|
||||
# 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
|
||||
|
||||
# client_auth can only be set if legacy is also set
|
||||
if client_auth and not legacy:
|
||||
print(
|
||||
"Client authentication (--client-auth) is only supported with with legacy onion services (--legacy)"
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
# Re-load settings, if a custom config was passed in
|
||||
if config_filename:
|
||||
common.load_settings(config_filename)
|
||||
else:
|
||||
common.load_settings()
|
||||
|
||||
# Mode settings
|
||||
if persistent_filename:
|
||||
mode_settings = ModeSettings(common, persistent_filename)
|
||||
mode_settings.set("persistent", "enabled", True)
|
||||
else:
|
||||
mode_settings = ModeSettings(common)
|
||||
|
||||
if mode_settings.just_created:
|
||||
# This means the mode settings were just created, not loaded from disk
|
||||
mode_settings.set("general", "public", public)
|
||||
mode_settings.set("general", "autostart_timer", autostart_timer)
|
||||
mode_settings.set("general", "autostop_timer", autostop_timer)
|
||||
mode_settings.set("general", "legacy", legacy)
|
||||
mode_settings.set("general", "client_auth", client_auth)
|
||||
if mode == "share":
|
||||
mode_settings.set("share", "autostop_sharing", autostop_sharing)
|
||||
if mode == "receive":
|
||||
if data_dir:
|
||||
mode_settings.set("receive", "data_dir", data_dir)
|
||||
if mode == "website":
|
||||
mode_settings.set("website", "disable_csp", disable_csp)
|
||||
else:
|
||||
# See what the persistent mode was
|
||||
mode = mode_settings.get("persistent", "mode")
|
||||
|
||||
# In share and website mode, you must supply a list of filenames
|
||||
if mode == "share" or mode == "website":
|
||||
# Unless you passed in a persistent filename, in which case get the filenames from
|
||||
# the mode settings
|
||||
if persistent_filename and not mode_settings.just_created:
|
||||
filenames = mode_settings.get(mode, "filenames")
|
||||
|
||||
else:
|
||||
# Make sure filenames given if not using receiver mode
|
||||
if len(filenames) == 0:
|
||||
if persistent_filename:
|
||||
mode_settings.delete()
|
||||
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# Validate filenames
|
||||
valid = True
|
||||
for filename in filenames:
|
||||
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
||||
print(f"{filename} is not a valid file.")
|
||||
valid = False
|
||||
if not os.access(filename, os.R_OK):
|
||||
print(f"{filename} is not a readable file.")
|
||||
valid = False
|
||||
if not valid:
|
||||
sys.exit()
|
||||
|
||||
# Create the Web object
|
||||
web = Web(common, False, mode)
|
||||
web = Web(common, False, mode_settings, mode)
|
||||
|
||||
# Start the Onion object
|
||||
onion = Onion(common)
|
||||
onion = Onion(common, use_tmp_dir=True)
|
||||
try:
|
||||
onion.connect(
|
||||
custom_settings=False, config=config, connect_timeout=connect_timeout
|
||||
custom_settings=False,
|
||||
config=config_filename,
|
||||
connect_timeout=connect_timeout,
|
||||
local_only=local_only,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
@ -195,36 +307,35 @@ def main(cwd=None):
|
||||
# Start the onionshare app
|
||||
try:
|
||||
common.settings.load()
|
||||
if not common.settings.get("public_mode"):
|
||||
web.generate_password(common.settings.get("password"))
|
||||
if not mode_settings.get("general", "public"):
|
||||
web.generate_password(mode_settings.get("onion", "password"))
|
||||
else:
|
||||
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:
|
||||
if autostop_timer > 0 and 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."
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
app.start_onion_service(False, True)
|
||||
url = build_url(common, app, web)
|
||||
app.start_onion_service(mode_settings, False, True)
|
||||
url = build_url(mode_settings, app, web)
|
||||
schedule = datetime.now() + timedelta(seconds=autostart_timer)
|
||||
if mode == "receive":
|
||||
print(
|
||||
f"Files sent to you appear in this folder: {common.settings.get('data_dir')}"
|
||||
f"Files sent to you appear in this folder: {mode_settings.get('receive', '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:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print(
|
||||
f"Give this address and HidServAuth lineto your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
|
||||
)
|
||||
@ -234,7 +345,7 @@ def main(cwd=None):
|
||||
f"Give this address to your sender, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
|
||||
)
|
||||
else:
|
||||
if stealth:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print(
|
||||
f"Give this address and HidServAuth line to your recipient, and tell them it won't be accessible until: {schedule.strftime('%I:%M:%S%p, %b %d, %y')}"
|
||||
)
|
||||
@ -248,9 +359,9 @@ def main(cwd=None):
|
||||
print("Waiting for the scheduled time before starting...")
|
||||
app.onion.cleanup(False)
|
||||
time.sleep(autostart_timer)
|
||||
app.start_onion_service()
|
||||
app.start_onion_service(mode_settings)
|
||||
else:
|
||||
app.start_onion_service()
|
||||
app.start_onion_service(mode_settings)
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
sys.exit()
|
||||
@ -284,10 +395,7 @@ def main(cwd=None):
|
||||
print("")
|
||||
|
||||
# Start OnionShare http service in new thread
|
||||
t = threading.Thread(
|
||||
target=web.start,
|
||||
args=(app.port, stay_open, common.settings.get("public_mode"), web.password),
|
||||
)
|
||||
t = threading.Thread(target=web.start, args=(app.port,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
@ -300,13 +408,13 @@ def main(cwd=None):
|
||||
app.autostop_timer_thread.start()
|
||||
|
||||
# Save the web password if we are using a persistent private key
|
||||
if common.settings.get("save_private_key"):
|
||||
if not common.settings.get("password"):
|
||||
common.settings.set("password", web.password)
|
||||
common.settings.save()
|
||||
if mode_settings.get("persistent", "enabled"):
|
||||
if not mode_settings.get("onion", "password"):
|
||||
mode_settings.set("onion", "password", web.password)
|
||||
# mode_settings.save()
|
||||
|
||||
# Build the URL
|
||||
url = build_url(common, app, web)
|
||||
url = build_url(mode_settings, app, web)
|
||||
|
||||
print("")
|
||||
if autostart_timer > 0:
|
||||
@ -314,7 +422,7 @@ def main(cwd=None):
|
||||
else:
|
||||
if mode == "receive":
|
||||
print(
|
||||
f"Files sent to you appear in this folder: {common.settings.get('data_dir')}"
|
||||
f"Files sent to you appear in this folder: {mode_settings.get('receive', 'data_dir')}"
|
||||
)
|
||||
print("")
|
||||
print(
|
||||
@ -322,7 +430,7 @@ def main(cwd=None):
|
||||
)
|
||||
print("")
|
||||
|
||||
if stealth:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print("Give this address and HidServAuth to the sender:")
|
||||
print(url)
|
||||
print(app.auth_string)
|
||||
@ -330,7 +438,7 @@ def main(cwd=None):
|
||||
print("Give this address to the sender:")
|
||||
print(url)
|
||||
else:
|
||||
if stealth:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
print("Give this address and HidServAuth line to the recipient:")
|
||||
print(url)
|
||||
print(app.auth_string)
|
||||
|
@ -32,7 +32,7 @@ import time
|
||||
from .settings import Settings
|
||||
|
||||
|
||||
class Common(object):
|
||||
class Common:
|
||||
"""
|
||||
The Common object is shared amongst all parts of OnionShare.
|
||||
"""
|
||||
@ -174,249 +174,46 @@ class Common(object):
|
||||
else:
|
||||
onionshare_data_dir = os.path.expanduser("~/.config/onionshare")
|
||||
|
||||
# Modify the data dir if running tests
|
||||
if getattr(sys, "onionshare_test_mode", False):
|
||||
onionshare_data_dir += "-testdata"
|
||||
|
||||
os.makedirs(onionshare_data_dir, 0o700, True)
|
||||
return onionshare_data_dir
|
||||
|
||||
def build_password(self):
|
||||
def build_tmp_dir(self):
|
||||
"""
|
||||
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
||||
Returns path to a folder that can hold temporary files
|
||||
"""
|
||||
tmp_dir = os.path.join(self.build_data_dir(), "tmp")
|
||||
os.makedirs(tmp_dir, 0o700, True)
|
||||
return tmp_dir
|
||||
|
||||
def build_persistent_dir(self):
|
||||
"""
|
||||
Returns the path to the folder that holds persistent files
|
||||
"""
|
||||
persistent_dir = os.path.join(self.build_data_dir(), "persistent")
|
||||
os.makedirs(persistent_dir, 0o700, True)
|
||||
return persistent_dir
|
||||
|
||||
def build_tor_dir(self):
|
||||
"""
|
||||
Returns path to the tor data directory
|
||||
"""
|
||||
tor_dir = os.path.join(self.build_data_dir(), "tor_data")
|
||||
os.makedirs(tor_dir, 0o700, True)
|
||||
return tor_dir
|
||||
|
||||
def build_password(self, word_count=2):
|
||||
"""
|
||||
Returns a random string made of words from the wordlist, such as "deter-trig".
|
||||
"""
|
||||
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))
|
||||
|
||||
def define_css(self):
|
||||
"""
|
||||
This defines all of the stylesheets used in GUI mode, to avoid repeating code.
|
||||
This method is only called in GUI mode.
|
||||
"""
|
||||
self.css = {
|
||||
# OnionShareGui styles
|
||||
"mode_switcher_selected_style": """
|
||||
QPushButton {
|
||||
color: #ffffff;
|
||||
background-color: #4e064f;
|
||||
border: 0;
|
||||
border-right: 1px solid #69266b;
|
||||
font-weight: bold;
|
||||
border-radius: 0;
|
||||
}""",
|
||||
"mode_switcher_unselected_style": """
|
||||
QPushButton {
|
||||
color: #ffffff;
|
||||
background-color: #601f61;
|
||||
border: 0;
|
||||
font-weight: normal;
|
||||
border-radius: 0;
|
||||
}""",
|
||||
"settings_button": """
|
||||
QPushButton {
|
||||
background-color: #601f61;
|
||||
border: 0;
|
||||
border-left: 1px solid #69266b;
|
||||
border-radius: 0;
|
||||
}""",
|
||||
"server_status_indicator_label": """
|
||||
QLabel {
|
||||
font-style: italic;
|
||||
color: #666666;
|
||||
padding: 2px;
|
||||
}""",
|
||||
"status_bar": """
|
||||
QStatusBar {
|
||||
font-style: italic;
|
||||
color: #666666;
|
||||
}
|
||||
QStatusBar::item {
|
||||
border: 0px;
|
||||
}""",
|
||||
# Common styles between modes and their child widgets
|
||||
"mode_info_label": """
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
""",
|
||||
"server_status_url": """
|
||||
QLabel {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
border: 1px solid #666666;
|
||||
font-size: 12px;
|
||||
}
|
||||
""",
|
||||
"server_status_url_buttons": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
}
|
||||
""",
|
||||
"server_status_button_stopped": """
|
||||
QPushButton {
|
||||
background-color: #5fa416;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}""",
|
||||
"server_status_button_working": """
|
||||
QPushButton {
|
||||
background-color: #4c8211;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
font-style: italic;
|
||||
}""",
|
||||
"server_status_button_started": """
|
||||
QPushButton {
|
||||
background-color: #d0011b;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}""",
|
||||
"downloads_uploads_empty": """
|
||||
QWidget {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #999999;
|
||||
}
|
||||
QWidget QLabel {
|
||||
background-color: none;
|
||||
border: 0px;
|
||||
}
|
||||
""",
|
||||
"downloads_uploads_empty_text": """
|
||||
QLabel {
|
||||
color: #999999;
|
||||
}""",
|
||||
"downloads_uploads_label": """
|
||||
QLabel {
|
||||
font-weight: bold;
|
||||
font-size 14px;
|
||||
text-align: center;
|
||||
background-color: none;
|
||||
border: none;
|
||||
}""",
|
||||
"downloads_uploads_clear": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
}
|
||||
""",
|
||||
"download_uploads_indicator": """
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #f44449;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
padding: 2px;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
}""",
|
||||
"downloads_uploads_progress_bar": """
|
||||
QProgressBar {
|
||||
border: 1px solid #4e064f;
|
||||
background-color: #ffffff !important;
|
||||
text-align: center;
|
||||
color: #9b9b9b;
|
||||
font-size: 14px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
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": """
|
||||
QProgressBar {
|
||||
border: 1px solid #4e064f;
|
||||
background-color: #ffffff !important;
|
||||
text-align: center;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
border: 0px;
|
||||
background-color: #4e064f;
|
||||
width: 10px;
|
||||
}""",
|
||||
"share_filesize_warning": """
|
||||
QLabel {
|
||||
padding: 10px 0;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
""",
|
||||
"share_file_selection_drop_here_label": """
|
||||
QLabel {
|
||||
color: #999999;
|
||||
}""",
|
||||
"share_file_selection_drop_count_label": """
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #f44449;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}""",
|
||||
"share_file_list_drag_enter": """
|
||||
FileList {
|
||||
border: 3px solid #538ad0;
|
||||
}
|
||||
""",
|
||||
"share_file_list_drag_leave": """
|
||||
FileList {
|
||||
border: none;
|
||||
}
|
||||
""",
|
||||
"share_file_list_item_size": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
font-size: 11px;
|
||||
}""",
|
||||
# Receive mode and child widget styles
|
||||
"receive_file": """
|
||||
QWidget {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
""",
|
||||
"receive_file_size": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
font-size: 11px;
|
||||
}""",
|
||||
# Settings dialog
|
||||
"settings_version": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
}""",
|
||||
"settings_tor_status": """
|
||||
QLabel {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
}""",
|
||||
"settings_whats_this": """
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}""",
|
||||
"settings_connect_to_tor": """
|
||||
QLabel {
|
||||
font-style: italic;
|
||||
}""",
|
||||
}
|
||||
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||
|
||||
@staticmethod
|
||||
def random_string(num_bytes, output_len=None):
|
||||
|
142
onionshare/mode_settings.py
Normal file
142
onionshare/mode_settings.py
Normal file
@ -0,0 +1,142 @@
|
||||
# -*- 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 pwd
|
||||
import json
|
||||
|
||||
|
||||
class ModeSettings:
|
||||
"""
|
||||
This stores the settings for a single instance of an OnionShare mode. In CLI there
|
||||
is only one ModeSettings, and in the GUI there is a separate ModeSettings for each tab
|
||||
"""
|
||||
|
||||
def __init__(self, common, filename=None, id=None):
|
||||
self.common = common
|
||||
|
||||
self.default_settings = {
|
||||
"onion": {
|
||||
"private_key": None,
|
||||
"hidservauth_string": None,
|
||||
"password": None,
|
||||
},
|
||||
"persistent": {"mode": None, "enabled": False},
|
||||
"general": {
|
||||
"public": False,
|
||||
"autostart_timer": False,
|
||||
"autostop_timer": False,
|
||||
"legacy": False,
|
||||
"client_auth": False,
|
||||
"service_id": None,
|
||||
},
|
||||
"share": {"autostop_sharing": True, "filenames": []},
|
||||
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
||||
"website": {"disable_csp": False, "filenames": []},
|
||||
}
|
||||
self._settings = {}
|
||||
|
||||
self.just_created = False
|
||||
if id:
|
||||
self.id = id
|
||||
else:
|
||||
self.id = self.common.build_password(3)
|
||||
|
||||
self.load(filename)
|
||||
|
||||
def fill_in_defaults(self):
|
||||
"""
|
||||
If there are any missing settings from self._settings, replace them with
|
||||
their default values.
|
||||
"""
|
||||
for key in self.default_settings:
|
||||
if key in self._settings:
|
||||
for inner_key in self.default_settings[key]:
|
||||
if inner_key not in self._settings[key]:
|
||||
self._settings[key][inner_key] = self.default_settings[key][
|
||||
inner_key
|
||||
]
|
||||
else:
|
||||
self._settings[key] = self.default_settings[key]
|
||||
|
||||
def get(self, group, key):
|
||||
return self._settings[group][key]
|
||||
|
||||
def set(self, group, key, val):
|
||||
self._settings[group][key] = val
|
||||
self.common.log(
|
||||
"ModeSettings", "set", f"updating {self.id}: {group}.{key} = {val}"
|
||||
)
|
||||
self.save()
|
||||
|
||||
def build_default_receive_data_dir(self):
|
||||
"""
|
||||
Returns the path of the default Downloads directory for receive mode.
|
||||
"""
|
||||
|
||||
if self.common.platform == "Darwin":
|
||||
# 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")
|
||||
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")
|
||||
else:
|
||||
# All other OSes
|
||||
return os.path.expanduser("~/OnionShare")
|
||||
|
||||
def load(self, filename=None):
|
||||
# Load persistent settings from disk. If the file doesn't exist, create it
|
||||
if filename:
|
||||
self.filename = filename
|
||||
else:
|
||||
self.filename = os.path.join(
|
||||
self.common.build_persistent_dir(), f"{self.id}.json"
|
||||
)
|
||||
|
||||
if os.path.exists(self.filename):
|
||||
try:
|
||||
with open(self.filename, "r") as f:
|
||||
self._settings = json.load(f)
|
||||
self.fill_in_defaults()
|
||||
self.common.log("ModeSettings", "load", f"loaded {self.filename}")
|
||||
return
|
||||
except:
|
||||
pass
|
||||
|
||||
# If loading settings didn't work, create the settings file
|
||||
self.common.log("ModeSettings", "load", f"creating {self.filename}")
|
||||
self.fill_in_defaults()
|
||||
self.just_created = True
|
||||
|
||||
def save(self):
|
||||
# Save persistent setting to disk
|
||||
if not self.get("persistent", "enabled"):
|
||||
return
|
||||
|
||||
if self.filename:
|
||||
with open(self.filename, "w") as file:
|
||||
file.write(json.dumps(self._settings, indent=2))
|
||||
|
||||
def delete(self):
|
||||
# Delete the file from disk
|
||||
if os.path.exists(self.filename):
|
||||
os.remove(self.filename)
|
@ -150,15 +150,11 @@ class Onion(object):
|
||||
is necessary for status updates to reach the GUI.
|
||||
"""
|
||||
|
||||
def __init__(self, common):
|
||||
def __init__(self, common, use_tmp_dir=False):
|
||||
self.common = common
|
||||
|
||||
self.common.log("Onion", "__init__")
|
||||
|
||||
self.stealth = False
|
||||
self.service_id = None
|
||||
self.scheduled_key = None
|
||||
self.scheduled_auth_cookie = None
|
||||
self.use_tmp_dir = use_tmp_dir
|
||||
|
||||
# Is bundled tor supported?
|
||||
if (
|
||||
@ -187,11 +183,18 @@ class Onion(object):
|
||||
|
||||
def connect(
|
||||
self,
|
||||
custom_settings=False,
|
||||
config=False,
|
||||
custom_settings=None,
|
||||
config=None,
|
||||
tor_status_update_func=None,
|
||||
connect_timeout=120,
|
||||
local_only=False,
|
||||
):
|
||||
if local_only:
|
||||
self.common.log(
|
||||
"Onion", "connect", "--local-only, so skip trying to connect"
|
||||
)
|
||||
return
|
||||
|
||||
self.common.log("Onion", "connect")
|
||||
|
||||
# Either use settings that are passed in, or use them from common
|
||||
@ -205,6 +208,7 @@ class Onion(object):
|
||||
self.settings = self.common.settings
|
||||
|
||||
strings.load_strings(self.common)
|
||||
|
||||
# The Tor controller
|
||||
self.c = None
|
||||
|
||||
@ -215,24 +219,30 @@ class Onion(object):
|
||||
)
|
||||
|
||||
# Create a torrc for this session
|
||||
self.tor_data_directory = tempfile.TemporaryDirectory(
|
||||
dir=self.common.build_data_dir()
|
||||
)
|
||||
if self.use_tmp_dir:
|
||||
self.tor_data_directory = tempfile.TemporaryDirectory(
|
||||
dir=self.common.build_tmp_dir()
|
||||
)
|
||||
self.tor_data_directory_name = self.tor_data_directory.name
|
||||
else:
|
||||
self.tor_data_directory_name = self.common.build_tor_dir()
|
||||
self.common.log(
|
||||
"Onion", "connect", f"tor_data_directory={self.tor_data_directory.name}"
|
||||
"Onion",
|
||||
"connect",
|
||||
f"tor_data_directory_name={self.tor_data_directory_name}",
|
||||
)
|
||||
|
||||
# Create the torrc
|
||||
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_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")
|
||||
self.tor_torrc = os.path.join(self.tor_data_directory_name, "torrc")
|
||||
|
||||
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||
# Windows doesn't support unix sockets, so it must use a network port.
|
||||
@ -250,11 +260,11 @@ class Onion(object):
|
||||
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"
|
||||
self.tor_data_directory_name, "control_socket"
|
||||
)
|
||||
|
||||
torrc_template = torrc_template.replace(
|
||||
"{{data_directory}}", self.tor_data_directory.name
|
||||
"{{data_directory}}", self.tor_data_directory_name
|
||||
)
|
||||
torrc_template = torrc_template.replace(
|
||||
"{{control_port}}", str(self.tor_control_port)
|
||||
@ -562,60 +572,45 @@ class Onion(object):
|
||||
else:
|
||||
return False
|
||||
|
||||
def start_onion_service(self, port, await_publication, save_scheduled_key=False):
|
||||
def start_onion_service(self, mode_settings, port, await_publication):
|
||||
"""
|
||||
Start a onion service on port 80, pointing to the given port, and
|
||||
return the onion hostname.
|
||||
"""
|
||||
self.common.log("Onion", "start_onion_service")
|
||||
# Settings may have changed in the frontend but not updated in our settings object,
|
||||
# such as persistence. Reload the settings now just to be sure.
|
||||
self.settings.load()
|
||||
|
||||
self.auth_string = None
|
||||
self.common.log("Onion", "start_onion_service", f"port={port}")
|
||||
|
||||
if not self.supports_ephemeral:
|
||||
raise TorTooOld(strings._("error_ephemeral_not_supported"))
|
||||
if self.stealth and not self.supports_stealth:
|
||||
if mode_settings.get("general", "client_auth") and not self.supports_stealth:
|
||||
raise TorTooOld(strings._("error_stealth_not_supported"))
|
||||
|
||||
if not save_scheduled_key:
|
||||
print(f"Setting up onion service on port {port}.")
|
||||
|
||||
if self.stealth:
|
||||
if self.settings.get("hidservauth_string"):
|
||||
hidservauth_string = self.settings.get("hidservauth_string").split()[2]
|
||||
basic_auth = {"onionshare": hidservauth_string}
|
||||
auth_cookie = None
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
if mode_settings.get("onion", "hidservauth_string"):
|
||||
auth_cookie = mode_settings.get("onion", "hidservauth_string").split()[
|
||||
2
|
||||
]
|
||||
if auth_cookie:
|
||||
basic_auth = {"onionshare": auth_cookie}
|
||||
else:
|
||||
if self.scheduled_auth_cookie:
|
||||
basic_auth = {"onionshare": self.scheduled_auth_cookie}
|
||||
else:
|
||||
basic_auth = {"onionshare": None}
|
||||
# If we had neither a scheduled auth cookie or a persistent hidservauth string,
|
||||
# set the cookie to 'None', which means Tor will create one for us
|
||||
basic_auth = {"onionshare": None}
|
||||
else:
|
||||
# Not using client auth at all
|
||||
basic_auth = None
|
||||
|
||||
if self.settings.get("private_key"):
|
||||
key_content = self.settings.get("private_key")
|
||||
if mode_settings.get("onion", "private_key"):
|
||||
key_content = mode_settings.get("onion", "private_key")
|
||||
if self.is_v2_key(key_content):
|
||||
key_type = "RSA1024"
|
||||
else:
|
||||
# Assume it was a v3 key. Stem will throw an error if it's something illegible
|
||||
key_type = "ED25519-V3"
|
||||
|
||||
elif self.scheduled_key:
|
||||
key_content = self.scheduled_key
|
||||
if self.is_v2_key(key_content):
|
||||
key_type = "RSA1024"
|
||||
else:
|
||||
# Assume it was a v3 key. Stem will throw an error if it's something illegible
|
||||
key_type = "ED25519-V3"
|
||||
|
||||
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 mode_settings.get("general", "legacy"):
|
||||
key_content = "ED25519-V3"
|
||||
else:
|
||||
# fall back to v2 onion services
|
||||
@ -626,87 +621,59 @@ class Onion(object):
|
||||
if (
|
||||
key_type == "NEW"
|
||||
and key_content == "ED25519-V3"
|
||||
and not self.settings.get("use_legacy_v2_onions")
|
||||
and not mode_settings.get("general", "legacy")
|
||||
):
|
||||
basic_auth = None
|
||||
self.stealth = False
|
||||
|
||||
debug_message = f"key_type={key_type}"
|
||||
if key_type == "NEW":
|
||||
debug_message += f", key_content={key_content}"
|
||||
self.common.log("Onion", "start_onion_service", 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,
|
||||
)
|
||||
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,
|
||||
basic_auth=basic_auth,
|
||||
key_type=key_type,
|
||||
key_content=key_content,
|
||||
)
|
||||
|
||||
except ProtocolError as e:
|
||||
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 = res.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)
|
||||
# Save the service_id
|
||||
mode_settings.set("general", "service_id", res.service_id)
|
||||
|
||||
# If we were scheduling a future share, register the private key for later re-use
|
||||
if save_scheduled_key:
|
||||
self.scheduled_key = res.private_key
|
||||
else:
|
||||
self.scheduled_key = None
|
||||
# Save the private key and hidservauth string
|
||||
if not mode_settings.get("onion", "private_key"):
|
||||
mode_settings.set("onion", "private_key", res.private_key)
|
||||
if mode_settings.get("general", "client_auth") and not mode_settings.get(
|
||||
"onion", "hidservauth_string"
|
||||
):
|
||||
auth_cookie = list(res.client_auth.values())[0]
|
||||
auth_string = f"HidServAuth {onion_host} {auth_cookie}"
|
||||
mode_settings.set("onion", "hidservauth_string", auth_string)
|
||||
|
||||
if self.stealth:
|
||||
# Similar to the PrivateKey, the Control port only returns the ClientAuth
|
||||
# in the response if it was responsible for creating the basic_auth password
|
||||
# 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")
|
||||
else:
|
||||
auth_cookie = list(res.client_auth.values())[0]
|
||||
self.auth_string = f"HidServAuth {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 = f"HidServAuth {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 = (
|
||||
f"HidServAuth {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
|
||||
return onion_host
|
||||
|
||||
if onion_host is not None:
|
||||
self.settings.save()
|
||||
return onion_host
|
||||
else:
|
||||
raise TorErrorProtocolError(strings._("error_tor_protocol_error_unknown"))
|
||||
def stop_onion_service(self, mode_settings):
|
||||
"""
|
||||
Stop a specific onion service
|
||||
"""
|
||||
onion_host = mode_settings.get("general", "service_id")
|
||||
self.common.log("Onion", "stop_onion_service", f"onion host: {onion_host}")
|
||||
try:
|
||||
self.c.remove_ephemeral_hidden_service(
|
||||
mode_settings.get("general", "service_id")
|
||||
)
|
||||
except:
|
||||
self.common.log(
|
||||
"Onion", "stop_onion_service", f"failed to remove {onion_host}"
|
||||
)
|
||||
|
||||
def cleanup(self, stop_tor=True):
|
||||
"""
|
||||
@ -717,48 +684,55 @@ class Onion(object):
|
||||
# Cleanup the ephemeral onion services, if we have any
|
||||
try:
|
||||
onions = self.c.list_ephemeral_hidden_services()
|
||||
for onion in onions:
|
||||
for service_id in onions:
|
||||
onion_host = f"{service_id}.onion"
|
||||
try:
|
||||
self.common.log(
|
||||
"Onion", "cleanup", f"trying to remove onion {onion}"
|
||||
"Onion", "cleanup", f"trying to remove onion {onion_host}"
|
||||
)
|
||||
self.c.remove_ephemeral_hidden_service(onion)
|
||||
self.c.remove_ephemeral_hidden_service(service_id)
|
||||
except:
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"cleanup",
|
||||
f"could not remove onion {onion}.. moving on anyway",
|
||||
"Onion", "cleanup", f"failed to remove onion {onion_host}"
|
||||
)
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
self.service_id = None
|
||||
|
||||
if stop_tor:
|
||||
# Stop tor process
|
||||
if self.tor_proc:
|
||||
self.tor_proc.terminate()
|
||||
time.sleep(0.2)
|
||||
if not self.tor_proc.poll():
|
||||
if self.tor_proc.poll() == None:
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"cleanup",
|
||||
"Tried to terminate tor process but it's still running",
|
||||
)
|
||||
try:
|
||||
self.tor_proc.kill()
|
||||
time.sleep(0.2)
|
||||
if self.tor_proc.poll() == None:
|
||||
self.common.log(
|
||||
"Onion",
|
||||
"cleanup",
|
||||
"Tried to kill tor process but it's still running",
|
||||
)
|
||||
except:
|
||||
pass
|
||||
self.common.log(
|
||||
"Onion", "cleanup", "Exception while killing tor process"
|
||||
)
|
||||
self.tor_proc = None
|
||||
|
||||
# Reset other Onion settings
|
||||
self.connected_to_tor = False
|
||||
self.stealth = False
|
||||
|
||||
try:
|
||||
# Delete the temporary tor data directory
|
||||
self.tor_data_directory.cleanup()
|
||||
except AttributeError:
|
||||
# Skip if cleanup was somehow run before connect
|
||||
pass
|
||||
except PermissionError:
|
||||
# Skip if the directory is still open (#550)
|
||||
# TODO: find a better solution
|
||||
if self.use_tmp_dir:
|
||||
self.tor_data_directory.cleanup()
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_tor_socks_port(self):
|
||||
|
@ -42,7 +42,6 @@ class OnionShare(object):
|
||||
self.hidserv_dir = None
|
||||
self.onion_host = None
|
||||
self.port = None
|
||||
self.stealth = None
|
||||
|
||||
# files and dirs to delete on shutdown
|
||||
self.cleanup_filenames = []
|
||||
@ -55,12 +54,6 @@ class OnionShare(object):
|
||||
# init auto-stop timer thread
|
||||
self.autostop_timer_thread = None
|
||||
|
||||
def set_stealth(self, stealth):
|
||||
self.common.log("OnionShare", f"set_stealth", "stealth={stealth}")
|
||||
|
||||
self.stealth = stealth
|
||||
self.onion.stealth = stealth
|
||||
|
||||
def choose_port(self):
|
||||
"""
|
||||
Choose a random port.
|
||||
@ -70,7 +63,7 @@ class OnionShare(object):
|
||||
except:
|
||||
raise OSError(strings._("no_available_port"))
|
||||
|
||||
def start_onion_service(self, await_publication=True, save_scheduled_key=False):
|
||||
def start_onion_service(self, mode_settings, await_publication=True):
|
||||
"""
|
||||
Start the onionshare onion service.
|
||||
"""
|
||||
@ -87,12 +80,18 @@ class OnionShare(object):
|
||||
return
|
||||
|
||||
self.onion_host = self.onion.start_onion_service(
|
||||
self.port, await_publication, save_scheduled_key
|
||||
mode_settings, self.port, await_publication
|
||||
)
|
||||
|
||||
if self.stealth:
|
||||
if mode_settings.get("general", "client_auth"):
|
||||
self.auth_string = self.onion.auth_string
|
||||
|
||||
def stop_onion_service(self, mode_settings):
|
||||
"""
|
||||
Stop the onion service
|
||||
"""
|
||||
self.onion.stop_onion_service(mode_settings)
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Shut everything down and clean up temporary files, etc.
|
||||
|
@ -106,24 +106,13 @@ class Settings(object):
|
||||
"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,
|
||||
"persistent_tabs": [],
|
||||
"locale": None, # this gets defined in fill_in_defaults()
|
||||
}
|
||||
self._settings = {}
|
||||
@ -163,24 +152,6 @@ class Settings(object):
|
||||
"""
|
||||
return os.path.join(self.common.build_data_dir(), "onionshare.json")
|
||||
|
||||
def build_default_data_dir(self):
|
||||
"""
|
||||
Returns the path of the default Downloads directory for receive mode.
|
||||
"""
|
||||
|
||||
if self.common.platform == "Darwin":
|
||||
# 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")
|
||||
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")
|
||||
else:
|
||||
# All other OSes
|
||||
return os.path.expanduser("~/OnionShare")
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load the settings from file.
|
||||
|
@ -292,7 +292,7 @@ class ReceiveModeRequest(Request):
|
||||
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.web.settings.get("receive", "data_dir"), date_dir, time_dir
|
||||
)
|
||||
|
||||
# Create that directory, which shouldn't exist yet
|
||||
@ -358,14 +358,9 @@ class ReceiveModeRequest(Request):
|
||||
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)
|
||||
),
|
||||
)
|
||||
)
|
||||
date_str = datetime.now().strftime("%b %d, %I:%M%p")
|
||||
size_str = self.web.common.human_readable_filesize(self.content_length)
|
||||
print(f"{date_str}: Upload of total size {size_str} is starting")
|
||||
|
||||
# Don't tell the GUI that a request has started until we start receiving files
|
||||
self.told_gui_about_request = False
|
||||
@ -453,10 +448,10 @@ class ReceiveModeRequest(Request):
|
||||
if self.previous_file != filename:
|
||||
self.previous_file = filename
|
||||
|
||||
print(
|
||||
f"\r=> {self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes'])} {filename}",
|
||||
end="",
|
||||
size_str = self.web.common.human_readable_filesize(
|
||||
self.progress[filename]["uploaded_bytes"]
|
||||
)
|
||||
print(f"\r=> {size_str} {filename} ", end="")
|
||||
|
||||
# Update the GUI on the upload progress
|
||||
if self.told_gui_about_request:
|
||||
|
@ -26,8 +26,7 @@ class SendBaseModeWeb:
|
||||
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.
|
||||
# If autostop_sharing, only allow one download at a time
|
||||
self.download_in_progress = False
|
||||
|
||||
# This tracks the history id
|
||||
|
@ -18,8 +18,8 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
self.common.log("ShareModeWeb", "init")
|
||||
|
||||
# Allow downloading individual files if "Stop sharing after files have been sent" is unchecked
|
||||
self.download_individual_files = not self.common.settings.get(
|
||||
"close_after_first_download"
|
||||
self.download_individual_files = not self.web.settings.get(
|
||||
"share", "autostop_sharing"
|
||||
)
|
||||
|
||||
def define_routes(self):
|
||||
@ -37,7 +37,10 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
|
||||
# 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
|
||||
deny_download = (
|
||||
self.web.settings.get("share", "autostop_sharing")
|
||||
and self.download_in_progress
|
||||
)
|
||||
if deny_download:
|
||||
r = make_response(
|
||||
render_template("denied.html"),
|
||||
@ -60,7 +63,10 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
"""
|
||||
# Deny new downloads if "Stop After First Download" is checked and there is
|
||||
# currently a download
|
||||
deny_download = not self.web.stay_open and self.download_in_progress
|
||||
deny_download = (
|
||||
self.web.settings.get("share", "autostop_sharing")
|
||||
and self.download_in_progress
|
||||
)
|
||||
if deny_download:
|
||||
r = make_response(
|
||||
render_template(
|
||||
@ -96,7 +102,7 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
|
||||
def generate():
|
||||
# Starting a new download
|
||||
if not self.web.stay_open:
|
||||
if self.web.settings.get("share", "autostop_sharing"):
|
||||
self.download_in_progress = True
|
||||
|
||||
chunk_size = 102400 # 100kb
|
||||
@ -161,11 +167,11 @@ class ShareModeWeb(SendBaseModeWeb):
|
||||
sys.stdout.write("\n")
|
||||
|
||||
# Download is finished
|
||||
if not self.web.stay_open:
|
||||
if self.web.settings.get("share", "autostop_sharing"):
|
||||
self.download_in_progress = False
|
||||
|
||||
# Close the server, if necessary
|
||||
if not self.web.stay_open and not canceled:
|
||||
if self.web.settings.get("share", "autostop_sharing") and not canceled:
|
||||
print("Stopped because transfer is complete")
|
||||
self.web.running = False
|
||||
try:
|
||||
|
@ -60,10 +60,12 @@ class Web:
|
||||
REQUEST_OTHER = 13
|
||||
REQUEST_INVALID_PASSWORD = 14
|
||||
|
||||
def __init__(self, common, is_gui, mode="share"):
|
||||
def __init__(self, common, is_gui, mode_settings, mode="share"):
|
||||
self.common = common
|
||||
self.common.log("Web", "__init__", f"is_gui={is_gui}, mode={mode}")
|
||||
|
||||
self.settings = mode_settings
|
||||
|
||||
# The flask app
|
||||
self.app = Flask(
|
||||
__name__,
|
||||
@ -186,7 +188,7 @@ class Web:
|
||||
return None
|
||||
|
||||
# If public mode is disabled, require authentication
|
||||
if not self.common.settings.get("public_mode"):
|
||||
if not self.settings.get("general", "public"):
|
||||
|
||||
@self.auth.login_required
|
||||
def _check_login():
|
||||
@ -284,10 +286,7 @@ class Web:
|
||||
for header, value in self.security_headers:
|
||||
r.headers.set(header, value)
|
||||
# Set a CSP header unless in website mode and the user has disabled it
|
||||
if (
|
||||
not self.common.settings.get("csp_header_disabled")
|
||||
or self.mode != "website"
|
||||
):
|
||||
if not self.settings.get("website", "disable_csp") or self.mode != "website":
|
||||
r.headers.set(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:;",
|
||||
@ -305,16 +304,14 @@ class Web:
|
||||
"""
|
||||
self.q.put({"type": request_type, "path": path, "data": data})
|
||||
|
||||
def generate_password(self, persistent_password=None):
|
||||
self.common.log(
|
||||
"Web", "generate_password", f"persistent_password={persistent_password}"
|
||||
)
|
||||
if persistent_password != None and persistent_password != "":
|
||||
self.password = persistent_password
|
||||
def generate_password(self, saved_password=None):
|
||||
self.common.log("Web", "generate_password", f"saved_password={saved_password}")
|
||||
if saved_password != None and saved_password != "":
|
||||
self.password = saved_password
|
||||
self.common.log(
|
||||
"Web",
|
||||
"generate_password",
|
||||
f'persistent_password sent, so password is: "{self.password}"',
|
||||
f'saved_password sent, so password is: "{self.password}"',
|
||||
)
|
||||
else:
|
||||
self.password = self.common.build_password()
|
||||
@ -349,17 +346,11 @@ class Web:
|
||||
pass
|
||||
self.running = False
|
||||
|
||||
def start(self, port, stay_open=False, public_mode=False, password=None):
|
||||
def start(self, port):
|
||||
"""
|
||||
Start the flask web server.
|
||||
"""
|
||||
self.common.log(
|
||||
"Web",
|
||||
"start",
|
||||
f"port={port}, stay_open={stay_open}, public_mode={public_mode}, password={password}",
|
||||
)
|
||||
|
||||
self.stay_open = stay_open
|
||||
self.common.log("Web", "start", f"port={port}")
|
||||
|
||||
# Make sure the stop_q is empty when starting a new server
|
||||
while not self.stop_q.empty():
|
||||
@ -389,10 +380,15 @@ class Web:
|
||||
# To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
|
||||
# (We're putting the shutdown_password in the path as well to make routing simpler)
|
||||
if self.running:
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown",
|
||||
auth=requests.auth.HTTPBasicAuth("onionshare", self.password),
|
||||
)
|
||||
if self.password:
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown",
|
||||
auth=requests.auth.HTTPBasicAuth("onionshare", self.password),
|
||||
)
|
||||
else:
|
||||
requests.get(
|
||||
f"http://127.0.0.1:{port}/{self.shutdown_password}/shutdown"
|
||||
)
|
||||
|
||||
# Reset any password that was in use
|
||||
self.password = None
|
||||
|
@ -23,14 +23,15 @@ import sys
|
||||
import platform
|
||||
import argparse
|
||||
import signal
|
||||
from .widgets import Alert
|
||||
import json
|
||||
import psutil
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from onionshare.common import Common
|
||||
from onionshare.onion import Onion
|
||||
from onionshare.onionshare import OnionShare
|
||||
|
||||
from .onionshare_gui import OnionShareGui
|
||||
from .gui_common import GuiCommon
|
||||
from .widgets import Alert
|
||||
from .main_window import MainWindow
|
||||
|
||||
|
||||
class Application(QtWidgets.QApplication):
|
||||
@ -60,7 +61,6 @@ def main():
|
||||
The main() function implements all of the logic that the GUI version of onionshare uses.
|
||||
"""
|
||||
common = Common()
|
||||
common.define_css()
|
||||
|
||||
# Display OnionShare banner
|
||||
print(f"OnionShare {common.version} | https://onionshare.org/")
|
||||
@ -96,12 +96,6 @@ def main():
|
||||
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
|
||||
@ -109,16 +103,15 @@ def main():
|
||||
for i in range(len(filenames)):
|
||||
filenames[i] = os.path.abspath(filenames[i])
|
||||
|
||||
config = args.config
|
||||
if config:
|
||||
common.load_settings(config)
|
||||
|
||||
local_only = bool(args.local_only)
|
||||
verbose = bool(args.verbose)
|
||||
|
||||
# Verbose mode?
|
||||
common.verbose = verbose
|
||||
|
||||
# Attach the GUI common parts to the common object
|
||||
common.gui = GuiCommon(common, qtapp, local_only)
|
||||
|
||||
# Validation
|
||||
if filenames:
|
||||
valid = True
|
||||
@ -132,19 +125,50 @@ def main():
|
||||
if not valid:
|
||||
sys.exit()
|
||||
|
||||
# Start the Onion
|
||||
onion = Onion(common)
|
||||
# Is there another onionshare-gui running?
|
||||
existing_pid = None
|
||||
for proc in psutil.process_iter(attrs=["pid", "name", "cmdline"]):
|
||||
if proc.info["pid"] == os.getpid():
|
||||
continue
|
||||
|
||||
# Start the OnionShare app
|
||||
app = OnionShare(common, onion, local_only)
|
||||
if proc.info["name"] == "onionshare-gui" and proc.status() != "zombie":
|
||||
existing_pid = proc.info["pid"]
|
||||
break
|
||||
else:
|
||||
# Dev mode onionshare?
|
||||
if proc.info["cmdline"] and len(proc.info["cmdline"]) >= 2:
|
||||
if (
|
||||
os.path.basename(proc.info["cmdline"][0]).lower() == "python"
|
||||
and os.path.basename(proc.info["cmdline"][1]) == "onionshare-gui"
|
||||
and proc.status() != "zombie"
|
||||
):
|
||||
existing_pid = proc.info["pid"]
|
||||
break
|
||||
|
||||
if existing_pid:
|
||||
print(f"Opening tab in existing OnionShare window (pid {proc.info['pid']})")
|
||||
|
||||
# Make an event for the existing OnionShare window
|
||||
if filenames:
|
||||
obj = {"type": "new_share_tab", "filenames": filenames}
|
||||
else:
|
||||
obj = {"type": "new_tab"}
|
||||
|
||||
# Write that event to disk
|
||||
with open(common.gui.events_filename, "a") as f:
|
||||
f.write(json.dumps(obj) + "\n")
|
||||
return
|
||||
|
||||
# Launch the gui
|
||||
gui = OnionShareGui(common, onion, qtapp, app, filenames, config, local_only)
|
||||
main_window = MainWindow(common, filenames)
|
||||
|
||||
# If filenames were passed in, open them in a tab
|
||||
if filenames:
|
||||
main_window.tabs.new_share_tab(filenames)
|
||||
|
||||
# Clean up when app quits
|
||||
def shutdown():
|
||||
onion.cleanup()
|
||||
app.cleanup()
|
||||
main_window.cleanup()
|
||||
|
||||
qtapp.aboutToQuit.connect(shutdown)
|
||||
|
||||
|
89
onionshare_gui/event_handler.py
Normal file
89
onionshare_gui/event_handler.py
Normal file
@ -0,0 +1,89 @@
|
||||
# -*- 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 json
|
||||
import os
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler, FileModifiedEvent
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
class EventHandler(FileSystemEventHandler, QtCore.QObject):
|
||||
"""
|
||||
To trigger an event, write a JSON line to the events file. When that file changes,
|
||||
each line will be handled as an event. Valid events are:
|
||||
{"type": "new_tab"}
|
||||
{"type": "new_share_tab", "filenames": ["file1", "file2"]}
|
||||
"""
|
||||
|
||||
new_tab = QtCore.pyqtSignal()
|
||||
new_share_tab = QtCore.pyqtSignal(list)
|
||||
|
||||
def __init__(self, common):
|
||||
super(EventHandler, self).__init__()
|
||||
self.common = common
|
||||
|
||||
def on_modified(self, event):
|
||||
if (
|
||||
type(event) == FileModifiedEvent
|
||||
and event.src_path == self.common.gui.events_filename
|
||||
):
|
||||
# Read all the lines in the events, then delete it
|
||||
with open(self.common.gui.events_filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
os.remove(self.common.gui.events_filename)
|
||||
|
||||
self.common.log(
|
||||
"EventHandler", "on_modified", f"processing {len(lines)} lines"
|
||||
)
|
||||
for line in lines:
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
if "type" not in obj:
|
||||
self.common.log(
|
||||
"EventHandler",
|
||||
"on_modified",
|
||||
f"event does not have a type: {obj}",
|
||||
)
|
||||
continue
|
||||
except json.decoder.JSONDecodeError:
|
||||
self.common.log(
|
||||
"EventHandler", "on_modified", f"ignoring invalid line: {line}"
|
||||
)
|
||||
continue
|
||||
|
||||
if obj["type"] == "new_tab":
|
||||
self.common.log("EventHandler", "on_modified", "new_tab event")
|
||||
self.new_tab.emit()
|
||||
|
||||
elif obj["type"] == "new_share_tab":
|
||||
if "filenames" in obj and type(obj["filenames"]) is list:
|
||||
self.new_share_tab.emit(obj["filenames"])
|
||||
else:
|
||||
self.common.log(
|
||||
"EventHandler",
|
||||
"on_modified",
|
||||
f"invalid new_share_tab event: {obj}",
|
||||
)
|
||||
|
||||
else:
|
||||
self.common.log(
|
||||
"EventHandler", "on_modified", f"invalid event type: {obj}"
|
||||
)
|
||||
|
287
onionshare_gui/gui_common.py
Normal file
287
onionshare_gui/gui_common.py
Normal file
@ -0,0 +1,287 @@
|
||||
# -*- 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
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.onion import Onion
|
||||
|
||||
|
||||
class GuiCommon:
|
||||
"""
|
||||
The shared code for all of the OnionShare GUI.
|
||||
"""
|
||||
|
||||
MODE_SHARE = "share"
|
||||
MODE_RECEIVE = "receive"
|
||||
MODE_WEBSITE = "website"
|
||||
|
||||
def __init__(self, common, qtapp, local_only):
|
||||
self.common = common
|
||||
self.qtapp = qtapp
|
||||
self.local_only = local_only
|
||||
|
||||
# Load settings
|
||||
self.common.load_settings()
|
||||
|
||||
# Load strings
|
||||
strings.load_strings(self.common)
|
||||
|
||||
# Start the Onion
|
||||
self.onion = Onion(common)
|
||||
|
||||
# Directory to watch for events
|
||||
self.events_dir = os.path.join(self.common.build_data_dir(), "events")
|
||||
if not os.path.exists(self.events_dir):
|
||||
os.makedirs(self.events_dir, 0o700, True)
|
||||
self.events_filename = os.path.join(self.events_dir, "events")
|
||||
|
||||
self.css = {
|
||||
# OnionShareGui styles
|
||||
"tab_widget_new_tab_button": """
|
||||
QPushButton {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}""",
|
||||
"mode_new_tab_button": """
|
||||
QPushButton {
|
||||
font-weight: bold;
|
||||
font-size: 30px;
|
||||
color: #601f61;
|
||||
}""",
|
||||
"mode_header_label": """
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #4e064f;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
border-radius: 0;
|
||||
padding: 10px 0 10px 0;
|
||||
}""",
|
||||
"settings_button": """
|
||||
QPushButton {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}""",
|
||||
"server_status_indicator_label": """
|
||||
QLabel {
|
||||
font-style: italic;
|
||||
color: #666666;
|
||||
padding: 2px;
|
||||
}""",
|
||||
"status_bar": """
|
||||
QStatusBar {
|
||||
font-style: italic;
|
||||
color: #666666;
|
||||
}
|
||||
QStatusBar::item {
|
||||
border: 0px;
|
||||
}""",
|
||||
# Common styles between modes and their child widgets
|
||||
"mode_settings_toggle_advanced": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
text-align: left;
|
||||
}
|
||||
""",
|
||||
"mode_info_label": """
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
""",
|
||||
"server_status_url": """
|
||||
QLabel {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
border: 1px solid #666666;
|
||||
font-size: 12px;
|
||||
}
|
||||
""",
|
||||
"server_status_url_buttons": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
}
|
||||
""",
|
||||
"server_status_button_stopped": """
|
||||
QPushButton {
|
||||
background-color: #5fa416;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}""",
|
||||
"server_status_button_working": """
|
||||
QPushButton {
|
||||
background-color: #4c8211;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
font-style: italic;
|
||||
}""",
|
||||
"server_status_button_started": """
|
||||
QPushButton {
|
||||
background-color: #d0011b;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}""",
|
||||
"downloads_uploads_empty": """
|
||||
QWidget {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #999999;
|
||||
}
|
||||
QWidget QLabel {
|
||||
background-color: none;
|
||||
border: 0px;
|
||||
}
|
||||
""",
|
||||
"downloads_uploads_empty_text": """
|
||||
QLabel {
|
||||
color: #999999;
|
||||
}""",
|
||||
"downloads_uploads_label": """
|
||||
QLabel {
|
||||
font-weight: bold;
|
||||
font-size 14px;
|
||||
text-align: center;
|
||||
background-color: none;
|
||||
border: none;
|
||||
}""",
|
||||
"downloads_uploads_clear": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
}
|
||||
""",
|
||||
"download_uploads_indicator": """
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #f44449;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
padding: 2px;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
}""",
|
||||
"downloads_uploads_progress_bar": """
|
||||
QProgressBar {
|
||||
border: 1px solid #4e064f;
|
||||
background-color: #ffffff !important;
|
||||
text-align: center;
|
||||
color: #9b9b9b;
|
||||
font-size: 14px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
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": """
|
||||
QProgressBar {
|
||||
border: 1px solid #4e064f;
|
||||
background-color: #ffffff !important;
|
||||
text-align: center;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
border: 0px;
|
||||
background-color: #4e064f;
|
||||
width: 10px;
|
||||
}""",
|
||||
"share_filesize_warning": """
|
||||
QLabel {
|
||||
padding: 10px 0;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
""",
|
||||
"share_file_selection_drop_here_label": """
|
||||
QLabel {
|
||||
color: #999999;
|
||||
}""",
|
||||
"share_file_selection_drop_count_label": """
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #f44449;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}""",
|
||||
"share_file_list_drag_enter": """
|
||||
FileList {
|
||||
border: 3px solid #538ad0;
|
||||
}
|
||||
""",
|
||||
"share_file_list_drag_leave": """
|
||||
FileList {
|
||||
border: none;
|
||||
}
|
||||
""",
|
||||
"share_file_list_item_size": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
font-size: 11px;
|
||||
}""",
|
||||
# Receive mode and child widget styles
|
||||
"receive_file": """
|
||||
QWidget {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
""",
|
||||
"receive_file_size": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
font-size: 11px;
|
||||
}""",
|
||||
# Settings dialog
|
||||
"settings_version": """
|
||||
QLabel {
|
||||
color: #666666;
|
||||
}""",
|
||||
"settings_tor_status": """
|
||||
QLabel {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
}""",
|
||||
"settings_whats_this": """
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}""",
|
||||
"settings_connect_to_tor": """
|
||||
QLabel {
|
||||
font-style: italic;
|
||||
}""",
|
||||
}
|
288
onionshare_gui/main_window.py
Normal file
288
onionshare_gui/main_window.py
Normal file
@ -0,0 +1,288 @@
|
||||
# -*- 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/>.
|
||||
"""
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.web import Web
|
||||
|
||||
from .tor_connection_dialog import TorConnectionDialog
|
||||
from .settings_dialog import SettingsDialog
|
||||
from .widgets import Alert
|
||||
from .update_checker import UpdateThread
|
||||
from .tab_widget import TabWidget
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
"""
|
||||
MainWindow is the OnionShare main window, which contains the GUI elements, including all open tabs
|
||||
"""
|
||||
|
||||
def __init__(self, common, filenames):
|
||||
super(MainWindow, self).__init__()
|
||||
|
||||
self.common = common
|
||||
self.common.log("MainWindow", "__init__")
|
||||
|
||||
# Initialize the window
|
||||
self.setMinimumWidth(1040)
|
||||
self.setMinimumHeight(700)
|
||||
self.setWindowTitle("OnionShare")
|
||||
self.setWindowIcon(
|
||||
QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
|
||||
)
|
||||
|
||||
# System tray
|
||||
menu = QtWidgets.QMenu()
|
||||
self.settings_action = menu.addAction(strings._("gui_settings_window_title"))
|
||||
self.settings_action.triggered.connect(self.open_settings)
|
||||
self.help_action = menu.addAction(strings._("gui_settings_button_help"))
|
||||
self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
||||
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"))
|
||||
)
|
||||
else:
|
||||
self.system_tray.setIcon(
|
||||
QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
|
||||
)
|
||||
self.system_tray.setContextMenu(menu)
|
||||
self.system_tray.show()
|
||||
|
||||
# Status bar
|
||||
self.status_bar = QtWidgets.QStatusBar()
|
||||
self.status_bar.setSizeGripEnabled(False)
|
||||
self.status_bar.setStyleSheet(self.common.gui.css["status_bar"])
|
||||
self.setStatusBar(self.status_bar)
|
||||
|
||||
# Server status indicator icons
|
||||
self.status_bar.server_status_image_stopped = QtGui.QImage(
|
||||
self.common.get_resource_path("images/server_stopped.png")
|
||||
)
|
||||
self.status_bar.server_status_image_working = QtGui.QImage(
|
||||
self.common.get_resource_path("images/server_working.png")
|
||||
)
|
||||
self.status_bar.server_status_image_started = QtGui.QImage(
|
||||
self.common.get_resource_path("images/server_started.png")
|
||||
)
|
||||
|
||||
# Server status indicator on the status bar
|
||||
self.status_bar.server_status_image_label = QtWidgets.QLabel()
|
||||
self.status_bar.server_status_image_label.setFixedWidth(20)
|
||||
self.status_bar.server_status_label = QtWidgets.QLabel("")
|
||||
self.status_bar.server_status_label.setStyleSheet(
|
||||
self.common.gui.css["server_status_indicator_label"]
|
||||
)
|
||||
server_status_indicator_layout = QtWidgets.QHBoxLayout()
|
||||
server_status_indicator_layout.addWidget(
|
||||
self.status_bar.server_status_image_label
|
||||
)
|
||||
server_status_indicator_layout.addWidget(self.status_bar.server_status_label)
|
||||
self.status_bar.server_status_indicator = QtWidgets.QWidget()
|
||||
self.status_bar.server_status_indicator.setLayout(
|
||||
server_status_indicator_layout
|
||||
)
|
||||
self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator)
|
||||
|
||||
# Settings button
|
||||
self.settings_button = QtWidgets.QPushButton()
|
||||
self.settings_button.setDefault(False)
|
||||
self.settings_button.setFixedSize(40, 50)
|
||||
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.gui.css["settings_button"])
|
||||
self.status_bar.addPermanentWidget(self.settings_button)
|
||||
|
||||
# Tabs
|
||||
self.tabs = TabWidget(self.common, self.system_tray, self.status_bar)
|
||||
self.tabs.bring_to_front.connect(self.bring_to_front)
|
||||
|
||||
# If we have saved persistent tabs, try opening those
|
||||
if len(self.common.settings.get("persistent_tabs")) > 0:
|
||||
for mode_settings_id in self.common.settings.get("persistent_tabs"):
|
||||
self.tabs.load_tab(mode_settings_id)
|
||||
else:
|
||||
# Start with opening the first tab
|
||||
self.tabs.new_tab_clicked()
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.tabs)
|
||||
|
||||
central_widget = QtWidgets.QWidget()
|
||||
central_widget.setLayout(layout)
|
||||
self.setCentralWidget(central_widget)
|
||||
self.show()
|
||||
|
||||
# Start the "Connecting to Tor" dialog, which calls onion.connect()
|
||||
tor_con = TorConnectionDialog(self.common)
|
||||
tor_con.canceled.connect(self.tor_connection_canceled)
|
||||
tor_con.open_settings.connect(self.tor_connection_open_settings)
|
||||
if not self.common.gui.local_only:
|
||||
tor_con.start()
|
||||
self.settings_have_changed()
|
||||
|
||||
# After connecting to Tor, check for updates
|
||||
self.check_for_updates()
|
||||
|
||||
# Create the close warning dialog -- the dialog widget needs to be in the constructor
|
||||
# in order to test it
|
||||
self.close_dialog = QtWidgets.QMessageBox()
|
||||
self.close_dialog.setWindowTitle(strings._("gui_quit_warning_title"))
|
||||
self.close_dialog.setText(strings._("gui_quit_warning_description"))
|
||||
self.close_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
self.close_dialog.accept_button = self.close_dialog.addButton(
|
||||
strings._("gui_quit_warning_quit"), QtWidgets.QMessageBox.AcceptRole
|
||||
)
|
||||
self.close_dialog.reject_button = self.close_dialog.addButton(
|
||||
strings._("gui_quit_warning_cancel"), QtWidgets.QMessageBox.NoRole
|
||||
)
|
||||
self.close_dialog.setDefaultButton(self.close_dialog.reject_button)
|
||||
|
||||
def tor_connection_canceled(self):
|
||||
"""
|
||||
If the user cancels before Tor finishes connecting, ask if they want to
|
||||
quit, or open settings.
|
||||
"""
|
||||
self.common.log("MainWindow", "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.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
||||
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
|
||||
a.setDefaultButton(settings_button)
|
||||
a.exec_()
|
||||
|
||||
if a.clickedButton() == settings_button:
|
||||
# Open settings
|
||||
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"
|
||||
)
|
||||
|
||||
# Wait 1ms for the event loop to finish, then quit
|
||||
QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)
|
||||
|
||||
# Wait 100ms before asking
|
||||
QtCore.QTimer.singleShot(100, ask)
|
||||
|
||||
def tor_connection_open_settings(self):
|
||||
"""
|
||||
The TorConnectionDialog wants to open the Settings dialog
|
||||
"""
|
||||
self.common.log("MainWindow", "tor_connection_open_settings")
|
||||
|
||||
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
|
||||
QtCore.QTimer.singleShot(1, self.open_settings)
|
||||
|
||||
def open_settings(self):
|
||||
"""
|
||||
Open the SettingsDialog.
|
||||
"""
|
||||
self.common.log("MainWindow", "open_settings")
|
||||
d = SettingsDialog(self.common)
|
||||
d.settings_saved.connect(self.settings_have_changed)
|
||||
d.exec_()
|
||||
|
||||
def settings_have_changed(self):
|
||||
self.common.log("OnionShareGui", "settings_have_changed")
|
||||
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.status_bar.clearMessage()
|
||||
|
||||
# Tell each tab that settings have changed
|
||||
for index in range(self.tabs.count()):
|
||||
tab = self.tabs.widget(index)
|
||||
tab.settings_have_changed()
|
||||
|
||||
def check_for_updates(self):
|
||||
"""
|
||||
Check for updates in a new thread, if enabled.
|
||||
"""
|
||||
if self.common.platform == "Windows" or self.common.platform == "Darwin":
|
||||
if self.common.settings.get("use_autoupdate"):
|
||||
|
||||
def update_available(update_url, installed_version, latest_version):
|
||||
Alert(
|
||||
self.common,
|
||||
strings._("update_available").format(
|
||||
update_url, installed_version, latest_version
|
||||
),
|
||||
)
|
||||
|
||||
self.update_thread = UpdateThread(self.common, self.common.gui.onion)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def bring_to_front(self):
|
||||
self.common.log("MainWindow", "bring_to_front")
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
|
||||
def closeEvent(self, e):
|
||||
self.common.log("MainWindow", "closeEvent")
|
||||
|
||||
if self.tabs.are_tabs_active():
|
||||
# Open the warning dialog
|
||||
self.common.log("MainWindow", "closeEvent, opening warning dialog")
|
||||
self.close_dialog.exec_()
|
||||
|
||||
# Close
|
||||
if self.close_dialog.clickedButton() == self.close_dialog.accept_button:
|
||||
self.system_tray.hide()
|
||||
e.accept()
|
||||
# Cancel
|
||||
else:
|
||||
e.ignore()
|
||||
return
|
||||
|
||||
self.system_tray.hide()
|
||||
e.accept()
|
||||
|
||||
def cleanup(self):
|
||||
self.tabs.cleanup()
|
||||
self.common.gui.onion.cleanup()
|
@ -1,763 +0,0 @@
|
||||
# -*- 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 queue
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
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
|
||||
from .widgets import Alert
|
||||
from .update_checker import UpdateThread
|
||||
from .server_status import ServerStatus
|
||||
|
||||
|
||||
class OnionShareGui(QtWidgets.QMainWindow):
|
||||
"""
|
||||
OnionShareGui is the main window for the GUI that contains all of the
|
||||
GUI elements.
|
||||
"""
|
||||
|
||||
MODE_SHARE = "share"
|
||||
MODE_RECEIVE = "receive"
|
||||
MODE_WEBSITE = "website"
|
||||
|
||||
def __init__(
|
||||
self, common, onion, qtapp, app, filenames, config=False, local_only=False
|
||||
):
|
||||
super(OnionShareGui, self).__init__()
|
||||
|
||||
self.common = common
|
||||
self.common.log("OnionShareGui", "__init__")
|
||||
self.setMinimumWidth(820)
|
||||
self.setMinimumHeight(660)
|
||||
|
||||
self.onion = onion
|
||||
self.qtapp = qtapp
|
||||
self.app = app
|
||||
self.local_only = local_only
|
||||
|
||||
self.mode = self.MODE_SHARE
|
||||
|
||||
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
|
||||
if self.config:
|
||||
self.common.load_settings(self.config)
|
||||
else:
|
||||
self.common.load_settings()
|
||||
|
||||
strings.load_strings(self.common)
|
||||
|
||||
# System tray
|
||||
menu = QtWidgets.QMenu()
|
||||
self.settings_action = menu.addAction(strings._("gui_settings_window_title"))
|
||||
self.settings_action.triggered.connect(self.open_settings)
|
||||
self.help_action = menu.addAction(strings._("gui_settings_button_help"))
|
||||
self.help_action.triggered.connect(lambda: SettingsDialog.help_clicked(self))
|
||||
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"))
|
||||
)
|
||||
else:
|
||||
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.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.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.clicked.connect(self.open_settings)
|
||||
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_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"]
|
||||
)
|
||||
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)
|
||||
self.server_status_indicator = QtWidgets.QWidget()
|
||||
self.server_status_indicator.setLayout(server_status_indicator_layout)
|
||||
|
||||
# Status bar
|
||||
self.status_bar = QtWidgets.QStatusBar()
|
||||
self.status_bar.setSizeGripEnabled(False)
|
||||
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.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.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)
|
||||
self.share_mode.server_status.url_copied.connect(self.copy_url)
|
||||
self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
||||
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.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.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.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()
|
||||
|
||||
# Layouts
|
||||
contents_layout = QtWidgets.QVBoxLayout()
|
||||
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)
|
||||
layout.addLayout(mode_switcher_layout)
|
||||
layout.addLayout(contents_layout)
|
||||
|
||||
central_widget = QtWidgets.QWidget()
|
||||
central_widget.setLayout(layout)
|
||||
self.setCentralWidget(central_widget)
|
||||
self.show()
|
||||
|
||||
# The server isn't active yet
|
||||
self.set_server_active(False)
|
||||
|
||||
# Create the timer
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.timeout.connect(self.timer_callback)
|
||||
|
||||
# Start the "Connecting to Tor" dialog, which calls onion.connect()
|
||||
tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion)
|
||||
tor_con.canceled.connect(self._tor_connection_canceled)
|
||||
tor_con.open_settings.connect(self._tor_connection_open_settings)
|
||||
if not self.local_only:
|
||||
tor_con.start()
|
||||
|
||||
# Start the timer
|
||||
self.timer.start(500)
|
||||
|
||||
# After connecting to Tor, check for updates
|
||||
self.check_for_updates()
|
||||
|
||||
def update_mode_switcher(self):
|
||||
# Based on the current mode, switch the mode switcher button styles,
|
||||
# and show and hide widgets to switch modes
|
||||
if self.mode == self.MODE_SHARE:
|
||||
self.share_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_selected_style"]
|
||||
)
|
||||
self.receive_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
self.website_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
|
||||
self.receive_mode.hide()
|
||||
self.share_mode.show()
|
||||
self.website_mode.hide()
|
||||
elif self.mode == self.MODE_WEBSITE:
|
||||
self.share_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
self.receive_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
self.website_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_selected_style"]
|
||||
)
|
||||
|
||||
self.receive_mode.hide()
|
||||
self.share_mode.hide()
|
||||
self.website_mode.show()
|
||||
else:
|
||||
self.share_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
self.receive_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_selected_style"]
|
||||
)
|
||||
self.website_mode_button.setStyleSheet(
|
||||
self.common.css["mode_switcher_unselected_style"]
|
||||
)
|
||||
|
||||
self.share_mode.hide()
|
||||
self.receive_mode.show()
|
||||
self.website_mode.hide()
|
||||
|
||||
self.update_server_status_indicator()
|
||||
|
||||
def share_mode_clicked(self):
|
||||
if self.mode != self.MODE_SHARE:
|
||||
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.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")
|
||||
)
|
||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_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")
|
||||
)
|
||||
else:
|
||||
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")
|
||||
)
|
||||
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")
|
||||
)
|
||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_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")
|
||||
)
|
||||
else:
|
||||
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")
|
||||
)
|
||||
|
||||
def stop_server_finished(self):
|
||||
# When the server stopped, cleanup the ephemeral onion service
|
||||
self.onion.cleanup(stop_tor=False)
|
||||
|
||||
def _tor_connection_canceled(self):
|
||||
"""
|
||||
If the user cancels before Tor finishes connecting, ask if they want to
|
||||
quit, or open settings.
|
||||
"""
|
||||
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.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
||||
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
|
||||
a.setDefaultButton(settings_button)
|
||||
a.exec_()
|
||||
|
||||
if a.clickedButton() == settings_button:
|
||||
# Open settings
|
||||
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"
|
||||
)
|
||||
|
||||
# Wait 1ms for the event loop to finish, then quit
|
||||
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
||||
|
||||
# Wait 100ms before asking
|
||||
QtCore.QTimer.singleShot(100, ask)
|
||||
|
||||
def _tor_connection_open_settings(self):
|
||||
"""
|
||||
The TorConnectionDialog wants to open the Settings dialog
|
||||
"""
|
||||
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)
|
||||
|
||||
def open_settings(self):
|
||||
"""
|
||||
Open the SettingsDialog.
|
||||
"""
|
||||
self.common.log("OnionShareGui", "open_settings")
|
||||
|
||||
def reload_settings():
|
||||
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.
|
||||
# If we've reloaded settings, we probably succeeded in obtaining a new
|
||||
# connection. If so, restart the timer.
|
||||
if not self.local_only:
|
||||
if self.onion.is_authenticated():
|
||||
if not self.timer.isActive():
|
||||
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"):
|
||||
self.share_mode.server_status.autostop_timer_container.hide()
|
||||
self.receive_mode.server_status.autostop_timer_container.hide()
|
||||
self.website_mode.server_status.autostop_timer_container.hide()
|
||||
# If we switched off the auto-start timer setting, ensure the widget is hidden.
|
||||
if not self.common.settings.get("autostart_timer"):
|
||||
self.share_mode.server_status.autostart_timer_datetime = None
|
||||
self.receive_mode.server_status.autostart_timer_datetime = None
|
||||
self.website_mode.server_status.autostart_timer_datetime = None
|
||||
self.share_mode.server_status.autostart_timer_container.hide()
|
||||
self.receive_mode.server_status.autostart_timer_container.hide()
|
||||
self.website_mode.server_status.autostart_timer_container.hide()
|
||||
|
||||
d = SettingsDialog(
|
||||
self.common, self.onion, self.qtapp, self.config, self.local_only
|
||||
)
|
||||
d.settings_saved.connect(reload_settings)
|
||||
d.exec_()
|
||||
|
||||
# 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"):
|
||||
|
||||
def update_available(update_url, installed_version, latest_version):
|
||||
Alert(
|
||||
self.common,
|
||||
strings._("update_available").format(
|
||||
update_url, installed_version, latest_version
|
||||
),
|
||||
)
|
||||
|
||||
self.update_thread = UpdateThread(self.common, self.onion, self.config)
|
||||
self.update_thread.update_available.connect(update_available)
|
||||
self.update_thread.start()
|
||||
|
||||
def timer_callback(self):
|
||||
"""
|
||||
Check for messages communicated from the web app, and update the GUI accordingly. Also,
|
||||
call ShareMode and ReceiveMode's timer_callbacks.
|
||||
"""
|
||||
self.update()
|
||||
|
||||
if not self.local_only:
|
||||
# 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.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
|
||||
|
||||
events = []
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
try:
|
||||
r = mode.web.q.get(False)
|
||||
events.append(r)
|
||||
except queue.Empty:
|
||||
done = True
|
||||
|
||||
for event in events:
|
||||
if event["type"] == Web.REQUEST_LOAD:
|
||||
mode.handle_request_load(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_STARTED:
|
||||
mode.handle_request_started(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_RATE_LIMIT:
|
||||
mode.handle_request_rate_limit(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_PROGRESS:
|
||||
mode.handle_request_progress(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_CANCELED:
|
||||
mode.handle_request_canceled(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED:
|
||||
mode.handle_request_upload_file_renamed(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_SET_DIR:
|
||||
mode.handle_request_upload_set_dir(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_FINISHED:
|
||||
mode.handle_request_upload_finished(event)
|
||||
|
||||
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"]
|
||||
),
|
||||
)
|
||||
|
||||
if event["type"] == Web.REQUEST_OTHER:
|
||||
if (
|
||||
event["path"] != "/favicon.ico"
|
||||
and event["path"] != f"/{mode.web.shutdown_password}/shutdown"
|
||||
):
|
||||
self.status_bar.showMessage(
|
||||
f"{strings._('other_page_loaded')}: {event['path']}"
|
||||
)
|
||||
|
||||
if event["type"] == Web.REQUEST_INVALID_PASSWORD:
|
||||
self.status_bar.showMessage(
|
||||
f"[#{mode.web.invalid_passwords_count}] {strings._('incorrect_password')}: {event['data']}"
|
||||
)
|
||||
|
||||
mode.timer_callback()
|
||||
|
||||
def copy_url(self):
|
||||
"""
|
||||
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")
|
||||
)
|
||||
|
||||
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"),
|
||||
)
|
||||
|
||||
def clear_message(self):
|
||||
"""
|
||||
Clear messages from the status bar.
|
||||
"""
|
||||
self.status_bar.clearMessage()
|
||||
|
||||
def set_server_active(self, active):
|
||||
"""
|
||||
Disable the Settings and Receive Files buttons while an Share Files server is active.
|
||||
"""
|
||||
if active:
|
||||
self.settings_button.hide()
|
||||
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.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")
|
||||
dialog = QtWidgets.QMessageBox()
|
||||
dialog.setWindowTitle(strings._("gui_quit_title"))
|
||||
if self.mode == OnionShareGui.MODE_SHARE:
|
||||
dialog.setText(strings._("gui_share_quit_warning"))
|
||||
else:
|
||||
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,
|
||||
)
|
||||
dialog.setDefaultButton(dont_quit_button)
|
||||
reply = dialog.exec_()
|
||||
|
||||
# Quit
|
||||
if reply == 0:
|
||||
self.stop_server()
|
||||
e.accept()
|
||||
# Don't Quit
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
except:
|
||||
e.accept()
|
@ -40,18 +40,13 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
|
||||
settings_saved = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, common, onion, qtapp, config=False, local_only=False):
|
||||
def __init__(self, common):
|
||||
super(SettingsDialog, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
||||
self.common.log("SettingsDialog", "__init__")
|
||||
|
||||
self.onion = onion
|
||||
self.qtapp = qtapp
|
||||
self.config = config
|
||||
self.local_only = local_only
|
||||
|
||||
self.setModal(True)
|
||||
self.setWindowTitle(strings._("gui_settings_window_title"))
|
||||
self.setWindowIcon(
|
||||
@ -63,273 +58,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
# If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog
|
||||
self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1"
|
||||
|
||||
# General settings
|
||||
|
||||
# Use a password or not ('public mode')
|
||||
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"])
|
||||
public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
public_mode_label.setOpenExternalLinks(True)
|
||||
public_mode_label.setMinimumSize(public_mode_label.sizeHint())
|
||||
public_mode_layout = QtWidgets.QHBoxLayout()
|
||||
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)
|
||||
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"])
|
||||
autostart_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
autostart_timer_label.setOpenExternalLinks(True)
|
||||
autostart_timer_label.setMinimumSize(public_mode_label.sizeHint())
|
||||
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
||||
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)
|
||||
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"])
|
||||
autostop_timer_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
autostop_timer_label.setOpenExternalLinks(True)
|
||||
autostop_timer_label.setMinimumSize(public_mode_label.sizeHint())
|
||||
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
||||
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)
|
||||
self.autostop_timer_widget = QtWidgets.QWidget()
|
||||
self.autostop_timer_widget.setLayout(autostop_timer_layout)
|
||||
|
||||
# General settings layout
|
||||
general_group_layout = QtWidgets.QVBoxLayout()
|
||||
general_group_layout.addWidget(self.public_mode_widget)
|
||||
general_group_layout.addWidget(self.autostart_timer_widget)
|
||||
general_group_layout.addWidget(self.autostop_timer_widget)
|
||||
general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label"))
|
||||
general_group.setLayout(general_group_layout)
|
||||
|
||||
# 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"]
|
||||
)
|
||||
|
||||
# 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"])
|
||||
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)
|
||||
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
|
||||
)
|
||||
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)
|
||||
self.use_legacy_v2_onions_widget = QtWidgets.QWidget()
|
||||
self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout)
|
||||
|
||||
# Stealth
|
||||
self.stealth_checkbox = QtWidgets.QCheckBox()
|
||||
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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
use_stealth_label.setOpenExternalLinks(True)
|
||||
use_stealth_label.setMinimumSize(use_stealth_label.sizeHint())
|
||||
use_stealth_layout = QtWidgets.QHBoxLayout()
|
||||
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)
|
||||
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.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.hide()
|
||||
|
||||
# Onion settings widget
|
||||
onion_settings_layout = QtWidgets.QVBoxLayout()
|
||||
onion_settings_layout.setContentsMargins(0, 0, 0, 0)
|
||||
onion_settings_layout.addWidget(self.save_private_key_widget)
|
||||
onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget)
|
||||
onion_settings_layout.addWidget(self.use_stealth_widget)
|
||||
onion_settings_layout.addWidget(self.hidservauth_details)
|
||||
onion_settings_layout.addWidget(self.hidservauth_copy_button)
|
||||
self.onion_settings_widget = QtWidgets.QWidget()
|
||||
self.onion_settings_widget.setLayout(onion_settings_layout)
|
||||
|
||||
# Onion settings layout
|
||||
onion_group_layout = QtWidgets.QVBoxLayout()
|
||||
onion_group_layout.addWidget(self.connect_to_tor_label)
|
||||
onion_group_layout.addWidget(self.onion_settings_widget)
|
||||
onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label"))
|
||||
onion_group.setLayout(onion_group_layout)
|
||||
|
||||
# Sharing options
|
||||
|
||||
# Close after first download
|
||||
self.close_after_first_download_checkbox = QtWidgets.QCheckBox()
|
||||
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.close_after_first_download_checkbox.setText(
|
||||
strings._("gui_settings_close_after_first_download_option")
|
||||
)
|
||||
individual_downloads_label = QtWidgets.QLabel(
|
||||
strings._("gui_settings_individual_downloads_label")
|
||||
)
|
||||
|
||||
# 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"))
|
||||
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.clicked.connect(self.data_dir_button_clicked)
|
||||
data_dir_layout = QtWidgets.QHBoxLayout()
|
||||
data_dir_layout.addWidget(data_dir_label)
|
||||
data_dir_layout.addWidget(self.data_dir_lineedit)
|
||||
data_dir_layout.addWidget(data_dir_button)
|
||||
|
||||
# Receiving options layout
|
||||
receiving_group_layout = QtWidgets.QVBoxLayout()
|
||||
receiving_group_layout.addLayout(data_dir_layout)
|
||||
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
|
||||
@ -346,7 +74,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
)
|
||||
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:
|
||||
if not self.common.gui.onion.connected_to_tor:
|
||||
self.check_for_updates_button.setEnabled(False)
|
||||
|
||||
# Autoupdate options layout
|
||||
@ -673,7 +401,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
)
|
||||
self.cancel_button.clicked.connect(self.cancel_clicked)
|
||||
version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}")
|
||||
version_label.setStyleSheet(self.common.css["settings_version"])
|
||||
version_label.setStyleSheet(self.common.gui.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()
|
||||
@ -685,33 +413,26 @@ 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.gui.css["settings_tor_status"])
|
||||
self.tor_status.hide()
|
||||
|
||||
# Layout
|
||||
left_col_layout = QtWidgets.QVBoxLayout()
|
||||
left_col_layout.addWidget(general_group)
|
||||
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()
|
||||
|
||||
right_col_layout = QtWidgets.QVBoxLayout()
|
||||
right_col_layout.addWidget(connection_type_radio_group)
|
||||
right_col_layout.addLayout(connection_type_layout)
|
||||
right_col_layout.addWidget(self.tor_status)
|
||||
right_col_layout.addStretch()
|
||||
|
||||
col_layout = QtWidgets.QHBoxLayout()
|
||||
col_layout.addLayout(left_col_layout)
|
||||
if not self.hide_tor_settings:
|
||||
col_layout.addLayout(right_col_layout)
|
||||
tor_layout = QtWidgets.QVBoxLayout()
|
||||
tor_layout.addWidget(connection_type_radio_group)
|
||||
tor_layout.addLayout(connection_type_layout)
|
||||
tor_layout.addWidget(self.tor_status)
|
||||
tor_layout.addStretch()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addLayout(col_layout)
|
||||
if not self.hide_tor_settings:
|
||||
layout.addLayout(tor_layout)
|
||||
layout.addSpacing(20)
|
||||
layout.addWidget(autoupdate_group)
|
||||
if autoupdate_group.isVisible():
|
||||
layout.addSpacing(20)
|
||||
layout.addLayout(language_layout)
|
||||
layout.addSpacing(20)
|
||||
layout.addStretch()
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
self.setLayout(layout)
|
||||
@ -721,67 +442,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
|
||||
def reload_settings(self):
|
||||
# Load settings, and fill them in
|
||||
self.old_settings = Settings(self.common, self.config)
|
||||
self.old_settings = Settings(self.common)
|
||||
self.old_settings.load()
|
||||
|
||||
close_after_first_download = self.old_settings.get("close_after_first_download")
|
||||
if close_after_first_download:
|
||||
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
csp_header_disabled = self.old_settings.get("csp_header_disabled")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
|
||||
if use_legacy_v2_onions:
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.use_stealth_widget.show()
|
||||
else:
|
||||
self.use_stealth_widget.hide()
|
||||
|
||||
data_dir = self.old_settings.get("data_dir")
|
||||
self.data_dir_lineedit.setText(data_dir)
|
||||
|
||||
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")
|
||||
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") != "":
|
||||
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")
|
||||
if use_autoupdate:
|
||||
self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
@ -860,25 +523,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
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
|
||||
if self.onion.is_authenticated():
|
||||
self.connect_to_tor_label.hide()
|
||||
self.onion_settings_widget.show()
|
||||
|
||||
# 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.use_legacy_v2_onions_checkbox.show()
|
||||
else:
|
||||
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.
|
||||
@ -995,55 +639,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
self.authenticate_password_extras.hide()
|
||||
|
||||
def hidservauth_copy_button_clicked(self):
|
||||
"""
|
||||
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",
|
||||
)
|
||||
clipboard = self.qtapp.clipboard()
|
||||
clipboard.setText(self.old_settings.get("hidservauth_string"))
|
||||
|
||||
def use_legacy_v2_onions_checkbox_clicked(self, checked):
|
||||
"""
|
||||
Show the legacy settings if the legacy mode is enabled.
|
||||
"""
|
||||
if checked:
|
||||
self.use_stealth_widget.show()
|
||||
else:
|
||||
self.use_stealth_widget.hide()
|
||||
|
||||
def stealth_checkbox_clicked_connect(self, checked):
|
||||
"""
|
||||
Prevent the v2 legacy mode being switched off if stealth is enabled
|
||||
"""
|
||||
if checked:
|
||||
self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(False)
|
||||
else:
|
||||
self.use_legacy_v2_onions_checkbox.setEnabled(True)
|
||||
|
||||
def data_dir_button_clicked(self):
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
if selected_dir:
|
||||
self.common.log(
|
||||
"SettingsDialog",
|
||||
"data_dir_button_clicked",
|
||||
f"selected dir: {selected_dir}",
|
||||
)
|
||||
self.data_dir_lineedit.setText(selected_dir)
|
||||
|
||||
def test_tor_clicked(self):
|
||||
"""
|
||||
Test Tor Settings button clicked. With the given settings, see if we can
|
||||
@ -1065,10 +660,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
else:
|
||||
tor_status_update_func = None
|
||||
|
||||
onion = Onion(self.common)
|
||||
onion = Onion(self.common, use_tmp_dir=True)
|
||||
onion.connect(
|
||||
custom_settings=settings,
|
||||
config=self.config,
|
||||
tor_status_update_func=tor_status_update_func,
|
||||
)
|
||||
|
||||
@ -1110,11 +704,11 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.common.log("SettingsDialog", "check_for_updates")
|
||||
# Disable buttons
|
||||
self._disable_buttons()
|
||||
self.qtapp.processEvents()
|
||||
self.common.gui.qtapp.processEvents()
|
||||
|
||||
def update_timestamp():
|
||||
# Update the last checked label
|
||||
settings = Settings(self.common, self.config)
|
||||
settings = Settings(self.common)
|
||||
settings.load()
|
||||
autoupdate_timestamp = settings.get("autoupdate_timestamp")
|
||||
self._update_autoupdate_timestamp(autoupdate_timestamp)
|
||||
@ -1157,7 +751,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
close_forced_update_thread()
|
||||
|
||||
forced_update_thread = UpdateThread(
|
||||
self.common, self.onion, self.config, force=True
|
||||
self.common, self.onion, force=True
|
||||
)
|
||||
forced_update_thread.update_available.connect(update_available)
|
||||
forced_update_thread.update_not_available.connect(update_not_available)
|
||||
@ -1205,8 +799,8 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
|
||||
# the Onion object
|
||||
reboot_onion = False
|
||||
if not self.local_only:
|
||||
if self.onion.is_authenticated():
|
||||
if not self.common.gui.local_only:
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
self.common.log(
|
||||
"SettingsDialog", "save_clicked", "Connected to Tor"
|
||||
)
|
||||
@ -1245,20 +839,18 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.common.log(
|
||||
"SettingsDialog", "save_clicked", "rebooting the Onion"
|
||||
)
|
||||
self.onion.cleanup()
|
||||
self.common.gui.onion.cleanup()
|
||||
|
||||
tor_con = TorConnectionDialog(
|
||||
self.common, self.qtapp, self.onion, settings
|
||||
)
|
||||
tor_con = TorConnectionDialog(self.common, settings)
|
||||
tor_con.start()
|
||||
|
||||
self.common.log(
|
||||
"SettingsDialog",
|
||||
"save_clicked",
|
||||
f"Onion done rebooting, connected to Tor: {self.onion.connected_to_tor}",
|
||||
f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}",
|
||||
)
|
||||
|
||||
if self.onion.is_authenticated() and not tor_con.wasCanceled():
|
||||
if self.common.gui.onion.is_authenticated() and not tor_con.wasCanceled():
|
||||
self.settings_saved.emit()
|
||||
self.close()
|
||||
|
||||
@ -1274,7 +866,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
Cancel button clicked.
|
||||
"""
|
||||
self.common.log("SettingsDialog", "cancel_clicked")
|
||||
if not self.local_only and not self.onion.is_authenticated():
|
||||
if not self.common.gui.local_only and not self.common.gui.onion.is_authenticated():
|
||||
Alert(
|
||||
self.common,
|
||||
strings._("gui_tor_connection_canceled"),
|
||||
@ -1301,51 +893,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
Return a Settings object that's full of values from the settings dialog.
|
||||
"""
|
||||
self.common.log("SettingsDialog", "settings_from_fields")
|
||||
settings = Settings(self.common, self.config)
|
||||
settings = Settings(self.common)
|
||||
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():
|
||||
use_legacy_v2_onions = True
|
||||
else:
|
||||
use_legacy_v2_onions = False
|
||||
|
||||
if self.save_private_key_checkbox.isChecked():
|
||||
settings.set("save_private_key", True)
|
||||
settings.set("private_key", self.old_settings.get("private_key"))
|
||||
settings.set("password", self.old_settings.get("password"))
|
||||
settings.set(
|
||||
"hidservauth_string", self.old_settings.get("hidservauth_string")
|
||||
)
|
||||
else:
|
||||
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", "")
|
||||
|
||||
if use_legacy_v2_onions:
|
||||
settings.set("use_legacy_v2_onions", True)
|
||||
else:
|
||||
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())
|
||||
# Always unset the HidServAuth if Stealth mode is unset
|
||||
if not self.stealth_checkbox.isChecked():
|
||||
settings.set("hidservauth_string", "")
|
||||
|
||||
# Language
|
||||
locale_index = self.language_combobox.currentIndex()
|
||||
locale = self.language_combobox.itemData(locale_index)
|
||||
@ -1448,14 +998,14 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
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():
|
||||
if not self.common.gui.local_only:
|
||||
if not self.common.gui.onion.is_authenticated():
|
||||
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)
|
||||
QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)
|
||||
|
||||
def _update_autoupdate_timestamp(self, autoupdate_timestamp):
|
||||
self.common.log("SettingsDialog", "_update_autoupdate_timestamp")
|
||||
@ -1473,7 +1023,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
self.tor_status.setText(
|
||||
f"<strong>{strings._('connecting_to_tor')}</strong><br>{progress}% {summary}"
|
||||
)
|
||||
self.qtapp.processEvents()
|
||||
self.common.gui.qtapp.processEvents()
|
||||
if "Done" in summary:
|
||||
self.tor_status.hide()
|
||||
self._enable_buttons()
|
||||
@ -1489,7 +1039,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||
def _enable_buttons(self):
|
||||
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:
|
||||
if not self.common.gui.onion.connected_to_tor:
|
||||
self.check_for_updates_button.setEnabled(False)
|
||||
else:
|
||||
self.check_for_updates_button.setEnabled(True)
|
||||
|
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OnionShare | https://onionshare.org/
|
||||
|
||||
@ -16,23 +17,4 @@ 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 tempfile
|
||||
import os
|
||||
|
||||
|
||||
class MockSubprocess:
|
||||
def __init__(self):
|
||||
self.last_call = None
|
||||
|
||||
def call(self, args):
|
||||
self.last_call = args
|
||||
|
||||
def last_call_args(self):
|
||||
return self.last_call
|
||||
|
||||
|
||||
def write_tempfile(text):
|
||||
path = os.path.join(tempfile.mkdtemp(), "/test-file.txt")
|
||||
with open(path, "w") as f:
|
||||
f.write(text)
|
||||
return path
|
||||
from .tab import Tab
|
@ -23,11 +23,11 @@ from onionshare import strings
|
||||
from onionshare.common import AutoStopTimer
|
||||
|
||||
from .history import IndividualFileHistoryItem
|
||||
from .mode_settings_widget import ModeSettingsWidget
|
||||
|
||||
from ..server_status import ServerStatus
|
||||
from ..threads import OnionThread
|
||||
from ..threads import AutoStartTimer
|
||||
from ..widgets import Alert
|
||||
from ...threads import OnionThread, AutoStartTimer
|
||||
from ...widgets import Alert
|
||||
|
||||
|
||||
class Mode(QtWidgets.QWidget):
|
||||
@ -42,43 +42,46 @@ class Mode(QtWidgets.QWidget):
|
||||
starting_server_error = QtCore.pyqtSignal(str)
|
||||
starting_server_early = QtCore.pyqtSignal()
|
||||
set_server_active = QtCore.pyqtSignal(bool)
|
||||
change_persistent = QtCore.pyqtSignal(int, bool)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
common,
|
||||
qtapp,
|
||||
app,
|
||||
status_bar,
|
||||
server_status_label,
|
||||
system_tray,
|
||||
filenames=None,
|
||||
local_only=False,
|
||||
):
|
||||
def __init__(self, tab):
|
||||
super(Mode, self).__init__()
|
||||
self.common = common
|
||||
self.qtapp = qtapp
|
||||
self.app = app
|
||||
self.tab = tab
|
||||
self.settings = tab.settings
|
||||
|
||||
self.status_bar = status_bar
|
||||
self.server_status_label = server_status_label
|
||||
self.system_tray = system_tray
|
||||
self.common = tab.common
|
||||
self.qtapp = self.common.gui.qtapp
|
||||
self.app = tab.app
|
||||
|
||||
self.filenames = filenames
|
||||
self.status_bar = tab.status_bar
|
||||
self.server_status_label = tab.status_bar.server_status_label
|
||||
self.system_tray = tab.system_tray
|
||||
|
||||
self.filenames = tab.filenames
|
||||
|
||||
# The web object gets created in init()
|
||||
self.web = None
|
||||
|
||||
# Local mode is passed from OnionShareGui
|
||||
self.local_only = local_only
|
||||
|
||||
# Threads start out as None
|
||||
self.onion_thread = None
|
||||
self.web_thread = None
|
||||
self.startup_thread = None
|
||||
|
||||
# Mode settings widget
|
||||
self.mode_settings_widget = ModeSettingsWidget(
|
||||
self.common, self.tab, self.settings
|
||||
)
|
||||
self.mode_settings_widget.change_persistent.connect(self.change_persistent)
|
||||
|
||||
# Server status
|
||||
self.server_status = ServerStatus(
|
||||
self.common, self.qtapp, self.app, None, self.local_only
|
||||
self.common,
|
||||
self.qtapp,
|
||||
self.app,
|
||||
self.settings,
|
||||
self.mode_settings_widget,
|
||||
None,
|
||||
self.common.gui.local_only,
|
||||
)
|
||||
self.server_status.server_started.connect(self.start_server)
|
||||
self.server_status.server_stopped.connect(self.stop_server)
|
||||
@ -90,18 +93,20 @@ class Mode(QtWidgets.QWidget):
|
||||
self.starting_server_early.connect(self.start_server_early)
|
||||
self.starting_server_error.connect(self.start_server_error)
|
||||
|
||||
# Header
|
||||
# Note: It's up to the downstream Mode to add this to its layout
|
||||
self.header_label = QtWidgets.QLabel()
|
||||
self.header_label.setStyleSheet(self.common.gui.css["mode_header_label"])
|
||||
self.header_label.setAlignment(QtCore.Qt.AlignHCenter)
|
||||
|
||||
# Primary action
|
||||
# Note: It's up to the downstream Mode to add this to its layout
|
||||
self.primary_action_layout = QtWidgets.QVBoxLayout()
|
||||
self.primary_action_layout.addWidget(self.mode_settings_widget)
|
||||
self.primary_action_layout.addWidget(self.server_status)
|
||||
self.primary_action = QtWidgets.QWidget()
|
||||
self.primary_action.setLayout(self.primary_action_layout)
|
||||
|
||||
# Hack to allow a minimum width on the main layout
|
||||
# Note: It's up to the downstream Mode to add this to its layout
|
||||
self.min_width_widget = QtWidgets.QWidget()
|
||||
self.min_width_widget.setMinimumWidth(600)
|
||||
|
||||
def init(self):
|
||||
"""
|
||||
Add custom initialization here.
|
||||
@ -137,7 +142,7 @@ class Mode(QtWidgets.QWidget):
|
||||
now = QtCore.QDateTime.currentDateTime()
|
||||
if self.server_status.local_only:
|
||||
seconds_remaining = now.secsTo(
|
||||
self.server_status.autostart_timer_widget.dateTime()
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime()
|
||||
)
|
||||
else:
|
||||
seconds_remaining = now.secsTo(
|
||||
@ -159,8 +164,8 @@ class Mode(QtWidgets.QWidget):
|
||||
|
||||
# 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.settings.get(
|
||||
"general", "autostop_timer"
|
||||
):
|
||||
if self.autostop_timer_datetime_delta > 0:
|
||||
now = QtCore.QDateTime.currentDateTime()
|
||||
@ -207,14 +212,15 @@ class Mode(QtWidgets.QWidget):
|
||||
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"))
|
||||
|
||||
# Clear the status bar
|
||||
self.status_bar.clearMessage()
|
||||
self.server_status_label.setText("")
|
||||
|
||||
# Hide the mode settings
|
||||
self.mode_settings_widget.hide()
|
||||
|
||||
# Ensure we always get a new random port each time we might launch an OnionThread
|
||||
self.app.port = None
|
||||
|
||||
@ -297,7 +303,7 @@ class Mode(QtWidgets.QWidget):
|
||||
|
||||
self.start_server_step3_custom()
|
||||
|
||||
if self.common.settings.get("autostop_timer"):
|
||||
if self.settings.get("general", "autostop_timer"):
|
||||
# Convert the date value to seconds between now and then
|
||||
now = QtCore.QDateTime.currentDateTime()
|
||||
self.autostop_timer_datetime_delta = now.secsTo(
|
||||
@ -385,6 +391,9 @@ class Mode(QtWidgets.QWidget):
|
||||
self.set_server_active.emit(False)
|
||||
self.stop_server_finished.emit()
|
||||
|
||||
# Show the mode settings
|
||||
self.mode_settings_widget.show()
|
||||
|
||||
def stop_server_custom(self):
|
||||
"""
|
||||
Add custom initialization here.
|
@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
|
||||
from ..widgets import Alert, AddFileDialog
|
||||
from ...widgets import Alert, AddFileDialog
|
||||
|
||||
|
||||
class DropHereLabel(QtWidgets.QLabel):
|
||||
@ -50,7 +50,9 @@ class DropHereLabel(QtWidgets.QLabel):
|
||||
)
|
||||
else:
|
||||
self.setText(strings._("gui_drag_and_drop"))
|
||||
self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"])
|
||||
self.setStyleSheet(
|
||||
self.common.gui.css["share_file_selection_drop_here_label"]
|
||||
)
|
||||
|
||||
self.hide()
|
||||
|
||||
@ -75,7 +77,7 @@ 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.setStyleSheet(self.common.gui.css["share_file_selection_drop_count_label"])
|
||||
self.hide()
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
@ -169,7 +171,7 @@ class FileList(QtWidgets.QListWidget):
|
||||
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.gui.css["share_file_list_drag_enter"])
|
||||
count = len(event.mimeData().urls())
|
||||
self.drop_count.setText(f"+{count}")
|
||||
|
||||
@ -189,7 +191,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.gui.css["share_file_list_drag_leave"])
|
||||
self.drop_count.hide()
|
||||
event.accept()
|
||||
self.update()
|
||||
@ -217,7 +219,7 @@ class FileList(QtWidgets.QListWidget):
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
self.setStyleSheet(self.common.css["share_file_list_drag_leave"])
|
||||
self.setStyleSheet(self.common.gui.css["share_file_list_drag_leave"])
|
||||
self.drop_count.hide()
|
||||
|
||||
self.files_dropped.emit()
|
||||
@ -254,7 +256,7 @@ 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.gui.css["share_file_list_item_size"])
|
||||
|
||||
item.basename = os.path.basename(filename.rstrip("/"))
|
||||
# Use the basename as the method with which to sort the list
|
||||
@ -381,6 +383,9 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
# Update the file list
|
||||
self.file_list.update()
|
||||
|
||||
# Save the latest file list to mode settings
|
||||
self.save_filenames()
|
||||
|
||||
def add(self):
|
||||
"""
|
||||
Add button clicked.
|
||||
@ -450,6 +455,25 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
"""
|
||||
return len(range(self.file_list.count()))
|
||||
|
||||
def get_filenames(self):
|
||||
"""
|
||||
Return the list of file and folder names
|
||||
"""
|
||||
filenames = []
|
||||
for index in range(self.file_list.count()):
|
||||
filenames.append(self.file_list.item(index).filename)
|
||||
return filenames
|
||||
|
||||
def save_filenames(self):
|
||||
"""
|
||||
Save the filenames to mode settings
|
||||
"""
|
||||
filenames = self.get_filenames()
|
||||
if self.parent.tab.mode == self.common.gui.MODE_SHARE:
|
||||
self.parent.settings.set("share", "filenames", filenames)
|
||||
elif self.parent.tab.mode == self.common.gui.MODE_WEBSITE:
|
||||
self.parent.settings.set("website", "filenames", filenames)
|
||||
|
||||
def setFocus(self):
|
||||
"""
|
||||
Set the Qt app focus on the file selection box.
|
@ -24,7 +24,7 @@ from datetime import datetime
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
from ..widgets import Alert
|
||||
from ...widgets import Alert
|
||||
|
||||
|
||||
class HistoryItem(QtWidgets.QWidget):
|
||||
@ -122,7 +122,7 @@ class ShareHistoryItem(HistoryItem):
|
||||
self.progress_bar.setMaximum(total_bytes)
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_bar.setStyleSheet(
|
||||
self.common.css["downloads_uploads_progress_bar"]
|
||||
self.common.gui.css["downloads_uploads_progress_bar"]
|
||||
)
|
||||
self.progress_bar.total_bytes = total_bytes
|
||||
|
||||
@ -193,7 +193,7 @@ 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.gui.css["receive_file_size"])
|
||||
self.filesize_label.hide()
|
||||
|
||||
# Folder button
|
||||
@ -290,14 +290,14 @@ class ReceiveHistoryItem(HistoryItem):
|
||||
self.progress_bar.setMinimum(0)
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_bar.setStyleSheet(
|
||||
self.common.css["downloads_uploads_progress_bar"]
|
||||
self.common.gui.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.gui.css["receive_file"])
|
||||
files_widget.setLayout(self.files_layout)
|
||||
|
||||
# Layout
|
||||
@ -405,7 +405,7 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||
self.started_dt.strftime("%b %d, %I:%M%p")
|
||||
)
|
||||
self.timestamp_label.setStyleSheet(
|
||||
self.common.css["history_individual_file_timestamp_label"]
|
||||
self.common.gui.css["history_individual_file_timestamp_label"]
|
||||
)
|
||||
self.path_label = QtWidgets.QLabel(self.path)
|
||||
self.status_code_label = QtWidgets.QLabel()
|
||||
@ -417,7 +417,7 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_bar.setStyleSheet(
|
||||
self.common.css["downloads_uploads_progress_bar"]
|
||||
self.common.gui.css["downloads_uploads_progress_bar"]
|
||||
)
|
||||
|
||||
# Text layout
|
||||
@ -438,11 +438,11 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||
self.status_code_label.setText(str(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"]
|
||||
self.common.gui.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.common.gui.css["history_individual_file_status_code_label_4xx"]
|
||||
)
|
||||
self.status = HistoryItem.STATUS_FINISHED
|
||||
self.progress_bar.hide()
|
||||
@ -464,7 +464,7 @@ class IndividualFileHistoryItem(HistoryItem):
|
||||
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.common.gui.css["history_individual_file_status_code_label_2xx"]
|
||||
)
|
||||
self.progress_bar.hide()
|
||||
self.status = HistoryItem.STATUS_FINISHED
|
||||
@ -586,19 +586,19 @@ class History(QtWidgets.QWidget):
|
||||
|
||||
# In progress, completed, and requests labels
|
||||
self.in_progress_label = QtWidgets.QLabel()
|
||||
self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"])
|
||||
self.in_progress_label.setStyleSheet(self.common.gui.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.gui.css["mode_info_label"])
|
||||
self.requests_label = QtWidgets.QLabel()
|
||||
self.requests_label.setStyleSheet(self.common.css["mode_info_label"])
|
||||
self.requests_label.setStyleSheet(self.common.gui.css["mode_info_label"])
|
||||
|
||||
# Header
|
||||
self.header_label = QtWidgets.QLabel(header_text)
|
||||
self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"])
|
||||
self.header_label.setStyleSheet(self.common.gui.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.setStyleSheet(self.common.gui.css["downloads_uploads_clear"])
|
||||
self.clear_button.setFlat(True)
|
||||
self.clear_button.clicked.connect(self.reset)
|
||||
header_layout = QtWidgets.QHBoxLayout()
|
||||
@ -615,14 +615,16 @@ 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.gui.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.gui.css["downloads_uploads_empty"])
|
||||
self.empty.setLayout(empty_layout)
|
||||
|
||||
# When there are items
|
||||
@ -759,7 +761,7 @@ class ToggleHistory(QtWidgets.QPushButton):
|
||||
self.indicator_count = 0
|
||||
self.indicator_label = QtWidgets.QLabel(parent=self)
|
||||
self.indicator_label.setStyleSheet(
|
||||
self.common.css["download_uploads_indicator"]
|
||||
self.common.gui.css["download_uploads_indicator"]
|
||||
)
|
||||
self.update_indicator()
|
||||
|
289
onionshare_gui/tab/mode/mode_settings_widget.py
Normal file
289
onionshare_gui/tab/mode/mode_settings_widget.py
Normal file
@ -0,0 +1,289 @@
|
||||
# -*- 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/>.
|
||||
"""
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from onionshare import strings
|
||||
|
||||
|
||||
class ModeSettingsWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
All of the common settings for each mode are in this widget
|
||||
"""
|
||||
|
||||
change_persistent = QtCore.pyqtSignal(int, bool)
|
||||
|
||||
def __init__(self, common, tab, mode_settings):
|
||||
super(ModeSettingsWidget, self).__init__()
|
||||
self.common = common
|
||||
self.tab = tab
|
||||
self.settings = mode_settings
|
||||
|
||||
# Downstream Mode need to fill in this layout with its settings
|
||||
self.mode_specific_layout = QtWidgets.QVBoxLayout()
|
||||
|
||||
# Persistent
|
||||
self.persistent_checkbox = QtWidgets.QCheckBox()
|
||||
self.persistent_checkbox.clicked.connect(self.persistent_checkbox_clicked)
|
||||
self.persistent_checkbox.setText(strings._("mode_settings_persistent_checkbox"))
|
||||
if self.settings.get("persistent", "enabled"):
|
||||
self.persistent_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.persistent_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# Public
|
||||
self.public_checkbox = QtWidgets.QCheckBox()
|
||||
self.public_checkbox.clicked.connect(self.public_checkbox_clicked)
|
||||
self.public_checkbox.setText(strings._("mode_settings_public_checkbox"))
|
||||
if self.settings.get("general", "public"):
|
||||
self.public_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.public_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# Whether or not to use an auto-start timer
|
||||
self.autostart_timer_checkbox = QtWidgets.QCheckBox()
|
||||
self.autostart_timer_checkbox.clicked.connect(
|
||||
self.autostart_timer_checkbox_clicked
|
||||
)
|
||||
self.autostart_timer_checkbox.setText(
|
||||
strings._("mode_settings_autostart_timer_checkbox")
|
||||
)
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.autostart_timer_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# The autostart timer widget
|
||||
self.autostart_timer_widget = QtWidgets.QDateTimeEdit()
|
||||
self.autostart_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
||||
self.autostart_timer_reset()
|
||||
self.autostart_timer_widget.setCurrentSection(
|
||||
QtWidgets.QDateTimeEdit.MinuteSection
|
||||
)
|
||||
self.autostart_timer_widget.hide()
|
||||
|
||||
# Autostart timer layout
|
||||
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
||||
autostart_timer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
autostart_timer_layout.addWidget(self.autostart_timer_checkbox)
|
||||
autostart_timer_layout.addWidget(self.autostart_timer_widget)
|
||||
|
||||
# Whether or not to use an auto-stop timer
|
||||
self.autostop_timer_checkbox = QtWidgets.QCheckBox()
|
||||
self.autostop_timer_checkbox.clicked.connect(
|
||||
self.autostop_timer_checkbox_clicked
|
||||
)
|
||||
self.autostop_timer_checkbox.setText(
|
||||
strings._("mode_settings_autostop_timer_checkbox")
|
||||
)
|
||||
if self.settings.get("general", "autostop_timer"):
|
||||
self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.autostop_timer_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# The autostop timer widget
|
||||
self.autostop_timer_widget = QtWidgets.QDateTimeEdit()
|
||||
self.autostop_timer_widget.setDisplayFormat("hh:mm A MMM d, yy")
|
||||
self.autostop_timer_reset()
|
||||
self.autostop_timer_widget.setCurrentSection(
|
||||
QtWidgets.QDateTimeEdit.MinuteSection
|
||||
)
|
||||
self.autostop_timer_widget.hide()
|
||||
|
||||
# Autostop timer layout
|
||||
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
||||
autostop_timer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
autostop_timer_layout.addWidget(self.autostop_timer_checkbox)
|
||||
autostop_timer_layout.addWidget(self.autostop_timer_widget)
|
||||
|
||||
# Legacy address
|
||||
self.legacy_checkbox = QtWidgets.QCheckBox()
|
||||
self.legacy_checkbox.clicked.connect(self.legacy_checkbox_clicked)
|
||||
self.legacy_checkbox.clicked.connect(self.update_ui)
|
||||
self.legacy_checkbox.setText(strings._("mode_settings_legacy_checkbox"))
|
||||
if self.settings.get("general", "legacy"):
|
||||
self.legacy_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.legacy_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# Client auth
|
||||
self.client_auth_checkbox = QtWidgets.QCheckBox()
|
||||
self.client_auth_checkbox.clicked.connect(self.client_auth_checkbox_clicked)
|
||||
self.client_auth_checkbox.clicked.connect(self.update_ui)
|
||||
self.client_auth_checkbox.setText(
|
||||
strings._("mode_settings_client_auth_checkbox")
|
||||
)
|
||||
if self.settings.get("general", "client_auth"):
|
||||
self.client_auth_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.client_auth_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
# Toggle advanced settings
|
||||
self.toggle_advanced_button = QtWidgets.QPushButton()
|
||||
self.toggle_advanced_button.clicked.connect(self.toggle_advanced_clicked)
|
||||
self.toggle_advanced_button.setFlat(True)
|
||||
self.toggle_advanced_button.setStyleSheet(
|
||||
self.common.gui.css["mode_settings_toggle_advanced"]
|
||||
)
|
||||
|
||||
# Advanced group itself
|
||||
advanced_layout = QtWidgets.QVBoxLayout()
|
||||
advanced_layout.setContentsMargins(0, 0, 0, 0)
|
||||
advanced_layout.addLayout(autostart_timer_layout)
|
||||
advanced_layout.addLayout(autostop_timer_layout)
|
||||
advanced_layout.addWidget(self.legacy_checkbox)
|
||||
advanced_layout.addWidget(self.client_auth_checkbox)
|
||||
self.advanced_widget = QtWidgets.QWidget()
|
||||
self.advanced_widget.setLayout(advanced_layout)
|
||||
self.advanced_widget.hide()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addLayout(self.mode_specific_layout)
|
||||
layout.addWidget(self.persistent_checkbox)
|
||||
layout.addWidget(self.public_checkbox)
|
||||
layout.addWidget(self.advanced_widget)
|
||||
layout.addWidget(self.toggle_advanced_button)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.update_ui()
|
||||
|
||||
def update_ui(self):
|
||||
# Update text on advanced group toggle button
|
||||
if self.advanced_widget.isVisible():
|
||||
self.toggle_advanced_button.setText(
|
||||
strings._("mode_settings_advanced_toggle_hide")
|
||||
)
|
||||
else:
|
||||
self.toggle_advanced_button.setText(
|
||||
strings._("mode_settings_advanced_toggle_show")
|
||||
)
|
||||
|
||||
# Client auth is only a legacy option
|
||||
if self.client_auth_checkbox.isChecked():
|
||||
self.legacy_checkbox.setChecked(True)
|
||||
self.legacy_checkbox.setEnabled(False)
|
||||
else:
|
||||
self.legacy_checkbox.setEnabled(True)
|
||||
if self.legacy_checkbox.isChecked():
|
||||
self.client_auth_checkbox.show()
|
||||
else:
|
||||
self.client_auth_checkbox.hide()
|
||||
|
||||
# If the server has been started in the past, prevent changing legacy option
|
||||
if self.settings.get("onion", "private_key"):
|
||||
if self.legacy_checkbox.isChecked():
|
||||
# If using legacy, disable legacy and client auth options
|
||||
self.legacy_checkbox.setEnabled(False)
|
||||
self.client_auth_checkbox.setEnabled(False)
|
||||
else:
|
||||
# If using v3, hide legacy and client auth options
|
||||
self.legacy_checkbox.hide()
|
||||
self.client_auth_checkbox.hide()
|
||||
|
||||
def persistent_checkbox_clicked(self):
|
||||
self.settings.set("persistent", "enabled", self.persistent_checkbox.isChecked())
|
||||
self.settings.set("persistent", "mode", self.tab.mode)
|
||||
self.change_persistent.emit(
|
||||
self.tab.tab_id, self.persistent_checkbox.isChecked()
|
||||
)
|
||||
|
||||
# If disabling persistence, delete the file from disk
|
||||
if not self.persistent_checkbox.isChecked():
|
||||
self.settings.delete()
|
||||
|
||||
def public_checkbox_clicked(self):
|
||||
self.settings.set("general", "public", self.public_checkbox.isChecked())
|
||||
|
||||
def autostart_timer_checkbox_clicked(self):
|
||||
self.settings.set(
|
||||
"general", "autostart_timer", self.autostart_timer_checkbox.isChecked()
|
||||
)
|
||||
|
||||
if self.autostart_timer_checkbox.isChecked():
|
||||
self.autostart_timer_widget.show()
|
||||
else:
|
||||
self.autostart_timer_widget.hide()
|
||||
|
||||
def autostop_timer_checkbox_clicked(self):
|
||||
self.settings.set(
|
||||
"general", "autostop_timer", self.autostop_timer_checkbox.isChecked()
|
||||
)
|
||||
|
||||
if self.autostop_timer_checkbox.isChecked():
|
||||
self.autostop_timer_widget.show()
|
||||
else:
|
||||
self.autostop_timer_widget.hide()
|
||||
|
||||
def legacy_checkbox_clicked(self):
|
||||
self.settings.set("general", "legacy", self.legacy_checkbox.isChecked())
|
||||
|
||||
def client_auth_checkbox_clicked(self):
|
||||
self.settings.set(
|
||||
"general", "client_auth", self.client_auth_checkbox.isChecked()
|
||||
)
|
||||
|
||||
def toggle_advanced_clicked(self):
|
||||
if self.advanced_widget.isVisible():
|
||||
self.advanced_widget.hide()
|
||||
else:
|
||||
self.advanced_widget.show()
|
||||
|
||||
self.update_ui()
|
||||
|
||||
def autostart_timer_reset(self):
|
||||
"""
|
||||
Reset the auto-start timer in the UI after stopping a share
|
||||
"""
|
||||
if self.common.gui.local_only:
|
||||
# For testing
|
||||
self.autostart_timer_widget.setDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(15)
|
||||
)
|
||||
self.autostart_timer_widget.setMinimumDateTime(
|
||||
QtCore.QDateTime.currentDateTime()
|
||||
)
|
||||
else:
|
||||
self.autostart_timer_widget.setDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(
|
||||
300
|
||||
) # 5 minutes in the future
|
||||
)
|
||||
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
|
||||
"""
|
||||
if self.common.gui.local_only:
|
||||
# For testing
|
||||
self.autostop_timer_widget.setDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(15)
|
||||
)
|
||||
self.autostop_timer_widget.setMinimumDateTime(
|
||||
QtCore.QDateTime.currentDateTime()
|
||||
)
|
||||
else:
|
||||
self.autostop_timer_widget.setDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(300)
|
||||
)
|
||||
self.autostop_timer_widget.setMinimumDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||
)
|
@ -24,6 +24,7 @@ from onionshare.web import Web
|
||||
|
||||
from ..history import History, ToggleHistory, ReceiveHistoryItem
|
||||
from .. import Mode
|
||||
from ....widgets import MinimumWidthWidget
|
||||
|
||||
|
||||
class ReceiveMode(Mode):
|
||||
@ -36,7 +37,28 @@ class ReceiveMode(Mode):
|
||||
Custom initialization for ReceiveMode.
|
||||
"""
|
||||
# Create the Web object
|
||||
self.web = Web(self.common, True, "receive")
|
||||
self.web = Web(self.common, True, self.settings, "receive")
|
||||
|
||||
# Header
|
||||
self.header_label.setText(strings._("gui_new_tab_receive_button"))
|
||||
|
||||
# Settings
|
||||
data_dir_label = QtWidgets.QLabel(
|
||||
strings._("mode_settings_receive_data_dir_label")
|
||||
)
|
||||
self.data_dir_lineedit = QtWidgets.QLineEdit()
|
||||
self.data_dir_lineedit.setReadOnly(True)
|
||||
self.data_dir_lineedit.setText(self.settings.get("receive", "data_dir"))
|
||||
data_dir_button = QtWidgets.QPushButton(
|
||||
strings._("mode_settings_receive_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)
|
||||
data_dir_layout.addWidget(self.data_dir_lineedit)
|
||||
data_dir_layout.addWidget(data_dir_button)
|
||||
|
||||
self.mode_settings_widget.mode_specific_layout.addLayout(data_dir_layout)
|
||||
|
||||
# Server status
|
||||
self.server_status.set_mode("receive")
|
||||
@ -90,14 +112,37 @@ class ReceiveMode(Mode):
|
||||
self.main_layout.addWidget(receive_warning)
|
||||
self.main_layout.addWidget(self.primary_action)
|
||||
self.main_layout.addStretch()
|
||||
self.main_layout.addWidget(self.min_width_widget)
|
||||
self.main_layout.addWidget(MinimumWidthWidget(700))
|
||||
|
||||
# Column layout
|
||||
self.column_layout = QtWidgets.QHBoxLayout()
|
||||
self.column_layout.addLayout(self.main_layout)
|
||||
self.column_layout.addWidget(self.history, stretch=1)
|
||||
|
||||
# Wrapper layout
|
||||
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
self.wrapper_layout.addLayout(self.main_layout)
|
||||
self.wrapper_layout.addWidget(self.history, stretch=1)
|
||||
self.wrapper_layout = QtWidgets.QVBoxLayout()
|
||||
self.wrapper_layout.addWidget(self.header_label)
|
||||
self.wrapper_layout.addLayout(self.column_layout)
|
||||
self.setLayout(self.wrapper_layout)
|
||||
|
||||
def data_dir_button_clicked(self):
|
||||
"""
|
||||
Browse for a new OnionShare data directory, and save to tab settings
|
||||
"""
|
||||
data_dir = self.data_dir_lineedit.text()
|
||||
selected_dir = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self, strings._("mode_settings_receive_data_dir_label"), data_dir
|
||||
)
|
||||
|
||||
if selected_dir:
|
||||
self.common.log(
|
||||
"ReceiveMode",
|
||||
"data_dir_button_clicked",
|
||||
f"selected dir: {selected_dir}",
|
||||
)
|
||||
self.data_dir_lineedit.setText(selected_dir)
|
||||
self.settings.set("receive", "data_dir", data_dir)
|
||||
|
||||
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
|
@ -29,7 +29,7 @@ from ..file_selection import FileSelection
|
||||
from .threads import CompressThread
|
||||
from .. import Mode
|
||||
from ..history import History, ToggleHistory, ShareHistoryItem
|
||||
from ...widgets import Alert
|
||||
from ....widgets import Alert, MinimumWidthWidget
|
||||
|
||||
|
||||
class ShareMode(Mode):
|
||||
@ -45,7 +45,27 @@ class ShareMode(Mode):
|
||||
self.compress_thread = None
|
||||
|
||||
# Create the Web object
|
||||
self.web = Web(self.common, True, "share")
|
||||
self.web = Web(self.common, True, self.settings, "share")
|
||||
|
||||
# Header
|
||||
self.header_label.setText(strings._("gui_new_tab_share_button"))
|
||||
|
||||
# Settings
|
||||
self.autostop_sharing_checkbox = QtWidgets.QCheckBox()
|
||||
self.autostop_sharing_checkbox.clicked.connect(
|
||||
self.autostop_sharing_checkbox_clicked
|
||||
)
|
||||
self.autostop_sharing_checkbox.setText(
|
||||
strings._("mode_settings_share_autostop_sharing_checkbox")
|
||||
)
|
||||
if self.settings.get("share", "autostop_sharing"):
|
||||
self.autostop_sharing_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.autostop_sharing_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
self.mode_settings_widget.mode_specific_layout.addWidget(
|
||||
self.autostop_sharing_checkbox
|
||||
)
|
||||
|
||||
# File selection
|
||||
self.file_selection = FileSelection(self.common, self)
|
||||
@ -69,7 +89,9 @@ 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.gui.css["share_filesize_warning"]
|
||||
)
|
||||
self.filesize_warning.hide()
|
||||
|
||||
# Download history
|
||||
@ -119,17 +141,30 @@ class ShareMode(Mode):
|
||||
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)
|
||||
self.main_layout.addWidget(MinimumWidthWidget(700))
|
||||
|
||||
# Column layout
|
||||
self.column_layout = QtWidgets.QHBoxLayout()
|
||||
self.column_layout.addLayout(self.main_layout)
|
||||
self.column_layout.addWidget(self.history, stretch=1)
|
||||
|
||||
# Wrapper layout
|
||||
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
self.wrapper_layout.addLayout(self.main_layout)
|
||||
self.wrapper_layout.addWidget(self.history, stretch=1)
|
||||
self.wrapper_layout = QtWidgets.QVBoxLayout()
|
||||
self.wrapper_layout.addWidget(self.header_label)
|
||||
self.wrapper_layout.addLayout(self.column_layout)
|
||||
self.setLayout(self.wrapper_layout)
|
||||
|
||||
# Always start with focus on file selection
|
||||
self.file_selection.setFocus()
|
||||
|
||||
def autostop_sharing_checkbox_clicked(self):
|
||||
"""
|
||||
Save autostop sharing setting to the tab settings
|
||||
"""
|
||||
self.settings.set(
|
||||
"share", "autostop_sharing", self.autostop_sharing_checkbox.isChecked()
|
||||
)
|
||||
|
||||
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
|
||||
@ -141,7 +176,7 @@ class ShareMode(Mode):
|
||||
The auto-stop timer expired, should we stop the server? Returns a bool
|
||||
"""
|
||||
# If there were no attempts to download the share, or all downloads are done, we can stop
|
||||
if self.web.share_mode.cur_history_id == 0 or self.web.done:
|
||||
if self.history.in_progress_count == 0 or self.web.done:
|
||||
self.server_status.stop_server()
|
||||
self.server_status_label.setText(strings._("close_on_autostop_timer"))
|
||||
return True
|
||||
@ -169,9 +204,7 @@ class ShareMode(Mode):
|
||||
"""
|
||||
# Add progress bar to the status bar, indicating the compressing of files.
|
||||
self._zip_progress_bar = ZipProgressBar(self.common, 0)
|
||||
self.filenames = []
|
||||
for index in range(self.file_selection.file_list.count()):
|
||||
self.filenames.append(self.file_selection.file_list.item(index).filename)
|
||||
self.filenames = self.file_selection.get_filenames()
|
||||
|
||||
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(
|
||||
self.filenames
|
||||
@ -278,7 +311,7 @@ class ShareMode(Mode):
|
||||
self.history.update_in_progress()
|
||||
|
||||
# Close on finish?
|
||||
if self.common.settings.get("close_after_first_download"):
|
||||
if self.settings.get("share", "autostop_sharing"):
|
||||
self.server_status.stop_server()
|
||||
self.status_bar.clearMessage()
|
||||
self.server_status_label.setText(strings._("closing_automatically"))
|
||||
@ -372,7 +405,7 @@ class ZipProgressBar(QtWidgets.QProgressBar):
|
||||
self.setMinimumWidth(200)
|
||||
self.setValue(0)
|
||||
self.setFormat(strings._("zip_progress_bar_format"))
|
||||
self.setStyleSheet(self.common.css["share_zip_progess_bar"])
|
||||
self.setStyleSheet(self.common.gui.css["share_zip_progess_bar"])
|
||||
|
||||
self._total_files_size = total_files_size
|
||||
self._processed_size = 0
|
@ -31,7 +31,7 @@ from onionshare.web import Web
|
||||
from ..file_selection import FileSelection
|
||||
from .. import Mode
|
||||
from ..history import History, ToggleHistory
|
||||
from ...widgets import Alert
|
||||
from ....widgets import Alert, MinimumWidthWidget
|
||||
|
||||
|
||||
class WebsiteMode(Mode):
|
||||
@ -47,7 +47,25 @@ class WebsiteMode(Mode):
|
||||
Custom initialization for ReceiveMode.
|
||||
"""
|
||||
# Create the Web object
|
||||
self.web = Web(self.common, True, "website")
|
||||
self.web = Web(self.common, True, self.settings, "website")
|
||||
|
||||
# Header
|
||||
self.header_label.setText(strings._("gui_new_tab_website_button"))
|
||||
|
||||
# Settings
|
||||
self.disable_csp_checkbox = QtWidgets.QCheckBox()
|
||||
self.disable_csp_checkbox.clicked.connect(self.disable_csp_checkbox_clicked)
|
||||
self.disable_csp_checkbox.setText(
|
||||
strings._("mode_settings_website_disable_csp_checkbox")
|
||||
)
|
||||
if self.settings.get("website", "disable_csp"):
|
||||
self.disable_csp_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.disable_csp_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
self.mode_settings_widget.mode_specific_layout.addWidget(
|
||||
self.disable_csp_checkbox
|
||||
)
|
||||
|
||||
# File selection
|
||||
self.file_selection = FileSelection(self.common, self)
|
||||
@ -71,7 +89,9 @@ class WebsiteMode(Mode):
|
||||
# Filesize warning
|
||||
self.filesize_warning = QtWidgets.QLabel()
|
||||
self.filesize_warning.setWordWrap(True)
|
||||
self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"])
|
||||
self.filesize_warning.setStyleSheet(
|
||||
self.common.gui.css["share_filesize_warning"]
|
||||
)
|
||||
self.filesize_warning.hide()
|
||||
|
||||
# Download history
|
||||
@ -121,17 +141,30 @@ class WebsiteMode(Mode):
|
||||
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)
|
||||
self.main_layout.addWidget(MinimumWidthWidget(700))
|
||||
|
||||
# Column layout
|
||||
self.column_layout = QtWidgets.QHBoxLayout()
|
||||
self.column_layout.addLayout(self.main_layout)
|
||||
self.column_layout.addWidget(self.history, stretch=1)
|
||||
|
||||
# Wrapper layout
|
||||
self.wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
self.wrapper_layout.addLayout(self.main_layout)
|
||||
self.wrapper_layout.addWidget(self.history, stretch=1)
|
||||
self.wrapper_layout = QtWidgets.QVBoxLayout()
|
||||
self.wrapper_layout.addWidget(self.header_label)
|
||||
self.wrapper_layout.addLayout(self.column_layout)
|
||||
self.setLayout(self.wrapper_layout)
|
||||
|
||||
# Always start with focus on file selection
|
||||
self.file_selection.setFocus()
|
||||
|
||||
def disable_csp_checkbox_clicked(self):
|
||||
"""
|
||||
Save disable CSP setting to the tab settings
|
||||
"""
|
||||
self.settings.set(
|
||||
"website", "disable_csp", self.disable_csp_checkbox.isChecked()
|
||||
)
|
||||
|
||||
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
|
@ -23,7 +23,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
|
||||
from .widgets import Alert
|
||||
from ..widgets import Alert
|
||||
|
||||
|
||||
class ServerStatus(QtWidgets.QWidget):
|
||||
@ -39,15 +39,20 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
url_copied = QtCore.pyqtSignal()
|
||||
hidservauth_copied = QtCore.pyqtSignal()
|
||||
|
||||
MODE_SHARE = "share"
|
||||
MODE_RECEIVE = "receive"
|
||||
MODE_WEBSITE = "website"
|
||||
|
||||
STATUS_STOPPED = 0
|
||||
STATUS_WORKING = 1
|
||||
STATUS_STARTED = 2
|
||||
|
||||
def __init__(self, common, qtapp, app, file_selection=None, local_only=False):
|
||||
def __init__(
|
||||
self,
|
||||
common,
|
||||
qtapp,
|
||||
app,
|
||||
mode_settings,
|
||||
mode_settings_widget,
|
||||
file_selection=None,
|
||||
local_only=False,
|
||||
):
|
||||
super(ServerStatus, self).__init__()
|
||||
|
||||
self.common = common
|
||||
@ -57,6 +62,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
|
||||
self.qtapp = qtapp
|
||||
self.app = app
|
||||
self.settings = mode_settings
|
||||
self.mode_settings_widget = mode_settings_widget
|
||||
|
||||
self.web = None
|
||||
self.autostart_timer_datetime = None
|
||||
@ -64,80 +71,6 @@ 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_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()
|
||||
)
|
||||
else:
|
||||
# Set proposed timer to be 5 minutes into the future
|
||||
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
|
||||
)
|
||||
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
||||
autostart_timer_layout.addWidget(self.autostart_timer_label)
|
||||
autostart_timer_layout.addWidget(self.autostart_timer_widget)
|
||||
|
||||
# Auto-start timer container, so it can all be hidden and shown as a group
|
||||
autostart_timer_container_layout = QtWidgets.QVBoxLayout()
|
||||
autostart_timer_container_layout.addLayout(autostart_timer_layout)
|
||||
self.autostart_timer_container = QtWidgets.QWidget()
|
||||
self.autostart_timer_container.setLayout(autostart_timer_container_layout)
|
||||
self.autostart_timer_container.hide()
|
||||
|
||||
# Auto-stop timer layout
|
||||
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()
|
||||
)
|
||||
else:
|
||||
# Set proposed timer to be 5 minutes into the future
|
||||
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
|
||||
)
|
||||
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
||||
autostop_timer_layout.addWidget(self.autostop_timer_label)
|
||||
autostop_timer_layout.addWidget(self.autostop_timer_widget)
|
||||
|
||||
# Auto-stop timer container, so it can all be hidden and shown as a group
|
||||
autostop_timer_container_layout = QtWidgets.QVBoxLayout()
|
||||
autostop_timer_container_layout.addLayout(autostop_timer_layout)
|
||||
self.autostop_timer_container = QtWidgets.QWidget()
|
||||
self.autostop_timer_container.setLayout(autostop_timer_container_layout)
|
||||
self.autostop_timer_container.hide()
|
||||
|
||||
# Server layout
|
||||
self.server_button = QtWidgets.QPushButton()
|
||||
self.server_button.clicked.connect(self.server_button_clicked)
|
||||
@ -151,11 +84,13 @@ 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.gui.css["server_status_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.gui.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(
|
||||
@ -163,7 +98,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
)
|
||||
self.copy_hidservauth_button.setFlat(True)
|
||||
self.copy_hidservauth_button.setStyleSheet(
|
||||
self.common.css["server_status_url_buttons"]
|
||||
self.common.gui.css["server_status_url_buttons"]
|
||||
)
|
||||
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
|
||||
url_buttons_layout = QtWidgets.QHBoxLayout()
|
||||
@ -180,8 +115,6 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.addWidget(self.server_button)
|
||||
layout.addLayout(url_layout)
|
||||
layout.addWidget(self.autostart_timer_container)
|
||||
layout.addWidget(self.autostop_timer_container)
|
||||
self.setLayout(layout)
|
||||
|
||||
def set_mode(self, share_mode, file_selection=None):
|
||||
@ -190,8 +123,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
"""
|
||||
self.mode = share_mode
|
||||
|
||||
if (self.mode == ServerStatus.MODE_SHARE) or (
|
||||
self.mode == ServerStatus.MODE_WEBSITE
|
||||
if (self.mode == self.common.gui.MODE_SHARE) or (
|
||||
self.mode == self.common.gui.MODE_WEBSITE
|
||||
):
|
||||
self.file_selection = file_selection
|
||||
|
||||
@ -214,30 +147,6 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
except:
|
||||
pass
|
||||
|
||||
def autostart_timer_reset(self):
|
||||
"""
|
||||
Reset the auto-start timer in the UI after stopping a share
|
||||
"""
|
||||
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)
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
if not self.local_only:
|
||||
self.autostop_timer_widget.setMinimumDateTime(
|
||||
QtCore.QDateTime.currentDateTime().addSecs(60)
|
||||
)
|
||||
|
||||
def show_url(self):
|
||||
"""
|
||||
Show the URL in the UI.
|
||||
@ -246,11 +155,11 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
|
||||
info_image = self.common.get_resource_path("images/info.png")
|
||||
|
||||
if self.mode == ServerStatus.MODE_SHARE:
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
self.url_description.setText(
|
||||
strings._("gui_share_url_description").format(info_image)
|
||||
)
|
||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||
elif self.mode == self.common.gui.MODE_WEBSITE:
|
||||
self.url_description.setText(
|
||||
strings._("gui_website_url_description").format(info_image)
|
||||
)
|
||||
@ -260,9 +169,9 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
)
|
||||
|
||||
# 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"
|
||||
if self.settings.get("persistent", "enabled"):
|
||||
if self.mode == self.common.gui.MODE_SHARE and self.settings.get(
|
||||
"share", "autostop_sharing"
|
||||
):
|
||||
self.url_description.setToolTip(
|
||||
strings._("gui_url_label_onetime_and_persistent")
|
||||
@ -270,8 +179,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
else:
|
||||
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"
|
||||
if self.mode == self.common.gui.MODE_SHARE and self.settings.get(
|
||||
"share", "autostop_sharing"
|
||||
):
|
||||
self.url_description.setToolTip(strings._("gui_url_label_onetime"))
|
||||
else:
|
||||
@ -281,7 +190,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.url.show()
|
||||
self.copy_url_button.show()
|
||||
|
||||
if self.app.stealth:
|
||||
if self.settings.get("general", "client_auth"):
|
||||
self.copy_hidservauth_button.show()
|
||||
else:
|
||||
self.copy_hidservauth_button.hide()
|
||||
@ -290,6 +199,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
"""
|
||||
Update the GUI elements based on the current state.
|
||||
"""
|
||||
self.common.log("ServerStatus", "update")
|
||||
# Set the URL fields
|
||||
if self.status == self.STATUS_STARTED:
|
||||
# The backend Onion may have saved new settings, such as the private key.
|
||||
@ -297,30 +207,34 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.common.settings.load()
|
||||
self.show_url()
|
||||
|
||||
if self.common.settings.get("save_private_key"):
|
||||
if not self.common.settings.get("password"):
|
||||
self.common.settings.set("password", self.web.password)
|
||||
self.common.settings.save()
|
||||
if not self.settings.get("onion", "password"):
|
||||
self.settings.set("onion", "password", self.web.password)
|
||||
self.settings.save()
|
||||
|
||||
if self.common.settings.get("autostart_timer"):
|
||||
self.autostart_timer_container.hide()
|
||||
|
||||
if self.common.settings.get("autostop_timer"):
|
||||
self.autostop_timer_container.hide()
|
||||
if self.settings.get("general", "autostop_timer"):
|
||||
self.server_button.setToolTip(
|
||||
strings._("gui_stop_server_autostop_timer_tooltip").format(
|
||||
self.mode_settings_widget.autostop_timer_widget.dateTime().toString(
|
||||
"h:mm AP, MMMM dd, yyyy"
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.url_description.hide()
|
||||
self.url.hide()
|
||||
self.copy_url_button.hide()
|
||||
self.copy_hidservauth_button.hide()
|
||||
|
||||
self.mode_settings_widget.update_ui()
|
||||
|
||||
# Button
|
||||
if (
|
||||
self.mode == ServerStatus.MODE_SHARE
|
||||
self.mode == self.common.gui.MODE_SHARE
|
||||
and self.file_selection.get_num_files() == 0
|
||||
):
|
||||
self.server_button.hide()
|
||||
elif (
|
||||
self.mode == ServerStatus.MODE_WEBSITE
|
||||
self.mode == self.common.gui.MODE_WEBSITE
|
||||
and self.file_selection.get_num_files() == 0
|
||||
):
|
||||
self.server_button.hide()
|
||||
@ -329,77 +243,57 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
|
||||
if self.status == self.STATUS_STOPPED:
|
||||
self.server_button.setStyleSheet(
|
||||
self.common.css["server_status_button_stopped"]
|
||||
self.common.gui.css["server_status_button_stopped"]
|
||||
)
|
||||
self.server_button.setEnabled(True)
|
||||
if self.mode == ServerStatus.MODE_SHARE:
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
self.server_button.setText(strings._("gui_share_start_server"))
|
||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||
elif self.mode == self.common.gui.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.autostart_timer_container.show()
|
||||
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.common.gui.css["server_status_button_started"]
|
||||
)
|
||||
self.server_button.setEnabled(True)
|
||||
if self.mode == ServerStatus.MODE_SHARE:
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
self.server_button.setText(strings._("gui_share_stop_server"))
|
||||
elif self.mode == ServerStatus.MODE_WEBSITE:
|
||||
elif self.mode == self.common.gui.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.autostart_timer_container.hide()
|
||||
if self.common.settings.get("autostop_timer"):
|
||||
self.autostop_timer_container.hide()
|
||||
self.server_button.setToolTip(
|
||||
strings._("gui_stop_server_autostop_timer_tooltip").format(
|
||||
self.autostop_timer_widget.dateTime().toString(
|
||||
"h:mm AP, MMMM dd, yyyy"
|
||||
)
|
||||
)
|
||||
)
|
||||
elif self.status == self.STATUS_WORKING:
|
||||
self.server_button.setStyleSheet(
|
||||
self.common.css["server_status_button_working"]
|
||||
self.common.gui.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(
|
||||
self.mode_settings_widget.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.autostop_timer_container.hide()
|
||||
else:
|
||||
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.autostart_timer_container.hide()
|
||||
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.server_button.setToolTip(
|
||||
strings._("gui_start_server_autostart_timer_tooltip").format(
|
||||
self.autostart_timer_widget.dateTime().toString(
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime().toString(
|
||||
"h:mm AP, MMMM dd, yyyy"
|
||||
)
|
||||
)
|
||||
)
|
||||
if self.common.settings.get("autostop_timer"):
|
||||
self.autostop_timer_container.hide()
|
||||
else:
|
||||
self.server_button.setStyleSheet(
|
||||
self.common.gui.css["server_status_button_working"]
|
||||
)
|
||||
self.server_button.setEnabled(False)
|
||||
self.server_button.setText(strings._("gui_please_wait"))
|
||||
|
||||
def server_button_clicked(self):
|
||||
"""
|
||||
@ -407,14 +301,14 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
"""
|
||||
if self.status == self.STATUS_STOPPED:
|
||||
can_start = True
|
||||
if self.common.settings.get("autostart_timer"):
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
if self.local_only:
|
||||
self.autostart_timer_datetime = (
|
||||
self.autostart_timer_widget.dateTime().toPyDateTime()
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime().toPyDateTime()
|
||||
)
|
||||
else:
|
||||
self.autostart_timer_datetime = (
|
||||
self.autostart_timer_widget.dateTime()
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime()
|
||||
.toPyDateTime()
|
||||
.replace(second=0, microsecond=0)
|
||||
)
|
||||
@ -429,15 +323,15 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
strings._("gui_server_autostart_timer_expired"),
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
)
|
||||
if self.common.settings.get("autostop_timer"):
|
||||
if self.settings.get("general", "autostop_timer"):
|
||||
if self.local_only:
|
||||
self.autostop_timer_datetime = (
|
||||
self.autostop_timer_widget.dateTime().toPyDateTime()
|
||||
self.mode_settings_widget.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()
|
||||
self.mode_settings_widget.autostop_timer_widget.dateTime()
|
||||
.toPyDateTime()
|
||||
.replace(second=0, microsecond=0)
|
||||
)
|
||||
@ -452,7 +346,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
strings._("gui_server_autostop_timer_expired"),
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
)
|
||||
if self.common.settings.get("autostart_timer"):
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
if self.autostop_timer_datetime <= self.autostart_timer_datetime:
|
||||
Alert(
|
||||
self.common,
|
||||
@ -492,8 +386,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
Stop the server.
|
||||
"""
|
||||
self.status = self.STATUS_WORKING
|
||||
self.autostart_timer_reset()
|
||||
self.autostop_timer_reset()
|
||||
self.mode_settings_widget.autostart_timer_reset()
|
||||
self.mode_settings_widget.autostop_timer_reset()
|
||||
self.update()
|
||||
self.server_stopped.emit()
|
||||
|
||||
@ -505,8 +399,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
"ServerStatus", "cancel_server", "Canceling the server mid-startup"
|
||||
)
|
||||
self.status = self.STATUS_WORKING
|
||||
self.autostart_timer_reset()
|
||||
self.autostop_timer_reset()
|
||||
self.mode_settings_widget.autostart_timer_reset()
|
||||
self.mode_settings_widget.autostop_timer_reset()
|
||||
self.update()
|
||||
self.server_canceled.emit()
|
||||
|
||||
@ -539,7 +433,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
"""
|
||||
Returns the OnionShare URL.
|
||||
"""
|
||||
if self.common.settings.get("public_mode"):
|
||||
if self.settings.get("general", "public"):
|
||||
url = f"http://{self.app.onion_host}"
|
||||
else:
|
||||
url = f"http://onionshare:{self.web.password}@{self.app.onion_host}"
|
539
onionshare_gui/tab/tab.py
Normal file
539
onionshare_gui/tab/tab.py
Normal file
@ -0,0 +1,539 @@
|
||||
# -*- 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 queue
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.onionshare import OnionShare
|
||||
from onionshare.web import Web
|
||||
from onionshare.mode_settings import ModeSettings
|
||||
|
||||
from .mode.share_mode import ShareMode
|
||||
from .mode.receive_mode import ReceiveMode
|
||||
from .mode.website_mode import WebsiteMode
|
||||
|
||||
from .server_status import ServerStatus
|
||||
|
||||
from ..widgets import Alert
|
||||
|
||||
|
||||
class Tab(QtWidgets.QWidget):
|
||||
"""
|
||||
A GUI tab, you know, sort of like in a web browser
|
||||
"""
|
||||
|
||||
change_title = QtCore.pyqtSignal(int, str)
|
||||
change_icon = QtCore.pyqtSignal(int, str)
|
||||
change_persistent = QtCore.pyqtSignal(int, bool)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
common,
|
||||
tab_id,
|
||||
system_tray,
|
||||
status_bar,
|
||||
mode_settings=None,
|
||||
filenames=None,
|
||||
):
|
||||
super(Tab, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("Tab", "__init__")
|
||||
|
||||
self.tab_id = tab_id
|
||||
self.system_tray = system_tray
|
||||
self.status_bar = status_bar
|
||||
self.filenames = filenames
|
||||
|
||||
self.mode = None
|
||||
|
||||
# Start the OnionShare app
|
||||
self.app = OnionShare(common, self.common.gui.onion, self.common.gui.local_only)
|
||||
|
||||
# Widgets to display on a new tab
|
||||
self.share_button = QtWidgets.QPushButton(strings._("gui_new_tab_share_button"))
|
||||
self.share_button.setStyleSheet(self.common.gui.css["mode_new_tab_button"])
|
||||
share_description = QtWidgets.QLabel(strings._("gui_new_tab_share_description"))
|
||||
share_description.setWordWrap(True)
|
||||
self.share_button.clicked.connect(self.share_mode_clicked)
|
||||
|
||||
self.receive_button = QtWidgets.QPushButton(
|
||||
strings._("gui_new_tab_receive_button")
|
||||
)
|
||||
self.receive_button.setStyleSheet(self.common.gui.css["mode_new_tab_button"])
|
||||
self.receive_button.clicked.connect(self.receive_mode_clicked)
|
||||
receive_description = QtWidgets.QLabel(
|
||||
strings._("gui_new_tab_receive_description")
|
||||
)
|
||||
receive_description.setWordWrap(True)
|
||||
|
||||
self.website_button = QtWidgets.QPushButton(
|
||||
strings._("gui_new_tab_website_button")
|
||||
)
|
||||
self.website_button.setStyleSheet(self.common.gui.css["mode_new_tab_button"])
|
||||
self.website_button.clicked.connect(self.website_mode_clicked)
|
||||
website_description = QtWidgets.QLabel(
|
||||
strings._("gui_new_tab_website_description")
|
||||
)
|
||||
website_description.setWordWrap(True)
|
||||
|
||||
new_tab_layout = QtWidgets.QVBoxLayout()
|
||||
new_tab_layout.addStretch(1)
|
||||
new_tab_layout.addWidget(self.share_button)
|
||||
new_tab_layout.addWidget(share_description)
|
||||
new_tab_layout.addSpacing(50)
|
||||
new_tab_layout.addWidget(self.receive_button)
|
||||
new_tab_layout.addWidget(receive_description)
|
||||
new_tab_layout.addSpacing(50)
|
||||
new_tab_layout.addWidget(self.website_button)
|
||||
new_tab_layout.addWidget(website_description)
|
||||
new_tab_layout.addStretch(3)
|
||||
|
||||
new_tab_inner = QtWidgets.QWidget()
|
||||
new_tab_inner.setFixedWidth(500)
|
||||
new_tab_inner.setLayout(new_tab_layout)
|
||||
|
||||
new_tab_outer_layout = QtWidgets.QHBoxLayout()
|
||||
new_tab_outer_layout.addStretch()
|
||||
new_tab_outer_layout.addWidget(new_tab_inner)
|
||||
new_tab_outer_layout.addStretch()
|
||||
|
||||
self.new_tab = QtWidgets.QWidget()
|
||||
self.new_tab.setLayout(new_tab_outer_layout)
|
||||
self.new_tab.show()
|
||||
|
||||
# Layout
|
||||
self.layout = QtWidgets.QVBoxLayout()
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.new_tab)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
# Create the timer
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.timeout.connect(self.timer_callback)
|
||||
|
||||
# Persistent image
|
||||
self.persistent_image_label = QtWidgets.QLabel()
|
||||
self.persistent_image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(
|
||||
QtGui.QImage(
|
||||
self.common.get_resource_path("images/persistent_enabled.png")
|
||||
)
|
||||
)
|
||||
)
|
||||
self.persistent_image_label.setFixedSize(20, 20)
|
||||
|
||||
# Create the close warning dialog -- the dialog widget needs to be in the constructor
|
||||
# in order to test it
|
||||
self.close_dialog = QtWidgets.QMessageBox()
|
||||
self.close_dialog.setWindowTitle(strings._("gui_close_tab_warning_title"))
|
||||
self.close_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
self.close_dialog.accept_button = self.close_dialog.addButton(
|
||||
strings._("gui_close_tab_warning_close"), QtWidgets.QMessageBox.AcceptRole
|
||||
)
|
||||
self.close_dialog.reject_button = self.close_dialog.addButton(
|
||||
strings._("gui_close_tab_warning_cancel"), QtWidgets.QMessageBox.RejectRole
|
||||
)
|
||||
self.close_dialog.setDefaultButton(self.close_dialog.reject_button)
|
||||
|
||||
def init(self, mode_settings=None):
|
||||
if mode_settings:
|
||||
# Load this tab
|
||||
self.settings = mode_settings
|
||||
mode = self.settings.get("persistent", "mode")
|
||||
if mode == "share":
|
||||
self.filenames = self.settings.get("share", "filenames")
|
||||
self.share_mode_clicked()
|
||||
elif mode == "receive":
|
||||
self.receive_mode_clicked()
|
||||
elif mode == "website":
|
||||
self.filenames = self.settings.get("website", "filenames")
|
||||
self.website_mode_clicked()
|
||||
else:
|
||||
# This is a new tab
|
||||
self.settings = ModeSettings(self.common)
|
||||
|
||||
def share_mode_clicked(self):
|
||||
self.common.log("Tab", "share_mode_clicked")
|
||||
self.mode = self.common.gui.MODE_SHARE
|
||||
self.new_tab.hide()
|
||||
|
||||
self.share_mode = ShareMode(self)
|
||||
self.share_mode.change_persistent.connect(self.change_persistent)
|
||||
|
||||
self.layout.addWidget(self.share_mode)
|
||||
self.share_mode.show()
|
||||
|
||||
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.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)
|
||||
self.share_mode.server_status.url_copied.connect(self.copy_url)
|
||||
self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
|
||||
|
||||
self.change_title.emit(self.tab_id, strings._("gui_new_tab_share_button"))
|
||||
|
||||
self.update_server_status_indicator()
|
||||
self.timer.start(500)
|
||||
|
||||
def receive_mode_clicked(self):
|
||||
self.common.log("Tab", "receive_mode_clicked")
|
||||
self.mode = self.common.gui.MODE_RECEIVE
|
||||
self.new_tab.hide()
|
||||
|
||||
self.receive_mode = ReceiveMode(self)
|
||||
self.receive_mode.change_persistent.connect(self.change_persistent)
|
||||
|
||||
self.layout.addWidget(self.receive_mode)
|
||||
self.receive_mode.show()
|
||||
|
||||
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.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.change_title.emit(self.tab_id, strings._("gui_new_tab_receive_button"))
|
||||
|
||||
self.update_server_status_indicator()
|
||||
self.timer.start(500)
|
||||
|
||||
def website_mode_clicked(self):
|
||||
self.common.log("Tab", "website_mode_clicked")
|
||||
self.mode = self.common.gui.MODE_WEBSITE
|
||||
self.new_tab.hide()
|
||||
|
||||
self.website_mode = WebsiteMode(self)
|
||||
self.website_mode.change_persistent.connect(self.change_persistent)
|
||||
|
||||
self.layout.addWidget(self.website_mode)
|
||||
self.website_mode.show()
|
||||
|
||||
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.change_title.emit(self.tab_id, strings._("gui_new_tab_website_button"))
|
||||
|
||||
self.update_server_status_indicator()
|
||||
self.timer.start(500)
|
||||
|
||||
def update_server_status_indicator(self):
|
||||
# Set the status image
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
# Share mode
|
||||
if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||
self.set_server_status_indicator_stopped(
|
||||
strings._("gui_status_indicator_share_stopped")
|
||||
)
|
||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
if self.share_mode.server_status.autostart_timer_datetime:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_scheduled")
|
||||
)
|
||||
else:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_working")
|
||||
)
|
||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||
self.set_server_status_indicator_started(
|
||||
strings._("gui_status_indicator_share_started")
|
||||
)
|
||||
elif self.mode == self.common.gui.MODE_WEBSITE:
|
||||
# Website mode
|
||||
if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||
self.set_server_status_indicator_stopped(
|
||||
strings._("gui_status_indicator_share_stopped")
|
||||
)
|
||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_working")
|
||||
)
|
||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||
self.set_server_status_indicator_started(
|
||||
strings._("gui_status_indicator_share_started")
|
||||
)
|
||||
elif self.mode == self.common.gui.MODE_RECEIVE:
|
||||
# Receive mode
|
||||
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
|
||||
self.set_server_status_indicator_stopped(
|
||||
strings._("gui_status_indicator_receive_stopped")
|
||||
)
|
||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
if self.receive_mode.server_status.autostart_timer_datetime:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_receive_scheduled")
|
||||
)
|
||||
else:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_receive_working")
|
||||
)
|
||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||
self.set_server_status_indicator_started(
|
||||
strings._("gui_status_indicator_receive_started")
|
||||
)
|
||||
|
||||
def set_server_status_indicator_stopped(self, label_text):
|
||||
self.change_icon.emit(self.tab_id, "images/server_stopped.png")
|
||||
self.status_bar.server_status_image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.status_bar.server_status_image_stopped)
|
||||
)
|
||||
self.status_bar.server_status_label.setText(label_text)
|
||||
|
||||
def set_server_status_indicator_working(self, label_text):
|
||||
self.change_icon.emit(self.tab_id, "images/server_working.png")
|
||||
self.status_bar.server_status_image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.status_bar.server_status_image_working)
|
||||
)
|
||||
self.status_bar.server_status_label.setText(label_text)
|
||||
|
||||
def set_server_status_indicator_started(self, label_text):
|
||||
self.change_icon.emit(self.tab_id, "images/server_started.png")
|
||||
self.status_bar.server_status_image_label.setPixmap(
|
||||
QtGui.QPixmap.fromImage(self.status_bar.server_status_image_started)
|
||||
)
|
||||
self.status_bar.server_status_label.setText(label_text)
|
||||
|
||||
def stop_server_finished(self):
|
||||
# When the server stopped, cleanup the ephemeral onion service
|
||||
self.get_mode().app.stop_onion_service(self.settings)
|
||||
|
||||
def timer_callback(self):
|
||||
"""
|
||||
Check for messages communicated from the web app, and update the GUI accordingly. Also,
|
||||
call ShareMode and ReceiveMode's timer_callbacks.
|
||||
"""
|
||||
self.update()
|
||||
|
||||
if not self.common.gui.local_only:
|
||||
# Have we lost connection to Tor somehow?
|
||||
if not self.common.gui.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.get_mode().handle_tor_broke()
|
||||
|
||||
# Process events from the web object
|
||||
mode = self.get_mode()
|
||||
|
||||
events = []
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
try:
|
||||
r = mode.web.q.get(False)
|
||||
events.append(r)
|
||||
except queue.Empty:
|
||||
done = True
|
||||
|
||||
for event in events:
|
||||
if event["type"] == Web.REQUEST_LOAD:
|
||||
mode.handle_request_load(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_STARTED:
|
||||
mode.handle_request_started(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_RATE_LIMIT:
|
||||
mode.handle_request_rate_limit(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_PROGRESS:
|
||||
mode.handle_request_progress(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_CANCELED:
|
||||
mode.handle_request_canceled(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED:
|
||||
mode.handle_request_upload_file_renamed(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_SET_DIR:
|
||||
mode.handle_request_upload_set_dir(event)
|
||||
|
||||
elif event["type"] == Web.REQUEST_UPLOAD_FINISHED:
|
||||
mode.handle_request_upload_finished(event)
|
||||
|
||||
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"]
|
||||
),
|
||||
)
|
||||
|
||||
if event["type"] == Web.REQUEST_OTHER:
|
||||
if (
|
||||
event["path"] != "/favicon.ico"
|
||||
and event["path"] != f"/{mode.web.shutdown_password}/shutdown"
|
||||
):
|
||||
self.status_bar.showMessage(
|
||||
f"{strings._('other_page_loaded')}: {event['path']}"
|
||||
)
|
||||
|
||||
if event["type"] == Web.REQUEST_INVALID_PASSWORD:
|
||||
self.status_bar.showMessage(
|
||||
f"[#{mode.web.invalid_passwords_count}] {strings._('incorrect_password')}: {event['data']}"
|
||||
)
|
||||
|
||||
mode.timer_callback()
|
||||
|
||||
def copy_url(self):
|
||||
"""
|
||||
When the URL gets copied to the clipboard, display this in the status bar.
|
||||
"""
|
||||
self.common.log("Tab", "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("Tab", "copy_hidservauth")
|
||||
self.system_tray.showMessage(
|
||||
strings._("gui_copied_hidservauth_title"),
|
||||
strings._("gui_copied_hidservauth"),
|
||||
)
|
||||
|
||||
def clear_message(self):
|
||||
"""
|
||||
Clear messages from the status bar.
|
||||
"""
|
||||
self.status_bar.clearMessage()
|
||||
|
||||
def get_mode(self):
|
||||
if self.mode:
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
return self.share_mode
|
||||
elif self.mode == self.common.gui.MODE_RECEIVE:
|
||||
return self.receive_mode
|
||||
else:
|
||||
return self.website_mode
|
||||
else:
|
||||
return None
|
||||
|
||||
def settings_have_changed(self):
|
||||
# Global settings have changed
|
||||
self.common.log("Tab", "settings_have_changed")
|
||||
|
||||
# We might've stopped the main requests timer if a Tor connection failed. If we've reloaded
|
||||
# settings, we probably succeeded in obtaining a new connection. If so, restart the timer.
|
||||
if not self.common.gui.local_only:
|
||||
if self.common.gui.onion.is_authenticated():
|
||||
mode = self.get_mode()
|
||||
if mode:
|
||||
if not self.timer.isActive():
|
||||
self.timer.start(500)
|
||||
mode.on_reload_settings()
|
||||
|
||||
def close_tab(self):
|
||||
self.common.log("Tab", "close_tab")
|
||||
if self.mode is None:
|
||||
return True
|
||||
|
||||
if self.settings.get("persistent", "enabled"):
|
||||
dialog_text = strings._("gui_close_tab_warning_persistent_description")
|
||||
else:
|
||||
server_status = self.get_mode().server_status
|
||||
if server_status.status == server_status.STATUS_STOPPED:
|
||||
return True
|
||||
else:
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
dialog_text = strings._("gui_close_tab_warning_share_description")
|
||||
elif self.mode == self.common.gui.MODE_RECEIVE:
|
||||
dialog_text = strings._("gui_close_tab_warning_receive_description")
|
||||
else:
|
||||
dialog_text = strings._("gui_close_tab_warning_website_description")
|
||||
|
||||
# Open the warning dialog
|
||||
self.common.log("Tab", "close_tab, opening warning dialog")
|
||||
self.close_dialog.setText(dialog_text)
|
||||
self.close_dialog.exec_()
|
||||
|
||||
# Close
|
||||
if self.close_dialog.clickedButton() == self.close_dialog.accept_button:
|
||||
self.common.log("Tab", "close_tab", "close, closing tab")
|
||||
self.get_mode().stop_server()
|
||||
self.app.cleanup()
|
||||
return True
|
||||
# Cancel
|
||||
else:
|
||||
self.common.log("Tab", "close_tab", "cancel, keeping tab open")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
self.app.cleanup()
|
235
onionshare_gui/tab_widget.py
Normal file
235
onionshare_gui/tab_widget.py
Normal file
@ -0,0 +1,235 @@
|
||||
# -*- 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/>.
|
||||
"""
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.mode_settings import ModeSettings
|
||||
|
||||
from .tab import Tab
|
||||
from .event_handler import EventHandler
|
||||
|
||||
|
||||
class TabWidget(QtWidgets.QTabWidget):
|
||||
"""
|
||||
A custom tab widget, that has a "+" button for adding new tabs
|
||||
"""
|
||||
|
||||
bring_to_front = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, common, system_tray, status_bar):
|
||||
super(TabWidget, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("TabWidget", "__init__")
|
||||
|
||||
self.system_tray = system_tray
|
||||
self.status_bar = status_bar
|
||||
|
||||
# Keep track of tabs in a dictionary
|
||||
self.tabs = {}
|
||||
self.current_tab_id = 0 # Each tab has a unique id
|
||||
|
||||
# Define the new tab button
|
||||
self.new_tab_button = QtWidgets.QPushButton("+", parent=self)
|
||||
self.new_tab_button.setFlat(True)
|
||||
self.new_tab_button.setAutoFillBackground(True)
|
||||
self.new_tab_button.setFixedSize(30, 30)
|
||||
self.new_tab_button.clicked.connect(self.new_tab_clicked)
|
||||
self.new_tab_button.setStyleSheet(
|
||||
self.common.gui.css["tab_widget_new_tab_button"]
|
||||
)
|
||||
self.new_tab_button.setToolTip(strings._("gui_new_tab_tooltip"))
|
||||
|
||||
# Use a custom tab bar
|
||||
tab_bar = TabBar()
|
||||
tab_bar.move_new_tab_button.connect(self.move_new_tab_button)
|
||||
self.setTabBar(tab_bar)
|
||||
|
||||
# Set up the tab widget
|
||||
self.setMovable(True)
|
||||
self.setTabsClosable(True)
|
||||
self.setUsesScrollButtons(True)
|
||||
|
||||
self.tabCloseRequested.connect(self.close_tab)
|
||||
|
||||
self.move_new_tab_button()
|
||||
|
||||
# Watch the events file for changes
|
||||
self.event_handler = EventHandler(common)
|
||||
self.event_handler.new_tab.connect(self.add_tab)
|
||||
self.event_handler.new_share_tab.connect(self.new_share_tab)
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(self.event_handler, self.common.gui.events_dir)
|
||||
self.observer.start()
|
||||
|
||||
def cleanup(self):
|
||||
# Stop the event thread
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
|
||||
# Clean up each tab
|
||||
for index in range(self.count()):
|
||||
tab = self.widget(index)
|
||||
tab.cleanup()
|
||||
|
||||
def move_new_tab_button(self):
|
||||
# Find the width of all tabs
|
||||
tabs_width = sum(
|
||||
[self.tabBar().tabRect(i).width() for i in range(self.count())]
|
||||
)
|
||||
|
||||
# The current position of the new tab button
|
||||
pos = self.new_tab_button.pos()
|
||||
|
||||
# If there are so many tabs it scrolls, move the button to the left of the scroll buttons
|
||||
if tabs_width > self.width():
|
||||
pos.setX(self.width() - 61)
|
||||
else:
|
||||
# Otherwise move the button to the right of the tabs
|
||||
pos.setX(self.tabBar().sizeHint().width())
|
||||
|
||||
self.new_tab_button.move(pos)
|
||||
self.new_tab_button.raise_()
|
||||
|
||||
def new_tab_clicked(self):
|
||||
# Create a new tab
|
||||
self.add_tab()
|
||||
|
||||
def load_tab(self, mode_settings_id):
|
||||
# Load the tab's mode settings
|
||||
mode_settings = ModeSettings(self.common, id=mode_settings_id)
|
||||
self.add_tab(mode_settings)
|
||||
|
||||
def new_share_tab(self, filenames):
|
||||
mode_settings = ModeSettings(self.common)
|
||||
mode_settings.set("persistent", "mode", "share")
|
||||
mode_settings.set("share", "filenames", filenames)
|
||||
self.add_tab(mode_settings)
|
||||
|
||||
def add_tab(self, mode_settings=None):
|
||||
tab = Tab(self.common, self.current_tab_id, self.system_tray, self.status_bar)
|
||||
tab.change_title.connect(self.change_title)
|
||||
tab.change_icon.connect(self.change_icon)
|
||||
tab.change_persistent.connect(self.change_persistent)
|
||||
|
||||
self.tabs[self.current_tab_id] = tab
|
||||
self.current_tab_id += 1
|
||||
|
||||
index = self.addTab(tab, strings._("gui_new_tab"))
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
tab.init(mode_settings)
|
||||
# If it's persistent, set the persistent image in the tab
|
||||
self.change_persistent(tab.tab_id, tab.settings.get("persistent", "enabled"))
|
||||
|
||||
# Bring the window to front, in case this is being added by an event
|
||||
self.bring_to_front.emit()
|
||||
|
||||
def change_title(self, tab_id, title):
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.setTabText(index, title)
|
||||
|
||||
def change_icon(self, tab_id, icon_path):
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
self.setTabIcon(index, QtGui.QIcon(self.common.get_resource_path(icon_path)))
|
||||
|
||||
def change_persistent(self, tab_id, is_persistent):
|
||||
index = self.indexOf(self.tabs[tab_id])
|
||||
if is_persistent:
|
||||
self.tabBar().setTabButton(
|
||||
index,
|
||||
QtWidgets.QTabBar.LeftSide,
|
||||
self.tabs[tab_id].persistent_image_label,
|
||||
)
|
||||
else:
|
||||
invisible_widget = QtWidgets.QWidget()
|
||||
invisible_widget.setFixedSize(0, 0)
|
||||
self.tabBar().setTabButton(
|
||||
index, QtWidgets.QTabBar.LeftSide, invisible_widget
|
||||
)
|
||||
|
||||
self.save_persistent_tabs()
|
||||
|
||||
def save_persistent_tabs(self):
|
||||
# Figure out the order of persistent tabs to save in settings
|
||||
persistent_tabs = []
|
||||
for index in range(self.count()):
|
||||
tab = self.widget(index)
|
||||
if tab.settings.get("persistent", "enabled"):
|
||||
persistent_tabs.append(tab.settings.id)
|
||||
# Only save if tabs have actually moved
|
||||
if persistent_tabs != self.common.settings.get("persistent_tabs"):
|
||||
self.common.settings.set("persistent_tabs", persistent_tabs)
|
||||
self.common.settings.save()
|
||||
|
||||
def close_tab(self, index):
|
||||
self.common.log("TabWidget", "close_tab", f"{index}")
|
||||
tab = self.widget(index)
|
||||
if tab.close_tab():
|
||||
# If the tab is persistent, delete the settings file from disk
|
||||
if tab.settings.get("persistent", "enabled"):
|
||||
tab.settings.delete()
|
||||
|
||||
# Remove the tab
|
||||
self.removeTab(index)
|
||||
del self.tabs[tab.tab_id]
|
||||
|
||||
# If the last tab is closed, open a new one
|
||||
if self.count() == 0:
|
||||
self.new_tab_clicked()
|
||||
|
||||
self.save_persistent_tabs()
|
||||
|
||||
def are_tabs_active(self):
|
||||
"""
|
||||
See if there are active servers in any open tabs
|
||||
"""
|
||||
for tab_id in self.tabs:
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
if mode.server_status.status != mode.server_status.STATUS_STOPPED:
|
||||
return True
|
||||
return False
|
||||
|
||||
def paintEvent(self, event):
|
||||
super(TabWidget, self).paintEvent(event)
|
||||
# Save the order of persistent tabs whenever a new tab is switched to -- ideally we would
|
||||
# do this whenever tabs gets moved, but paintEvent is the only event that seems to get triggered
|
||||
# when this happens
|
||||
self.save_persistent_tabs()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
# Make sure to move new tab button on each resize
|
||||
super(TabWidget, self).resizeEvent(event)
|
||||
self.move_new_tab_button()
|
||||
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
"""
|
||||
A custom tab bar
|
||||
"""
|
||||
|
||||
move_new_tab_button = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(TabBar, self).__init__()
|
||||
|
||||
def tabLayoutChange(self):
|
||||
self.move_new_tab_button.emit()
|
@ -20,7 +20,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import time
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from onionshare.onion import *
|
||||
from onionshare import strings
|
||||
from onionshare.onion import (
|
||||
TorTooOld,
|
||||
TorErrorInvalidSetting,
|
||||
TorErrorAutomatic,
|
||||
TorErrorSocketPort,
|
||||
TorErrorSocketFile,
|
||||
TorErrorMissingPassword,
|
||||
TorErrorUnreadableCookieFile,
|
||||
TorErrorAuthError,
|
||||
TorErrorProtocolError,
|
||||
BundledTorTimeout,
|
||||
)
|
||||
|
||||
|
||||
class OnionThread(QtCore.QThread):
|
||||
@ -47,29 +59,28 @@ class OnionThread(QtCore.QThread):
|
||||
self.mode.web.generate_static_url_path()
|
||||
|
||||
# Choose port and password early, because we need them to exist in advance for scheduled shares
|
||||
self.mode.app.stay_open = not self.mode.common.settings.get(
|
||||
"close_after_first_download"
|
||||
)
|
||||
if not self.mode.app.port:
|
||||
self.mode.app.choose_port()
|
||||
if not self.mode.common.settings.get("public_mode"):
|
||||
if not self.mode.settings.get("general", "public"):
|
||||
if not self.mode.web.password:
|
||||
self.mode.web.generate_password(
|
||||
self.mode.common.settings.get("password")
|
||||
self.mode.settings.get("onion", "password")
|
||||
)
|
||||
|
||||
try:
|
||||
if self.mode.obtain_onion_early:
|
||||
self.mode.app.start_onion_service(
|
||||
await_publication=False, save_scheduled_key=True
|
||||
self.mode.settings, await_publication=False
|
||||
)
|
||||
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||
time.sleep(0.2)
|
||||
self.success_early.emit()
|
||||
# Unregister the onion so we can use it in the next OnionThread
|
||||
self.mode.app.onion.cleanup(False)
|
||||
self.mode.app.stop_onion_service(self.mode.settings)
|
||||
else:
|
||||
self.mode.app.start_onion_service(await_publication=True)
|
||||
self.mode.app.start_onion_service(
|
||||
self.mode.settings, await_publication=True
|
||||
)
|
||||
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||
time.sleep(0.2)
|
||||
# start onionshare http service in new thread
|
||||
@ -109,12 +120,7 @@ class WebThread(QtCore.QThread):
|
||||
|
||||
def run(self):
|
||||
self.mode.common.log("WebThread", "run")
|
||||
self.mode.web.start(
|
||||
self.mode.app.port,
|
||||
self.mode.app.stay_open,
|
||||
self.mode.common.settings.get("public_mode"),
|
||||
self.mode.web.password,
|
||||
)
|
||||
self.mode.web.start(self.mode.app.port)
|
||||
self.success.emit()
|
||||
|
||||
|
||||
|
@ -32,7 +32,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
|
||||
open_settings = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, common, qtapp, onion, custom_settings=False):
|
||||
def __init__(self, common, custom_settings=False):
|
||||
super(TorConnectionDialog, self).__init__(None)
|
||||
|
||||
self.common = common
|
||||
@ -44,9 +44,6 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
|
||||
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"))
|
||||
@ -68,7 +65,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
def start(self):
|
||||
self.common.log("TorConnectionDialog", "start")
|
||||
|
||||
t = TorConnectionThread(self.common, self.settings, self, self.onion)
|
||||
t = TorConnectionThread(self.common, self.settings, self)
|
||||
t.tor_status_update.connect(self._tor_status_update)
|
||||
t.connected_to_tor.connect(self._connected_to_tor)
|
||||
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
|
||||
@ -81,7 +78,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
self.active = True
|
||||
while self.active:
|
||||
time.sleep(0.1)
|
||||
self.qtapp.processEvents()
|
||||
self.common.gui.qtapp.processEvents()
|
||||
|
||||
def _tor_status_update(self, progress, summary):
|
||||
self.setValue(int(progress))
|
||||
@ -99,7 +96,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||
def _canceled_connecting_to_tor(self):
|
||||
self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
|
||||
self.active = False
|
||||
self.onion.cleanup()
|
||||
self.common.gui.onion.cleanup()
|
||||
|
||||
# Cancel connecting to Tor
|
||||
QtCore.QTimer.singleShot(1, self.cancel)
|
||||
@ -131,7 +128,7 @@ class TorConnectionThread(QtCore.QThread):
|
||||
canceled_connecting_to_tor = QtCore.pyqtSignal()
|
||||
error_connecting_to_tor = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, common, settings, dialog, onion):
|
||||
def __init__(self, common, settings, dialog):
|
||||
super(TorConnectionThread, self).__init__()
|
||||
|
||||
self.common = common
|
||||
@ -141,15 +138,14 @@ class TorConnectionThread(QtCore.QThread):
|
||||
self.settings = settings
|
||||
|
||||
self.dialog = dialog
|
||||
self.onion = onion
|
||||
|
||||
def run(self):
|
||||
self.common.log("TorConnectionThread", "run")
|
||||
|
||||
# Connect to the Onion
|
||||
try:
|
||||
self.onion.connect(self.settings, False, self._tor_status_update)
|
||||
if self.onion.connected_to_tor:
|
||||
self.common.gui.onion.connect(self.settings, False, self._tor_status_update)
|
||||
if self.common.gui.onion.connected_to_tor:
|
||||
self.connected_to_tor.emit()
|
||||
else:
|
||||
self.canceled_connecting_to_tor.emit()
|
||||
|
@ -61,19 +61,18 @@ class UpdateChecker(QtCore.QObject):
|
||||
update_error = QtCore.pyqtSignal()
|
||||
update_invalid_version = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, common, onion, config=False):
|
||||
def __init__(self, common, onion):
|
||||
super(UpdateChecker, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
||||
self.common.log("UpdateChecker", "__init__")
|
||||
self.onion = onion
|
||||
self.config = config
|
||||
|
||||
def check(self, force=False, config=False):
|
||||
def check(self, force=False):
|
||||
self.common.log("UpdateChecker", "check", f"force={force}")
|
||||
# Load the settings
|
||||
settings = Settings(self.common, config)
|
||||
settings = Settings(self.common)
|
||||
settings.load()
|
||||
|
||||
# If force=True, then definitely check
|
||||
@ -188,27 +187,26 @@ class UpdateThread(QtCore.QThread):
|
||||
update_error = QtCore.pyqtSignal()
|
||||
update_invalid_version = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, common, onion, config=False, force=False):
|
||||
def __init__(self, common, onion, force=False):
|
||||
super(UpdateThread, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
||||
self.common.log("UpdateThread", "__init__")
|
||||
self.onion = onion
|
||||
self.config = config
|
||||
self.force = force
|
||||
|
||||
def run(self):
|
||||
self.common.log("UpdateThread", "run")
|
||||
|
||||
u = UpdateChecker(self.common, self.onion, self.config)
|
||||
u = UpdateChecker(self.common, self.onion)
|
||||
u.update_available.connect(self._update_available)
|
||||
u.update_not_available.connect(self._update_not_available)
|
||||
u.update_error.connect(self._update_error)
|
||||
u.update_invalid_version.connect(self._update_invalid_version)
|
||||
|
||||
try:
|
||||
u.check(config=self.config, force=self.force)
|
||||
u.check(force=self.force)
|
||||
except Exception as e:
|
||||
# If update check fails, silently ignore
|
||||
self.common.log("UpdateThread", "run", str(e))
|
||||
|
@ -79,3 +79,14 @@ class AddFileDialog(QtWidgets.QFileDialog):
|
||||
def accept(self):
|
||||
self.common.log("AddFileDialog", "accept")
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
|
||||
class MinimumWidthWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
An empty widget with a minimum width, just to force layouts to behave
|
||||
"""
|
||||
|
||||
def __init__(self, width):
|
||||
super(MinimumWidthWidget, self).__init__()
|
||||
self.setMinimumWidth(width)
|
||||
|
||||
|
703
poetry.lock
generated
Normal file
703
poetry.lock
generated
Normal file
@ -0,0 +1,703 @@
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python graph (network) package"
|
||||
name = "altgraph"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.17"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Atomic file writes."
|
||||
name = "atomicwrites"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.3.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Classes Without Boilerplate"
|
||||
name = "attrs"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "19.3.0"
|
||||
|
||||
[package.extras]
|
||||
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||
docs = ["sphinx", "zope.interface"]
|
||||
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
name = "certifi"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.11.28"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
name = "chardet"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "3.0.4"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Composable command line interface toolkit"
|
||||
name = "click"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "7.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Cross-platform colored terminal text."
|
||||
marker = "sys_platform == \"win32\""
|
||||
name = "colorama"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "0.4.3"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python 2.7 backport of the \"dis\" module from Python 3.5+"
|
||||
marker = "sys_platform == \"darwin\""
|
||||
name = "dis3"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.1.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A simple framework for building complex web applications."
|
||||
name = "flask"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.10.1"
|
||||
Werkzeug = ">=0.15"
|
||||
click = ">=5.1"
|
||||
itsdangerous = ">=0.24"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Basic and Digest HTTP authentication for Flask routes"
|
||||
name = "flask-httpauth"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "3.3.0"
|
||||
|
||||
[package.dependencies]
|
||||
Flask = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Clean single-source support for Python 3 and 2"
|
||||
name = "future"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "0.18.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
name = "idna"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.9"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Read metadata from Python packages"
|
||||
marker = "python_version < \"3.8\""
|
||||
name = "importlib-metadata"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
version = "1.5.0"
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "rst.linker"]
|
||||
testing = ["packaging", "importlib-resources"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
name = "itsdangerous"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A very fast and expressive template engine."
|
||||
name = "jinja2"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "2.11.1"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=0.23"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=0.8)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Mach-O header analysis and editing"
|
||||
name = "macholib"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.14"
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = ">=0.15"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
name = "markupsafe"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
name = "more-itertools"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "8.2.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Core utilities for Python packages"
|
||||
name = "packaging"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "20.3"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "File system general utilities"
|
||||
name = "pathtools"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.1.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python PE parsing module"
|
||||
name = "pefile"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.4.18"
|
||||
|
||||
[package.dependencies]
|
||||
future = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
name = "pluggy"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.13.1"
|
||||
|
||||
[package.dependencies]
|
||||
[package.dependencies.importlib-metadata]
|
||||
python = "<3.8"
|
||||
version = ">=0.12"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Cross-platform lib for process and system monitoring in Python."
|
||||
name = "psutil"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "5.7.0"
|
||||
|
||||
[package.extras]
|
||||
enum = ["enum34"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
name = "py"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.8.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Cryptographic library for Python"
|
||||
name = "pycryptodome"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "3.9.7"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||
marker = "sys_platform == \"darwin\""
|
||||
name = "pyinstaller"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "3.6"
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
dis3 = "*"
|
||||
setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python parsing module"
|
||||
name = "pyparsing"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "2.4.6"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python bindings for the Qt cross platform application toolkit"
|
||||
name = "pyqt5"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.14.0"
|
||||
|
||||
[package.dependencies]
|
||||
PyQt5-sip = ">=12.7,<13"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The sip module support for PyQt5"
|
||||
name = "pyqt5-sip"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "12.7.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
|
||||
name = "pysocks"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.7.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
name = "pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.4.1"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = ">=1.0"
|
||||
attrs = ">=17.4.0"
|
||||
colorama = "*"
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
|
||||
[package.dependencies.importlib-metadata]
|
||||
python = "<3.8"
|
||||
version = ">=0.12"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (v0.761)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "py.test plugin that activates the fault handler module for tests (dummy package)"
|
||||
name = "pytest-faulthandler"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2.0.1"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=5.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "pytest support for PyQt and PySide applications"
|
||||
name = "pytest-qt"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "3.3.0"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=3.0.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python HTTP for Humans."
|
||||
name = "requests"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "2.23.0"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
chardet = ">=3.0.2,<4"
|
||||
idna = ">=2.5,<3"
|
||||
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||
|
||||
[package.extras]
|
||||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
name = "six"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "1.14.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Stem is a Python controller library that allows applications to interact with Tor (https://www.torproject.org/)."
|
||||
name = "stem"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.8.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
name = "urllib3"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
version = "1.25.8"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Filesystem events monitoring"
|
||||
name = "watchdog"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.10.2"
|
||||
|
||||
[package.dependencies]
|
||||
pathtools = ">=0.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Measures number of Terminal column cells of wide-character codes"
|
||||
name = "wcwidth"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.1.8"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
name = "werkzeug"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "1.0.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
marker = "python_version < \"3.8\""
|
||||
name = "zipp"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "3.1.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["jaraco.itertools", "func-timeout"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "41d68ea93701fdaa1aa56159195db7a65863e3b34cc7305ef4a3f5d02f2bdf13"
|
||||
python-versions = "^3.7"
|
||||
|
||||
[metadata.files]
|
||||
altgraph = [
|
||||
{file = "altgraph-0.17-py2.py3-none-any.whl", hash = "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"},
|
||||
{file = "altgraph-0.17.tar.gz", hash = "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
|
||||
{file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
|
||||
{file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"},
|
||||
{file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||
]
|
||||
dis3 = [
|
||||
{file = "dis3-0.1.3-py2-none-any.whl", hash = "sha256:61f7720dd0d8749d23fda3d7227ce74d73da11c2fade993a67ab2f9852451b14"},
|
||||
{file = "dis3-0.1.3-py3-none-any.whl", hash = "sha256:30b6412d33d738663e8ded781b138f4b01116437f0872aa56aa3adba6aeff218"},
|
||||
{file = "dis3-0.1.3.tar.gz", hash = "sha256:9259b881fc1df02ed12ac25f82d4a85b44241854330b1a651e40e0c675cb2d1e"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"},
|
||||
{file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"},
|
||||
]
|
||||
flask-httpauth = [
|
||||
{file = "Flask-HTTPAuth-3.3.0.tar.gz", hash = "sha256:6ef8b761332e780f9ff74d5f9056c2616f52babc1998b01d9f361a1e439e61b9"},
|
||||
{file = "Flask_HTTPAuth-3.3.0-py2.py3-none-any.whl", hash = "sha256:0149953720489407e51ec24bc2f86273597b7973d71cd51f9443bd0e2a89bd72"},
|
||||
]
|
||||
future = [
|
||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
|
||||
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"},
|
||||
{file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"},
|
||||
{file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"},
|
||||
]
|
||||
macholib = [
|
||||
{file = "macholib-1.14-py2.py3-none-any.whl", hash = "sha256:c500f02867515e6c60a27875b408920d18332ddf96b4035ef03beddd782d4281"},
|
||||
{file = "macholib-1.14.tar.gz", hash = "sha256:0c436bc847e7b1d9bda0560351bf76d7caf930fb585a828d13608839ef42c432"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
|
||||
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
|
||||
{file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"},
|
||||
{file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"},
|
||||
]
|
||||
pathtools = [
|
||||
{file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
|
||||
]
|
||||
pefile = [
|
||||
{file = "pefile-2019.4.18.tar.gz", hash = "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
psutil = [
|
||||
{file = "psutil-5.7.0-cp27-none-win32.whl", hash = "sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953"},
|
||||
{file = "psutil-5.7.0-cp27-none-win_amd64.whl", hash = "sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38"},
|
||||
{file = "psutil-5.7.0-cp35-cp35m-win32.whl", hash = "sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"},
|
||||
{file = "psutil-5.7.0-cp35-cp35m-win_amd64.whl", hash = "sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5"},
|
||||
{file = "psutil-5.7.0-cp36-cp36m-win32.whl", hash = "sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e"},
|
||||
{file = "psutil-5.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058"},
|
||||
{file = "psutil-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8"},
|
||||
{file = "psutil-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f"},
|
||||
{file = "psutil-5.7.0-cp38-cp38-win32.whl", hash = "sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4"},
|
||||
{file = "psutil-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26"},
|
||||
{file = "psutil-5.7.0.tar.gz", hash = "sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"},
|
||||
{file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"},
|
||||
]
|
||||
pycryptodome = [
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:0e10f352ccbbcb5bb2dc4ecaf106564e65702a717d72ab260f9ac4c19753cfc2"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9c739b7795ccf2ef1fdad8d44e539a39ad300ee6786e804ea7f0c6a786eb5343"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9977086e0f93adb326379897437373871b80501e1d176fec63c7f46fb300c862"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27m-win32.whl", hash = "sha256:83295a3fb5cf50c48631eb5b440cb5e9832d8c14d81d1d45f4497b67a9987de8"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27m-win_amd64.whl", hash = "sha256:b1e332587b3b195542e77681389c296e1837ca01240399d88803a075447d3557"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9378c309aec1f8cd8bad361ed0816a440151b97a2a3f6ffdaba1d1a1fb76873a"},
|
||||
{file = "pycryptodome-3.9.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4f94368ce2d65873a87ad867eb3bf63f4ba81eb97a9ee66d38c2b71ce5a7439"},
|
||||
{file = "pycryptodome-3.9.7-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:f655addaaaa9974108d4808f4150652589cada96074c87115c52e575bfcd87d5"},
|
||||
{file = "pycryptodome-3.9.7-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:9a94fca11fdc161460bd8659c15b6adef45c1b20da86402256eaf3addfaab324"},
|
||||
{file = "pycryptodome-3.9.7-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:ea83bcd9d6c03248ebd46e71ac313858e0afd5aa2fa81478c0e653242f3eb476"},
|
||||
{file = "pycryptodome-3.9.7-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:07024fc364869eae8d6ac0d316e089956e6aeffe42dbdcf44fe1320d96becf7f"},
|
||||
{file = "pycryptodome-3.9.7-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:426c188c83c10df71f053e04b4003b1437bae5cb37606440e498b00f160d71d0"},
|
||||
{file = "pycryptodome-3.9.7-cp35-cp35m-win32.whl", hash = "sha256:d61b012baa8c2b659e9890011358455c0019a4108536b811602d2f638c40802a"},
|
||||
{file = "pycryptodome-3.9.7-cp35-cp35m-win_amd64.whl", hash = "sha256:1f4752186298caf2e9ff5354f2e694d607ca7342aa313a62005235d46e28cf04"},
|
||||
{file = "pycryptodome-3.9.7-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:767ad0fb5d23efc36a4d5c2fc608ac603f3de028909bcf59abc943e0d0bc5a36"},
|
||||
{file = "pycryptodome-3.9.7-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2fbc472e0b567318fe2052281d5a8c0ae70099b446679815f655e9fbc18c3a65"},
|
||||
{file = "pycryptodome-3.9.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9230fcb5d948c3fb40049bace4d33c5d254f8232c2c0bba05d2570aea3ba4520"},
|
||||
{file = "pycryptodome-3.9.7-cp36-cp36m-win32.whl", hash = "sha256:8f06556a8f7ea7b1e42eff39726bb0dca1c251205debae64e6eebea3cd7b438a"},
|
||||
{file = "pycryptodome-3.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:d6e1bc5c94873bec742afe2dfadce0d20445b18e75c47afc0c115b19e5dd38dd"},
|
||||
{file = "pycryptodome-3.9.7-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:3ec3dc2f80f71fd0c955ce48b81bfaf8914c6f63a41a738f28885a1c4892968a"},
|
||||
{file = "pycryptodome-3.9.7-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cff31f5a8977534f255f729d5d2467526f2b10563a30bbdade92223e0bf264bd"},
|
||||
{file = "pycryptodome-3.9.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ed5761b37615a1f222c5345bbf45272ae2cf8c7dff88a4f53a1e9f977cbb6d95"},
|
||||
{file = "pycryptodome-3.9.7-cp37-cp37m-win32.whl", hash = "sha256:f011cd0062e54658b7086a76f8cf0f4222812acc66e219e196ea2d0a8849d0ed"},
|
||||
{file = "pycryptodome-3.9.7-cp37-cp37m-win_amd64.whl", hash = "sha256:626c0a1d4d83ec6303f970a17158114f75c3ba1736f7f2983f7b40a265861bd8"},
|
||||
{file = "pycryptodome-3.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be56bde3312e022d9d1d6afa124556460ad5c844c2fc63642f6af723c098d35"},
|
||||
{file = "pycryptodome-3.9.7-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c818dc1f3eace93ee50c2b6b5c2becf7c418fa5dd1ba6fc0ef7db279ea21d5e4"},
|
||||
{file = "pycryptodome-3.9.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:09b6d6bcc01a4eb1a2b4deeff5aa602a108ec5aed8ac75ae554f97d1d7f0a5ad"},
|
||||
{file = "pycryptodome-3.9.7-cp38-cp38-win32.whl", hash = "sha256:7ac729d9091ed5478af2b4a4f44f5335a98febbc008af619e4569a59fe503e40"},
|
||||
{file = "pycryptodome-3.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:c109a26a21f21f695d369ff9b87f5d43e0d6c768d8384e10bc74142bed2e092e"},
|
||||
{file = "pycryptodome-3.9.7.tar.gz", hash = "sha256:f1add21b6d179179b3c177c33d18a2186a09cc0d3af41ff5ed3f377360b869f2"},
|
||||
]
|
||||
pyinstaller = [
|
||||
{file = "PyInstaller-3.6.tar.gz", hash = "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
|
||||
{file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
|
||||
]
|
||||
pyqt5 = [
|
||||
{file = "PyQt5-5.14.0-5.14.0-cp35.cp36.cp37.cp38-abi3-macosx_10_6_intel.whl", hash = "sha256:895d4101f7f8c82bc728d7eb9da1c756955ce27a0c945eafe7f234dd03402853"},
|
||||
{file = "PyQt5-5.14.0-5.14.0-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl", hash = "sha256:a757ba71c51f428b52ba404e781e2f19b4436b2c31298b8313339d5817781b65"},
|
||||
{file = "PyQt5-5.14.0-5.14.0-cp35.cp36.cp37.cp38-none-win32.whl", hash = "sha256:cc3529c0f7cbbe7491073458d5d15e7518ce544ad8c627f485e5db8a27fcaf61"},
|
||||
{file = "PyQt5-5.14.0-5.14.0-cp35.cp36.cp37.cp38-none-win_amd64.whl", hash = "sha256:0dcc128b72f83cce0fc7926c83f05a9b74b652b5eb31a4ab71693ac8829e73c8"},
|
||||
{file = "PyQt5-5.14.0.tar.gz", hash = "sha256:0145a6b7de15756366decb736c349a0cb510d706c83fda5b8cd9e0557bc1da72"},
|
||||
]
|
||||
pyqt5-sip = [
|
||||
{file = "PyQt5_sip-12.7.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:f314f31f5fd39b06897f013f425137e511d45967150eb4e424a363d8138521c6"},
|
||||
{file = "PyQt5_sip-12.7.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b42021229424aa44e99b3b49520b799fd64ff6ae8b53f79f903bbd85719a28e4"},
|
||||
{file = "PyQt5_sip-12.7.1-cp35-cp35m-win32.whl", hash = "sha256:6b4860c4305980db509415d0af802f111d15f92016c9422eb753bc8883463456"},
|
||||
{file = "PyQt5_sip-12.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:d46b0f8effc554de52a1466b1bd80e5cb4bce635a75ac4e7ad6247c965dec5b9"},
|
||||
{file = "PyQt5_sip-12.7.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3f665376d9e52faa9855c3736a66ce6d825f85c86d7774d3c393f09da23f4f86"},
|
||||
{file = "PyQt5_sip-12.7.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1115728644bbadcde5fc8a16e7918bd31915a42dd6fb36b10d4afb78c582753e"},
|
||||
{file = "PyQt5_sip-12.7.1-cp36-cp36m-win32.whl", hash = "sha256:cbeeae6b45234a1654657f79943f8bccd3d14b4e7496746c62cf6fbce69442c7"},
|
||||
{file = "PyQt5_sip-12.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8da842d3d7bf8931d1093105fb92702276b6dbb7e801abbaaa869405d616171a"},
|
||||
{file = "PyQt5_sip-12.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f4289276d355b6521dc2cc956189315da6f13adfb6bbab8f25ebd15e3bce1d4"},
|
||||
{file = "PyQt5_sip-12.7.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c1e730a9eb2ec3869ed5d81b0f99f6e2460fb4d77750444c0ec183b771d798f7"},
|
||||
{file = "PyQt5_sip-12.7.1-cp37-cp37m-win32.whl", hash = "sha256:b5b4906445fe980aee76f20400116b6904bf5f30d0767489c13370e42a764020"},
|
||||
{file = "PyQt5_sip-12.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7ffa39763097f64de129cf5cc770a651c3f65d2466b4fe05bef2bd2efbaa38e6"},
|
||||
{file = "PyQt5_sip-12.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:288c6dc18a8d6a20981c07b715b5695d9b66880778565f3792bc6e38f14f20fb"},
|
||||
{file = "PyQt5_sip-12.7.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ee1a12f09d5af2304273bfd2f6b43835c1467d5ed501a6c95f5405637fa7750a"},
|
||||
{file = "PyQt5_sip-12.7.1-cp38-cp38-win32.whl", hash = "sha256:8a18e6f45d482ddfe381789979d09ee13aa6450caa3a0476503891bccb3ac709"},
|
||||
{file = "PyQt5_sip-12.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:e28c3abc9b62a1b7e796891648b9f14f8167b31c8e7990fae79654777252bb4d"},
|
||||
{file = "PyQt5_sip-12.7.1.tar.gz", hash = "sha256:e6078f5ee7d31c102910d0c277a110e1c2a20a3fc88cd017a39e170120586d3f"},
|
||||
]
|
||||
pysocks = [
|
||||
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
|
||||
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
|
||||
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"},
|
||||
{file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"},
|
||||
]
|
||||
pytest-faulthandler = [
|
||||
{file = "pytest-faulthandler-2.0.1.tar.gz", hash = "sha256:ed72bbce87ac344da81eb7d882196a457d4a1026a3da4a57154dacd85cd71ae5"},
|
||||
{file = "pytest_faulthandler-2.0.1-py2.py3-none-any.whl", hash = "sha256:236430ba962fd1c910d670922be55fe5b25ea9bc3fc6561a0cafbb8759e7504d"},
|
||||
]
|
||||
pytest-qt = [
|
||||
{file = "pytest-qt-3.3.0.tar.gz", hash = "sha256:714b0bf86c5313413f2d300ac613515db3a1aef595051ab8ba2ffe619dbe8925"},
|
||||
{file = "pytest_qt-3.3.0-py2.py3-none-any.whl", hash = "sha256:5f8928288f50489d83f5d38caf2d7d9fcd6e7cf769947902caa4661dc7c851e3"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
|
||||
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||
]
|
||||
stem = [
|
||||
{file = "stem-1.8.0.tar.gz", hash = "sha256:a0b48ea6224e95f22aa34c0bc3415f0eb4667ddeae3dfb5e32a6920c185568c2"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
|
||||
{file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
|
||||
]
|
||||
watchdog = [
|
||||
{file = "watchdog-0.10.2.tar.gz", hash = "sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
|
||||
{file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-1.0.0-py2.py3-none-any.whl", hash = "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16"},
|
||||
{file = "Werkzeug-1.0.0.tar.gz", hash = "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
|
||||
{file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
|
||||
]
|
50
pyproject.toml
Normal file
50
pyproject.toml
Normal file
@ -0,0 +1,50 @@
|
||||
[tool.poetry]
|
||||
name = "onionshare"
|
||||
version = "2.2"
|
||||
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 service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service."
|
||||
authors = ["Micah Lee <micah@micahflee.com>"]
|
||||
license = "GPLv3+"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
altgraph = "*"
|
||||
certifi = "*"
|
||||
chardet = "*"
|
||||
Click = "*"
|
||||
Flask = "*"
|
||||
Flask-HTTPAuth = "*"
|
||||
future = "*"
|
||||
idna = "*"
|
||||
itsdangerous = "*"
|
||||
Jinja2 = "*"
|
||||
macholib = "*"
|
||||
MarkupSafe = "*"
|
||||
pefile = "*"
|
||||
pycryptodome = "*"
|
||||
PyQt5 = "5.14"
|
||||
PyQt5-sip = "*"
|
||||
PySocks = "*"
|
||||
requests = "*"
|
||||
stem = "*"
|
||||
urllib3 = "*"
|
||||
Werkzeug = "*"
|
||||
watchdog = "*"
|
||||
psutil = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
atomicwrites = "*"
|
||||
attrs = "*"
|
||||
more-itertools = "*"
|
||||
pluggy = "*"
|
||||
py = "*"
|
||||
pytest = "*"
|
||||
pytest-faulthandler = "*"
|
||||
pytest-qt = "*"
|
||||
six = "*"
|
||||
urllib3 = "*"
|
||||
pyinstaller = {version = "*", platform = "darwin"}
|
||||
setuptools = {version = "*", platform = "windows"}
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
23
setup.py
23
setup.py
@ -69,16 +69,16 @@ classifiers = [
|
||||
"Environment :: Web Environment",
|
||||
]
|
||||
data_files = [
|
||||
("share/applications", ["install/org.onionshare.OnionShare.desktop"]),
|
||||
("share/icons/hicolor/scalable/apps", ["install/org.onionshare.OnionShare.svg"]),
|
||||
("share/metainfo", ["install/org.onionshare.OnionShare.appdata.xml"]),
|
||||
("share/applications", ["install/org.onionshare.OnionShare.desktop"],),
|
||||
("share/icons/hicolor/scalable/apps", ["install/org.onionshare.OnionShare.svg"],),
|
||||
("share/metainfo", ["install/org.onionshare.OnionShare.appdata.xml"],),
|
||||
("share/onionshare", file_list("share")),
|
||||
("share/onionshare/images", file_list("share/images")),
|
||||
("share/onionshare/locale", file_list("share/locale")),
|
||||
("share/onionshare/templates", file_list("share/templates")),
|
||||
("share/onionshare/static/css", file_list("share/static/css")),
|
||||
("share/onionshare/static/img", file_list("share/static/img")),
|
||||
("share/onionshare/static/js", file_list("share/static/js")),
|
||||
("share/onionshare/templates", file_list("share/templates"),),
|
||||
("share/onionshare/static/css", file_list("share/static/css"),),
|
||||
("share/onionshare/static/img", file_list("share/static/img"),),
|
||||
("share/onionshare/static/js", file_list("share/static/js"),),
|
||||
]
|
||||
if not platform.system().endswith("BSD") and platform.system() != "DragonFly":
|
||||
data_files.append(
|
||||
@ -105,10 +105,11 @@ setup(
|
||||
"onionshare",
|
||||
"onionshare.web",
|
||||
"onionshare_gui",
|
||||
"onionshare_gui.mode",
|
||||
"onionshare_gui.mode.share_mode",
|
||||
"onionshare_gui.mode.receive_mode",
|
||||
"onionshare_gui.mode.website_mode",
|
||||
"onionshare_gui.tab",
|
||||
"onionshare_gui.tab.mode",
|
||||
"onionshare_gui.tab.mode.share_mode",
|
||||
"onionshare_gui.tab.mode.receive_mode",
|
||||
"onionshare_gui.tab.mode.website_mode",
|
||||
],
|
||||
include_package_data=True,
|
||||
scripts=["install/scripts/onionshare", "install/scripts/onionshare-gui"],
|
||||
|
BIN
share/images/persistent_enabled.png
Normal file
BIN
share/images/persistent_enabled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 443 B After Width: | Height: | Size: 1.1 KiB |
@ -30,11 +30,6 @@
|
||||
"gui_copied_hidservauth": "HidServAuth line copied to clipboard",
|
||||
"gui_waiting_to_start": "Scheduled to start in {}. Click to cancel.",
|
||||
"gui_please_wait": "Starting… Click to cancel.",
|
||||
"gui_quit_title": "Not so fast",
|
||||
"gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
|
||||
"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 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.",
|
||||
@ -93,7 +88,7 @@
|
||||
"settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.",
|
||||
"settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.",
|
||||
"settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?",
|
||||
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
|
||||
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}",
|
||||
"settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.",
|
||||
"error_tor_protocol_error": "There was an error with Tor: {}",
|
||||
"error_tor_protocol_error_unknown": "There was an unknown error with Tor",
|
||||
@ -175,9 +170,39 @@
|
||||
"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",
|
||||
"days_first_letter": "d",
|
||||
"hours_first_letter": "h",
|
||||
"minutes_first_letter": "m",
|
||||
"seconds_first_letter": "s"
|
||||
}
|
||||
"seconds_first_letter": "s",
|
||||
"gui_new_tab": "New Tab",
|
||||
"gui_new_tab_tooltip": "Open a new tab",
|
||||
"gui_new_tab_share_button": "Share Files",
|
||||
"gui_new_tab_share_description": "Choose files on your computer to send to someone else. The person or people who you want to send files to will need to use Tor Browser to download them from you.",
|
||||
"gui_new_tab_receive_button": "Receive Files",
|
||||
"gui_new_tab_receive_description": "Turn your computer into an online dropbox. People will be able to use Tor Browser to send files to your computer.",
|
||||
"gui_new_tab_website_button": "Publish Website",
|
||||
"gui_new_tab_website_description": "Host a static HTML onion website from your computer.",
|
||||
"gui_close_tab_warning_title": "Are you sure?",
|
||||
"gui_close_tab_warning_persistent_description": "This tab is persistent. If you close it you'll lose the onion address that it's using. Are you sure you want to close it?",
|
||||
"gui_close_tab_warning_share_description": "You're in the process of sending files. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_receive_description": "You're in the process of receiving files. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_website_description": "You're actively hosting a website. Are you sure you want to close this tab?",
|
||||
"gui_close_tab_warning_close": "Close",
|
||||
"gui_close_tab_warning_cancel": "Cancel",
|
||||
"gui_quit_warning_title": "Are you sure?",
|
||||
"gui_quit_warning_description": "Sharing is active in some of your tabs. If you quit, all of your tabs will close. Are you sure you want to quit?",
|
||||
"gui_quit_warning_quit": "Quit",
|
||||
"gui_quit_warning_cancel": "Cancel",
|
||||
"mode_settings_advanced_toggle_show": "Show advanced settings",
|
||||
"mode_settings_advanced_toggle_hide": "Hide advanced settings",
|
||||
"mode_settings_persistent_checkbox": "Save this tab, and automatically open it when I open OnionShare",
|
||||
"mode_settings_public_checkbox": "Don't use a password",
|
||||
"mode_settings_autostart_timer_checkbox": "Start onion service at scheduled time",
|
||||
"mode_settings_autostop_timer_checkbox": "Stop onion service at scheduled time",
|
||||
"mode_settings_legacy_checkbox": "Use a legacy address (v2 onion service, not recommended)",
|
||||
"mode_settings_client_auth_checkbox": "Use client authorization",
|
||||
"mode_settings_share_autostop_sharing_checkbox": "Stop sharing after files have been sent (uncheck to allow downloading individual files)",
|
||||
"mode_settings_receive_data_dir_label": "Save files to",
|
||||
"mode_settings_receive_data_dir_browse_button": "Browse",
|
||||
"mode_settings_website_disable_csp_checkbox": "Disable Content Security Policy header (allows your website to use third-party resources)"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[DEFAULT]
|
||||
Package3: onionshare
|
||||
Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy
|
||||
Build-Depends: python3, python3-all, python3-pytest, python3-requests, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils
|
||||
Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy, python3-psutil
|
||||
Build-Depends: python3, python3-all, python3-pytest, python3-requests, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python3-psutil
|
||||
Suite: disco
|
||||
X-Python3-Version: >= 3.6
|
||||
|
@ -1,383 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import base64
|
||||
|
||||
from PyQt5 import QtCore, QtTest
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.common import Common
|
||||
from onionshare.settings import Settings
|
||||
from onionshare.onion import Onion
|
||||
from onionshare.web import Web
|
||||
from onionshare_gui import Application, OnionShare, OnionShareGui
|
||||
from onionshare_gui.mode.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 our test file
|
||||
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")
|
||||
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/test.txt", "/tmp/testdir"],
|
||||
"/tmp/settings.json",
|
||||
True,
|
||||
)
|
||||
return gui
|
||||
|
||||
@staticmethod
|
||||
def tear_down():
|
||||
"""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")
|
||||
except:
|
||||
pass
|
||||
|
||||
def gui_loaded(self):
|
||||
"""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")
|
||||
|
||||
def settings_button_is_visible(self):
|
||||
"""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"""
|
||||
self.assertFalse(self.gui.settings_button.isVisible())
|
||||
|
||||
def server_status_bar_is_visible(self):
|
||||
"""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"""
|
||||
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"""
|
||||
currently_visible = mode.history.isVisible()
|
||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||
self.assertEqual(mode.history.isVisible(), not currently_visible)
|
||||
|
||||
def history_indicator(self, mode, public_mode, indicator_count="1"):
|
||||
"""Test that we can make sure the history is toggled off, do an action, and the indiciator works"""
|
||||
# Make sure history is toggled off
|
||||
if mode.history.isVisible():
|
||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||
self.assertFalse(mode.history.isVisible())
|
||||
|
||||
# Indicator should not be visible yet
|
||||
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||
|
||||
if type(mode) == ReceiveMode:
|
||||
# Upload a file
|
||||
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||
url = f"http://127.0.0.1:{self.gui.app.port}/upload"
|
||||
if public_mode:
|
||||
r = requests.post(url, files=files)
|
||||
else:
|
||||
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 = f"http://127.0.0.1:{self.gui.app.port}/download"
|
||||
if public_mode:
|
||||
r = requests.get(url)
|
||||
else:
|
||||
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(), 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"""
|
||||
self.assertFalse(mode.history.isVisible())
|
||||
|
||||
def history_is_visible(self, mode):
|
||||
"""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"""
|
||||
# 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"),
|
||||
)
|
||||
|
||||
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"),
|
||||
)
|
||||
|
||||
def server_is_started(self, mode, startup_time=2000):
|
||||
"""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"""
|
||||
try:
|
||||
r = requests.get(f"http://127.0.0.1:{self.gui.app.port}/")
|
||||
self.assertTrue(True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.assertTrue(False)
|
||||
|
||||
def have_a_password(self, mode, public_mode):
|
||||
"""Test that we have a valid password"""
|
||||
if not public_mode:
|
||||
self.assertRegex(mode.server_status.web.password, r"(\w+)-(\w+)")
|
||||
else:
|
||||
self.assertIsNone(mode.server_status.web.password, r"(\w+)-(\w+)")
|
||||
|
||||
def add_button_visible(self, mode):
|
||||
"""Test that the add button should be visible"""
|
||||
self.assertTrue(mode.server_status.file_selection.add_button.isVisible())
|
||||
|
||||
def url_description_shown(self, mode):
|
||||
"""Test that the URL label is showing"""
|
||||
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"""
|
||||
self.assertTrue(mode.server_status.copy_url_button.isVisible())
|
||||
|
||||
QtTest.QTest.mouseClick(
|
||||
mode.server_status.copy_url_button, QtCore.Qt.LeftButton
|
||||
)
|
||||
clipboard = self.gui.qtapp.clipboard()
|
||||
if public_mode:
|
||||
self.assertEqual(clipboard.text(), f"http://127.0.0.1:{self.gui.app.port}")
|
||||
else:
|
||||
self.assertEqual(
|
||||
clipboard.text(),
|
||||
f"http://onionshare:{mode.server_status.web.password}@127.0.0.1:{self.gui.app.port}",
|
||||
)
|
||||
|
||||
def server_status_indicator_says_started(self, mode):
|
||||
"""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"),
|
||||
)
|
||||
if type(mode) == ShareMode:
|
||||
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"""
|
||||
|
||||
url = f"http://127.0.0.1:{self.gui.app.port}/"
|
||||
if public_mode:
|
||||
r = requests.get(url)
|
||||
else:
|
||||
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"""
|
||||
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"""
|
||||
self.assertEqual(mode.history.completed_count, count)
|
||||
|
||||
def server_is_stopped(self, mode, stay_open):
|
||||
"""Test that the server stops when we click Stop"""
|
||||
if (
|
||||
type(mode) == ReceiveMode
|
||||
or (type(mode) == ShareMode and stay_open)
|
||||
or (type(mode) == WebsiteMode)
|
||||
):
|
||||
QtTest.QTest.mouseClick(
|
||||
mode.server_status.server_button, QtCore.Qt.LeftButton
|
||||
)
|
||||
self.assertEqual(mode.server_status.status, 0)
|
||||
|
||||
def web_server_is_stopped(self):
|
||||
"""Test that the web server also stopped"""
|
||||
QtTest.QTest.qWait(2000)
|
||||
|
||||
try:
|
||||
r = requests.get(f"http://127.0.0.1:{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"""
|
||||
if type(mode) == ReceiveMode:
|
||||
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"),
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
self.gui.share_mode.server_status_label.text(),
|
||||
strings._("closing_automatically"),
|
||||
)
|
||||
|
||||
def clear_all_history_items(self, mode, count):
|
||||
if count == 0:
|
||||
QtTest.QTest.mouseClick(mode.history.clear_button, QtCore.Qt.LeftButton)
|
||||
self.assertEquals(len(mode.history.item_list.items.keys()), count)
|
||||
|
||||
# Auto-stop timer tests
|
||||
def set_timeout(self, mode, timeout):
|
||||
"""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"""
|
||||
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"""
|
||||
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"""
|
||||
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"""
|
||||
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"""
|
||||
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 """
|
||||
self.server_working_on_start_button_pressed(mode)
|
||||
self.server_status_indicator_says_scheduled(mode)
|
||||
self.add_delete_buttons_hidden()
|
||||
self.settings_button_is_hidden()
|
||||
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
|
||||
)
|
||||
self.assertEqual(mode.server_status.status, 0)
|
||||
self.server_is_stopped(mode, False)
|
||||
self.web_server_is_stopped()
|
||||
|
||||
# Hack to close an Alert dialog that would otherwise block tests
|
||||
def accept_dialog(self):
|
||||
window = self.gui.qtapp.activeWindow()
|
||||
if window:
|
||||
window.close()
|
||||
|
||||
# 'Grouped' tests follow from here
|
||||
|
||||
def run_all_common_setup_tests(self):
|
||||
self.gui_loaded()
|
||||
self.windowTitle_seen()
|
||||
self.settings_button_is_visible()
|
||||
self.server_status_bar_is_visible()
|
@ -1,169 +0,0 @@
|
||||
import os
|
||||
import requests
|
||||
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"""
|
||||
|
||||
# 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 = f"http://127.0.0.1:{self.gui.app.port}/upload"
|
||||
if public_mode:
|
||||
r = requests.post(url, files=files)
|
||||
if identical_files_at_once:
|
||||
# Send a duplicate upload to test for collisions
|
||||
r = requests.post(url, files=files)
|
||||
else:
|
||||
r = requests.post(
|
||||
url,
|
||||
files=files,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", self.gui.receive_mode.web.password
|
||||
),
|
||||
)
|
||||
if identical_files_at_once:
|
||||
# Send a duplicate upload to test for collisions
|
||||
r = requests.post(
|
||||
url,
|
||||
files=files,
|
||||
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 fileames
|
||||
exists = False
|
||||
now = datetime.now()
|
||||
for i in range(10):
|
||||
date_dir = now.strftime("%Y-%m-%d")
|
||||
if identical_files_at_once:
|
||||
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
|
||||
)
|
||||
expected_filename = os.path.join(receive_mode_dir, expected_basename)
|
||||
if os.path.exists(expected_filename):
|
||||
exists = True
|
||||
break
|
||||
now = now - timedelta(seconds=1)
|
||||
|
||||
self.assertTrue(exists)
|
||||
|
||||
def upload_file_should_fail(self, public_mode):
|
||||
"""Test that we can't upload the file when permissions are wrong, and expected content is shown"""
|
||||
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||
url = f"http://127.0.0.1:{self.gui.app.port}/upload"
|
||||
if public_mode:
|
||||
r = requests.post(url, files=files)
|
||||
else:
|
||||
r = requests.post(
|
||||
url,
|
||||
files=files,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", self.gui.receive_mode.web.password
|
||||
),
|
||||
)
|
||||
|
||||
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
||||
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_without_auth_in_non_public_mode(self):
|
||||
r = requests.post(f"http://127.0.0.1:{self.gui.app.port}/upload")
|
||||
self.assertEqual(r.status_code, 401)
|
||||
r = requests.get(f"http://127.0.0.1:{self.gui.app.port}/close")
|
||||
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"""
|
||||
self.click_mode(self.gui.receive_mode)
|
||||
self.history_is_not_visible(self.gui.receive_mode)
|
||||
self.click_toggle_history(self.gui.receive_mode)
|
||||
self.history_is_visible(self.gui.receive_mode)
|
||||
self.server_working_on_start_button_pressed(self.gui.receive_mode)
|
||||
self.server_status_indicator_says_starting(self.gui.receive_mode)
|
||||
self.settings_button_is_hidden()
|
||||
self.server_is_started(self.gui.receive_mode)
|
||||
self.web_server_is_running()
|
||||
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):
|
||||
"""Upload files in receive mode and stop the share"""
|
||||
self.run_all_receive_mode_setup_tests(public_mode)
|
||||
if not public_mode:
|
||||
self.try_without_auth_in_non_public_mode()
|
||||
self.upload_file(public_mode, "/tmp/test.txt", "test.txt")
|
||||
self.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.counter_incremented(self.gui.receive_mode, 2)
|
||||
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.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.counter_incremented(self.gui.receive_mode, 6)
|
||||
self.history_indicator(self.gui.receive_mode, public_mode, "2")
|
||||
self.server_is_stopped(self.gui.receive_mode, False)
|
||||
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, "2")
|
||||
|
||||
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)
|
||||
self.server_is_stopped(self.gui.receive_mode, True)
|
||||
self.web_server_is_stopped()
|
||||
self.server_status_indicator_says_closed(self.gui.receive_mode, False)
|
||||
self.upload_dir_permissions(0o755)
|
||||
|
||||
def run_all_receive_mode_timer_tests(self, public_mode):
|
||||
"""Auto-stop timer tests in receive mode"""
|
||||
self.run_all_receive_mode_setup_tests(public_mode)
|
||||
self.set_timeout(self.gui.receive_mode, 5)
|
||||
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)
|
@ -1,328 +0,0 @@
|
||||
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_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
|
||||
)
|
||||
|
||||
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(),
|
||||
)
|
||||
# Delete button should be visible
|
||||
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,
|
||||
)
|
||||
|
||||
# No more files, the delete button should be hidden
|
||||
self.assertFalse(
|
||||
self.gui.share_mode.server_status.file_selection.delete_button.isVisible()
|
||||
)
|
||||
|
||||
def add_a_file_and_delete_using_its_delete_widget(self):
|
||||
"""Test that we can also delete a file by clicking on its [X] widget"""
|
||||
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||
"/etc/hosts"
|
||||
)
|
||||
QtTest.QTest.mouseClick(
|
||||
self.gui.share_mode.server_status.file_selection.file_list.item(
|
||||
0
|
||||
).item_button,
|
||||
QtCore.Qt.LeftButton,
|
||||
)
|
||||
self.file_selection_widget_has_files(0)
|
||||
|
||||
def file_selection_widget_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:
|
||||
fout.write(os.urandom(size))
|
||||
self.gui.share_mode.server_status.file_selection.file_list.add_file(
|
||||
"/tmp/large_file"
|
||||
)
|
||||
|
||||
def add_delete_buttons_hidden(self):
|
||||
"""Test that the add and delete buttons are hidden when the server starts"""
|
||||
self.assertFalse(
|
||||
self.gui.share_mode.server_status.file_selection.add_button.isVisible()
|
||||
)
|
||||
self.assertFalse(
|
||||
self.gui.share_mode.server_status.file_selection.delete_button.isVisible()
|
||||
)
|
||||
|
||||
def download_share(self, public_mode):
|
||||
"""Test that we can download the share"""
|
||||
url = f"http://127.0.0.1:{self.gui.app.port}/download"
|
||||
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 = f"http://127.0.0.1:{self.gui.app.port}"
|
||||
download_file_url = f"http://127.0.0.1:{self.gui.app.port}/test.txt"
|
||||
if public_mode:
|
||||
r = requests.get(url)
|
||||
else:
|
||||
r = requests.get(
|
||||
url,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", self.gui.share_mode.server_status.web.password
|
||||
),
|
||||
)
|
||||
|
||||
if stay_open:
|
||||
self.assertTrue('a href="test.txt"' in r.text)
|
||||
|
||||
if public_mode:
|
||||
r = requests.get(download_file_url)
|
||||
else:
|
||||
r = requests.get(
|
||||
download_file_url,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", self.gui.share_mode.server_status.web.password
|
||||
),
|
||||
)
|
||||
|
||||
tmp_file = tempfile.NamedTemporaryFile()
|
||||
with open(tmp_file.name, "wb") as f:
|
||||
f.write(r.content)
|
||||
|
||||
with open(tmp_file.name, "r") as f:
|
||||
self.assertEqual("onionshare", f.read())
|
||||
else:
|
||||
self.assertFalse('a href="/test.txt"' in r.text)
|
||||
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)
|
||||
|
||||
def hit_401(self, public_mode):
|
||||
"""Test that the server stops after too many 401s, or doesn't when in public_mode"""
|
||||
url = f"http://127.0.0.1:{self.gui.app.port}/"
|
||||
|
||||
for _ in range(20):
|
||||
password_guess = self.gui.common.build_password()
|
||||
r = requests.get(
|
||||
url, auth=requests.auth.HTTPBasicAuth("onionshare", password_guess)
|
||||
)
|
||||
|
||||
# A nasty hack to avoid the Alert dialog that blocks the rest of the test
|
||||
if not public_mode:
|
||||
QtCore.QTimer.singleShot(1000, self.accept_dialog)
|
||||
|
||||
# In public mode, we should still be running (no rate-limiting)
|
||||
if public_mode:
|
||||
self.web_server_is_running()
|
||||
# In non-public mode, we should be shut down (rate-limiting)
|
||||
else:
|
||||
self.web_server_is_stopped()
|
||||
|
||||
# 'Grouped' tests follow from here
|
||||
|
||||
def run_all_share_mode_setup_tests(self):
|
||||
"""Tests in share mode prior to starting a share"""
|
||||
self.click_mode(self.gui.share_mode)
|
||||
self.file_selection_widget_has_files()
|
||||
self.history_is_not_visible(self.gui.share_mode)
|
||||
self.click_toggle_history(self.gui.share_mode)
|
||||
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_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)
|
||||
self.server_status_indicator_says_starting(self.gui.share_mode)
|
||||
self.add_delete_buttons_hidden()
|
||||
self.settings_button_is_hidden()
|
||||
self.server_is_started(self.gui.share_mode, startup_time)
|
||||
self.web_server_is_running()
|
||||
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.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.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"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.add_large_file()
|
||||
self.run_all_share_mode_started_tests(public_mode, startup_time=15000)
|
||||
self.assertTrue(self.gui.share_mode.filesize_warning.isVisible())
|
||||
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)
|
||||
|
||||
def run_all_share_mode_persistent_tests(self, public_mode, stay_open):
|
||||
"""Same as end-to-end share tests but also test the password is the same on multiple shared"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.run_all_share_mode_started_tests(public_mode)
|
||||
password = self.gui.share_mode.server_status.web.password
|
||||
self.run_all_share_mode_download_tests(public_mode, stay_open)
|
||||
self.have_same_password(password)
|
||||
|
||||
def run_all_share_mode_timer_tests(self, public_mode):
|
||||
"""Auto-stop timer tests in share mode"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.set_timeout(self.gui.share_mode, 5)
|
||||
self.run_all_share_mode_started_tests(public_mode)
|
||||
self.autostop_timer_widget_hidden(self.gui.share_mode)
|
||||
self.server_timed_out(self.gui.share_mode, 10000)
|
||||
self.web_server_is_stopped()
|
||||
|
||||
def run_all_share_mode_autostart_timer_tests(self, public_mode):
|
||||
"""Auto-start timer tests in share mode"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.set_autostart_timer(self.gui.share_mode, 5)
|
||||
self.server_working_on_start_button_pressed(self.gui.share_mode)
|
||||
self.autostart_timer_widget_hidden(self.gui.share_mode)
|
||||
self.server_status_indicator_says_scheduled(self.gui.share_mode)
|
||||
self.web_server_is_stopped()
|
||||
self.scheduled_service_started(self.gui.share_mode, 7000)
|
||||
self.web_server_is_running()
|
||||
|
||||
def run_all_share_mode_autostop_autostart_mismatch_tests(self, public_mode):
|
||||
"""Auto-stop timer tests in share mode"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
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
|
||||
)
|
||||
self.server_is_stopped(self.gui.share_mode, False)
|
||||
|
||||
def run_all_share_mode_unreadable_file_tests(self):
|
||||
"""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.file_selection_widget_has_files(2)
|
@ -1,136 +0,0 @@
|
||||
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 = f"http://127.0.0.1:{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 = f"http://127.0.0.1:{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)
|
@ -1,333 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
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_gui import Application, OnionShare
|
||||
from onionshare_gui.settings_dialog import SettingsDialog
|
||||
|
||||
|
||||
class OnionStub(object):
|
||||
def __init__(self, is_authenticated, supports_v3_onions):
|
||||
self._is_authenticated = is_authenticated
|
||||
self.supports_v3_onions = supports_v3_onions
|
||||
|
||||
def is_authenticated(self):
|
||||
return self._is_authenticated
|
||||
|
||||
|
||||
class SettingsGuiBaseTest(object):
|
||||
@staticmethod
|
||||
def set_up():
|
||||
"""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",
|
||||
}
|
||||
|
||||
# Create our test file
|
||||
testfile = open("/tmp/test.txt", "w")
|
||||
testfile.write("onionshare")
|
||||
testfile.close()
|
||||
|
||||
common = Common()
|
||||
common.settings = Settings(common)
|
||||
common.define_css()
|
||||
strings.load_strings(common)
|
||||
|
||||
# Start the Onion
|
||||
testonion = Onion(common)
|
||||
global qtapp
|
||||
qtapp = Application(common)
|
||||
app = OnionShare(common, testonion, True, 0)
|
||||
|
||||
for key, val in common.settings.default_settings.items():
|
||||
if key not in test_settings:
|
||||
test_settings[key] = val
|
||||
|
||||
open("/tmp/settings.json", "w").write(json.dumps(test_settings))
|
||||
|
||||
gui = SettingsDialog(common, testonion, qtapp, "/tmp/settings.json", True)
|
||||
return gui
|
||||
|
||||
@staticmethod
|
||||
def tear_down():
|
||||
"""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"))
|
||||
|
||||
# Check for updates button is hidden
|
||||
self.assertFalse(self.gui.check_for_updates_button.isVisible())
|
||||
|
||||
# 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),
|
||||
)
|
||||
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),
|
||||
)
|
||||
self.assertTrue(self.gui.autostop_timer_checkbox.isChecked())
|
||||
|
||||
# legacy mode checkbox and related widgets
|
||||
if self.gui.onion.is_authenticated():
|
||||
if self.gui.onion.supports_v3_onions:
|
||||
# legacy mode is off
|
||||
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked())
|
||||
# persistence is still available, stealth is hidden and disabled
|
||||
self.assertTrue(self.gui.save_private_key_widget.isVisible())
|
||||
self.assertFalse(self.gui.save_private_key_checkbox.isChecked())
|
||||
self.assertFalse(self.gui.use_stealth_widget.isVisible())
|
||||
self.assertFalse(self.gui.stealth_checkbox.isChecked())
|
||||
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
|
||||
),
|
||||
)
|
||||
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
|
||||
),
|
||||
)
|
||||
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),
|
||||
)
|
||||
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),
|
||||
)
|
||||
# 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
|
||||
),
|
||||
)
|
||||
# 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
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
# legacy mode setting is hidden
|
||||
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
|
||||
# legacy options are showing
|
||||
self.assertTrue(self.gui.save_private_key_widget.isVisible())
|
||||
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),
|
||||
)
|
||||
else:
|
||||
# None of the onion settings should appear
|
||||
self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible())
|
||||
self.assertFalse(self.gui.save_private_key_widget.isVisible())
|
||||
self.assertFalse(self.gui.save_private_key_checkbox.isChecked())
|
||||
self.assertFalse(self.gui.use_stealth_widget.isVisible())
|
||||
self.assertFalse(self.gui.stealth_checkbox.isChecked())
|
||||
self.assertFalse(self.gui.hidservauth_copy_button.isVisible())
|
||||
|
||||
# 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
|
||||
),
|
||||
)
|
||||
self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked())
|
||||
|
||||
# receive mode
|
||||
self.gui.data_dir_lineedit.setText("/tmp/OnionShareSettingsTest")
|
||||
|
||||
# bundled mode is enabled
|
||||
self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled())
|
||||
self.assertTrue(self.gui.connection_type_bundled_radio.isChecked())
|
||||
# bridge options are shown
|
||||
self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible())
|
||||
# bridges are set to custom
|
||||
self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked())
|
||||
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),
|
||||
)
|
||||
self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked())
|
||||
|
||||
# custom bridges are hidden
|
||||
self.assertFalse(self.gui.tor_bridges_use_custom_textbox_options.isVisible())
|
||||
# other modes are unchecked but enabled
|
||||
self.assertTrue(self.gui.connection_type_automatic_radio.isEnabled())
|
||||
self.assertTrue(self.gui.connection_type_control_port_radio.isEnabled())
|
||||
self.assertTrue(self.gui.connection_type_socket_file_radio.isEnabled())
|
||||
self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
|
||||
self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
|
||||
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),
|
||||
)
|
||||
self.assertTrue(self.gui.connection_type_automatic_radio.isChecked())
|
||||
# bundled is off
|
||||
self.assertFalse(self.gui.connection_type_bundled_radio.isChecked())
|
||||
# bridges are hidden
|
||||
self.assertFalse(self.gui.connection_type_bridges_radio_group.isVisible())
|
||||
|
||||
# auth type is hidden in bundled or automatic mode
|
||||
self.assertFalse(self.gui.authenticate_no_auth_radio.isVisible())
|
||||
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
|
||||
),
|
||||
)
|
||||
self.assertTrue(self.gui.connection_type_control_port_radio.isChecked())
|
||||
# automatic is off
|
||||
self.assertFalse(self.gui.connection_type_automatic_radio.isChecked())
|
||||
# auth options appear
|
||||
self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible())
|
||||
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
|
||||
),
|
||||
)
|
||||
self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked())
|
||||
# control port is off
|
||||
self.assertFalse(self.gui.connection_type_control_port_radio.isChecked())
|
||||
# auth options are still present
|
||||
self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible())
|
||||
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),
|
||||
)
|
||||
# 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),
|
||||
)
|
||||
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"
|
||||
)
|
||||
|
||||
# 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:
|
||||
data = json.load(f)
|
||||
|
||||
self.assertTrue(data["public_mode"])
|
||||
self.assertTrue(data["autostop_timer"])
|
||||
|
||||
if self.gui.onion.is_authenticated():
|
||||
if self.gui.onion.supports_v3_onions:
|
||||
self.assertTrue(data["use_legacy_v2_onions"])
|
||||
self.assertTrue(data["save_private_key"])
|
||||
self.assertTrue(data["use_stealth"])
|
||||
else:
|
||||
self.assertFalse(data["use_legacy_v2_onions"])
|
||||
self.assertFalse(data["save_private_key"])
|
||||
self.assertFalse(data["use_stealth"])
|
||||
|
||||
self.assertEqual(data["data_dir"], "/tmp/OnionShareSettingsTest")
|
||||
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",
|
||||
)
|
@ -1,176 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import socks
|
||||
|
||||
from PyQt5 import QtCore, QtTest
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.common import Common
|
||||
from onionshare.settings import Settings
|
||||
from onionshare.onion import Onion
|
||||
from onionshare.web import Web
|
||||
from onionshare_gui import Application, OnionShare, OnionShareGui
|
||||
from onionshare_gui.mode.share_mode import ShareMode
|
||||
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 our test file
|
||||
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")
|
||||
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["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
|
||||
|
||||
# Start the Onion
|
||||
testonion = Onion(common)
|
||||
global qtapp
|
||||
qtapp = Application(common)
|
||||
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,
|
||||
)
|
||||
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"""
|
||||
# Make sure history is toggled off
|
||||
if mode.history.isVisible():
|
||||
QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton)
|
||||
self.assertFalse(mode.history.isVisible())
|
||||
|
||||
# Indicator should not be visible yet
|
||||
self.assertFalse(mode.toggle_history.indicator_label.isVisible())
|
||||
|
||||
# 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"] = f"socks5h://{socks_address}:{socks_port}"
|
||||
|
||||
if type(mode) == ReceiveMode:
|
||||
# Upload a file
|
||||
files = {"file[]": open("/tmp/test.txt", "rb")}
|
||||
if not public_mode:
|
||||
path = f"http://{self.gui.app.onion_host}/{mode.web.password}/upload"
|
||||
else:
|
||||
path = f"http://{self.gui.app.onion_host}/upload"
|
||||
response = session.post(path, files=files)
|
||||
QtTest.QTest.qWait(4000)
|
||||
|
||||
if type(mode) == ShareMode:
|
||||
# Download files
|
||||
if public_mode:
|
||||
path = f"http://{self.gui.app.onion_host}/download"
|
||||
else:
|
||||
path = f"http://{self.gui.app.onion_host}/{mode.web.password}/download"
|
||||
response = session.get(path)
|
||||
QtTest.QTest.qWait(4000)
|
||||
|
||||
# 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")
|
||||
|
||||
# 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 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")
|
||||
|
||||
def web_page(self, mode, string, public_mode):
|
||||
"""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 = f"/{mode.server_status.web.password}"
|
||||
else:
|
||||
path = "/"
|
||||
http_request = f"GET {path} HTTP/1.0\r\n"
|
||||
http_request += f"Host: {self.gui.app.onion_host}\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()
|
||||
|
||||
def have_copy_url_button(self, mode, public_mode):
|
||||
"""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
|
||||
)
|
||||
clipboard = self.gui.qtapp.clipboard()
|
||||
if public_mode:
|
||||
self.assertEqual(clipboard.text(), f"http://{self.gui.app.onion_host}")
|
||||
else:
|
||||
self.assertEqual(
|
||||
clipboard.text(),
|
||||
f"http://{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"""
|
||||
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),
|
||||
)
|
||||
|
||||
# Miscellaneous tests
|
||||
def tor_killed_statusbar_message_shown(self, mode):
|
||||
"""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")
|
||||
)
|
@ -1,61 +0,0 @@
|
||||
import os
|
||||
import requests
|
||||
from PyQt5 import QtTest
|
||||
from .TorGuiBaseTest import TorGuiBaseTest
|
||||
|
||||
|
||||
class TorGuiReceiveTest(TorGuiBaseTest):
|
||||
def upload_file(self, public_mode, file_to_upload, expected_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"] = f"socks5h://{socks_address}:{socks_port}"
|
||||
files = {"file[]": open(file_to_upload, "rb")}
|
||||
if not public_mode:
|
||||
path = f"http://{self.gui.app.onion_host}/{self.gui.receive_mode.web.password}/upload"
|
||||
else:
|
||||
path = f"http://{self.gui.app.onion_host}/upload"
|
||||
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"""
|
||||
self.click_mode(self.gui.receive_mode)
|
||||
self.history_is_not_visible(self.gui.receive_mode)
|
||||
self.click_toggle_history(self.gui.receive_mode)
|
||||
self.history_is_visible(self.gui.receive_mode)
|
||||
self.server_working_on_start_button_pressed(self.gui.receive_mode)
|
||||
self.server_status_indicator_says_starting(self.gui.receive_mode)
|
||||
self.settings_button_is_hidden()
|
||||
self.server_is_started(self.gui.receive_mode, startup_time=45000)
|
||||
self.web_server_is_running()
|
||||
self.have_an_onion_service()
|
||||
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.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.counter_incremented(self.gui.receive_mode, 2)
|
||||
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.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)
|
||||
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, startup_time=45000)
|
||||
self.history_indicator(self.gui.receive_mode, public_mode)
|
@ -1,91 +0,0 @@
|
||||
import requests
|
||||
import zipfile
|
||||
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"""
|
||||
# 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"] = f"socks5h://{socks_address}:{socks_port}"
|
||||
|
||||
# Download files
|
||||
if public_mode:
|
||||
path = f"http://{self.gui.app.onion_host}/download"
|
||||
else:
|
||||
path = f"http://{self.gui.app.onion_host}/{self.gui.share_mode.web.password}/download"
|
||||
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:
|
||||
for chunk in response.iter_content(chunk_size=128):
|
||||
file_to_write.write(chunk)
|
||||
file_to_write.close()
|
||||
zip = zipfile.ZipFile("/tmp/download.zip")
|
||||
QtTest.QTest.qWait(4000)
|
||||
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"""
|
||||
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")
|
||||
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"""
|
||||
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()
|
||||
self.settings_button_is_hidden()
|
||||
self.server_is_started(self.gui.share_mode, startup_time=45000)
|
||||
self.web_server_is_running()
|
||||
self.have_an_onion_service()
|
||||
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.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.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 password is the same on multiple shared"""
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.run_all_share_mode_started_tests(public_mode)
|
||||
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_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()
|
||||
self.set_timeout(self.gui.share_mode, 120)
|
||||
self.run_all_share_mode_started_tests(public_mode)
|
||||
self.autostop_timer_widget_hidden(self.gui.share_mode)
|
||||
self.server_timed_out(self.gui.share_mode, 125000)
|
||||
self.web_server_is_stopped()
|
@ -3,6 +3,9 @@ import sys
|
||||
# Force tests to look for resources in the source code tree
|
||||
sys.onionshare_dev_mode = True
|
||||
|
||||
# Let OnionShare know the tests are running, to avoid colliding with settings files
|
||||
sys.onionshare_test_mode = True
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
@ -12,6 +15,10 @@ import pytest
|
||||
from onionshare import common, web, settings, strings
|
||||
|
||||
|
||||
# The temporary directory for CLI tests
|
||||
test_temp_dir = None
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--rungui", action="store_true", default=False, help="run GUI tests"
|
||||
@ -38,51 +45,60 @@ def pytest_collection_modifyitems(config, items):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir_1024():
|
||||
def temp_dir():
|
||||
"""Creates a persistent temporary directory for the CLI tests to use"""
|
||||
global test_temp_dir
|
||||
if not test_temp_dir:
|
||||
test_temp_dir = tempfile.mkdtemp()
|
||||
return test_temp_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir_1024(temp_dir):
|
||||
""" Create a temporary directory that has a single file of a
|
||||
particular size (1024 bytes).
|
||||
"""
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||
new_temp_dir = tempfile.mkdtemp(dir=temp_dir)
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=new_temp_dir)
|
||||
with open(tmp_file, "wb") as f:
|
||||
f.write(b"*" * 1024)
|
||||
return tmp_dir
|
||||
return new_temp_dir
|
||||
|
||||
|
||||
# pytest > 2.9 only needs @pytest.fixture
|
||||
@pytest.yield_fixture
|
||||
def temp_dir_1024_delete():
|
||||
def temp_dir_1024_delete(temp_dir):
|
||||
""" Create a temporary directory that has a single file of a
|
||||
particular size (1024 bytes). The temporary directory (including
|
||||
the file inside) will be deleted after fixture usage.
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir)
|
||||
with tempfile.TemporaryDirectory(dir=temp_dir) as new_temp_dir:
|
||||
tmp_file, tmp_file_path = tempfile.mkstemp(dir=new_temp_dir)
|
||||
with open(tmp_file, "wb") as f:
|
||||
f.write(b"*" * 1024)
|
||||
yield tmp_dir
|
||||
yield new_temp_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_file_1024():
|
||||
def temp_file_1024(temp_dir):
|
||||
""" Create a temporary file of a particular size (1024 bytes). """
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(delete=False, dir=temp_dir) as tmp_file:
|
||||
tmp_file.write(b"*" * 1024)
|
||||
return tmp_file.name
|
||||
|
||||
|
||||
# pytest > 2.9 only needs @pytest.fixture
|
||||
@pytest.yield_fixture
|
||||
def temp_file_1024_delete():
|
||||
def temp_file_1024_delete(temp_dir):
|
||||
"""
|
||||
Create a temporary file of a particular size (1024 bytes).
|
||||
The temporary file will be deleted after fixture usage.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||
with tempfile.NamedTemporaryFile(dir=temp_dir) as tmp_file:
|
||||
tmp_file.write(b"*" * 1024)
|
||||
tmp_file.flush()
|
||||
yield tmp_file.name
|
||||
@ -108,7 +124,10 @@ def default_zw():
|
||||
yield zw
|
||||
zw.close()
|
||||
tmp_dir = os.path.dirname(zw.zip_filename)
|
||||
shutil.rmtree(tmp_dir)
|
||||
try:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
426
tests/gui_base_test.py
Normal file
426
tests/gui_base_test.py
Normal file
@ -0,0 +1,426 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import base64
|
||||
import tempfile
|
||||
import secrets
|
||||
|
||||
from PyQt5 import QtCore, QtTest, QtWidgets
|
||||
|
||||
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, MainWindow, GuiCommon
|
||||
from onionshare_gui.tab.mode.share_mode import ShareMode
|
||||
from onionshare_gui.tab.mode.receive_mode import ReceiveMode
|
||||
from onionshare_gui.tab.mode.website_mode import WebsiteMode
|
||||
|
||||
|
||||
class GuiBaseTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
common = Common(verbose=True)
|
||||
|
||||
# Delete any old test data that might exist
|
||||
shutil.rmtree(common.build_data_dir(), ignore_errors=True)
|
||||
|
||||
qtapp = Application(common)
|
||||
common.gui = GuiCommon(common, qtapp, local_only=True)
|
||||
cls.gui = MainWindow(common, filenames=None)
|
||||
cls.gui.qtapp = qtapp
|
||||
|
||||
# Create some random files to test with
|
||||
cls.tmpdir = tempfile.TemporaryDirectory()
|
||||
cls.tmpfiles = []
|
||||
for _ in range(10):
|
||||
filename = os.path.join(cls.tmpdir.name, f"{secrets.token_hex(4)}.txt")
|
||||
with open(filename, "w") as file:
|
||||
file.write(secrets.token_hex(10))
|
||||
cls.tmpfiles.append(filename)
|
||||
|
||||
# A file called "test.txt"
|
||||
cls.tmpfile_test = os.path.join(cls.tmpdir.name, "test.txt")
|
||||
with open(cls.tmpfile_test, "w") as file:
|
||||
file.write("onionshare")
|
||||
|
||||
# A file called "test2.txt"
|
||||
cls.tmpfile_test2 = os.path.join(cls.tmpdir.name, "test2.txt")
|
||||
with open(cls.tmpfile_test2, "w") as file:
|
||||
file.write("onionshare2")
|
||||
|
||||
# A file called "index.html"
|
||||
cls.tmpfile_index_html = os.path.join(cls.tmpdir.name, "index.html")
|
||||
with open(cls.tmpfile_index_html, "w") as file:
|
||||
file.write(
|
||||
"<html><body><p>This is a test website hosted by OnionShare</p></body></html>"
|
||||
)
|
||||
|
||||
# A large file
|
||||
size = 1024 * 1024 * 155
|
||||
cls.tmpfile_large = os.path.join(cls.tmpdir.name, "large_file")
|
||||
with open(cls.tmpfile_large, "wb") as fout:
|
||||
fout.write(os.urandom(size))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# Quit
|
||||
cls.gui.qtapp.clipboard().clear()
|
||||
QtCore.QTimer.singleShot(200, cls.gui.close_dialog.accept_button.click)
|
||||
cls.gui.close()
|
||||
|
||||
cls.gui.cleanup()
|
||||
|
||||
# Shared test methods
|
||||
|
||||
def verify_new_tab(self, tab):
|
||||
# Make sure the new tab widget is showing, and no mode has been started
|
||||
QtTest.QTest.qWait(1000)
|
||||
self.assertTrue(tab.new_tab.isVisible())
|
||||
self.assertFalse(hasattr(tab, "share_mode"))
|
||||
self.assertFalse(hasattr(tab, "receive_mode"))
|
||||
self.assertFalse(hasattr(tab, "website_mode"))
|
||||
|
||||
def new_share_tab(self):
|
||||
tab = self.gui.tabs.widget(0)
|
||||
self.verify_new_tab(tab)
|
||||
|
||||
# Share files
|
||||
tab.share_button.click()
|
||||
self.assertFalse(tab.new_tab.isVisible())
|
||||
self.assertTrue(tab.share_mode.isVisible())
|
||||
|
||||
return tab
|
||||
|
||||
def new_share_tab_with_files(self):
|
||||
tab = self.new_share_tab()
|
||||
|
||||
# Add files
|
||||
for filename in self.tmpfiles:
|
||||
tab.share_mode.server_status.file_selection.file_list.add_file(filename)
|
||||
|
||||
return tab
|
||||
|
||||
def new_receive_tab(self):
|
||||
tab = self.gui.tabs.widget(0)
|
||||
self.verify_new_tab(tab)
|
||||
|
||||
# Receive files
|
||||
tab.receive_button.click()
|
||||
self.assertFalse(tab.new_tab.isVisible())
|
||||
self.assertTrue(tab.receive_mode.isVisible())
|
||||
|
||||
return tab
|
||||
|
||||
def new_website_tab(self):
|
||||
tab = self.gui.tabs.widget(0)
|
||||
self.verify_new_tab(tab)
|
||||
|
||||
# Publish website
|
||||
tab.website_button.click()
|
||||
self.assertFalse(tab.new_tab.isVisible())
|
||||
self.assertTrue(tab.website_mode.isVisible())
|
||||
|
||||
return tab
|
||||
|
||||
def new_website_tab_with_files(self):
|
||||
tab = self.new_website_tab()
|
||||
|
||||
# Add files
|
||||
for filename in self.tmpfiles:
|
||||
tab.website_mode.server_status.file_selection.file_list.add_file(filename)
|
||||
|
||||
return tab
|
||||
|
||||
def close_all_tabs(self):
|
||||
for _ in range(self.gui.tabs.count()):
|
||||
tab = self.gui.tabs.widget(0)
|
||||
QtCore.QTimer.singleShot(200, tab.close_dialog.accept_button.click)
|
||||
self.gui.tabs.tabBar().tabButton(0, QtWidgets.QTabBar.RightSide).click()
|
||||
|
||||
def gui_loaded(self):
|
||||
"""Test that the GUI actually is shown"""
|
||||
self.assertTrue(self.gui.show)
|
||||
|
||||
def window_title_seen(self):
|
||||
"""Test that the window title is OnionShare"""
|
||||
self.assertEqual(self.gui.windowTitle(), "OnionShare")
|
||||
|
||||
def server_status_bar_is_visible(self):
|
||||
"""Test that the status bar is visible"""
|
||||
self.assertTrue(self.gui.status_bar.isVisible())
|
||||
|
||||
def mode_settings_widget_is_visible(self, tab):
|
||||
"""Test that the mode settings are visible"""
|
||||
self.assertTrue(tab.get_mode().mode_settings_widget.isVisible())
|
||||
|
||||
def mode_settings_widget_is_hidden(self, tab):
|
||||
"""Test that the mode settings are hidden when the server starts"""
|
||||
self.assertFalse(tab.get_mode().mode_settings_widget.isVisible())
|
||||
|
||||
def click_toggle_history(self, tab):
|
||||
"""Test that we can toggle Download or Upload history by clicking the toggle button"""
|
||||
currently_visible = tab.get_mode().history.isVisible()
|
||||
tab.get_mode().toggle_history.click()
|
||||
self.assertEqual(tab.get_mode().history.isVisible(), not currently_visible)
|
||||
|
||||
def history_indicator(self, tab, 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 tab.get_mode().history.isVisible():
|
||||
tab.get_mode().toggle_history.click()
|
||||
self.assertFalse(tab.get_mode().history.isVisible())
|
||||
|
||||
# Indicator should not be visible yet
|
||||
self.assertFalse(tab.get_mode().toggle_history.indicator_label.isVisible())
|
||||
|
||||
if type(tab.get_mode()) == ReceiveMode:
|
||||
# Upload a file
|
||||
files = {"file[]": open(self.tmpfiles[0], "rb")}
|
||||
url = f"http://127.0.0.1:{tab.app.port}/upload"
|
||||
if tab.settings.get("general", "public"):
|
||||
requests.post(url, files=files)
|
||||
else:
|
||||
requests.post(
|
||||
url,
|
||||
files=files,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", tab.get_mode().web.password
|
||||
),
|
||||
)
|
||||
QtTest.QTest.qWait(2000)
|
||||
|
||||
if type(tab.get_mode()) == ShareMode:
|
||||
# Download files
|
||||
url = f"http://127.0.0.1:{tab.app.port}/download"
|
||||
if tab.settings.get("general", "public"):
|
||||
requests.get(url)
|
||||
else:
|
||||
requests.get(
|
||||
url,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", tab.get_mode().web.password
|
||||
),
|
||||
)
|
||||
QtTest.QTest.qWait(2000)
|
||||
|
||||
# Indicator should be visible, have a value of "1"
|
||||
self.assertTrue(tab.get_mode().toggle_history.indicator_label.isVisible())
|
||||
self.assertEqual(
|
||||
tab.get_mode().toggle_history.indicator_label.text(), indicator_count
|
||||
)
|
||||
|
||||
# Toggle history back on, indicator should be hidden again
|
||||
tab.get_mode().toggle_history.click()
|
||||
self.assertFalse(tab.get_mode().toggle_history.indicator_label.isVisible())
|
||||
|
||||
def history_is_not_visible(self, tab):
|
||||
"""Test that the History section is not visible"""
|
||||
self.assertFalse(tab.get_mode().history.isVisible())
|
||||
|
||||
def history_is_visible(self, tab):
|
||||
"""Test that the History section is visible"""
|
||||
self.assertTrue(tab.get_mode().history.isVisible())
|
||||
|
||||
def server_working_on_start_button_pressed(self, tab):
|
||||
"""Test we can start the service"""
|
||||
# Should be in SERVER_WORKING state
|
||||
tab.get_mode().server_status.server_button.click()
|
||||
self.assertEqual(tab.get_mode().server_status.status, 1)
|
||||
|
||||
def toggle_indicator_is_reset(self, tab):
|
||||
self.assertEqual(tab.get_mode().toggle_history.indicator_count, 0)
|
||||
self.assertFalse(tab.get_mode().toggle_history.indicator_label.isVisible())
|
||||
|
||||
def server_status_indicator_says_starting(self, tab):
|
||||
"""Test that the Server Status indicator shows we are Starting"""
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_share_working"),
|
||||
)
|
||||
|
||||
def server_status_indicator_says_scheduled(self, tab):
|
||||
"""Test that the Server Status indicator shows we are Scheduled"""
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_share_scheduled"),
|
||||
)
|
||||
|
||||
def server_is_started(self, tab, startup_time=2000):
|
||||
"""Test that the server has started"""
|
||||
QtTest.QTest.qWait(startup_time)
|
||||
# Should now be in SERVER_STARTED state
|
||||
self.assertEqual(tab.get_mode().server_status.status, 2)
|
||||
|
||||
def web_server_is_running(self, tab):
|
||||
"""Test that the web server has started"""
|
||||
try:
|
||||
requests.get(f"http://127.0.0.1:{tab.app.port}/")
|
||||
self.assertTrue(True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.assertTrue(False)
|
||||
|
||||
def have_a_password(self, tab):
|
||||
"""Test that we have a valid password"""
|
||||
if not tab.settings.get("general", "public"):
|
||||
self.assertRegex(tab.get_mode().server_status.web.password, r"(\w+)-(\w+)")
|
||||
else:
|
||||
self.assertIsNone(tab.get_mode().server_status.web.password, r"(\w+)-(\w+)")
|
||||
|
||||
def add_button_visible(self, tab):
|
||||
"""Test that the add button should be visible"""
|
||||
self.assertTrue(
|
||||
tab.get_mode().server_status.file_selection.add_button.isVisible()
|
||||
)
|
||||
|
||||
def url_description_shown(self, tab):
|
||||
"""Test that the URL label is showing"""
|
||||
self.assertTrue(tab.get_mode().server_status.url_description.isVisible())
|
||||
|
||||
def have_copy_url_button(self, tab):
|
||||
"""Test that the Copy URL button is shown and that the clipboard is correct"""
|
||||
self.assertTrue(tab.get_mode().server_status.copy_url_button.isVisible())
|
||||
|
||||
tab.get_mode().server_status.copy_url_button.click()
|
||||
clipboard = tab.common.gui.qtapp.clipboard()
|
||||
if tab.settings.get("general", "public"):
|
||||
self.assertEqual(clipboard.text(), f"http://127.0.0.1:{tab.app.port}")
|
||||
else:
|
||||
self.assertEqual(
|
||||
clipboard.text(),
|
||||
f"http://onionshare:{tab.get_mode().server_status.web.password}@127.0.0.1:{tab.app.port}",
|
||||
)
|
||||
|
||||
def server_status_indicator_says_started(self, tab):
|
||||
"""Test that the Server Status indicator shows we are started"""
|
||||
if type(tab.get_mode()) == ReceiveMode:
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_receive_started"),
|
||||
)
|
||||
if type(tab.get_mode()) == ShareMode:
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_share_started"),
|
||||
)
|
||||
|
||||
def web_page(self, tab, string):
|
||||
"""Test that the web page contains a string"""
|
||||
|
||||
url = f"http://127.0.0.1:{tab.app.port}/"
|
||||
if tab.settings.get("general", "public"):
|
||||
r = requests.get(url)
|
||||
else:
|
||||
r = requests.get(
|
||||
url,
|
||||
auth=requests.auth.HTTPBasicAuth(
|
||||
"onionshare", tab.get_mode().web.password
|
||||
),
|
||||
)
|
||||
|
||||
self.assertTrue(string in r.text)
|
||||
|
||||
def history_widgets_present(self, tab):
|
||||
"""Test that the relevant widgets are present in the history view after activity has taken place"""
|
||||
self.assertFalse(tab.get_mode().history.empty.isVisible())
|
||||
self.assertTrue(tab.get_mode().history.not_empty.isVisible())
|
||||
|
||||
def counter_incremented(self, tab, count):
|
||||
"""Test that the counter has incremented"""
|
||||
self.assertEqual(tab.get_mode().history.completed_count, count)
|
||||
|
||||
def server_is_stopped(self, tab):
|
||||
"""Test that the server stops when we click Stop"""
|
||||
if (
|
||||
type(tab.get_mode()) == ReceiveMode
|
||||
or (
|
||||
type(tab.get_mode()) == ShareMode
|
||||
and not tab.settings.get("share", "autostop_sharing")
|
||||
)
|
||||
or (type(tab.get_mode()) == WebsiteMode)
|
||||
):
|
||||
tab.get_mode().server_status.server_button.click()
|
||||
self.assertEqual(tab.get_mode().server_status.status, 0)
|
||||
|
||||
def web_server_is_stopped(self, tab):
|
||||
"""Test that the web server also stopped"""
|
||||
QtTest.QTest.qWait(800)
|
||||
|
||||
try:
|
||||
requests.get(f"http://127.0.0.1:{tab.app.port}/")
|
||||
self.assertTrue(False)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.assertTrue(True)
|
||||
|
||||
def server_status_indicator_says_closed(self, tab):
|
||||
"""Test that the Server Status indicator shows we closed"""
|
||||
if type(tab.get_mode()) == ReceiveMode:
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_receive_stopped"),
|
||||
)
|
||||
if type(tab.get_mode()) == ShareMode:
|
||||
if not tab.settings.get("share", "autostop_sharing"):
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("gui_status_indicator_share_stopped"),
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status_label.text(),
|
||||
strings._("closing_automatically"),
|
||||
)
|
||||
|
||||
def clear_all_history_items(self, tab, count):
|
||||
if count == 0:
|
||||
tab.get_mode().history.clear_button.click()
|
||||
self.assertEqual(len(tab.get_mode().history.item_list.items.keys()), count)
|
||||
|
||||
def file_selection_widget_has_files(self, tab, num=3):
|
||||
"""Test that the number of items in the list is as expected"""
|
||||
self.assertEqual(
|
||||
tab.get_mode().server_status.file_selection.get_num_files(), num
|
||||
)
|
||||
|
||||
def add_delete_buttons_hidden(self, tab):
|
||||
"""Test that the add and delete buttons are hidden when the server starts"""
|
||||
self.assertFalse(
|
||||
tab.get_mode().server_status.file_selection.add_button.isVisible()
|
||||
)
|
||||
self.assertFalse(
|
||||
tab.get_mode().server_status.file_selection.delete_button.isVisible()
|
||||
)
|
||||
|
||||
# Auto-stop timer tests
|
||||
def set_timeout(self, tab, timeout):
|
||||
"""Test that the timeout can be set"""
|
||||
timer = QtCore.QDateTime.currentDateTime().addSecs(timeout)
|
||||
tab.get_mode().mode_settings_widget.autostop_timer_widget.setDateTime(timer)
|
||||
self.assertTrue(
|
||||
tab.get_mode().mode_settings_widget.autostop_timer_widget.dateTime(), timer
|
||||
)
|
||||
|
||||
def autostop_timer_widget_hidden(self, tab):
|
||||
"""Test that the auto-stop timer widget is hidden when share has started"""
|
||||
self.assertFalse(
|
||||
tab.get_mode().mode_settings_widget.autostop_timer_widget.isVisible()
|
||||
)
|
||||
|
||||
def server_timed_out(self, tab, wait):
|
||||
"""Test that the server has timed out after the timer ran out"""
|
||||
QtTest.QTest.qWait(wait)
|
||||
# We should have timed out now
|
||||
self.assertEqual(tab.get_mode().server_status.status, 0)
|
||||
|
||||
# Grouped tests follow from here
|
||||
|
||||
def run_all_common_setup_tests(self):
|
||||
self.gui_loaded()
|
||||
self.window_title_seen()
|
||||
self.server_status_bar_is_visible()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class Local401PublicModeRateLimitTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"close_after_first_download": False, "public_mode": True}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(True, True)
|
||||
self.hit_401(True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class Local401RateLimitTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"close_after_first_download": False}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(False, True)
|
||||
self.hit_401(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
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}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(False, True)
|
||||
# Prepare our auto-accept of prompt
|
||||
QtCore.QTimer.singleShot(5000, self.accept_dialog)
|
||||
# Try to close the app
|
||||
self.gui.close()
|
||||
# Server should still be running (we've been prompted first)
|
||||
self.server_is_started(self.gui.share_mode, 0)
|
||||
self.web_server_is_running()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiReceiveTest import GuiReceiveTest
|
||||
|
||||
|
||||
class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||
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_receive_mode_timer_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiReceiveTest import GuiReceiveTest
|
||||
|
||||
|
||||
class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||
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_receive_mode_unwritable_dir_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
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}
|
||||
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_receive_mode_unwritable_dir_tests(True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
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}
|
||||
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_receive_mode_tests(True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiReceiveTest import GuiReceiveTest
|
||||
|
||||
|
||||
class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||
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_receive_mode_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from onionshare import strings
|
||||
from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
|
||||
|
||||
|
||||
class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.gui = SettingsGuiBaseTest.set_up()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
SettingsGuiBaseTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@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()
|
||||
self.run_settings_gui_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from onionshare import strings
|
||||
from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
|
||||
|
||||
|
||||
class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.gui = SettingsGuiBaseTest.set_up()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
SettingsGuiBaseTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@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()
|
||||
self.run_settings_gui_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from onionshare import strings
|
||||
from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub
|
||||
|
||||
|
||||
class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.gui = SettingsGuiBaseTest.set_up()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
SettingsGuiBaseTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@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()
|
||||
self.run_settings_gui_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {
|
||||
"public_mode": False,
|
||||
"autostart_timer": True,
|
||||
"autostop_timer": 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_autostop_autostart_mismatch_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeAutoStartTimerTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": False, "autostart_timer": 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_autostart_timer_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
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}
|
||||
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_setup_tests()
|
||||
# Set a low timeout
|
||||
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
|
||||
)
|
||||
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeCancelTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"autostart_timer": 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_setup_tests()
|
||||
self.cancel_the_share(self.gui.share_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": True}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(True, False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"close_after_first_download": False}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(False, True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_tests(False, False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {}
|
||||
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_large_file_tests(False, True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModePersistentPasswordTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {
|
||||
"public_mode": False,
|
||||
"password": "",
|
||||
"save_private_key": True,
|
||||
"close_after_first_download": False,
|
||||
}
|
||||
cls.gui = GuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
GuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(pytest.__version__ < "2.9", reason="requires newer pytest")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_persistent_tests(False, True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": False, "autostop_timer": 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_timer_tests(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
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}
|
||||
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_setup_tests()
|
||||
# Set a low timeout
|
||||
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
|
||||
)
|
||||
self.assertEqual(self.gui.share_mode.server_status.status, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .GuiShareTest import GuiShareTest
|
||||
|
||||
|
||||
class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {}
|
||||
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_unreadable_file_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,26 +0,0 @@
|
||||
#!/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()
|
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
# Tests #790 regression
|
||||
class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"close_after_first_download": True}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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.cancel_the_share(self.gui.share_mode)
|
||||
self.server_is_stopped(self.gui.share_mode, False)
|
||||
self.web_server_is_stopped()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
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}
|
||||
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiReceiveTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiReceiveTest import TorGuiReceiveTest
|
||||
|
||||
|
||||
class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"receive_allow_receiver_shutdown": True}
|
||||
cls.gui = TorGuiReceiveTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiReceiveTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"autostart_timer": True}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": True}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"close_after_first_download": False}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModePersistentPasswordTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {
|
||||
"use_legacy_v2_onions": True,
|
||||
"public_mode": False,
|
||||
"password": "",
|
||||
"save_private_key": True,
|
||||
"close_after_first_download": False,
|
||||
}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
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}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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.run_all_share_mode_started_tests(False)
|
||||
self.hidserv_auth_string()
|
||||
self.copy_have_hidserv_auth_button(self.gui.share_mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"public_mode": False, "autostop_timer": True}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
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")
|
||||
def test_gui(self):
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_share_mode_setup_tests()
|
||||
self.run_all_share_mode_started_tests(False)
|
||||
self.tor_killed_statusbar_message_shown(self.gui.share_mode)
|
||||
self.server_is_stopped(self.gui.share_mode, False)
|
||||
self.web_server_is_stopped()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from .TorGuiShareTest import TorGuiShareTest
|
||||
|
||||
|
||||
class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_settings = {"use_legacy_v2_onions": True}
|
||||
cls.gui = TorGuiShareTest.set_up(test_settings)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
TorGuiShareTest.tear_down()
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.tor
|
||||
@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()
|
4
tests/pytest.ini
Normal file
4
tests/pytest.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
markers =
|
||||
gui: marks tests as a GUI test
|
||||
tor: marks tests as a Tor GUI test
|
26
tests/run.sh
Executable file
26
tests/run.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# The script runs python tests
|
||||
# Firstly, all CLI tests are run
|
||||
# Then, all the GUI tests are run individually
|
||||
# to avoid segmentation fault
|
||||
|
||||
PARAMS=""
|
||||
|
||||
while [ ! $# -eq 0 ]
|
||||
do
|
||||
case "$1" in
|
||||
--rungui)
|
||||
PARAMS="$PARAMS --rungui"
|
||||
;;
|
||||
--runtor)
|
||||
PARAMS="$PARAMS --runtor"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
pytest $PARAMS -vvv ./tests/test_cli*.py
|
||||
for filename in ./tests/test_gui_*.py; do
|
||||
pytest $PARAMS -vvv --no-qt-log $filename
|
||||
done
|
@ -23,17 +23,17 @@ import pytest
|
||||
|
||||
from onionshare import OnionShare
|
||||
from onionshare.common import Common
|
||||
from onionshare.mode_settings import ModeSettings
|
||||
|
||||
|
||||
class MyOnion:
|
||||
def __init__(self, stealth=False):
|
||||
def __init__(self):
|
||||
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):
|
||||
def start_onion_service(self, mode_settings_obj, await_publication=True, save_scheduled_key=False):
|
||||
return "test_service_id.onion"
|
||||
|
||||
|
||||
@ -43,38 +43,27 @@ def onionshare_obj():
|
||||
return OnionShare(common, MyOnion())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mode_settings_obj():
|
||||
common = Common()
|
||||
return ModeSettings(common)
|
||||
|
||||
|
||||
class TestOnionShare:
|
||||
def test_init(self, onionshare_obj):
|
||||
assert onionshare_obj.hidserv_dir is None
|
||||
assert onionshare_obj.onion_host is None
|
||||
assert onionshare_obj.stealth is None
|
||||
assert onionshare_obj.cleanup_filenames == []
|
||||
assert onionshare_obj.local_only is False
|
||||
|
||||
def test_set_stealth_true(self, onionshare_obj):
|
||||
onionshare_obj.set_stealth(True)
|
||||
assert onionshare_obj.stealth is True
|
||||
assert onionshare_obj.onion.stealth is True
|
||||
|
||||
def test_set_stealth_false(self, onionshare_obj):
|
||||
onionshare_obj.set_stealth(False)
|
||||
assert onionshare_obj.stealth is False
|
||||
assert onionshare_obj.onion.stealth is False
|
||||
|
||||
def test_start_onion_service(self, onionshare_obj):
|
||||
onionshare_obj.set_stealth(False)
|
||||
onionshare_obj.start_onion_service()
|
||||
def test_start_onion_service(self, onionshare_obj, mode_settings_obj):
|
||||
onionshare_obj.start_onion_service(mode_settings_obj)
|
||||
assert 17600 <= onionshare_obj.port <= 17650
|
||||
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"
|
||||
|
||||
def test_start_onion_service_local_only(self, onionshare_obj):
|
||||
def test_start_onion_service_local_only(self, onionshare_obj, mode_settings_obj):
|
||||
onionshare_obj.local_only = True
|
||||
onionshare_obj.start_onion_service()
|
||||
onionshare_obj.start_onion_service(mode_settings_obj)
|
||||
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):
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user