Expand 'public mode' (optional slugs) to be possible for sharing too, not just receiving, with no rate-limiting/self-destruct on invalid routes.

This commit is contained in:
Miguel Jacq 2018-07-21 17:06:11 +10:00
parent f5ccfcf2cc
commit 3b45f93dbe
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
8 changed files with 87 additions and 47 deletions

View File

@ -128,7 +128,7 @@ def main(cwd=None):
print('') print('')
# Start OnionShare http service in new thread # Start OnionShare http service in new thread
t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('slug'))) t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug')))
t.daemon = True t.daemon = True
t.start() t.start()
@ -147,7 +147,7 @@ def main(cwd=None):
common.settings.save() common.settings.save()
# Build the URL # Build the URL
if receive and common.settings.get('receive_public_mode'): if common.settings.get('public_mode'):
url = 'http://{0:s}'.format(app.onion_host) url = 'http://{0:s}'.format(app.onion_host)
else: else:
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)

View File

@ -70,11 +70,11 @@ class Settings(object):
'tor_bridges_use_custom_bridges': '', 'tor_bridges_use_custom_bridges': '',
'save_private_key': False, 'save_private_key': False,
'private_key': '', 'private_key': '',
'public_mode': False,
'slug': '', 'slug': '',
'hidservauth_string': '', 'hidservauth_string': '',
'downloads_dir': self.build_default_downloads_dir(), 'downloads_dir': self.build_default_downloads_dir(),
'receive_allow_receiver_shutdown': True, 'receive_allow_receiver_shutdown': True
'receive_public_mode': False
} }
self._settings = {} self._settings = {}
self.fill_in_defaults() self.fill_in_defaults()

View File

@ -143,11 +143,19 @@ class Web(object):
""" """
@self.app.route("/<slug_candidate>") @self.app.route("/<slug_candidate>")
def index(slug_candidate): def index(slug_candidate):
self.check_slug_candidate(slug_candidate)
return index_logic()
@self.app.route("/")
def index_public():
if not self.common.settings.get('public_mode'):
return self.error404()
return index_logic()
def index_logic(slug_candidate=''):
""" """
Render the template for the onionshare landing page. Render the template for the onionshare landing page.
""" """
self.check_slug_candidate(slug_candidate)
self.add_request(Web.REQUEST_LOAD, request.path) self.add_request(Web.REQUEST_LOAD, request.path)
# Deny new downloads if "Stop After First Download" is checked and there is # Deny new downloads if "Stop After First Download" is checked and there is
@ -158,22 +166,39 @@ class Web(object):
return self.add_security_headers(r) return self.add_security_headers(r)
# If download is allowed to continue, serve download page # If download is allowed to continue, serve download page
r = make_response(render_template( if self.slug:
'send.html', r = make_response(render_template(
slug=self.slug, 'send.html',
file_info=self.file_info, slug=self.slug,
filename=os.path.basename(self.zip_filename), file_info=self.file_info,
filesize=self.zip_filesize, filename=os.path.basename(self.zip_filename),
filesize_human=self.common.human_readable_filesize(self.zip_filesize))) filesize=self.zip_filesize,
filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
else:
# If download is allowed to continue, serve download page
r = make_response(render_template(
'send.html',
file_info=self.file_info,
filename=os.path.basename(self.zip_filename),
filesize=self.zip_filesize,
filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
return self.add_security_headers(r) return self.add_security_headers(r)
@self.app.route("/<slug_candidate>/download") @self.app.route("/<slug_candidate>/download")
def download(slug_candidate): def download(slug_candidate):
self.check_slug_candidate(slug_candidate)
return download_logic()
@self.app.route("/download")
def download_public():
if not self.common.settings.get('public_mode'):
return self.error404()
return download_logic()
def download_logic(slug_candidate=''):
""" """
Download the zip file. Download the zip file.
""" """
self.check_slug_candidate(slug_candidate)
# Deny new downloads if "Stop After First Download" is checked and there is # Deny new downloads if "Stop After First Download" is checked and there is
# currently a download # currently a download
deny_download = not self.stay_open and self.download_in_progress deny_download = not self.stay_open and self.download_in_progress
@ -288,7 +313,7 @@ class Web(object):
def index_logic(): def index_logic():
self.add_request(Web.REQUEST_LOAD, request.path) self.add_request(Web.REQUEST_LOAD, request.path)
if self.common.settings.get('receive_public_mode'): if self.common.settings.get('public_mode'):
upload_action = '/upload' upload_action = '/upload'
close_action = '/close' close_action = '/close'
else: else:
@ -309,7 +334,7 @@ class Web(object):
@self.app.route("/") @self.app.route("/")
def index_public(): def index_public():
if not self.common.settings.get('receive_public_mode'): if not self.common.settings.get('public_mode'):
return self.error404() return self.error404()
return index_logic() return index_logic()
@ -332,7 +357,7 @@ class Web(object):
valid = False valid = False
if not valid: if not valid:
flash('Error uploading, please inform the OnionShare user') flash('Error uploading, please inform the OnionShare user')
if self.common.settings.get('receive_public_mode'): if self.common.settings.get('public_mode'):
return redirect('/') return redirect('/')
else: else:
return redirect('/{}'.format(slug_candidate)) return redirect('/{}'.format(slug_candidate))
@ -395,7 +420,7 @@ class Web(object):
for filename in filenames: for filename in filenames:
flash('Uploaded {}'.format(filename)) flash('Uploaded {}'.format(filename))
if self.common.settings.get('receive_public_mode'): if self.common.settings.get('public_mode'):
return redirect('/') return redirect('/')
else: else:
return redirect('/{}'.format(slug_candidate)) return redirect('/{}'.format(slug_candidate))
@ -407,7 +432,7 @@ class Web(object):
@self.app.route("/upload", methods=['POST']) @self.app.route("/upload", methods=['POST'])
def upload_public(): def upload_public():
if not self.common.settings.get('receive_public_mode'): if not self.common.settings.get('public_mode'):
return self.error404() return self.error404()
return upload_logic() return upload_logic()
@ -428,7 +453,7 @@ class Web(object):
@self.app.route("/close", methods=['POST']) @self.app.route("/close", methods=['POST'])
def close_public(): def close_public():
if not self.common.settings.get('receive_public_mode'): if not self.common.settings.get('public_mode'):
return self.error404() return self.error404()
return close_logic() return close_logic()
@ -458,7 +483,7 @@ class Web(object):
self.error404_count += 1 self.error404_count += 1
# In receive mode, with public mode enabled, skip rate limiting 404s # In receive mode, with public mode enabled, skip rate limiting 404s
if not (self.receive_mode and self.common.settings.get('receive_public_mode')): if not self.common.settings.get('public_mode'):
if self.error404_count == 20: if self.error404_count == 20:
self.add_request(Web.REQUEST_RATE_LIMIT, request.path) self.add_request(Web.REQUEST_RATE_LIMIT, request.path)
self.force_shutdown() self.force_shutdown()
@ -563,12 +588,13 @@ class Web(object):
pass pass
self.running = False self.running = False
def start(self, port, stay_open=False, persistent_slug=None): def start(self, port, stay_open=False, public_mode=False, persistent_slug=None):
""" """
Start the flask web server. Start the flask web server.
""" """
self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug)) self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug))
self.generate_slug(persistent_slug) if not public_mode:
self.generate_slug(persistent_slug)
self.stay_open = stay_open self.stay_open = stay_open
@ -719,7 +745,7 @@ class ReceiveModeRequest(Request):
if self.path == '/{}/upload'.format(self.web.slug): if self.path == '/{}/upload'.format(self.web.slug):
self.upload_request = True self.upload_request = True
else: else:
if self.web.common.settings.get('receive_public_mode'): if self.web.common.settings.get('public_mode'):
if self.path == '/upload': if self.path == '/upload':
self.upload_request = True self.upload_request = True

View File

@ -144,13 +144,14 @@ class Mode(QtWidgets.QWidget):
self.app.choose_port() self.app.choose_port()
# Start http service in new thread # Start http service in new thread
t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('slug'))) t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('public_mode'), self.common.settings.get('slug')))
t.daemon = True t.daemon = True
t.start() t.start()
# Wait for the web app slug to generate before continuing # Wait for the web app slug to generate before continuing
while self.web.slug == None: if not self.common.settings.get('public_mode'):
time.sleep(0.1) while self.web.slug == None:
time.sleep(0.1)
# Now start the onion service # Now start the onion service
try: try:

View File

@ -314,7 +314,7 @@ class ServerStatus(QtWidgets.QWidget):
""" """
Returns the OnionShare URL. Returns the OnionShare URL.
""" """
if self.mode == ServerStatus.MODE_RECEIVE and self.common.settings.get('receive_public_mode'): if self.common.settings.get('public_mode'):
url = 'http://{0:s}'.format(self.app.onion_host) url = 'http://{0:s}'.format(self.app.onion_host)
else: else:
url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)

View File

@ -52,6 +52,25 @@ class SettingsDialog(QtWidgets.QDialog):
self.system = platform.system() self.system = platform.system()
# General options
# Whether or not to save the Onion private key for reuse (persistent URL mode)
self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
# Use a slug
self.public_mode_checkbox = QtWidgets.QCheckBox()
self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True))
# General options layout
general_group_layout = QtWidgets.QVBoxLayout()
general_group_layout.addWidget(self.save_private_key_checkbox)
general_group_layout.addWidget(self.public_mode_checkbox)
general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True))
general_group.setLayout(general_group_layout)
# Sharing options # Sharing options
# Close after first download # Close after first download
@ -64,16 +83,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked)
self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True))
# Whether or not to save the Onion private key for reuse
self.save_private_key_checkbox = QtWidgets.QCheckBox()
self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True))
# Sharing options layout # Sharing options layout
sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout = QtWidgets.QVBoxLayout()
sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.close_after_first_download_checkbox)
sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox)
sharing_group_layout.addWidget(self.save_private_key_checkbox)
sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True))
sharing_group.setLayout(sharing_group_layout) sharing_group.setLayout(sharing_group_layout)
@ -93,16 +106,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked) self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked)
self.receive_allow_receiver_shutdown_checkbox.setText(strings._("gui_settings_receive_allow_receiver_shutdown_checkbox", True)) self.receive_allow_receiver_shutdown_checkbox.setText(strings._("gui_settings_receive_allow_receiver_shutdown_checkbox", True))
# Use a slug
self.receive_public_mode_checkbox = QtWidgets.QCheckBox()
self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked)
self.receive_public_mode_checkbox.setText(strings._("gui_settings_receive_public_mode_checkbox", True))
# Receiving options layout # Receiving options layout
receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout = QtWidgets.QVBoxLayout()
receiving_group_layout.addLayout(downloads_layout) receiving_group_layout.addLayout(downloads_layout)
receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox) receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox)
receiving_group_layout.addWidget(self.receive_public_mode_checkbox)
receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True))
receiving_group.setLayout(receiving_group_layout) receiving_group.setLayout(receiving_group_layout)
@ -377,6 +384,7 @@ class SettingsDialog(QtWidgets.QDialog):
# Layout # Layout
left_col_layout = QtWidgets.QVBoxLayout() left_col_layout = QtWidgets.QVBoxLayout()
left_col_layout.addWidget(general_group)
left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(sharing_group)
left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(receiving_group)
left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(stealth_group)
@ -431,11 +439,11 @@ class SettingsDialog(QtWidgets.QDialog):
else: else:
self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked) self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked)
receive_public_mode = self.old_settings.get('receive_public_mode') public_mode = self.old_settings.get('public_mode')
if receive_public_mode: if public_mode:
self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked)
else: else:
self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked)
use_stealth = self.old_settings.get('use_stealth') use_stealth = self.old_settings.get('use_stealth')
if use_stealth: if use_stealth:
@ -819,7 +827,7 @@ class SettingsDialog(QtWidgets.QDialog):
settings.set('hidservauth_string', '') settings.set('hidservauth_string', '')
settings.set('downloads_dir', self.downloads_dir_lineedit.text()) settings.set('downloads_dir', self.downloads_dir_lineedit.text())
settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked())
settings.set('receive_public_mode', self.receive_public_mode_checkbox.isChecked()) settings.set('public_mode', self.public_mode_checkbox.isChecked())
settings.set('use_stealth', self.stealth_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked())
# Always unset the HidServAuth if Stealth mode is unset # Always unset the HidServAuth if Stealth mode is unset
if not self.stealth_checkbox.isChecked(): if not self.stealth_checkbox.isChecked():

View File

@ -91,6 +91,7 @@
"gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp": "Last checked: {}",
"gui_settings_autoupdate_timestamp_never": "Never", "gui_settings_autoupdate_timestamp_never": "Never",
"gui_settings_autoupdate_check_button": "Check For Upgrades", "gui_settings_autoupdate_check_button": "Check For Upgrades",
"gui_settings_general_label": "General options",
"gui_settings_sharing_label": "Sharing options", "gui_settings_sharing_label": "Sharing options",
"gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_close_after_first_download_option": "Stop sharing after first download",
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
@ -183,7 +184,7 @@
"gui_settings_downloads_label": "Save files to", "gui_settings_downloads_label": "Save files to",
"gui_settings_downloads_button": "Browse", "gui_settings_downloads_button": "Browse",
"gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender",
"gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)", "gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)",
"systray_close_server_title": "OnionShare Server Closed", "systray_close_server_title": "OnionShare Server Closed",
"systray_close_server_message": "A user closed the server", "systray_close_server_message": "A user closed the server",
"systray_page_loaded_title": "OnionShare Page Loaded", "systray_page_loaded_title": "OnionShare Page Loaded",

View File

@ -13,7 +13,11 @@
<div class="right"> <div class="right">
<ul> <ul>
<li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li> <li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
{% if slug %}
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li> <li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
{% else %}
<li><a class="button" href='/download'>Download Files</a></li>
{% endif %}
</ul> </ul>
</div> </div>
<img class="logo" src="/static/img/logo.png" title="OnionShare"> <img class="logo" src="/static/img/logo.png" title="OnionShare">