This commit is contained in:
Ulrike Uhlig 2017-01-18 20:50:03 +01:00
commit ebdc92bfa7
35 changed files with 1531 additions and 708 deletions

View File

@ -1,51 +1,34 @@
# Building OnionShare
## GNU/Linux
Start by getting a copy of the source code:
Start by getting the source code:
```sh
git clone https://github.com/micahflee/onionshare.git
cd onionshare
```
*For .deb-based distros (like Debian, Ubuntu, Linux Mint):*
## Linux
Then install the needed dependencies:
Install the needed dependencies:
```sh
sudo apt-get install -y python3-flask python3-stem python3-pyqt5 python-nautilus
```
For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-nose`
For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 nautilus-python`
After that you can try both the CLI and the GUI version of OnionShare:
```sh
./install/scripts/onionshare
./install/scripts/onionshare-gui
./dev_scripts/onionshare
./dev_scripts/onionshare-gui
```
A script to build a .deb package and install OnionShare easily is also provided for your convenience:
You can also build OnionShare packages to install:
```sh
sudo apt-get install -y build-essential fakeroot python3-all python3-stdeb dh-python python-nautilus python3-nose
./install/build_deb.sh
sudo dpkg -i deb_dist/onionshare_*.deb
```
Note that OnionShare uses stdeb to generate Debian packages, and `python3-stdeb` is not available in Ubuntu 14.04 (Trusty). Because of this, you can't use the `build_install.sh` script to build the .deb file in versions of Ubuntu 14.04 and earlier. However, .deb files you build in later versions of Ubuntu will install and work fine in 14.04.
Create a .deb on Debian-like distros: `./install/build_deb.sh`
*For .rpm-based distros (Red Hat, Fedora, CentOS):*
Create a .rpm on Fedora-like distros: `./install/build_rpm.sh`
```sh
sudo sudo dnf install -y rpm-build python3-flask python3-stem python3-qt5 nautilus-python
./install/build_rpm.sh
sudo yum install -y dist/onionshare-*.rpm
```
Depending on your distribution, you may need to use `yum` instead of `dnf`.
*For ArchLinux:*
There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare.
For ArchLinux: There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare.
## Mac OS X
@ -65,20 +48,22 @@ Install some dependencies using pip3:
sudo pip3 install flask stem
```
After that you can try both the CLI and the GUI version of OnionShare:
```sh
./dev_scripts/onionshare
./dev_scripts/onionshare-gui
```
If you want to build a Mac OS X app bundle:
Install the latest development version of cx_Freeze:
* Download a [snapshot](https://bitbucket.org/anthony_tuininga/cx_freeze/downloads) of the latest development version of cx_Freeze, extract it, and cd into the folder you extracted it to
* Build the package: `python3 setup.py bdist_wheel`
* Install it with pip: `sudo pip3 install dist/cx_Freeze-5.0-cp35-cp35m-macosx_10_11_x86_64.whl`
Get the source code:
```sh
git clone https://github.com/micahflee/onionshare.git
cd onionshare
```
To build the .app:
To build the app bundle:
```sh
install/build_osx.sh
@ -86,7 +71,7 @@ install/build_osx.sh
Now you should have `dist/OnionShare.app`.
To codesign and build a .pkg for distribution:
To codesign and build a pkg for distribution:
```sh
install/build_osx.sh --release
@ -98,14 +83,23 @@ Now you should have `dist/OnionShare.pkg`.
### Setting up your dev environment
Download the latest Python 3.6.x, 32-bit (x86) from https://www.python.org/downloads/. I downloaded `python-3.6.0.exe`. When installing it, make sure to check the "Add Python 3.6 to PATH" checkbox on the first page of the installer.
Open a command prompt and install dependencies with pip: `pip install flask stem PyQt5`
Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-2.0.4-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.7.
After that you can try both the CLI and the GUI version of OnionShare:
```
python dev_scripts\onionshare
python dev_scripts\onionshare-gui
```
If you want to build an .exe:
These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH.
Download the latest Python 3.5.x, 32-bit (x86) from https://www.python.org/downloads/. I downloaded `python-3.5.2.exe`. When installing it, make sure to check the "Add Python 3.5 to PATH" checkbox on the first page of the installer.
Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-2.0.3-1-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x.
Open a command prompt and install dependencies with pip: `pip install pypiwin32 flask stem PyQt5`
Download and install the [Microsoft Visual C++ 2008 Redistributable Package (x86)](http://www.microsoft.com/en-us/download/details.aspx?id=29).
Installing cx_Freeze with support for Python 3.5 is annoying. Here are the steps (thanks https://github.com/sekrause/cx_Freeze-Wheels):

View File

@ -21,24 +21,8 @@ If you're interested in exactly what OnionShare does and does not protect agains
## Quick Start
Check out [the wiki](https://github.com/micahflee/onionshare/wiki) for information about how to use OnionShare and it's various features.
You can download OnionShare to install on your computer from <https://onionshare.org/>.
You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md).
## How to Use
Before you can share files, you need to open [Tor Browser](https://www.torproject.org/) in the background. This will provide the Tor service that OnionShare uses to start the onion service.
Open OnionShare and drag and drop files and folders you wish to share, and click Start Sharing. It will show you a .onion URL such as `http://asxmi4q6i7pajg2b.onion/egg-cain` and copy it to your clipboard. This is the secret URL that can be used to download the file you're sharing. If you'd like multiple people to be able to download this file, uncheck the "close automatically" checkbox.
Send this URL to the person you're trying to send the files to. If the files you're sending aren't secret, you can use normal means of sending the URL: emailing it, posting it to Facebook or Twitter, etc. If you're trying to send secret files then it's important to send this URL securely.
The person who is receiving the files doesn't need OnionShare. All they need is to open the URL you send them in Tor Browser to be able to download the file.
## Using the command line version
In Linux: Just run `onionshare` from the terminal.
In Windows: Add `C:\Program Files (x86)\OnionShare` to your PATH. Now you can run `onionshare.exe` in a command prompt.
In Mac OS X: Run `ln -s /Applications/OnionShare.app/Contents/MacOS/onionshare /usr/local/bin`. Now you can run `onionshare` from the terminal.

28
dev_scripts/onionshare Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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/>.
"""
# Load onionshare module and resources from the source code tree
import os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.onionshare_dev_mode = True
import onionshare
onionshare.main()

28
dev_scripts/onionshare-gui Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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/>.
"""
# Load onionshare module and resources from the source code tree
import os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.onionshare_dev_mode = True
import onionshare_gui
onionshare_gui.main()

View File

@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -17,4 +17,195 @@ 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 .onionshare import *
import os, sys, time, argparse, shutil, socket, threading
from . import strings, helpers, web, onion
class OnionShare(object):
"""
OnionShare is the main application class. Pass in options and run
start_onion_service and it will do the magic.
"""
def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False, stealth=False):
self.port = None
self.onion = None
self.hidserv_dir = None
self.onion_host = None
# files and dirs to delete on shutdown
self.cleanup_filenames = []
# debug mode
if debug:
web.debug_mode()
# do not use tor -- for development
self.local_only = local_only
# automatically close when download is finished
self.stay_open = stay_open
# traffic automatically goes through Tor
self.transparent_torification = transparent_torification
# use stealth onion service
self.set_stealth(stealth)
def set_stealth(self, stealth):
self.stealth = stealth
if self.onion:
self.onion.stealth = stealth
def choose_port(self):
"""
Pick an un-used port in the range 17600-17650 to bind to.
"""
# let the OS choose a port
tmpsock = socket.socket()
for port in range(17600, 17650):
try:
tmpsock.bind(("127.0.0.1", port))
break
except OSError:
pass
self.port = tmpsock.getsockname()[1]
tmpsock.close()
def start_onion_service(self):
"""
Start the onionshare onion service.
"""
if not self.port:
self.choose_port()
if self.local_only:
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
return
if not self.onion:
self.onion = onion.Onion(self.transparent_torification, self.stealth)
self.onion_host = self.onion.start(self.port)
if self.stealth:
self.auth_string = self.onion.auth_string
def cleanup(self):
"""
Shut everything down and clean up temporary files, etc.
"""
# cleanup files
for filename in self.cleanup_filenames:
if os.path.isfile(filename):
os.remove(filename)
elif os.path.isdir(filename):
shutil.rmtree(filename)
self.cleanup_filenames = []
# cleanup the onion
if self.onion:
self.onion.cleanup()
def main(cwd=None):
"""
The main() function implements all of the logic that the command-line version of
onionshare uses.
"""
strings.load_strings(helpers)
print(strings._('version_string').format(helpers.get_version()))
# onionshare CLI in OSX needs to change current working directory (#132)
if helpers.get_platform() == 'Darwin':
if cwd:
os.chdir(cwd)
# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
parser.add_argument('--transparent', action='store_true', dest='transparent_torification', help=strings._("help_transparent_torification"))
parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth"))
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
args = parser.parse_args()
filenames = args.filename
for i in range(len(filenames)):
filenames[i] = os.path.abspath(filenames[i])
local_only = bool(args.local_only)
debug = bool(args.debug)
stay_open = bool(args.stay_open)
transparent_torification = bool(args.transparent_torification)
stealth = bool(args.stealth)
# validation
valid = True
for filename in filenames:
if not os.path.exists(filename):
print(strings._("not_a_file").format(filename))
valid = False
if not valid:
sys.exit()
# start the onionshare app
try:
app = OnionShare(debug, local_only, stay_open, transparent_torification, stealth)
app.choose_port()
app.start_onion_service()
except (onion.TorTooOld, onion.TorErrorInvalidSetting, onion.TorErrorAutomatic, onion.TorErrorSocketPort, onion.TorErrorSocketFile, onion.TorErrorMissingPassword, onion.TorErrorUnreadableCookieFile, onion.TorErrorAuthError) as e:
sys.exit(e.args[0])
except KeyboardInterrupt:
print("")
sys.exit()
# prepare files to share
print(strings._("preparing_files"))
web.set_file_info(filenames)
app.cleanup_filenames.append(web.zip_filename)
# warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb
print('')
print(strings._("large_filesize"))
print('')
# start onionshare http service in new thread
t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.transparent_torification))
t.daemon = True
t.start()
try: # Trap Ctrl-C
# wait for hs, only if using old version of tor
if not app.local_only and not app.onion.supports_ephemeral:
ready = app.onion.wait_for_hs(app.onion_host)
if not ready:
sys.exit()
else:
# Wait for web.generate_slug() to finish running
time.sleep(0.2)
if(stealth):
print(strings._("give_this_url_stealth"))
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
print(app.auth_string)
else:
print(strings._("give_this_url"))
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
print('')
print(strings._("ctrlc_to_stop"))
# wait for app to close
while t.is_alive():
# t.join() can't catch KeyboardInterrupt in such as Ubuntu
t.join(0.5)
except KeyboardInterrupt:
web.stop(app.port)
finally:
# shutdown
app.cleanup()
if __name__ == '__main__':
main()

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -34,14 +34,17 @@ def get_resource_path(filename):
systemwide, and whether regardless of platform
"""
p = get_platform()
if p == 'Linux' and sys.argv and sys.argv[0].startswith(sys.prefix):
if getattr(sys, 'onionshare_dev_mode', False):
# Look for resources directory relative to python file
resources_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'resources')
elif p == 'Linux' and sys.argv and sys.argv[0].startswith(sys.prefix):
# OnionShare is installed systemwide in Linux
resources_dir = os.path.join(sys.prefix, 'share/onionshare')
elif getattr(sys, 'frozen', False): # Check if app is "frozen" with cx_Freeze
# http://cx-freeze.readthedocs.io/en/latest/faq.html#using-data-files
resources_dir = os.path.join(os.path.dirname(sys.executable), 'resources')
else: # Look for resources directory relative to python file
resources_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'resources')
return os.path.join(resources_dir, filename)
@ -176,19 +179,26 @@ class ZipWriter(object):
with. If a zip_filename is not passed in, it will use the default onionshare
filename.
"""
def __init__(self, zip_filename=None):
def __init__(self, zip_filename=None, processed_size_callback=None):
if zip_filename:
self.zip_filename = zip_filename
else:
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), random_string(4, 6))
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
self.processed_size_callback = processed_size_callback
if self.processed_size_callback is None:
self.processed_size_callback = lambda _: None
self._size = 0
self.processed_size_callback(self._size)
def add_file(self, filename):
"""
Add a file to the zip archive.
"""
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
self._size += os.path.getsize(filename)
self.processed_size_callback(self._size)
def add_dir(self, filename):
"""
@ -201,6 +211,8 @@ class ZipWriter(object):
if not os.path.islink(full_filename):
arc_filename = full_filename[len(dir_to_strip):]
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
self._size += os.path.getsize(full_filename)
self.processed_size_callback(self._size)
def close(self):
"""

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -20,15 +20,63 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from stem.control import Controller
from stem import SocketError
import os, sys, tempfile, shutil, urllib
from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure
import os, sys, tempfile, shutil, urllib, platform
from . import socks
from . import helpers, strings
from .settings import Settings
class NoTor(Exception):
class TorErrorAutomatic(Exception):
"""
This exception is raised if onionshare can't find a Tor control port
to connect to, or if it can't find a Tor socks5 proxy to proxy though.
OnionShare is failing to connect and authenticate to the Tor controller,
using automatic settings that should work with Tor Browser.
"""
pass
class TorErrorInvalidSetting(Exception):
"""
This exception is raised if the settings just don't make sense.
"""
pass
class TorErrorSocketPort(Exception):
"""
OnionShare can't connect to the Tor controller using the supplied address and port.
"""
pass
class TorErrorSocketFile(Exception):
"""
OnionShare can't connect to the Tor controller using the supplied socket file.
"""
pass
class TorErrorMissingPassword(Exception):
"""
OnionShare connected to the Tor controller, but it requires a password.
"""
pass
class TorErrorUnreadableCookieFile(Exception):
"""
OnionShare connected to the Tor controller, but your user does not have permission
to access the cookie file.
"""
pass
class TorErrorAuthError(Exception):
"""
OnionShare connected to the address and port, but can't authenticate. It's possible
that a Tor controller isn't listening on this port.
"""
pass
class TorTooOld(Exception):
"""
This exception is raised if onionshare needs to use a feature of Tor or stem
(like stealth ephemeral onion services) but the version you have installed
is too old.
"""
pass
@ -47,48 +95,169 @@ class Onion(object):
onion services are supported. If not, it falls back to modifying the
Tor configuration.
"""
def __init__(self, transparent_torification=False):
def __init__(self, transparent_torification=False, stealth=False, settings=False):
self.transparent_torification = transparent_torification
self.stealth = stealth
# Either use settings that are passed in, or load them from disk
if settings:
self.settings = settings
else:
self.settings = Settings()
self.settings.load()
# files and dirs to delete on shutdown
self.cleanup_filenames = []
self.service_id = None
# connect to the tor controlport
found_tor = False
# Try to connect to Tor
self.c = None
env_port = os.environ.get('TOR_CONTROL_PORT')
if env_port:
ports = [int(env_port)]
else:
ports = [9151, 9153, 9051]
for port in ports:
if self.settings.get('connection_type') == 'automatic':
# Automatically try to guess the right way to connect to Tor Browser
p = platform.system()
# Try connecting to control port
found_tor = False
# If the TOR_CONTROL_PORT environment variable is set, use that
env_port = os.environ.get('TOR_CONTROL_PORT')
if env_port:
try:
self.c = Controller.from_port(port=int(env_port))
found_tor = True
except:
pass
else:
# Otherwise, try default ports for Tor Browser, Tor Messenger, and system tor
try:
ports = [9151, 9153, 9051]
for port in ports:
self.c = Controller.from_port(port=port)
found_tor = True
except:
pass
# If this still didn't work, try guessing the default socket file path
socket_file_path = ''
if not found_tor:
try:
if p == 'Darwin':
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
self.c = Controller.from_socket_file(path=socket_file_path)
found_tor = True
except:
pass
# If connecting to default control ports failed, so let's try
# guessing the socket file name next
if not found_tor:
try:
if p == 'Linux':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
elif p == 'Darwin':
# TODO: figure out the unix socket path in OS X
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
elif p == 'Windows':
# Windows doesn't support unix sockets
raise TorErrorAutomatic(strings._('settings_error_automatic'))
self.c = Controller.from_socket_file(path=socket_file_path)
except:
raise TorErrorAutomatic(strings._('settings_error_automatic'))
# Try authenticating
try:
self.c = Controller.from_port(port=port)
self.c.authenticate()
found_tor = True
break
except SocketError:
pass
if not found_tor:
raise NoTor(strings._("cant_connect_ctrlport").format(str(ports)))
except:
raise TorErrorAutomatic(strings._('settings_error_automatic'))
else:
# Use specific settings to connect to tor
# Try connecting
try:
if self.settings.get('connection_type') == 'control_port':
self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port'))
elif self.settings.get('connection_type') == 'socket_file':
self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path'))
else:
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
except:
if self.settings.get('connection_type') == 'control_port':
raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
else:
raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path')))
# Try authenticating
try:
if self.settings.get('auth_type') == 'no_auth':
self.c.authenticate()
elif self.settings.get('auth_type') == 'password':
self.c.authenticate(self.settings.get('auth_password'))
else:
raise TorErrorInvalidSetting(strings._("settings_error_unknown"))
except MissingPassword:
raise TorErrorMissingPassword(strings._('settings_error_missing_password'))
except UnreadableCookieFile:
raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file'))
except AuthenticationFailure:
raise TorErrorAuthError(strings._('settings_error_auth').format(self.settings.get('control_port_address'), self.settings.get('control_port_port')))
# get the tor version
self.tor_version = self.c.get_version().version_str
# do the versions of stem and tor that I'm using support ephemeral onion services?
tor_version = self.c.get_version().version_str
list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None)
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1'
self.supports_ephemeral = callable(list_ephemeral_hidden_services) and self.tor_version >= '0.2.7.1'
# do the versions of stem and tor that I'm using support stealth onion services?
try:
res = self.c.create_ephemeral_hidden_service({1:1}, basic_auth={'onionshare':None}, await_publication=False)
tmp_service_id = res.content()[0][2].split('=')[1]
self.c.remove_ephemeral_hidden_service(tmp_service_id)
self.supports_stealth = True
except:
# ephemeral stealth onion services are not supported
self.supports_stealth = False
def start(self, port):
"""
Start a onion service on port 80, pointing to the given port, and
return the onion hostname.
"""
print(strings._("connecting_ctrlport").format(int(port)))
self.auth_string = None
if self.stealth and not self.supports_stealth:
raise TorTooOld(strings._('error_stealth_not_supported'))
print(strings._("config_onion_service").format(int(port)))
if self.supports_ephemeral:
print(strings._('using_ephemeral'))
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication = True)
if self.stealth:
basic_auth = {'onionshare':None}
else:
basic_auth = None
if basic_auth != None :
res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth)
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=True)
self.service_id = res.content()[0][2].split('=')[1]
onion_host = self.service_id + '.onion'
if self.stealth:
auth_cookie = res.content()[2][2].split('=')[1].split(':')[1]
self.auth_string = 'HidServAuth {} {}'.format(onion_host, auth_cookie)
return onion_host
else:

View File

@ -1,190 +0,0 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 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, sys, time, argparse, shutil, socket, threading
from . import strings, helpers, web, onion
class OnionShare(object):
"""
OnionShare is the main application class. Pass in options and run
start_onion_service and it will do the magic.
"""
def __init__(self, debug=False, local_only=False, stay_open=False, transparent_torification=False):
self.port = None
self.onion = None
self.hidserv_dir = None
self.onion_host = None
# files and dirs to delete on shutdown
self.cleanup_filenames = []
# debug mode
if debug:
web.debug_mode()
# do not use tor -- for development
self.local_only = local_only
# automatically close when download is finished
self.stay_open = stay_open
# traffic automatically goes through Tor
self.transparent_torification = transparent_torification
def choose_port(self):
"""
Pick an un-used port in the range 17600-17650 to bind to.
"""
# let the OS choose a port
tmpsock = socket.socket()
for port in range(17600, 17650):
try:
tmpsock.bind(("127.0.0.1", port))
break
except OSError:
pass
self.port = tmpsock.getsockname()[1]
tmpsock.close()
def start_onion_service(self):
"""
Start the onionshare onion service.
"""
if not self.port:
self.choose_port()
if self.local_only:
self.onion_host = '127.0.0.1:{0:d}'.format(self.port)
return
if not self.onion:
self.onion = onion.Onion(self.transparent_torification)
self.onion_host = self.onion.start(self.port)
def cleanup(self):
"""
Shut everything down and clean up temporary files, etc.
"""
# cleanup files
for filename in self.cleanup_filenames:
if os.path.isfile(filename):
os.remove(filename)
elif os.path.isdir(filename):
shutil.rmtree(filename)
self.cleanup_filenames = []
# cleanup the onion
if self.onion:
self.onion.cleanup()
def main(cwd=None):
"""
The main() function implements all of the logic that the command-line version of
onionshare uses.
"""
strings.load_strings(helpers)
print(strings._('version_string').format(helpers.get_version()))
# onionshare CLI in OSX needs to change current working directory (#132)
if helpers.get_platform() == 'Darwin':
if cwd:
os.chdir(cwd)
# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
parser.add_argument('--transparent', action='store_true', dest='transparent_torification', help=strings._("help_transparent_torification"))
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
args = parser.parse_args()
filenames = args.filename
for i in range(len(filenames)):
filenames[i] = os.path.abspath(filenames[i])
local_only = bool(args.local_only)
debug = bool(args.debug)
stay_open = bool(args.stay_open)
transparent_torification = bool(args.transparent_torification)
# validation
valid = True
for filename in filenames:
if not os.path.exists(filename):
print(strings._("not_a_file").format(filename))
valid = False
if not valid:
sys.exit()
# start the onionshare app
try:
app = OnionShare(debug, local_only, stay_open, transparent_torification)
app.choose_port()
app.start_onion_service()
except onion.NoTor as e:
sys.exit(e.args[0])
# prepare files to share
print(strings._("preparing_files"))
web.set_file_info(filenames)
app.cleanup_filenames.append(web.zip_filename)
# warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb
print('')
print(strings._("large_filesize"))
print('')
# start onionshare http service in new thread
t = threading.Thread(target=web.start, args=(app.port, app.stay_open, app.transparent_torification))
t.daemon = True
t.start()
try: # Trap Ctrl-C
# wait for hs, only if using old version of tor
if not app.local_only and not app.onion.supports_ephemeral:
ready = app.onion.wait_for_hs(app.onion_host)
if not ready:
sys.exit()
else:
# Wait for web.generate_slug() to finish running
time.sleep(0.2)
print(strings._("give_this_url"))
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
print('')
print(strings._("ctrlc_to_stop"))
# wait for app to close
while t.is_alive():
# t.join() can't catch KeyboardInterrupt in such as Ubuntu
t.join(0.5)
except KeyboardInterrupt:
web.stop(app.port)
finally:
# shutdown
app.cleanup()
if __name__ == '__main__':
main()

85
onionshare/settings.py Normal file
View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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 platform, os, json
from . import strings, helpers
class Settings(object):
"""
This class stores all of the settings for OnionShare, specifically for how
to connect to Tor. If it can't find the settings file, it uses the default,
which is to attempt to connect automatically using default Tor Browser
settings.
"""
def __init__(self):
self.filename = self.build_filename()
# These are the default settings. They will get overwritten when loading from disk
self._settings = {
'version': helpers.get_version(),
'connection_type': 'automatic',
'control_port_address': '127.0.0.1',
'control_port_port': 9051,
'socket_file_path': '/var/run/tor/control',
'auth_type': 'no_auth',
'auth_password': ''
}
def build_filename(self):
"""
Returns the path of the settings file.
"""
p = platform.system()
if p == 'Windows':
appdata = os.environ['APPDATA']
return '{}\\OnionShare\\onionshare.json'.format(appdata)
elif p == 'Darwin':
return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json')
else:
return os.path.expanduser('~/.config/onionshare/onionshare.json')
def load(self):
"""
Load the settings from file.
"""
# If the settings file exists, load it
if os.path.exists(self.filename):
try:
self._settings = json.loads(open(self.filename, 'r').read())
except:
pass
def save(self):
"""
Save settings to file.
"""
try:
os.makedirs(os.path.dirname(self.filename))
except:
pass
open(self.filename, 'w').write(json.dumps(self._settings))
print(strings._('settings_saved').format(self.filename))
def get(self, key):
return self._settings[key]
def set(self, key, val):
self._settings[key] = val

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -17,12 +17,29 @@ 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, mimetypes, platform, os, sys, socket, logging, html
from distutils.version import StrictVersion as Version
import queue, mimetypes, platform, os, sys, socket, logging
from urllib.request import urlopen
from flask import Flask, Response, request, render_template_string, abort
from flask import __version__ as flask_version
from . import strings, helpers
def _safe_select_jinja_autoescape(self, filename):
if filename is None:
return True
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
# Starting in Flask 0.11, render_template_string autoescapes template variables
# by default. To prevent content injection through template variables in
# earlier versions of Flask, we force autoescaping in the Jinja2 template
# engine if we detect a Flask version with insecure default behavior.
if Version(flask_version) < Version('0.11'):
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
Flask.select_jinja_autoescape = _safe_select_jinja_autoescape
app = Flask(__name__)
# information about the file
@ -30,7 +47,7 @@ file_info = []
zip_filename = None
zip_filesize = None
def set_file_info(filenames):
def set_file_info(filenames, processed_size_callback=None):
"""
Using the list of filenames being shared, fill in details that the web
page will need to display. This includes zipping up the file in order to
@ -41,11 +58,9 @@ def set_file_info(filenames):
# build file info list
file_info = {'files': [], 'dirs': []}
for filename in filenames:
# strips trailing '/' and sanitizes filename
basename = html.escape(os.path.basename(filename.rstrip('/')))
info = {
'filename': filename,
'basename': basename
'basename': os.path.basename(filename.rstrip('/'))
}
if os.path.isfile(filename):
info['size'] = os.path.getsize(filename)
@ -55,13 +70,11 @@ def set_file_info(filenames):
info['size'] = helpers.dir_size(filename)
info['size_human'] = helpers.human_readable_filesize(info['size'])
file_info['dirs'].append(info)
# sort list of files and directories by basename
file_info['files'] = sorted(file_info['files'], key=lambda k: k['basename'])
file_info['dirs'] = sorted(file_info['dirs'], key=lambda k: k['basename'])
# zip up the files and folders
z = helpers.ZipWriter()
z = helpers.ZipWriter(processed_size_callback=processed_size_callback)
for info in file_info['files']:
z.add_file(info['filename'])
for info in file_info['dirs']:

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -17,4 +17,445 @@ 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 .onionshare_gui import *
from __future__ import division
import os, sys, subprocess, inspect, platform, argparse, threading, time, math, inspect, platform
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtCore import pyqtSlot
import onionshare
from onionshare import strings, helpers, web
from .menu import Menu
from .file_selection import FileSelection
from .server_status import ServerStatus
from .downloads import Downloads
from .options import Options
from .alert import Alert
class Application(QtWidgets.QApplication):
"""
This is Qt's QApplication class. It has been overridden to support threads
and the quick keyboard shortcut.
"""
def __init__(self):
platform = helpers.get_platform()
if platform == 'Linux':
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtWidgets.QApplication.__init__(self, sys.argv)
self.installEventFilter(self)
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.KeyPress and
event.key() == QtCore.Qt.Key_Q and
event.modifiers() == QtCore.Qt.ControlModifier):
self.quit()
return False
class OnionShareGui(QtWidgets.QMainWindow):
"""
OnionShareGui is the main window for the GUI that contains all of the
GUI elements.
"""
start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal()
starting_server_step2 = QtCore.pyqtSignal()
starting_server_step3 = QtCore.pyqtSignal()
starting_server_error = QtCore.pyqtSignal(str)
def __init__(self, qtapp, app):
super(OnionShareGui, self).__init__()
self.qtapp = qtapp
self.app = app
self.setWindowTitle('OnionShare')
self.setWindowIcon(window_icon)
# the menu bar
self.setMenuBar(Menu())
def send_files(self, filenames=None):
"""
Build the GUI in send files mode.
Note that this is the only mode currently implemented.
"""
# file selection
self.file_selection = FileSelection()
if filenames:
for filename in filenames:
self.file_selection.file_list.add_file(filename)
# server status
self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection)
self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_started.connect(self.start_server)
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
self.server_status.server_stopped.connect(self.stop_server)
self.start_server_finished.connect(self.clear_message)
self.start_server_finished.connect(self.server_status.start_server_finished)
self.stop_server_finished.connect(self.server_status.stop_server_finished)
self.file_selection.file_list.files_updated.connect(self.server_status.update)
self.server_status.url_copied.connect(self.copy_url)
self.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3)
self.starting_server_error.connect(self.start_server_error)
# filesize warning
self.filesize_warning = QtWidgets.QLabel()
self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.filesize_warning.hide()
# downloads
self.downloads = Downloads()
self.downloads_container = QtWidgets.QScrollArea()
self.downloads_container.setWidget(self.downloads)
self.downloads_container.setWidgetResizable(True)
self.downloads_container.setMaximumHeight(200)
self.vbar = self.downloads_container.verticalScrollBar()
self.downloads_container.hide() # downloads start out hidden
self.new_download = False
# options
self.options = Options(web, self.app)
# status bar
self.status_bar = QtWidgets.QStatusBar()
self.status_bar.setSizeGripEnabled(False)
version_label = QtWidgets.QLabel('v{0:s}'.format(helpers.get_version()))
version_label.setStyleSheet('color: #666666; padding: 0 10px;')
self.status_bar.addPermanentWidget(version_label)
self.setStatusBar(self.status_bar)
# status bar, zip progress bar
self._zip_progress_bar = None
# main layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addLayout(self.file_selection)
self.layout.addLayout(self.server_status)
self.layout.addWidget(self.filesize_warning)
self.layout.addWidget(self.downloads_container)
self.layout.addLayout(self.options)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(self.layout)
self.setCentralWidget(central_widget)
self.show()
# check for requests frequently
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.check_for_requests)
self.timer.start(500)
def start_server(self):
"""
Start the onionshare server. This uses multiple threads to start the Tor onion
server and the web app.
"""
# Reset web counters
web.download_count = 0
web.error404_count = 0
web.set_gui_mode()
# pick an available local port for the http service to listen on
self.app.choose_port()
# disable the stealth option
self.options.set_advanced_enabled(False)
# start onionshare http service in new thread
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.app.transparent_torification))
t.daemon = True
t.start()
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
time.sleep(0.2)
# start the onion service in a new thread
def start_onion_service(self):
self.status_bar.showMessage(strings._('gui_starting_server1', True))
try:
self.app.start_onion_service()
self.starting_server_step2.emit()
except (onionshare.onion.TorTooOld, onionshare.onion.TorErrorInvalidSetting, onionshare.onion.TorErrorAutomatic, onionshare.onion.TorErrorSocketPort, onionshare.onion.TorErrorSocketFile, onionshare.onion.TorErrorMissingPassword, onionshare.onion.TorErrorUnreadableCookieFile, onionshare.onion.TorErrorAuthError) as e:
self.starting_server_error.emit(e.args[0])
return
t = threading.Thread(target=start_onion_service, kwargs={'self': self})
t.daemon = True
t.start()
def start_server_step2(self):
"""
Step 2 in starting the onionshare server. Zipping up files.
"""
# add progress bar to the status bar, indicating the crunching of files.
self._zip_progress_bar = ZipProgressBar(0)
self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(
self.file_selection.file_list.filenames)
self.status_bar.clearMessage()
self.status_bar.insertWidget(0, self._zip_progress_bar)
# prepare the files for sending in a new thread
def finish_starting_server(self):
# prepare files to share
def _set_processed_size(x):
if self._zip_progress_bar != None:
self._zip_progress_bar.update_processed_size_signal.emit(x)
web.set_file_info(self.file_selection.file_list.filenames, processed_size_callback=_set_processed_size)
self.app.cleanup_filenames.append(web.zip_filename)
self.starting_server_step3.emit()
# wait for hs
if not self.app.local_only and not self.app.onion.supports_ephemeral:
self.status_bar.showMessage(strings._('gui_starting_server3', True))
self.app.onion.wait_for_hs(self.app.onion_host)
# done
self.start_server_finished.emit()
#self.status_bar.showMessage(strings._('gui_starting_server2', True))
t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True
t.start()
def start_server_step3(self):
"""
Step 3 in starting the onionshare server. This displays the large filesize
warning, if applicable.
"""
# Remove zip progress bar
if self._zip_progress_bar is not None:
self.status_bar.removeWidget(self._zip_progress_bar)
self._zip_progress_bar = None
# warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show()
def start_server_error(self, error):
"""
If there's an error when trying to start the onion service
"""
Alert(error, QtWidgets.QMessageBox.Warning)
self.server_status.stop_server()
self.status_bar.clearMessage()
def stop_server(self):
"""
Stop the onionshare server.
"""
if self.server_status.status != self.server_status.STATUS_STOPPED:
web.stop(self.app.port)
self.app.cleanup()
self.filesize_warning.hide()
self.options.set_advanced_enabled(True)
self.stop_server_finished.emit()
@staticmethod
def _compute_total_size(filenames):
total_size = 0
for filename in filenames:
if os.path.isfile(filename):
total_size += os.path.getsize(filename)
if os.path.isdir(filename):
total_size += helpers.dir_size(filename)
return total_size
def check_for_requests(self):
"""
Check for messages communicated from the web app, and update the GUI accordingly.
"""
self.update()
# scroll to the bottom of the dl progress bar log pane
# if a new download has been added
if self.new_download:
self.vbar.setValue(self.vbar.maximum())
self.new_download = False
# only check for requests if the server is running
if self.server_status.status != self.server_status.STATUS_STARTED:
return
events = []
done = False
while not done:
try:
r = web.q.get(False)
events.append(r)
except web.queue.Empty:
done = True
for event in events:
if event["type"] == web.REQUEST_LOAD:
self.status_bar.showMessage(strings._('download_page_loaded', True))
elif event["type"] == web.REQUEST_DOWNLOAD:
self.downloads_container.show() # show the downloads layout
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
self.new_download = True
elif event["type"] == web.REQUEST_RATE_LIMIT:
self.stop_server()
Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
elif event["type"] == web.REQUEST_PROGRESS:
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
# is the download complete?
if event["data"]["bytes"] == web.zip_filesize:
# close on finish?
if not web.get_stay_open():
self.server_status.stop_server()
elif event["type"] == web.REQUEST_CANCELED:
self.downloads.cancel_download(event["data"]["id"])
elif event["path"] != '/favicon.ico':
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"]))
def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
self.status_bar.showMessage(strings._('gui_copied_url', True), 2000)
def copy_hidservauth(self):
"""
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
"""
self.status_bar.showMessage(strings._('gui_copied_hidservauth', True), 2000)
def clear_message(self):
"""
Clear messages from the status bar.
"""
self.status_bar.clearMessage()
def closeEvent(self, e):
if self.server_status.status != self.server_status.STATUS_STOPPED:
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("OnionShare")
dialog.setText(strings._('gui_quit_warning', True))
quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole)
dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), 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()
class ZipProgressBar(QtWidgets.QProgressBar):
update_processed_size_signal = QtCore.pyqtSignal(int)
def __init__(self, total_files_size):
super(ZipProgressBar, self).__init__()
self.setMaximumHeight(15)
self.setMinimumWidth(200)
self.setValue(0)
self.setFormat(strings._('zip_progress_bar_format'))
self.setStyleSheet(
"QProgressBar::chunk { background-color: #05B8CC; } "
)
self._total_files_size = total_files_size
self._processed_size = 0
self.update_processed_size_signal.connect(self.update_processed_size)
@property
def total_files_size(self):
return self._total_files_size
@total_files_size.setter
def total_files_size(self, val):
self._total_files_size = val
@property
def processed_size(self):
return self._processed_size
@processed_size.setter
def processed_size(self, val):
self.update_processed_size(val)
def update_processed_size(self, val):
self._processed_size = val
if self.processed_size < self.total_files_size:
self.setValue(int((self.processed_size * 100) / self.total_files_size))
elif self.total_files_size != 0:
self.setValue(100)
else:
self.setValue(0)
def main():
"""
The main() function implements all of the logic that the GUI version of onionshare uses.
"""
strings.load_strings(helpers)
print(strings._('version_string').format(helpers.get_version()))
# start the Qt app
global qtapp
qtapp = Application()
# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('--transparent', action='store_true', dest='transparent_torification', help=strings._("help_transparent_torification"))
parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename'))
args = parser.parse_args()
filenames = args.filenames
if filenames:
for i in range(len(filenames)):
filenames[i] = os.path.abspath(filenames[i])
local_only = bool(args.local_only)
stay_open = bool(args.stay_open)
debug = bool(args.debug)
transparent_torification = bool(args.transparent_torification)
# create the onionshare icon
global window_icon
window_icon = QtGui.QIcon(helpers.get_resource_path('images/logo.png'))
# validation
if filenames:
valid = True
for filename in filenames:
if not os.path.exists(filename):
Alert(strings._("not_a_file", True).format(filename))
valid = False
if not valid:
sys.exit()
# start the onionshare app
web.set_stay_open(stay_open)
web.set_transparent_torification(transparent_torification)
app = onionshare.OnionShare(debug, local_only, stay_open, transparent_torification)
# clean up when app quits
def shutdown():
app.cleanup()
qtapp.aboutToQuit.connect(shutdown)
# launch the gui
gui = OnionShareGui(qtapp, app)
gui.send_files(filenames)
# all done
sys.exit(qtapp.exec_())
if __name__ == '__main__':
main()

34
onionshare_gui/alert.py Normal file
View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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 helpers
class Alert(QtWidgets.QMessageBox):
"""
An alert box dialog.
"""
def __init__(self, message, icon=QtWidgets.QMessageBox.NoIcon):
super(Alert, self).__init__(None)
self.setWindowTitle("OnionShare")
self.setWindowIcon(QtGui.QIcon(helpers.get_resource_path('images/logo.png')))
self.setText(message)
self.setIcon(icon)
self.exec_()

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -56,7 +56,7 @@ class Download(object):
elapsed = time.time() - self.started
if elapsed < 10:
# Wait a couple of seconds for the download rate to stabilize.
# This prevents an "Windows copy dialog"-esque experience at
# This prevents a "Windows copy dialog"-esque experience at
# the beginning of the download.
pb_fmt = strings._('gui_download_progress_starting').format(
helpers.human_readable_filesize(downloaded_bytes))
@ -77,33 +77,27 @@ class Download(object):
self.started)
class Downloads(QtWidgets.QVBoxLayout):
class Downloads(QtWidgets.QWidget):
"""
The downloads chunk of the GUI. This lists all of the active download
progress bars.
"""
def __init__(self):
super(Downloads, self).__init__()
self.downloads = {}
# downloads label
self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True))
self.downloads_label.hide()
# add the widgets
self.addWidget(self.downloads_label)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
def add_download(self, download_id, total_bytes):
"""
Add a new download progress bar.
"""
self.downloads_label.show()
self.parent().show()
# add it to the list
download = Download(download_id, total_bytes)
self.downloads[download_id] = download
self.addWidget(download.progress_bar)
self.layout.insertWidget(-1, download.progress_bar)
def update_download(self, download_id, downloaded_bytes):
"""

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

49
onionshare_gui/menu.py Normal file
View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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
from .settings_dialog import SettingsDialog
class Menu(QtWidgets.QMenuBar):
"""
OnionShare's menu bar.
"""
def __init__(self, parent=None):
super(Menu, self).__init__(parent)
file_menu = self.addMenu(strings._('gui_menu_file_menu', True))
settings_action = file_menu.addAction(strings._('gui_menu_settings_action', True))
settings_action.triggered.connect(self.settings)
quit_action = file_menu.addAction(strings._('gui_menu_quit_action', True))
quit_action.triggered.connect(self.quit)
def settings(self):
"""
Settings action triggered.
"""
SettingsDialog()
def quit(self):
"""
Quit action triggered.
"""
self.parent().qtapp.quit()

View File

@ -1,371 +0,0 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 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 __future__ import division
import os, sys, subprocess, inspect, platform, argparse, threading, time, math, inspect, platform
from PyQt5 import QtCore, QtWidgets, QtGui
import onionshare
from onionshare import strings, helpers, web
from .file_selection import FileSelection
from .server_status import ServerStatus
from .downloads import Downloads
from .options import Options
class Application(QtWidgets.QApplication):
"""
This is Qt's QApplication class. It has been overridden to support threads
and the quick keyboard shortcut.
"""
def __init__(self):
platform = helpers.get_platform()
if platform == 'Linux':
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtWidgets.QApplication.__init__(self, sys.argv)
self.installEventFilter(self)
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.KeyPress and
event.key() == QtCore.Qt.Key_Q and
event.modifiers() == QtCore.Qt.ControlModifier):
self.quit()
return False
class OnionShareGui(QtWidgets.QMainWindow):
"""
OnionShareGui is the main window for the GUI that contains all of the
GUI elements.
"""
start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal()
starting_server_step2 = QtCore.pyqtSignal()
starting_server_step3 = QtCore.pyqtSignal()
starting_server_error = QtCore.pyqtSignal(str)
def __init__(self, qtapp, app):
super(OnionShareGui, self).__init__()
self.qtapp = qtapp
self.app = app
self.setWindowTitle('OnionShare')
self.setWindowIcon(window_icon)
def send_files(self, filenames=None):
"""
Build the GUI in send files mode.
Note that this is the only mode currently implemented.
"""
# file selection
self.file_selection = FileSelection()
if filenames:
for filename in filenames:
self.file_selection.file_list.add_file(filename)
# server status
self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection)
self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_started.connect(self.start_server)
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
self.server_status.server_stopped.connect(self.stop_server)
self.start_server_finished.connect(self.clear_message)
self.start_server_finished.connect(self.server_status.start_server_finished)
self.stop_server_finished.connect(self.server_status.stop_server_finished)
self.file_selection.file_list.files_updated.connect(self.server_status.update)
self.server_status.url_copied.connect(self.copy_url)
self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3)
self.starting_server_error.connect(self.start_server_error)
# filesize warning
self.filesize_warning = QtWidgets.QLabel()
self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.filesize_warning.hide()
# downloads
self.downloads = Downloads()
# options
self.options = Options(web, self.app)
# status bar
self.status_bar = QtWidgets.QStatusBar()
self.status_bar.setSizeGripEnabled(False)
version_label = QtWidgets.QLabel('v{0:s}'.format(helpers.get_version()))
version_label.setStyleSheet('color: #666666; padding: 0 10px;')
self.status_bar.addPermanentWidget(version_label)
self.setStatusBar(self.status_bar)
# main layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addLayout(self.file_selection)
self.layout.addLayout(self.server_status)
self.layout.addWidget(self.filesize_warning)
self.layout.addLayout(self.downloads)
self.layout.addLayout(self.options)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(self.layout)
self.setCentralWidget(central_widget)
self.show()
# check for requests frequently
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.check_for_requests)
self.timer.start(500)
def start_server(self):
"""
Start the onionshare server. This uses multiple threads to start the Tor onion
server and the web app.
"""
# Reset web counters
web.download_count = 0
web.error404_count = 0
web.set_gui_mode()
# pick an available local port for the http service to listen on
self.app.choose_port()
# start onionshare http service in new thread
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.app.transparent_torification))
t.daemon = True
t.start()
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
time.sleep(0.2)
# start the onion service in a new thread
def start_onion_service(self):
self.status_bar.showMessage(strings._('gui_starting_server1', True))
try:
self.app.start_onion_service()
self.starting_server_step2.emit()
except onionshare.onion.NoTor as e:
self.starting_server_error.emit(e.args[0])
return
t = threading.Thread(target=start_onion_service, kwargs={'self': self})
t.daemon = True
t.start()
def start_server_step2(self):
"""
Step 2 in starting the onionshare server. Prepare files for serving.
"""
# prepare the files for sending in a new thread
def finish_starting_server(self):
# prepare files to share
web.set_file_info(self.file_selection.file_list.filenames)
self.app.cleanup_filenames.append(web.zip_filename)
self.starting_server_step3.emit()
# wait for hs
if not self.app.local_only and not self.app.onion.supports_ephemeral:
self.status_bar.showMessage(strings._('gui_starting_server3', True))
self.app.onion.wait_for_hs(self.app.onion_host)
# done
self.start_server_finished.emit()
self.status_bar.showMessage(strings._('gui_starting_server2', True))
t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True
t.start()
def start_server_step3(self):
"""
Step 3 in starting the onionshare server. This displays the large filesize
warning, if applicable.
"""
# warn about sending large files over Tor
if web.zip_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show()
def start_server_error(self, error):
"""
If there's an error when trying to start the onion service
"""
alert(error, QtWidgets.QMessageBox.Warning)
self.server_status.stop_server()
self.status_bar.clearMessage()
def stop_server(self):
"""
Stop the onionshare server.
"""
if self.server_status.status != self.server_status.STATUS_STOPPED:
web.stop(self.app.port)
self.app.cleanup()
self.filesize_warning.hide()
self.stop_server_finished.emit()
def check_for_requests(self):
"""
Check for messages communicated from the web app, and update the GUI accordingly.
"""
self.update()
# only check for requests if the server is running
if self.server_status.status != self.server_status.STATUS_STARTED:
return
events = []
done = False
while not done:
try:
r = web.q.get(False)
events.append(r)
except web.queue.Empty:
done = True
for event in events:
if event["type"] == web.REQUEST_LOAD:
self.status_bar.showMessage(strings._('download_page_loaded', True))
elif event["type"] == web.REQUEST_DOWNLOAD:
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
elif event["type"] == web.REQUEST_RATE_LIMIT:
self.stop_server()
alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
elif event["type"] == web.REQUEST_PROGRESS:
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
# is the download complete?
if event["data"]["bytes"] == web.zip_filesize:
# close on finish?
if not web.get_stay_open():
self.server_status.stop_server()
elif event["type"] == web.REQUEST_CANCELED:
self.downloads.cancel_download(event["data"]["id"])
elif event["path"] != '/favicon.ico':
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"]))
def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
self.status_bar.showMessage(strings._('gui_copied_url', True), 2000)
def clear_message(self):
"""
Clear messages from the status bar.
"""
self.status_bar.clearMessage()
def closeEvent(self, e):
if self.server_status.status != self.server_status.STATUS_STOPPED:
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("OnionShare")
dialog.setText(strings._('gui_quit_warning', True))
quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole)
dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), 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()
def alert(msg, icon=QtWidgets.QMessageBox.NoIcon):
"""
Pop up a message in a dialog window.
"""
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("OnionShare")
dialog.setWindowIcon(window_icon)
dialog.setText(msg)
dialog.setIcon(icon)
dialog.exec_()
def main():
"""
The main() function implements all of the logic that the GUI version of onionshare uses.
"""
strings.load_strings(helpers)
print(strings._('version_string').format(helpers.get_version()))
# start the Qt app
global qtapp
qtapp = Application()
# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
parser.add_argument('--transparent', action='store_true', dest='transparent_torification', help=strings._("help_transparent_torification"))
parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename'))
args = parser.parse_args()
filenames = args.filenames
if filenames:
for i in range(len(filenames)):
filenames[i] = os.path.abspath(filenames[i])
local_only = bool(args.local_only)
stay_open = bool(args.stay_open)
debug = bool(args.debug)
transparent_torification = bool(args.transparent_torification)
# create the onionshare icon
global window_icon
window_icon = QtGui.QIcon(helpers.get_resource_path('images/logo.png'))
# validation
if filenames:
valid = True
for filename in filenames:
if not os.path.exists(filename):
alert(strings._("not_a_file", True).format(filename))
valid = False
if not valid:
sys.exit()
# start the onionshare app
web.set_stay_open(stay_open)
web.set_transparent_torification(transparent_torification)
app = onionshare.OnionShare(debug, local_only, stay_open, transparent_torification)
# clean up when app quits
def shutdown():
app.cleanup()
qtapp.aboutToQuit.connect(shutdown)
# launch the gui
gui = OnionShareGui(qtapp, app)
gui.send_files(filenames)
# all done
sys.exit(qtapp.exec_())
if __name__ == '__main__':
main()

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -21,7 +21,7 @@ from PyQt5 import QtCore, QtWidgets
from onionshare import strings, helpers
class Options(QtWidgets.QHBoxLayout):
class Options(QtWidgets.QVBoxLayout):
"""
The extra onionshare options in the GUI.
"""
@ -40,16 +40,59 @@ class Options(QtWidgets.QHBoxLayout):
self.close_automatically.setText(strings._("close_on_finish", True))
self.close_automatically.stateChanged.connect(self.stay_open_changed)
# stealth
self.stealth = QtWidgets.QCheckBox()
self.stealth.setCheckState(QtCore.Qt.Unchecked)
self.stealth.setText(strings._("gui_create_stealth", True))
self.stealth.stateChanged.connect(self.stealth_changed)
# advanced options group
self.advanced_group = QtWidgets.QGroupBox(strings._("gui_advanced_options", True))
self.advanced_group.setCheckable(True)
self.advanced_group.setChecked(False)
self.advanced_group.setFlat(True)
self.advanced_group.toggled.connect(self.advanced_options_changed)
advanced_group_layout = QtWidgets.QVBoxLayout()
advanced_group_layout.addWidget(self.stealth)
self.advanced_group.setLayout(advanced_group_layout)
# add the widgets
self.addWidget(self.close_automatically)
self.addWidget(self.advanced_group)
def stay_open_changed(self, state):
"""
When the 'close automatically' checkbox is toggled, let the web app know.
"""
if state > 0:
self.web.set_stay_open(False)
self.app.stay_open = False
else:
if state == 0:
self.web.set_stay_open(True)
self.app.stay_open = True
else:
self.web.set_stay_open(False)
self.app.stay_open = False
def advanced_options_changed(self, checked):
"""
When the 'advanced options' checkbox is unchecked, uncheck all advanced
options, and let the onionshare app know.
"""
if not checked:
self.stealth.setChecked(False)
self.app.set_stealth(False)
def stealth_changed(self, state):
"""
When the 'stealth' checkbox is toggled, let the onionshare app know.
"""
if state == 2:
self.app.set_stealth(True)
else:
self.app.set_stealth(False)
def set_advanced_enabled(self, enabled):
"""
You cannot toggle stealth after an onion service has started. This method
disables and re-enabled the advanced options group, including the stealth
checkbox.
"""
self.advanced_group.setEnabled(enabled)

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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
@ -29,6 +29,7 @@ class ServerStatus(QtWidgets.QVBoxLayout):
server_started = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal()
url_copied = QtCore.pyqtSignal()
hidservauth_copied = QtCore.pyqtSignal()
STATUS_STOPPED = 0
STATUS_WORKING = 1
@ -63,9 +64,12 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.url_label.setAlignment(QtCore.Qt.AlignCenter)
self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True))
self.copy_url_button.clicked.connect(self.copy_url)
self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
url_layout = QtWidgets.QHBoxLayout()
url_layout.addWidget(self.url_label)
url_layout.addWidget(self.copy_url_button)
url_layout.addWidget(self.copy_hidservauth_button)
# add the widgets
self.addLayout(server_layout)
@ -91,12 +95,18 @@ class ServerStatus(QtWidgets.QVBoxLayout):
self.url_label.show()
self.copy_url_button.show()
if self.app.stealth:
self.copy_hidservauth_button.show()
else:
self.copy_hidservauth_button.hide()
# resize parent widget
p = self.parentWidget()
p.resize(p.sizeHint())
else:
self.url_label.hide()
self.copy_url_button.hide()
self.copy_hidservauth_button.hide()
# button
if self.file_selection.get_num_files() == 0:
@ -163,3 +173,12 @@ class ServerStatus(QtWidgets.QVBoxLayout):
clipboard.setText(url)
self.url_copied.emit()
def copy_hidservauth(self):
"""
Copy the HidServAuth line to the clipboard.
"""
clipboard = self.qtapp.clipboard()
clipboard.setText(self.app.auth_string)
self.hidservauth_copied.emit()

View File

@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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.settings import Settings
from onionshare.onion import Onion, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError
from .alert import Alert
class SettingsDialog(QtWidgets.QDialog):
"""
Settings dialog.
"""
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setModal(True)
self.setWindowTitle(strings._('gui_settings_window_title', True))
# Connection type: either automatic, control port, or socket file
# Automatic
self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True))
self.connection_type_automatic_radio.toggled.connect(self.connection_type_automatic_toggled)
# Control port
self.connection_type_control_port_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_control_port_option', True))
self.connection_type_control_port_radio.toggled.connect(self.connection_type_control_port_toggled)
connection_type_control_port_extras_label = QtWidgets.QLabel(strings._('gui_settings_control_port_label', True))
self.connection_type_control_port_extras_address = QtWidgets.QLineEdit()
self.connection_type_control_port_extras_port = QtWidgets.QLineEdit()
connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout()
connection_type_control_port_extras_layout.addWidget(connection_type_control_port_extras_label)
connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_address)
connection_type_control_port_extras_layout.addWidget(self.connection_type_control_port_extras_port)
self.connection_type_control_port_extras = QtWidgets.QWidget()
self.connection_type_control_port_extras.setLayout(connection_type_control_port_extras_layout)
self.connection_type_control_port_extras.hide()
# Socket file
self.connection_type_socket_file_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_socket_file_option', True))
self.connection_type_socket_file_radio.toggled.connect(self.connection_type_socket_file_toggled)
connection_type_socket_file_extras_label = QtWidgets.QLabel(strings._('gui_settings_socket_file_label', True))
self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit()
connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout()
connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label)
connection_type_socket_file_extras_layout.addWidget(self.connection_type_socket_file_extras_path)
self.connection_type_socket_file_extras = QtWidgets.QWidget()
self.connection_type_socket_file_extras.setLayout(connection_type_socket_file_extras_layout)
self.connection_type_socket_file_extras.hide()
# Connection type layout
connection_type_group_layout = QtWidgets.QVBoxLayout()
connection_type_group_layout.addWidget(self.connection_type_automatic_radio)
connection_type_group_layout.addWidget(self.connection_type_control_port_radio)
connection_type_group_layout.addWidget(self.connection_type_socket_file_radio)
connection_type_group_layout.addWidget(self.connection_type_control_port_extras)
connection_type_group_layout.addWidget(self.connection_type_socket_file_extras)
connection_type_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True))
connection_type_group.setLayout(connection_type_group_layout)
# Authentication options
# No authentication
self.authenticate_no_auth_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_no_auth_option', True))
self.authenticate_no_auth_radio.toggled.connect(self.authenticate_no_auth_toggled)
# Password
self.authenticate_password_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_password_option', True))
self.authenticate_password_radio.toggled.connect(self.authenticate_password_toggled)
authenticate_password_extras_label = QtWidgets.QLabel(strings._('gui_settings_password_label', True))
self.authenticate_password_extras_password = QtWidgets.QLineEdit('')
authenticate_password_extras_layout = QtWidgets.QHBoxLayout()
authenticate_password_extras_layout.addWidget(authenticate_password_extras_label)
authenticate_password_extras_layout.addWidget(self.authenticate_password_extras_password)
self.authenticate_password_extras = QtWidgets.QWidget()
self.authenticate_password_extras.setLayout(authenticate_password_extras_layout)
self.authenticate_password_extras.hide()
# Authentication options layout
authenticate_group_layout = QtWidgets.QVBoxLayout()
authenticate_group_layout.addWidget(self.authenticate_no_auth_radio)
authenticate_group_layout.addWidget(self.authenticate_password_radio)
authenticate_group_layout.addWidget(self.authenticate_password_extras)
self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True))
self.authenticate_group.setLayout(authenticate_group_layout)
# Buttons
test_button = QtWidgets.QPushButton(strings._('gui_settings_button_test', True))
test_button.clicked.connect(self.test_clicked)
save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True))
save_button.clicked.connect(self.save_clicked)
cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(test_button)
buttons_layout.addWidget(save_button)
buttons_layout.addWidget(cancel_button)
# Layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(connection_type_group)
layout.addWidget(self.authenticate_group)
layout.addStretch()
layout.addLayout(buttons_layout)
self.setLayout(layout)
# Load settings, and fill them in
settings = Settings()
settings.load()
connection_type = settings.get('connection_type')
if connection_type == 'automatic':
self.connection_type_automatic_radio.setChecked(True)
elif connection_type == 'control_port':
self.connection_type_control_port_radio.setChecked(True)
elif connection_type == 'socket_file':
self.connection_type_socket_file_radio.setChecked(True)
self.connection_type_control_port_extras_address.setText(settings.get('control_port_address'))
self.connection_type_control_port_extras_port.setText(str(settings.get('control_port_port')))
self.connection_type_socket_file_extras_path.setText(settings.get('socket_file_path'))
auth_type = settings.get('auth_type')
if auth_type == 'no_auth':
self.authenticate_no_auth_radio.setChecked(True)
elif auth_type == 'password':
self.authenticate_password_radio.setChecked(True)
self.authenticate_password_extras_password.setText(settings.get('auth_password'))
# Show the dialog
self.exec_()
def connection_type_automatic_toggled(self, checked):
"""
Connection type automatic was toggled. If checked, disable all other
fields. If unchecked, enable all other fields.
"""
if checked:
self.authenticate_group.setEnabled(False)
else:
self.authenticate_group.setEnabled(True)
def connection_type_control_port_toggled(self, checked):
"""
Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields.
"""
if checked:
self.connection_type_control_port_extras.show()
else:
self.connection_type_control_port_extras.hide()
def connection_type_socket_file_toggled(self, checked):
"""
Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields.
"""
if checked:
self.connection_type_socket_file_extras.show()
else:
self.connection_type_socket_file_extras.hide()
def authenticate_no_auth_toggled(self, checked):
"""
Authentication option no authentication was toggled.
"""
pass
def authenticate_password_toggled(self, checked):
"""
Authentication option password was toggled. If checked, show extra fields
for password auth. If unchecked, hide those extra fields.
"""
if checked:
self.authenticate_password_extras.show()
else:
self.authenticate_password_extras.hide()
def test_clicked(self):
"""
Test Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor.
"""
settings = self.settings_from_fields()
try:
onion = Onion(settings=settings)
# If an exception hasn't been raised yet, the Tor settings work
Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError) as e:
Alert(e.args[0], QtWidgets.QMessageBox.Warning)
def save_clicked(self):
"""
Save button clicked. Save current settings to disk.
"""
settings = self.settings_from_fields()
settings.save()
self.close()
def cancel_clicked(self):
"""
Cancel button clicked.
"""
self.close()
def settings_from_fields(self):
"""
Return a Settings object that's full of values from the settings dialog.
"""
settings = Settings()
if self.connection_type_automatic_radio.isChecked():
settings.set('connection_type', 'automatic')
if self.connection_type_control_port_radio.isChecked():
settings.set('connection_type', 'control_port')
if self.connection_type_socket_file_radio.isChecked():
settings.set('connection_type', 'socket_file')
settings.set('control_port_address', self.connection_type_control_port_extras_address.text())
settings.set('control_port_port', int(self.connection_type_control_port_extras_port.text()))
settings.set('socket_file_path', self.connection_type_socket_file_extras_path.text())
if self.authenticate_no_auth_radio.isChecked():
settings.set('auth_type', 'no_auth')
if self.authenticate_password_radio.isChecked():
settings.set('auth_type', 'password')
settings.set('auth_password', self.authenticate_password_extras_password.text())
return settings

View File

@ -1,4 +1,4 @@
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@ -41,5 +41,7 @@
"gui_please_wait": "Prosím čekejte...",
"error_hs_dir_cannot_create": "Nejde vytvořit složka {0:s} pro onion service",
"error_hs_dir_not_writable": "Nejde zapisovat do složky {0:s} pro onion service",
"using_ephemeral": "Staring ephemeral Tor onion service and awaiting publication"
"using_ephemeral": "Staring ephemeral Tor onion service and awaiting publication",
"zip_progress_bar_format": "Crunching files: %p%"
}

View File

@ -1,13 +1,12 @@
{
"connecting_ctrlport": "Connecting to Tor control port to set up onion service on port {0:d}.",
"cant_connect_ctrlport": "Can't connect to Tor control port on port {0:s}. OnionShare requires Tor Browser to be running in the background to work. If you don't have it you can get it from https://www.torproject.org/.",
"cant_connect_socksport": "Can't connect to Tor SOCKS5 server on port {0:s}. OnionShare requires Tor Browser to be running in the background to work. If you don't have it you can get it from https://www.torproject.org/.",
"config_onion_service": "Configuring onion service on port {0:d}.",
"preparing_files": "Preparing files to share.",
"wait_for_hs": "Waiting for HS to be ready:",
"wait_for_hs_trying": "Trying...",
"wait_for_hs_nope": "Not ready yet.",
"wait_for_hs_yup": "Ready!",
"give_this_url": "Give this URL to the person you're sending the file to:",
"give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:",
"ctrlc_to_stop": "Press Ctrl-C to stop server",
"not_a_file": "{0:s} is not a file.",
"download_page_loaded": "Download page loaded",
@ -17,10 +16,10 @@
"large_filesize": "Warning: Sending large files could take hours",
"error_tails_invalid_port": "Invalid value, port must be an integer",
"error_tails_unknown_root": "Unknown error with Tails root process",
"help_tails_port": "Tails only: port for opening firewall, starting onion service",
"help_local_only": "Do not attempt to use tor: for development only",
"help_stay_open": "Keep onion service running after download has finished",
"help_transparent_torification": "My system is transparently torified",
"help_stealth": "Create stealth onion service (advanced)",
"help_debug": "Log errors to disk",
"help_filename": "List of files or folders to share",
"gui_drag_and_drop": "Drag and drop\nfiles here",
@ -32,9 +31,11 @@
"gui_start_server": "Start Sharing",
"gui_stop_server": "Stop Sharing",
"gui_copy_url": "Copy URL",
"gui_copy_hidservauth": "Copy HidServAuth",
"gui_downloads": "Downloads:",
"gui_canceled": "Canceled",
"gui_copied_url": "Copied URL to clipboard",
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard",
"gui_starting_server1": "Starting Tor onion service...",
"gui_starting_server2": "Crunching files...",
"gui_starting_server3": "Waiting for Tor onion service...",
@ -49,5 +50,37 @@
"gui_quit_warning": "Are you sure you want to quit?\nThe URL you are sharing won't exist anymore.",
"gui_quit_warning_quit": "Quit",
"gui_quit_warning_dont_quit": "Don't Quit",
"error_rate_limit": "An attacker might be trying to guess your URL. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new URL."
"error_rate_limit": "An attacker might be trying to guess your URL. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new URL.",
"zip_progress_bar_format": "Crunching files: %p%",
"error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.",
"gui_advanced_options": "Advanced options",
"gui_create_stealth": "Create stealth onion service",
"gui_menu_file_menu": "&File",
"gui_menu_settings_action": "&Settings",
"gui_menu_quit_action": "&Quit",
"gui_settings_window_title": "Settings",
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
"gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser",
"gui_settings_connection_type_control_port_option": "Connect using control port",
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
"gui_settings_control_port_label": "Control port",
"gui_settings_socket_file_label": "Socket file",
"gui_settings_authenticate_label": "Tor authentication options",
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
"gui_settings_authenticate_password_option": "Password",
"gui_settings_authenticate_cookie_option": "Cookie",
"gui_settings_password_label": "Password",
"gui_settings_cookie_label": "Cookie path",
"gui_settings_button_test": "Test Settings",
"gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel",
"settings_saved": "Settings saved to {}",
"settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.",
"settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.",
"settings_error_socket_port": "Can't connect to Tor controller on {}:{}.",
"settings_error_socket_file": "Can't connect to Tor controller using socket file {}.",
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
"settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.",
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}"
}

View File

@ -41,5 +41,6 @@
"gui_please_wait": "Bonvolu atendi...",
"error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}",
"error_hs_dir_not_writable": "Ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}",
"using_ephemeral": "Staring ephemeral Tor onion service and awaiting publication"
"using_ephemeral": "Staring ephemeral Tor hidden service and awaiting publication",
"zip_progress_bar_format": "Crunching files: %p%"
}

View File

@ -41,5 +41,6 @@
"gui_please_wait": "Odota...",
"error_hs_dir_cannot_create": "Piilopalvelulle ei pystytty luomaan hakemistoa {0:s}",
"error_hs_dir_not_writable": "Piilopalvelun hakemistoon {0:s} ei voi kirjoittaa",
"using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua"
"using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua",
"zip_progress_bar_format": "Tiivistän tiedostoja: %p%"
}

View File

@ -41,5 +41,6 @@
"gui_please_wait": "Attendere prego...",
"error_hs_dir_cannot_create": "Impossibile create la cartella per il servizio nascosto {0:s}",
"error_hs_dir_not_writable": "La cartella per il servizio nascosto {0:s} non ha i permessi di scrittura",
"using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione"
}
"using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione",
"zip_progress_bar_format": "Elaborazione files: %p%"
}

View File

@ -41,5 +41,6 @@
"gui_please_wait": "Moment geduld...",
"error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken",
"error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar",
"using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie"
"using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie",
"zip_progress_bar_format": "Bestanden verwerken: %p%"
}

View File

@ -41,5 +41,6 @@
"gui_please_wait": "Lütfen bekleyin...",
"error_hs_dir_cannot_create": "Gizli hizmet klasörü {0:s} oluşturulamıyor",
"error_hs_dir_not_writable": "Gizle hizmet klasörü {0:s} yazılabilir değil",
"using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor"
"using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor",
"zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%"
}

View File

@ -3,7 +3,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -2,7 +2,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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

View File

@ -1,7 +1,7 @@
"""
OnionShare | https://onionshare.org/
Copyright (C) 2016 Micah Lee <micah@micahflee.com>
Copyright (C) 2017 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