Merge branch 'static-websites' of https://github.com/hiromipaw/onionshare into hiromipaw-static-websites

This commit is contained in:
Micah Lee 2019-05-10 09:46:54 -07:00
commit c378a32c51
No known key found for this signature in database
GPG key ID: 403C2657CD994F73
18 changed files with 654 additions and 21 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=strings._("help_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(strings._("preparing_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,171 @@
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()
# Information about the file to be shared
self.file_info = []
self.website_folder = ''
self.download_filesize = 0
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('/download/<path:page_path>')
def path_download(page_path):
return path_download(page_path)
@self.web.app.route('/<path:page_path>')
def path_public(page_path):
return path_logic(page_path)
@self.web.app.route("/")
def index_public():
return path_logic('')
def path_download(file_path=''):
"""
Render the download links.
"""
self.web.add_request(self.web.REQUEST_LOAD, request.path)
if not os.path.isfile(os.path.join(self.website_folder, file_path)):
return self.web.error404()
return send_from_directory(self.website_folder, file_path)
def path_logic(page_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, page_path, {
'id': visit_id,
'action': 'visit'
})
filelist = []
if self.file_info['files']:
self.website_folder = os.path.dirname(self.file_info['files'][0]['filename'])
filelist = [v['basename'] for v in self.file_info['files']]
elif self.file_info['dirs']:
self.website_folder = self.file_info['dirs'][0]['filename']
filelist = os.listdir(self.website_folder)
else:
return self.web.error404()
if any((fname == 'index.html') for fname in filelist):
self.web.app.static_url_path = self.website_folder
self.web.app.static_folder = self.website_folder
if not os.path.isfile(os.path.join(self.website_folder, page_path)):
page_path = os.path.join(page_path, 'index.html')
return send_from_directory(self.website_folder, page_path)
elif any(os.path.isfile(os.path.join(self.website_folder, i)) for i in filelist):
filenames = []
for i in filelist:
filenames.append(os.path.join(self.website_folder, i))
self.web.app.static_folder=self.common.get_resource_path('static')
self.set_file_info(filenames)
r = make_response(render_template(
'listing.html',
file_info=self.file_info,
filesize=self.download_filesize,
filesize_human=self.common.human_readable_filesize(self.download_filesize)))
return self.web.add_security_headers(r)
else:
return self.web.error404()
def set_file_info(self, 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
get the zip file's name and size.
"""
self.common.log("WebsiteModeWeb", "set_file_info")
self.web.cancel_compression = True
self.cleanup_filenames = []
# build file info list
self.file_info = {'files': [], 'dirs': []}
for filename in filenames:
info = {
'filename': filename,
'basename': os.path.basename(filename.rstrip('/'))
}
if os.path.isfile(filename):
info['size'] = os.path.getsize(filename)
info['size_human'] = self.common.human_readable_filesize(info['size'])
self.file_info['files'].append(info)
if os.path.isdir(filename):
info['size'] = self.common.dir_size(filename)
info['size_human'] = self.common.human_readable_filesize(info['size'])
self.file_info['dirs'].append(info)
self.download_filesize += info['size']
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
# Check if there's only 1 file and no folders
if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0:
self.download_filename = self.file_info['files'][0]['filename']
self.download_filesize = self.file_info['files'][0]['size']
self.download_filesize = os.path.getsize(self.download_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,281 @@
# -*- 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 secrets
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.
"""
# Warn about sending large files over Tor
if self.web.website_mode.download_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize"))
self.filesize_warning.show()
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 ) ):
filesize = self.web.website_mode.download_filesize
item = VisitHistoryItem(self.common, event["data"]["id"], filesize)
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,6 +367,7 @@ 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.
@ -337,6 +388,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 +419,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 +505,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 +528,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,40 @@
<!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">
<div class="right">
<ul>
<li>Total size: <strong>{{ filesize_human }}</strong></li>
</ul>
</div>
<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 file_info.files %}
<tr>
<td>
<img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
{{ info.basename }}
</td>
<td>{{ info.size_human }}</td>
<td><a href="/download/{{ info.basename }}">download</td>
</tr>
{% endfor %}
</table>
<script src="/static/js/send.js"></script>
</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