mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
commit
35c373b5a3
@ -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 ""
|
||||
|
@ -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')]
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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 = []
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
202
onionshare/web/website_mode.py
Normal file
202
onionshare/web/website_mode.py
Normal 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
|
@ -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):
|
||||
"""
|
@ -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,7 +433,6 @@ 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]
|
||||
@ -414,9 +442,10 @@ 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,10 +564,12 @@ class History(QtWidgets.QWidget):
|
||||
"""
|
||||
Update the 'in progress' widget.
|
||||
"""
|
||||
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))
|
||||
|
||||
|
@ -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
|
||||
|
274
onionshare_gui/mode/website_mode/__init__.py
Normal file
274
onionshare_gui/mode/website_mode/__init__.py
Normal 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
|
@ -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,18 +367,22 @@ 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)
|
||||
@ -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:
|
||||
|
@ -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
|
||||
|
3
setup.py
3
setup.py
@ -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'],
|
||||
|
@ -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",
|
||||
|
47
share/templates/listing.html
Normal file
47
share/templates/listing.html
Normal 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>—</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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user