Merge pull request #961 from hiromipaw/static-websites

Static websites
This commit is contained in:
Micah Lee 2019-05-24 10:21:34 -07:00 committed by GitHub
commit 35c373b5a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 689 additions and 22 deletions

View File

@ -9,7 +9,7 @@ VERSION=`cat share/version.txt`
rm -r build dist >/dev/null 2>&1
# build binary package
python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
python3 setup.py bdist_rpm --requires="python3-flask, python3-flask-httpauth, python3-stem, python3-qt5, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4"
# install it
echo ""

View File

@ -59,6 +59,7 @@ def main():
files_in(dir, 'onionshare_gui/mode') + \
files_in(dir, 'onionshare_gui/mode/share_mode') + \
files_in(dir, 'onionshare_gui/mode/receive_mode') + \
files_in(dir, 'onionshare_gui/mode/website_mode') + \
files_in(dir, 'install/scripts') + \
files_in(dir, 'tests')
pysrc = [p for p in src if p.endswith('.py')]

View File

@ -3,6 +3,7 @@ certifi==2019.3.9
chardet==3.0.4
Click==7.0
Flask==1.0.2
Flask-HTTPAuth==3.2.4
future==0.17.1
idna==2.8
itsdangerous==1.1.0

View File

@ -51,6 +51,7 @@ def main(cwd=None):
parser.add_argument('--connect-timeout', metavar='<int>', dest='connect_timeout', default=120, help="Give up connecting to Tor after a given amount of seconds (default: 120)")
parser.add_argument('--stealth', action='store_true', dest='stealth', help="Use client authorization (advanced)")
parser.add_argument('--receive', action='store_true', dest='receive', help="Receive shares instead of sending them")
parser.add_argument('--website', action='store_true', dest='website', help="Publish a static website")
parser.add_argument('--config', metavar='config', default=False, help="Custom JSON config file location (optional)")
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Log OnionShare errors to stdout, and web errors to disk")
parser.add_argument('filename', metavar='filename', nargs='*', help="List of files or folders to share")
@ -68,10 +69,13 @@ def main(cwd=None):
connect_timeout = int(args.connect_timeout)
stealth = bool(args.stealth)
receive = bool(args.receive)
website = bool(args.website)
config = args.config
if receive:
mode = 'receive'
elif website:
mode = 'website'
else:
mode = 'share'
@ -168,6 +172,15 @@ def main(cwd=None):
print(e.args[0])
sys.exit()
if mode == 'website':
# Prepare files to share
print("Preparing files to publish website...")
try:
web.website_mode.set_file_info(filenames)
except OSError as e:
print(e.strerror)
sys.exit(1)
if mode == 'share':
# Prepare files to share
print("Compressing files.")
@ -206,6 +219,8 @@ def main(cwd=None):
# Build the URL
if common.settings.get('public_mode'):
url = 'http://{0:s}'.format(app.onion_host)
elif mode == 'website':
url = 'http://onionshare:{0:s}@{1:s}'.format(web.slug, app.onion_host)
else:
url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug)
@ -242,7 +257,7 @@ def main(cwd=None):
if app.autostop_timer > 0:
# if the auto-stop timer was set and has run out, stop the server
if not app.autostop_timer_thread.is_alive():
if mode == 'share':
if mode == 'share' or (mode == 'website'):
# If there were no attempts to download the share, or all downloads are done, we can stop
if web.share_mode.download_count == 0 or web.done:
print("Stopped because auto-stop timer ran out")

View File

@ -18,6 +18,9 @@ class ReceiveModeWeb(object):
self.web = web
# Reset assets path
self.web.app.static_folder=self.common.get_resource_path('static')
self.can_upload = True
self.upload_count = 0
self.uploads_in_progress = []

View File

@ -34,6 +34,10 @@ class ShareModeWeb(object):
# one download at a time.
self.download_in_progress = False
# Reset assets path
self.web.app.static_folder=self.common.get_resource_path('static')
self.define_routes()
def define_routes(self):

View File

@ -15,7 +15,7 @@ from .. import strings
from .share_mode import ShareModeWeb
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
from .website_mode import WebsiteModeWeb
# Stub out flask's show_server_banner function, to avoiding showing warnings that
# are not applicable to OnionShare
@ -111,13 +111,15 @@ class Web(object):
self.receive_mode = None
if self.mode == 'receive':
self.receive_mode = ReceiveModeWeb(self.common, self)
elif self.mode == 'website':
self.website_mode = WebsiteModeWeb(self.common, self)
elif self.mode == 'share':
self.share_mode = ShareModeWeb(self.common, self)
def define_common_routes(self):
"""
Common web app routes between sending and receiving
Common web app routes between sending, receiving and website modes.
"""
@self.app.errorhandler(404)
def page_not_found(e):

View File

@ -0,0 +1,202 @@
import os
import sys
import tempfile
import mimetypes
from flask import Response, request, render_template, make_response, send_from_directory
from flask_httpauth import HTTPBasicAuth
from .. import strings
class WebsiteModeWeb(object):
"""
All of the web logic for share mode
"""
def __init__(self, common, web):
self.common = common
self.common.log('WebsiteModeWeb', '__init__')
self.web = web
self.auth = HTTPBasicAuth()
# Dictionary mapping file paths to filenames on disk
self.files = {}
self.visit_count = 0
# Reset assets path
self.web.app.static_folder=self.common.get_resource_path('static')
self.users = { }
self.define_routes()
def define_routes(self):
"""
The web app routes for sharing a website
"""
@self.auth.get_password
def get_pw(username):
self.users['onionshare'] = self.web.slug
if username in self.users:
return self.users.get(username)
else:
return None
@self.web.app.before_request
def conditional_auth_check():
if not self.common.settings.get('public_mode'):
@self.auth.login_required
def _check_login():
return None
return _check_login()
@self.web.app.route('/', defaults={'path': ''})
@self.web.app.route('/<path:path>')
def path_public(path):
return path_logic(path)
def path_logic(path=''):
"""
Render the onionshare website.
"""
# Each download has a unique id
visit_id = self.visit_count
self.visit_count += 1
# Tell GUI the page has been visited
self.web.add_request(self.web.REQUEST_STARTED, path, {
'id': visit_id,
'action': 'visit'
})
if path in self.files:
filesystem_path = self.files[path]
# If it's a directory
if os.path.isdir(filesystem_path):
# Is there an index.html?
index_path = os.path.join(path, 'index.html')
if index_path in self.files:
# Render it
dirname = os.path.dirname(self.files[index_path])
basename = os.path.basename(self.files[index_path])
return send_from_directory(dirname, basename)
else:
# Otherwise, render directory listing
filenames = []
for filename in os.listdir(filesystem_path):
if os.path.isdir(os.path.join(filesystem_path, filename)):
filenames.append(filename + '/')
else:
filenames.append(filename)
filenames.sort()
return self.directory_listing(path, filenames, filesystem_path)
# If it's a file
elif os.path.isfile(filesystem_path):
dirname = os.path.dirname(filesystem_path)
basename = os.path.basename(filesystem_path)
return send_from_directory(dirname, basename)
# If it's not a directory or file, throw a 404
else:
return self.web.error404()
else:
# Special case loading /
if path == '':
index_path = 'index.html'
if index_path in self.files:
# Render it
dirname = os.path.dirname(self.files[index_path])
basename = os.path.basename(self.files[index_path])
return send_from_directory(dirname, basename)
else:
# Root directory listing
filenames = list(self.root_files)
filenames.sort()
return self.directory_listing(path, filenames)
else:
# If the path isn't found, throw a 404
return self.web.error404()
def directory_listing(self, path, filenames, filesystem_path=None):
# If filesystem_path is None, this is the root directory listing
files = []
dirs = []
for filename in filenames:
if filesystem_path:
this_filesystem_path = os.path.join(filesystem_path, filename)
else:
this_filesystem_path = self.files[filename]
is_dir = os.path.isdir(this_filesystem_path)
if is_dir:
dirs.append({
'basename': filename
})
else:
size = os.path.getsize(this_filesystem_path)
size_human = self.common.human_readable_filesize(size)
files.append({
'basename': filename,
'size_human': size_human
})
r = make_response(render_template('listing.html',
path=path,
files=files,
dirs=dirs))
return self.web.add_security_headers(r)
def set_file_info(self, filenames):
"""
Build a data structure that describes the list of files that make up
the static website.
"""
self.common.log("WebsiteModeWeb", "set_file_info")
# This is a dictionary that maps HTTP routes to filenames on disk
self.files = {}
# This is only the root files and dirs, as opposed to all of them
self.root_files = {}
# If there's just one folder, replace filenames with a list of files inside that folder
if len(filenames) == 1 and os.path.isdir(filenames[0]):
filenames = [os.path.join(filenames[0], x) for x in os.listdir(filenames[0])]
# Loop through the files
for filename in filenames:
basename = os.path.basename(filename.rstrip('/'))
# If it's a filename, add it
if os.path.isfile(filename):
self.files[basename] = filename
self.root_files[basename] = filename
# If it's a directory, add it recursively
elif os.path.isdir(filename):
self.root_files[basename + '/'] = filename
for root, _, nested_filenames in os.walk(filename):
# Normalize the root path. So if the directory name is "/home/user/Documents/some_folder",
# and it has a nested folder foobar, the root is "/home/user/Documents/some_folder/foobar".
# The normalized_root should be "some_folder/foobar"
normalized_root = os.path.join(basename, root[len(filename):].lstrip('/')).rstrip('/')
# Add the dir itself
self.files[normalized_root + '/'] = root
# Add the files in this dir
for nested_filename in nested_filenames:
self.files[os.path.join(normalized_root, nested_filename)] = os.path.join(root, nested_filename)
return True

View File

@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
from ...widgets import Alert, AddFileDialog
from ..widgets import Alert, AddFileDialog
class DropHereLabel(QtWidgets.QLabel):
"""

View File

@ -341,6 +341,35 @@ class ReceiveHistoryItem(HistoryItem):
self.label.setText(self.get_canceled_label_text(self.started))
class VisitHistoryItem(HistoryItem):
"""
Download history item, for share mode
"""
def __init__(self, common, id, total_bytes):
super(VisitHistoryItem, self).__init__()
self.status = HistoryItem.STATUS_STARTED
self.common = common
self.id = id
self.visited = time.time()
self.visited_dt = datetime.fromtimestamp(self.visited)
# Label
self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.visited_dt.strftime("%b %d, %I:%M%p")))
# Layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
def update(self):
self.label.setText(self.get_finished_label_text(self.started_dt))
self.status = HistoryItem.STATUS_FINISHED
def cancel(self):
self.progress_bar.setFormat(strings._('gui_canceled'))
self.status = HistoryItem.STATUS_CANCELED
class HistoryItemList(QtWidgets.QScrollArea):
"""
List of items
@ -404,19 +433,19 @@ class HistoryItemList(QtWidgets.QScrollArea):
Reset all items, emptying the list. Override this method.
"""
for key, item in self.items.copy().items():
if item.status != HistoryItem.STATUS_STARTED:
self.items_layout.removeWidget(item)
item.close()
del self.items[key]
self.items_layout.removeWidget(item)
item.close()
del self.items[key]
class History(QtWidgets.QWidget):
"""
A history of what's happened so far in this mode. This contains an internal
object full of a scrollable list of items.
"""
def __init__(self, common, empty_image, empty_text, header_text):
def __init__(self, common, empty_image, empty_text, header_text, mode=''):
super(History, self).__init__()
self.common = common
self.mode = mode
self.setMinimumWidth(350)
@ -535,12 +564,14 @@ class History(QtWidgets.QWidget):
"""
Update the 'in progress' widget.
"""
if self.in_progress_count == 0:
image = self.common.get_resource_path('images/share_in_progress_none.png')
else:
image = self.common.get_resource_path('images/share_in_progress.png')
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
if self.mode != 'website':
if self.in_progress_count == 0:
image = self.common.get_resource_path('images/share_in_progress_none.png')
else:
image = self.common.get_resource_path('images/share_in_progress.png')
self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count))
self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count))
class ToggleHistory(QtWidgets.QPushButton):

View File

@ -25,7 +25,7 @@ from onionshare.onion import *
from onionshare.common import Common
from onionshare.web import Web
from .file_selection import FileSelection
from ..file_selection import FileSelection
from .threads import CompressThread
from .. import Mode
from ..history import History, ToggleHistory, ShareHistoryItem

View File

@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import random
import string
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
from onionshare.onion import *
from onionshare.common import Common
from onionshare.web import Web
from ..file_selection import FileSelection
from .. import Mode
from ..history import History, ToggleHistory, VisitHistoryItem
from ...widgets import Alert
class WebsiteMode(Mode):
"""
Parts of the main window UI for sharing files.
"""
success = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str)
def init(self):
"""
Custom initialization for ReceiveMode.
"""
# Create the Web object
self.web = Web(self.common, True, 'website')
# File selection
self.file_selection = FileSelection(self.common, self)
if self.filenames:
for filename in self.filenames:
self.file_selection.file_list.add_file(filename)
# Server status
self.server_status.set_mode('website', self.file_selection)
self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
self.server_status.server_stopped.connect(self.update_primary_action)
self.server_status.server_canceled.connect(self.file_selection.server_stopped)
self.server_status.server_canceled.connect(self.update_primary_action)
self.file_selection.file_list.files_updated.connect(self.server_status.update)
self.file_selection.file_list.files_updated.connect(self.update_primary_action)
# Tell server_status about web, then update
self.server_status.web = self.web
self.server_status.update()
# Filesize warning
self.filesize_warning = QtWidgets.QLabel()
self.filesize_warning.setWordWrap(True)
self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning'])
self.filesize_warning.hide()
# Download history
self.history = History(
self.common,
QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))),
strings._('gui_website_mode_no_files'),
strings._('gui_all_modes_history'),
'website'
)
self.history.hide()
# Info label
self.info_label = QtWidgets.QLabel()
self.info_label.hide()
# Toggle history
self.toggle_history = ToggleHistory(
self.common, self, self.history,
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')),
QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png'))
)
# Top bar
top_bar_layout = QtWidgets.QHBoxLayout()
top_bar_layout.addWidget(self.info_label)
top_bar_layout.addStretch()
top_bar_layout.addWidget(self.toggle_history)
# Primary action layout
self.primary_action_layout.addWidget(self.filesize_warning)
self.primary_action.hide()
self.update_primary_action()
# Main layout
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addLayout(top_bar_layout)
self.main_layout.addLayout(self.file_selection)
self.main_layout.addWidget(self.primary_action)
self.main_layout.addWidget(self.min_width_widget)
# Wrapper layout
self.wrapper_layout = QtWidgets.QHBoxLayout()
self.wrapper_layout.addLayout(self.main_layout)
self.wrapper_layout.addWidget(self.history, stretch=1)
self.setLayout(self.wrapper_layout)
# Always start with focus on file selection
self.file_selection.setFocus()
def get_stop_server_autostop_timer_text(self):
"""
Return the string to put on the stop server button, if there's an auto-stop timer
"""
return strings._('gui_share_stop_server_autostop_timer')
def autostop_timer_finished_should_stop_server(self):
"""
The auto-stop timer expired, should we stop the server? Returns a bool
"""
self.server_status.stop_server()
self.server_status_label.setText(strings._('close_on_autostop_timer'))
return True
def start_server_custom(self):
"""
Starting the server.
"""
# Reset web counters
self.web.website_mode.visit_count = 0
self.web.error404_count = 0
# Hide and reset the downloads if we have previously shared
self.reset_info_counters()
def start_server_step2_custom(self):
"""
Step 2 in starting the server. Zipping up files.
"""
self.filenames = []
for index in range(self.file_selection.file_list.count()):
self.filenames.append(self.file_selection.file_list.item(index).filename)
# Continue
self.starting_server_step3.emit()
self.start_server_finished.emit()
def start_server_step3_custom(self):
"""
Step 3 in starting the server. Display large filesize
warning, if applicable.
"""
if self.web.website_mode.set_file_info(self.filenames):
self.success.emit()
else:
# Cancelled
pass
def start_server_error_custom(self):
"""
Start server error.
"""
if self._zip_progress_bar is not None:
self.status_bar.removeWidget(self._zip_progress_bar)
self._zip_progress_bar = None
def stop_server_custom(self):
"""
Stop server.
"""
self.filesize_warning.hide()
self.history.completed_count = 0
self.file_selection.file_list.adjustSize()
def cancel_server_custom(self):
"""
Log that the server has been cancelled
"""
self.common.log('WebsiteMode', 'cancel_server')
def handle_tor_broke_custom(self):
"""
Connection to Tor broke.
"""
self.primary_action.hide()
def handle_request_load(self, event):
"""
Handle REQUEST_LOAD event.
"""
self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message'))
def handle_request_started(self, event):
"""
Handle REQUEST_STARTED event.
"""
if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ):
item = VisitHistoryItem(self.common, event["data"]["id"], 0)
self.history.add(event["data"]["id"], item)
self.toggle_history.update_indicator(True)
self.history.completed_count += 1
self.history.update_completed()
self.system_tray.showMessage(strings._('systray_website_started_title'), strings._('systray_website_started_message'))
def on_reload_settings(self):
"""
If there were some files listed for sharing, we should be ok to re-enable
the 'Start Sharing' button now.
"""
if self.server_status.file_selection.get_num_files() > 0:
self.primary_action.show()
self.info_label.show()
def update_primary_action(self):
self.common.log('WebsiteMode', 'update_primary_action')
# Show or hide primary action layout
file_count = self.file_selection.file_list.count()
if file_count > 0:
self.primary_action.show()
self.info_label.show()
# Update the file count in the info label
total_size_bytes = 0
for index in range(self.file_selection.file_list.count()):
item = self.file_selection.file_list.item(index)
total_size_bytes += item.size_bytes
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
if file_count > 1:
self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable))
else:
self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable))
else:
self.primary_action.hide()
self.info_label.hide()
def reset_info_counters(self):
"""
Set the info counters back to zero.
"""
self.history.reset()
@staticmethod
def _compute_total_size(filenames):
total_size = 0
for filename in filenames:
if os.path.isfile(filename):
total_size += os.path.getsize(filename)
if os.path.isdir(filename):
total_size += Common.dir_size(filename)
return total_size

View File

@ -25,6 +25,7 @@ from onionshare.web import Web
from .mode.share_mode import ShareMode
from .mode.receive_mode import ReceiveMode
from .mode.website_mode import WebsiteMode
from .tor_connection_dialog import TorConnectionDialog
from .settings_dialog import SettingsDialog
@ -39,6 +40,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
"""
MODE_SHARE = 'share'
MODE_RECEIVE = 'receive'
MODE_WEBSITE = 'website'
def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False):
super(OnionShareGui, self).__init__()
@ -92,6 +94,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button'));
self.receive_mode_button.setFixedHeight(50)
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
self.website_mode_button = QtWidgets.QPushButton(strings._('gui_mode_website_button'));
self.website_mode_button.setFixedHeight(50)
self.website_mode_button.clicked.connect(self.website_mode_clicked)
self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False)
self.settings_button.setFixedWidth(40)
@ -103,6 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
mode_switcher_layout.setSpacing(0)
mode_switcher_layout.addWidget(self.share_mode_button)
mode_switcher_layout.addWidget(self.receive_mode_button)
mode_switcher_layout.addWidget(self.website_mode_button)
mode_switcher_layout.addWidget(self.settings_button)
# Server status indicator on the status bar
@ -154,6 +160,20 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.receive_mode.set_server_active.connect(self.set_server_active)
# Website mode
self.website_mode = WebsiteMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames)
self.website_mode.init()
self.website_mode.server_status.server_started.connect(self.update_server_status_indicator)
self.website_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
self.website_mode.start_server_finished.connect(self.update_server_status_indicator)
self.website_mode.stop_server_finished.connect(self.update_server_status_indicator)
self.website_mode.stop_server_finished.connect(self.stop_server_finished)
self.website_mode.start_server_finished.connect(self.clear_message)
self.website_mode.server_status.button_clicked.connect(self.clear_message)
self.website_mode.server_status.url_copied.connect(self.copy_url)
self.website_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.website_mode.set_server_active.connect(self.set_server_active)
self.update_mode_switcher()
self.update_server_status_indicator()
@ -162,6 +182,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
contents_layout.setContentsMargins(10, 0, 10, 0)
contents_layout.addWidget(self.receive_mode)
contents_layout.addWidget(self.share_mode)
contents_layout.addWidget(self.website_mode)
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
@ -199,15 +220,27 @@ class OnionShareGui(QtWidgets.QMainWindow):
if self.mode == self.MODE_SHARE:
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.receive_mode.hide()
self.share_mode.show()
self.website_mode.hide()
elif self.mode == self.MODE_WEBSITE:
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
self.receive_mode.hide()
self.share_mode.hide()
self.website_mode.show()
else:
self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style'])
self.website_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style'])
self.share_mode.hide()
self.receive_mode.show()
self.website_mode.hide()
self.update_server_status_indicator()
@ -223,6 +256,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.mode = self.MODE_RECEIVE
self.update_mode_switcher()
def website_mode_clicked(self):
if self.mode != self.MODE_WEBSITE:
self.common.log('OnionShareGui', 'website_mode_clicked')
self.mode = self.MODE_WEBSITE
self.update_mode_switcher()
def update_server_status_indicator(self):
# Set the status image
if self.mode == self.MODE_SHARE:
@ -239,6 +278,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
elif self.mode == self.MODE_WEBSITE:
# Website mode
if self.website_mode.server_status.status == ServerStatus.STATUS_STOPPED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
self.server_status_label.setText(strings._('gui_status_indicator_share_stopped'))
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
self.server_status_label.setText(strings._('gui_status_indicator_share_working'))
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
self.server_status_label.setText(strings._('gui_status_indicator_share_started'))
else:
# Receive mode
if self.receive_mode.server_status.status == ServerStatus.STATUS_STOPPED:
@ -317,19 +367,23 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.timer.start(500)
self.share_mode.on_reload_settings()
self.receive_mode.on_reload_settings()
self.website_mode.on_reload_settings()
self.status_bar.clearMessage()
# If we switched off the auto-stop timer setting, ensure the widget is hidden.
if not self.common.settings.get('autostop_timer'):
self.share_mode.server_status.autostop_timer_container.hide()
self.receive_mode.server_status.autostop_timer_container.hide()
self.website_mode.server_status.autostop_timer_container.hide()
# If we switched off the auto-start timer setting, ensure the widget is hidden.
if not self.common.settings.get('autostart_timer'):
self.share_mode.server_status.autostart_timer_datetime = None
self.receive_mode.server_status.autostart_timer_datetime = None
self.website_mode.server_status.autostart_timer_datetime = None
self.share_mode.server_status.autostart_timer_container.hide()
self.receive_mode.server_status.autostart_timer_container.hide()
self.website_mode.server_status.autostart_timer_container.hide()
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
d.settings_saved.connect(reload_settings)
d.exec_()
@ -337,6 +391,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
# When settings close, refresh the server status UI
self.share_mode.server_status.update()
self.receive_mode.server_status.update()
self.website_mode.server_status.update()
def check_for_updates(self):
"""
@ -367,10 +422,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.share_mode.handle_tor_broke()
self.receive_mode.handle_tor_broke()
self.website_mode.handle_tor_broke()
# Process events from the web object
if self.mode == self.MODE_SHARE:
mode = self.share_mode
elif self.mode == self.MODE_WEBSITE:
mode = self.website_mode
else:
mode = self.receive_mode
@ -450,13 +508,20 @@ class OnionShareGui(QtWidgets.QMainWindow):
if self.mode == self.MODE_SHARE:
self.share_mode_button.show()
self.receive_mode_button.hide()
self.website_mode_button.hide()
elif self.mode == self.MODE_WEBSITE:
self.share_mode_button.hide()
self.receive_mode_button.hide()
self.website_mode_button.show()
else:
self.share_mode_button.hide()
self.receive_mode_button.show()
self.website_mode_button.hide()
else:
self.settings_button.show()
self.share_mode_button.show()
self.receive_mode_button.show()
self.website_mode_button.show()
# Disable settings menu action when server is active
self.settings_action.setEnabled(not active)
@ -466,6 +531,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
try:
if self.mode == OnionShareGui.MODE_SHARE:
server_status = self.share_mode.server_status
if self.mode == OnionShareGui.MODE_WEBSITE:
server_status = self.website_mode.server_status
else:
server_status = self.receive_mode.server_status
if server_status.status != server_status.STATUS_STOPPED:

View File

@ -39,6 +39,7 @@ class ServerStatus(QtWidgets.QWidget):
MODE_SHARE = 'share'
MODE_RECEIVE = 'receive'
MODE_WEBSITE = 'website'
STATUS_STOPPED = 0
STATUS_WORKING = 1
@ -159,7 +160,7 @@ class ServerStatus(QtWidgets.QWidget):
"""
self.mode = share_mode
if self.mode == ServerStatus.MODE_SHARE:
if (self.mode == ServerStatus.MODE_SHARE) or (self.mode == ServerStatus.MODE_WEBSITE):
self.file_selection = file_selection
self.update()
@ -207,6 +208,8 @@ class ServerStatus(QtWidgets.QWidget):
if self.mode == ServerStatus.MODE_SHARE:
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
elif self.mode == ServerStatus.MODE_WEBSITE:
self.url_description.setText(strings._('gui_share_url_description').format(info_image))
else:
self.url_description.setText(strings._('gui_receive_url_description').format(info_image))
@ -258,6 +261,8 @@ class ServerStatus(QtWidgets.QWidget):
# Button
if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0:
self.server_button.hide()
elif self.mode == ServerStatus.MODE_WEBSITE and self.file_selection.get_num_files() == 0:
self.server_button.hide()
else:
self.server_button.show()
@ -266,6 +271,8 @@ class ServerStatus(QtWidgets.QWidget):
self.server_button.setEnabled(True)
if self.mode == ServerStatus.MODE_SHARE:
self.server_button.setText(strings._('gui_share_start_server'))
elif self.mode == ServerStatus.MODE_WEBSITE:
self.server_button.setText(strings._('gui_share_start_server'))
else:
self.server_button.setText(strings._('gui_receive_start_server'))
self.server_button.setToolTip('')
@ -278,6 +285,8 @@ class ServerStatus(QtWidgets.QWidget):
self.server_button.setEnabled(True)
if self.mode == ServerStatus.MODE_SHARE:
self.server_button.setText(strings._('gui_share_stop_server'))
if self.mode == ServerStatus.MODE_WEBSITE:
self.server_button.setText(strings._('gui_share_stop_server'))
else:
self.server_button.setText(strings._('gui_receive_stop_server'))
if self.common.settings.get('autostart_timer'):
@ -411,6 +420,8 @@ class ServerStatus(QtWidgets.QWidget):
"""
if self.common.settings.get('public_mode'):
url = 'http://{0:s}'.format(self.app.onion_host)
elif self.mode == ServerStatus.MODE_WEBSITE:
url = 'http://onionshare:{0:s}@{1:s}'.format(self.web.slug, self.app.onion_host)
else:
url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)
return url

View File

@ -88,7 +88,8 @@ setup(
'onionshare_gui',
'onionshare_gui.mode',
'onionshare_gui.mode.share_mode',
'onionshare_gui.mode.receive_mode'
'onionshare_gui.mode.receive_mode',
'onionshare_gui.mode.website_mode'
],
include_package_data=True,
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],

View File

@ -114,6 +114,7 @@
"gui_use_legacy_v2_onions_checkbox": "Use legacy addresses",
"gui_save_private_key_checkbox": "Use a persistent address",
"gui_share_url_description": "<b>Anyone</b> with this OnionShare address can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
"gui_website_url_description": "<b>Anyone</b> with this OnionShare address can <b>visit</b> your website using the <b>Tor Browser</b>: <img src='{}' />",
"gui_receive_url_description": "<b>Anyone</b> with this OnionShare address can <b>upload</b> files to your computer using the <b>Tor Browser</b>: <img src='{}' />",
"gui_url_label_persistent": "This share will not auto-stop.<br><br>Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)",
"gui_url_label_stay_open": "This share will not auto-stop.",
@ -135,6 +136,7 @@
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
"gui_mode_share_button": "Share Files",
"gui_mode_receive_button": "Receive Files",
"gui_mode_website_button": "Publish Website",
"gui_settings_receiving_label": "Receiving settings",
"gui_settings_data_dir_label": "Save files to",
"gui_settings_data_dir_browse_button": "Browse",
@ -145,6 +147,8 @@
"systray_menu_exit": "Quit",
"systray_page_loaded_title": "Page Loaded",
"systray_page_loaded_message": "OnionShare address loaded",
"systray_site_loaded_title": "Site Loaded",
"systray_site_loaded_message": "OnionShare site loaded",
"systray_share_started_title": "Sharing Started",
"systray_share_started_message": "Starting to send files to someone",
"systray_share_completed_title": "Sharing Complete",
@ -153,6 +157,8 @@
"systray_share_canceled_message": "Someone canceled receiving your files",
"systray_receive_started_title": "Receiving Started",
"systray_receive_started_message": "Someone is sending files to you",
"systray_website_started_title": "Starting sharing website",
"systray_website_started_message": "Someone is visiting your website",
"gui_all_modes_history": "History",
"gui_all_modes_clear_history": "Clear All",
"gui_all_modes_transfer_started": "Started {}",
@ -165,8 +171,10 @@
"gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%",
"gui_share_mode_no_files": "No Files Sent Yet",
"gui_share_mode_autostop_timer_waiting": "Waiting to finish sending",
"gui_website_mode_no_files": "No Website Shared Yet",
"gui_receive_mode_no_files": "No Files Received Yet",
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
"gui_visit_started": "Someone has visited your website {}",
"receive_mode_upload_starting": "Upload of total size {} is starting",
"days_first_letter": "d",
"hours_first_letter": "h",

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>OnionShare</title>
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
<link href="/static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<header class="clearfix">
<img class="logo" src="/static/img/logo.png" title="OnionShare">
<h1>OnionShare</h1>
</header>
<table class="file-list" id="file-list">
<tr>
<th id="filename-header">Filename</th>
<th id="size-header">Size</th>
<th></th>
</tr>
{% for info in dirs %}
<tr>
<td>
<img width="30" height="30" title="" alt="" src="/static/img/web_folder.png" />
<a href="{{ info.basename }}">
{{ info.basename }}
</a>
</td>
<td>&mdash;</td>
</tr>
{% endfor %}
{% for info in files %}
<tr>
<td>
<img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
<a href="{{ info.basename }}">
{{ info.basename }}
</a>
</td>
<td>{{ info.size_human }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>

View File

@ -1,6 +1,6 @@
[DEFAULT]
Package3: onionshare
Depends3: python3, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
Build-Depends: python3, python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-requests, python-nautilus, tor, obfs4proxy
Depends3: python3, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy
Build-Depends: python3, python3-pytest, python3-flask, python3-flask-httpauth, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-requests, python-nautilus, tor, obfs4proxy
Suite: cosmic
X-Python3-Version: >= 3.5.3