summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2018-12-19 13:39:41 -0800
committerGitHub <noreply@github.com>2018-12-19 13:39:41 -0800
commit80becc73fc08d8f11da08623047ac54fb576c305 (patch)
treeffe6f7e7b7fb4e2a8d32063d722c584f186c4dfb
parent510da50aab2ac8132967e2d2526ea4864da680cf (diff)
parent391619e6b55345a07421b784c486af53c9726884 (diff)
downloadonionshare-80becc73fc08d8f11da08623047ac54fb576c305.tar.gz
onionshare-80becc73fc08d8f11da08623047ac54fb576c305.zip
Merge pull request #823 from micahflee/406_osx_sandbox
Enable macOS sandbox
-rw-r--r--CHANGELOG.md10
-rwxr-xr-xinstall/build_osx.sh5
-rw-r--r--install/macos_sandbox/child.plist10
-rw-r--r--install/macos_sandbox/parent.plist39
-rw-r--r--install/onionshare.nsi8
-rw-r--r--install/pyinstaller.spec1
-rw-r--r--onionshare/common.py17
-rw-r--r--onionshare/onion.py42
-rw-r--r--onionshare/settings.py46
-rw-r--r--onionshare/web/web.py14
-rw-r--r--onionshare_gui/mode/share_mode/__init__.py2
-rw-r--r--onionshare_gui/mode/share_mode/file_selection.py51
-rw-r--r--onionshare_gui/widgets.py4
-rw-r--r--share/locale/en.json2
-rw-r--r--share/torrc_template1
-rw-r--r--share/torrc_template-windows9
-rw-r--r--share/version.txt2
-rw-r--r--tests/test_onionshare_settings.py2
18 files changed, 185 insertions, 80 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 136dd2b1..e437b939 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# OnionShare Changelog
+## 2.0
+
+* New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files
+* New feature: macOS sandbox is enabled
+* New feature: Support for next generation onion services (TODO waiting on Tor release)
+* New feature: If you're sharing a single file, don't zip it up
+* New feature: Allow selecting your language from a dropdown
+* New translations: (TODO fill in for final release)
+* Several bugfixes
+
## 1.3.1
* Updated Tor to 0.2.3.10
diff --git a/install/build_osx.sh b/install/build_osx.sh
index f6b27d9b..010e3edb 100755
--- a/install/build_osx.sh
+++ b/install/build_osx.sh
@@ -23,9 +23,12 @@ if [ "$1" = "--release" ]; then
PKG_PATH="$ROOT/dist/OnionShare.pkg"
IDENTITY_NAME_APPLICATION="Developer ID Application: Micah Lee"
IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee"
+ ENTITLEMENTS_CHILD_PATH="$ROOT/install/macos_sandbox/child.plist"
+ ENTITLEMENTS_PARENT_PATH="$ROOT/install/macos_sandbox/parent.plist"
echo "Codesigning the app bundle"
- codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH"
+ codesign --deep -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_CHILD_PATH" "$APP_PATH"
+ codesign -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_PARENT_PATH" "$APP_PATH"
echo "Creating an installer"
productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH"
diff --git a/install/macos_sandbox/child.plist b/install/macos_sandbox/child.plist
new file mode 100644
index 00000000..06d88f66
--- /dev/null
+++ b/install/macos_sandbox/child.plist
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.inherit</key>
+ <true/>
+</dict>
+</plist>
diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist
new file mode 100644
index 00000000..3929abe9
--- /dev/null
+++ b/install/macos_sandbox/parent.plist
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <!-- Enable app sandbox -->
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+
+ <!-- Both OnionShare and Tor need network server and client -->
+ <key>com.apple.security.network.server</key>
+ <true/>
+ <key>com.apple.security.network.client</key>
+ <true/>
+
+ <!-- In share mode, users need to be able to select files, and in receive mode,
+ users need to be able to choose a folder to save files to -->
+ <key>com.apple.security.files.user-selected.read-write</key>
+ <true/>
+
+ <!-- Flask needs to read this mime.types file when starting an HTTP server -->
+ <key>com.apple.security.temporary-exception.files.absolute-path.read-only</key>
+ <array>
+ <string>/private/etc/apache2/mime.types</string>
+ </array>
+
+ <!-- For OnionShare to be able to connect to Tor Browser's tor control port,
+ it needs to read it's control_auth_cookie file -->
+ <key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
+ <array>
+ <string>/Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie</string>
+ </array>
+
+ <!-- In receive mode, OnionShare needs to be able to write to ~/OnionShare -->
+ <key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
+ <array>
+ <string>/OnionShare/</string>
+ </array>
+</dict>
+</plist>
diff --git a/install/onionshare.nsi b/install/onionshare.nsi
index f0b28535..3a4c6c2a 100644
--- a/install/onionshare.nsi
+++ b/install/onionshare.nsi
@@ -3,10 +3,10 @@
!define ABOUTURL "https:\\onionshare.org\"
# change these with each release
-!define INSTALLSIZE 66537
-!define VERSIONMAJOR 1
-!define VERSIONMINOR 3
-!define VERSIONSTRING "1.3.1"
+!define INSTALLSIZE 115186
+!define VERSIONMAJOR 2
+!define VERSIONMINOR 0
+!define VERSIONSTRING "2.0"
RequestExecutionLevel admin
diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec
index 6811997b..24664bf9 100644
--- a/install/pyinstaller.spec
+++ b/install/pyinstaller.spec
@@ -15,7 +15,6 @@ a = Analysis(
('../share/torrc_template', 'share'),
('../share/torrc_template-obfs4', 'share'),
('../share/torrc_template-meek_lite_azure', 'share'),
- ('../share/torrc_template-windows', 'share'),
('../share/images/*', 'share/images'),
('../share/locale/*', 'share/locale'),
('../share/static/*', 'share/static'),
diff --git a/onionshare/common.py b/onionshare/common.py
index 250972f9..c84046f0 100644
--- a/onionshare/common.py
+++ b/onionshare/common.py
@@ -123,6 +123,23 @@ class Common(object):
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
+ def build_data_dir(self):
+ """
+ Returns the path of the OnionShare data directory.
+ """
+ if self.platform == 'Windows':
+ try:
+ appdata = os.environ['APPDATA']
+ return '{}\\OnionShare'.format(appdata)
+ except:
+ # If for some reason we don't have the 'APPDATA' environment variable
+ # (like running tests in Linux while pretending to be in Windows)
+ return os.path.expanduser('~/.config/onionshare')
+ elif self.platform == 'Darwin':
+ return os.path.expanduser('~/Library/Application Support/OnionShare')
+ else:
+ return os.path.expanduser('~/.config/onionshare')
+
def build_slug(self):
"""
Returns a random string made from two words from the wordlist, such as "deter-trig".
diff --git a/onionshare/onion.py b/onionshare/onion.py
index 3d7b4514..c747984e 100644
--- a/onionshare/onion.py
+++ b/onionshare/onion.py
@@ -169,34 +169,35 @@ class Onion(object):
raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported'))
# Create a torrc for this session
- self.tor_data_directory = tempfile.TemporaryDirectory()
+ self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir())
+ self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name))
- if self.common.platform == 'Windows':
- # Windows needs to use network ports, doesn't support unix sockets
- torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read()
+ # Create the torrc
+ with open(self.common.get_resource_path('torrc_template')) as f:
+ torrc_template = f.read()
+ self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
+ try:
+ self.tor_socks_port = self.common.get_available_port(1000, 65535)
+ except:
+ raise OSError(strings._('no_available_port'))
+ self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
+
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
+ # Windows doesn't support unix sockets, so it must use a network port.
+ # macOS can't use unix sockets either because socket filenames are limited to
+ # 100 chars, and the macOS sandbox forces us to put the socket file in a place
+ # with a really long path.
+ torrc_template += 'ControlPort {{control_port}}\n'
try:
self.tor_control_port = self.common.get_available_port(1000, 65535)
except:
raise OSError(strings._('no_available_port'))
self.tor_control_socket = None
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = self.common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
else:
- # Linux, Mac and BSD can use unix sockets
- with open(self.common.get_resource_path('torrc_template')) as f:
- torrc_template = f.read()
+ # Linux and BSD can use unix sockets
+ torrc_template += 'ControlSocket {{control_socket}}\n'
self.tor_control_port = None
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
- self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
- try:
- self.tor_socks_port = self.common.get_available_port(1000, 65535)
- except:
- raise OSError(strings._('no_available_port'))
- self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name)
torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port))
@@ -205,6 +206,7 @@ class Onion(object):
torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path)
torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path)
torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port))
+
with open(self.tor_torrc, 'w') as f:
f.write(torrc_template)
@@ -243,7 +245,7 @@ class Onion(object):
# Connect to the controller
try:
- if self.common.platform == 'Windows':
+ if self.common.platform == 'Windows' or self.common.platform == "Darwin":
self.c = Controller.from_port(port=self.tor_control_port)
self.c.authenticate()
else:
diff --git a/onionshare/settings.py b/onionshare/settings.py
index 91844c8d..38478dbd 100644
--- a/onionshare/settings.py
+++ b/onionshare/settings.py
@@ -23,6 +23,12 @@ import os
import platform
import locale
+try:
+ # We only need pwd module in macOS, and it's not available in Windows
+ import pwd
+except:
+ pass
+
from . import strings
@@ -132,30 +138,24 @@ class Settings(object):
"""
Returns the path of the settings file.
"""
- p = platform.system()
- if p == 'Windows':
- try:
- appdata = os.environ['APPDATA']
- return '{}\\OnionShare\\onionshare.json'.format(appdata)
- except:
- # If for some reason we don't have the 'APPDATA' environment variable
- # (like running tests in Linux while pretending to be in Windows)
- return os.path.expanduser('~/.config/onionshare/onionshare.json')
- elif p == 'Darwin':
- return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json')
- else:
- return os.path.expanduser('~/.config/onionshare/onionshare.json')
+ return os.path.join(self.common.build_data_dir(), 'onionshare.json')
def build_default_downloads_dir(self):
"""
Returns the path of the default Downloads directory for receive mode.
"""
- # On Windows, os.path.expanduser() needs to use backslash, or else it
- # retains the forward slash, which breaks opening the folder in explorer.
- p = platform.system()
- if p == 'Windows':
+
+ if self.common.platform == "Darwin":
+ # We can't use os.path.expanduser() in macOS because in the sandbox it
+ # returns the path to the sandboxed homedir
+ real_homedir = pwd.getpwuid(os.getuid()).pw_dir
+ return os.path.join(real_homedir, 'OnionShare')
+ elif self.common.platform == "Windows":
+ # On Windows, os.path.expanduser() needs to use backslash, or else it
+ # retains the forward slash, which breaks opening the folder in explorer.
return os.path.expanduser('~\OnionShare')
else:
+ # All other OSes
return os.path.expanduser('~/OnionShare')
def load(self):
@@ -174,16 +174,18 @@ class Settings(object):
except:
pass
+ # Make sure downloads_dir exists
+ try:
+ os.makedirs(self.get('downloads_dir'), exist_ok=True)
+ except:
+ pass
+
def save(self):
"""
Save settings to file.
"""
self.common.log('Settings', 'save')
-
- try:
- os.makedirs(os.path.dirname(self.filename))
- except:
- pass
+ os.makedirs(os.path.dirname(self.filename), exist_ok=True)
open(self.filename, 'w').write(json.dumps(self._settings))
self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename))
diff --git a/onionshare/web/web.py b/onionshare/web/web.py
index 21e9cd8f..0f156941 100644
--- a/onionshare/web/web.py
+++ b/onionshare/web/web.py
@@ -184,19 +184,7 @@ class Web(object):
"""
Turn on debugging mode, which will log flask errors to a debug file.
"""
- if self.common.platform == 'Windows':
- try:
- appdata = os.environ['APPDATA']
- flask_debug_filename = '{}\\OnionShare\\flask_debug.log'.format(appdata)
- except:
- # If for some reason we don't have the 'APPDATA' environment variable
- # (like running tests in Linux while pretending to be in Windows)
- flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log')
- elif self.common.platform == 'Darwin':
- flask_debug_filename = os.path.expanduser('~/Library/Application Support/OnionShare/flask_debug.log')
- else:
- flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log')
-
+ flask_debug_filename = os.path.join(self.common.build_data_dir(), 'flask_debug.log')
log_handler = logging.FileHandler(flask_debug_filename)
log_handler.setLevel(logging.WARNING)
self.app.logger.addHandler(log_handler)
diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py
index 436d42f7..0cc00f92 100644
--- a/onionshare_gui/mode/share_mode/__init__.py
+++ b/onionshare_gui/mode/share_mode/__init__.py
@@ -47,7 +47,7 @@ class ShareMode(Mode):
self.web = Web(self.common, True, 'share')
# File selection
- self.file_selection = FileSelection(self.common)
+ self.file_selection = FileSelection(self.common, self)
if self.filenames:
for filename in self.filenames:
self.file_selection.file_list.add_file(filename)
diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py
index ec3b5ea5..0d4229fe 100644
--- a/onionshare_gui/mode/share_mode/file_selection.py
+++ b/onionshare_gui/mode/share_mode/file_selection.py
@@ -288,10 +288,11 @@ class FileSelection(QtWidgets.QVBoxLayout):
The list of files and folders in the GUI, as well as buttons to add and
delete the files and folders.
"""
- def __init__(self, common):
+ def __init__(self, common, parent):
super(FileSelection, self).__init__()
self.common = common
+ self.parent = parent
self.server_on = False
@@ -302,13 +303,25 @@ class FileSelection(QtWidgets.QVBoxLayout):
self.file_list.files_updated.connect(self.update)
# Buttons
- self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
- self.add_button.clicked.connect(self.add)
+ if self.common.platform == 'Darwin':
+ # The macOS sandbox makes it so the Mac version needs separate add files
+ # and folders buttons, in order to use native file selection dialogs
+ self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files'))
+ self.add_files_button.clicked.connect(self.add_files)
+ self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder'))
+ self.add_folder_button.clicked.connect(self.add_folder)
+ else:
+ self.add_button = QtWidgets.QPushButton(strings._('gui_add'))
+ self.add_button.clicked.connect(self.add)
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete'))
self.delete_button.clicked.connect(self.delete)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
- button_layout.addWidget(self.add_button)
+ if self.common.platform == 'Darwin':
+ button_layout.addWidget(self.add_files_button)
+ button_layout.addWidget(self.add_folder_button)
+ else:
+ button_layout.addWidget(self.add_button)
button_layout.addWidget(self.delete_button)
# Add the widgets
@@ -323,10 +336,18 @@ class FileSelection(QtWidgets.QVBoxLayout):
"""
# All buttons should be hidden if the server is on
if self.server_on:
- self.add_button.hide()
+ if self.common.platform == 'Darwin':
+ self.add_files_button.hide()
+ self.add_folder_button.hide()
+ else:
+ self.add_button.hide()
self.delete_button.hide()
else:
- self.add_button.show()
+ if self.common.platform == 'Darwin':
+ self.add_files_button.show()
+ self.add_folder_button.show()
+ else:
+ self.add_button.show()
# Delete button should be hidden if item isn't selected
if len(self.file_list.selectedItems()) == 0:
@@ -349,6 +370,24 @@ class FileSelection(QtWidgets.QVBoxLayout):
self.file_list.setCurrentItem(None)
self.update()
+ def add_files(self):
+ """
+ Add files button clicked.
+ """
+ files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items'))
+ filenames = files[0]
+ for filename in filenames:
+ self.file_list.add_file(filename)
+
+ def add_folder(self):
+ """
+ Add folder button clicked.
+ """
+ filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent,
+ caption=strings._('gui_choose_items'),
+ options=QtWidgets.QFileDialog.ShowDirsOnly)
+ self.file_list.add_file(filename)
+
def delete(self):
"""
Delete button clicked
diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py
index eaa5904d..600165aa 100644
--- a/onionshare_gui/widgets.py
+++ b/onionshare_gui/widgets.py
@@ -44,6 +44,10 @@ class AddFileDialog(QtWidgets.QFileDialog):
"""
Overridden version of QFileDialog which allows us to select folders as well
as, or instead of, files. For adding files/folders to share.
+
+ Note that this dialog can't be used in macOS, only in Windows, Linux, and BSD.
+ This is because the macOS sandbox requires native dialogs, and this is a Qt5
+ dialog.
"""
def __init__(self, common, *args, **kwargs):
QtWidgets.QFileDialog.__init__(self, *args, **kwargs)
diff --git a/share/locale/en.json b/share/locale/en.json
index 43c7cfe3..44eff150 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -34,6 +34,8 @@
"help_config": "Custom JSON config file location (optional)",
"gui_drag_and_drop": "Drag and drop files and folders\nto start sharing",
"gui_add": "Add",
+ "gui_add_files": "Add Files",
+ "gui_add_folder": "Add Folder",
"gui_delete": "Delete",
"gui_choose_items": "Choose",
"gui_share_start_server": "Start sharing",
diff --git a/share/torrc_template b/share/torrc_template
index 464adf32..8ac9e1ef 100644
--- a/share/torrc_template
+++ b/share/torrc_template
@@ -1,6 +1,5 @@
DataDirectory {{data_directory}}
SocksPort {{socks_port}}
-ControlSocket {{control_socket}}
CookieAuthentication 1
CookieAuthFile {{cookie_auth_file}}
AvoidDiskWrites 1
diff --git a/share/torrc_template-windows b/share/torrc_template-windows
deleted file mode 100644
index 38a5bf1e..00000000
--- a/share/torrc_template-windows
+++ /dev/null
@@ -1,9 +0,0 @@
-DataDirectory {{data_directory}}
-SocksPort {{socks_port}}
-ControlPort {{control_port}}
-CookieAuthentication 1
-CookieAuthFile {{cookie_auth_file}}
-AvoidDiskWrites 1
-Log notice stdout
-GeoIPFile {{geo_ip_file}}
-GeoIPv6File {{geo_ipv6_file}}
diff --git a/share/version.txt b/share/version.txt
index 22351bb8..aa8add45 100644
--- a/share/version.txt
+++ b/share/version.txt
@@ -1 +1 @@
-2.0.dev
+2.0.dev1
diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py
index bb619c4d..d67621c4 100644
--- a/tests/test_onionshare_settings.py
+++ b/tests/test_onionshare_settings.py
@@ -175,7 +175,7 @@ class TestSettings:
platform_windows):
monkeypatch.setenv('APPDATA', 'C:')
obj = settings.Settings(common.Common())
- assert obj.filename == 'C:\\OnionShare\\onionshare.json'
+ assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json'
def test_set_custom_bridge(self, settings_obj):
settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E')