Start making IndividualFileHistoryItem widgets appear in the history, and make non-GET requests return 405 Method Not Allowed

This commit is contained in:
Micah Lee 2019-09-03 21:46:32 -07:00
parent c55925c1ce
commit 644b47082a
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
9 changed files with 185 additions and 101 deletions

View File

@ -132,18 +132,28 @@ class SendBaseModeWeb:
file_to_download = filesystem_path file_to_download = filesystem_path
filesize = os.path.getsize(filesystem_path) filesize = os.path.getsize(filesystem_path)
# TODO: Tell GUI the download started # Each download has a unique id
#self.web.add_request(self.web.REQUEST_STARTED, path, { download_id = self.download_count
# 'id': download_id, self.download_count += 1
# 'use_gzip': use_gzip
#}) path = request.path
# Tell GUI the individual file started
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, {
'id': download_id,
'filesize': filesize,
'method': request.method
})
# Only GET requests are allowed, any other method should fail
if request.method != "GET":
return self.web.error405()
def generate(): def generate():
chunk_size = 102400 # 100kb chunk_size = 102400 # 100kb
fp = open(file_to_download, 'rb') fp = open(file_to_download, 'rb')
done = False done = False
canceled = False
while not done: while not done:
chunk = fp.read(chunk_size) chunk = fp.read(chunk_size)
if chunk == b'': if chunk == b'':
@ -152,7 +162,7 @@ class SendBaseModeWeb:
try: try:
yield chunk yield chunk
# TODO: Tell GUI the progress # Tell GUI the progress
downloaded_bytes = fp.tell() downloaded_bytes = fp.tell()
percent = (1.0 * downloaded_bytes / filesize) * 100 percent = (1.0 * downloaded_bytes / filesize) * 100
if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD':
@ -160,20 +170,19 @@ class SendBaseModeWeb:
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
sys.stdout.flush() sys.stdout.flush()
#self.web.add_request(self.web.REQUEST_PROGRESS, path, { self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, {
# 'id': download_id, 'id': download_id,
# 'bytes': downloaded_bytes 'bytes': downloaded_bytes
# }) })
done = False done = False
except: except:
# Looks like the download was canceled # Looks like the download was canceled
done = True done = True
canceled = True
# TODO: Tell the GUI the download has canceled # Tell the GUI the individual file was canceled
#self.web.add_request(self.web.REQUEST_CANCELED, path, { self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, {
# 'id': download_id 'id': download_id
#}) })
fp.close() fp.close()

View File

@ -37,15 +37,18 @@ class Web:
REQUEST_LOAD = 0 REQUEST_LOAD = 0
REQUEST_STARTED = 1 REQUEST_STARTED = 1
REQUEST_PROGRESS = 2 REQUEST_PROGRESS = 2
REQUEST_OTHER = 3 REQUEST_CANCELED = 3
REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 4
REQUEST_RATE_LIMIT = 5 REQUEST_UPLOAD_FILE_RENAMED = 5
REQUEST_UPLOAD_FILE_RENAMED = 6 REQUEST_UPLOAD_SET_DIR = 6
REQUEST_UPLOAD_SET_DIR = 7 REQUEST_UPLOAD_FINISHED = 7
REQUEST_UPLOAD_FINISHED = 8 REQUEST_UPLOAD_CANCELED = 8
REQUEST_UPLOAD_CANCELED = 9 REQUEST_INDIVIDUAL_FILE_STARTED = 9
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10 REQUEST_INDIVIDUAL_FILE_PROGRESS = 10
REQUEST_INVALID_PASSWORD = 11 REQUEST_INDIVIDUAL_FILE_CANCELED = 11
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 12
REQUEST_OTHER = 13
REQUEST_INVALID_PASSWORD = 14
def __init__(self, common, is_gui, mode='share'): def __init__(self, common, is_gui, mode='share'):
self.common = common self.common = common
@ -193,15 +196,18 @@ class Web:
r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401) r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401)
return self.add_security_headers(r) return self.add_security_headers(r)
def error403(self):
self.add_request(Web.REQUEST_OTHER, request.path)
r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
return self.add_security_headers(r)
def error404(self): def error404(self):
self.add_request(Web.REQUEST_OTHER, request.path) self.add_request(Web.REQUEST_OTHER, request.path)
r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404) r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
return self.add_security_headers(r) return self.add_security_headers(r)
def error403(self): def error405(self):
self.add_request(Web.REQUEST_OTHER, request.path) r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405)
r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
return self.add_security_headers(r) return self.add_security_headers(r)
def add_security_headers(self, r): def add_security_headers(self, r):

View File

@ -22,6 +22,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings from onionshare import strings
from onionshare.common import AutoStopTimer from onionshare.common import AutoStopTimer
from .history import IndividualFileHistoryItem
from ..server_status import ServerStatus from ..server_status import ServerStatus
from ..threads import OnionThread from ..threads import OnionThread
from ..threads import AutoStartTimer from ..threads import AutoStartTimer
@ -29,7 +31,7 @@ from ..widgets import Alert
class Mode(QtWidgets.QWidget): class Mode(QtWidgets.QWidget):
""" """
The class that ShareMode and ReceiveMode inherit from. The class that all modes inherit from
""" """
start_server_finished = QtCore.pyqtSignal() start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal()
@ -417,3 +419,46 @@ class Mode(QtWidgets.QWidget):
Handle REQUEST_UPLOAD_CANCELED event. Handle REQUEST_UPLOAD_CANCELED event.
""" """
pass pass
def handle_request_individual_file_started(self, event):
"""
Handle REQUEST_INDVIDIDUAL_FILES_STARTED event.
Used in both Share and Website modes, so implemented here.
"""
item = IndividualFileHistoryItem(self.common, event["data"], event["path"])
self.history.add(event["data"]["id"], item)
self.toggle_history.update_indicator(True)
self.history.in_progress_count += 1
self.history.update_in_progress()
def handle_request_individual_file_progress(self, event):
"""
Handle REQUEST_INDVIDIDUAL_FILES_PROGRESS event.
Used in both Share and Website modes, so implemented here.
"""
self.history.update(event["data"]["id"], event["data"]["bytes"])
# Is the download complete?
if event["data"]["bytes"] == self.web.share_mode.filesize:
# Update completed and in progress labels
self.history.completed_count += 1
self.history.in_progress_count -= 1
self.history.update_completed()
self.history.update_in_progress()
else:
if self.server_status.status == self.server_status.STATUS_STOPPED:
self.history.cancel(event["data"]["id"])
self.history.in_progress_count = 0
self.history.update_in_progress()
def handle_request_individual_file_canceled(self, event):
"""
Handle REQUEST_INDVIDIDUAL_FILES_CANCELED event.
Used in both Share and Website modes, so implemented here.
"""
self.history.cancel(event["data"]["id"])
# Update in progress count
self.history.in_progress_count -= 1
self.history.update_in_progress()

View File

@ -345,61 +345,88 @@ class IndividualFileHistoryItem(HistoryItem):
""" """
Individual file history item, for share mode viewing of individual files Individual file history item, for share mode viewing of individual files
""" """
def __init__(self, common, path): def __init__(self, common, data, path):
super(IndividualFileHistoryItem, self).__init__() super(IndividualFileHistoryItem, self).__init__()
self.status = HistoryItem.STATUS_STARTED self.status = HistoryItem.STATUS_STARTED
self.common = common self.common = common
self.visited = time.time() self.id = id
self.visited_dt = datetime.fromtimestamp(self.visited) self.path = path
self.method = data['method']
self.total_bytes = data['filesize']
self.downloaded_bytes = 0
self.started = time.time()
self.started_dt = datetime.fromtimestamp(self.started)
self.status = HistoryItem.STATUS_STARTED
# Labels # Labels
self.timestamp_label = QtWidgets.QLabel(self.visited_dt.strftime("%b %d, %I:%M%p")) self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p"))
self.path_viewed_label = QtWidgets.QLabel(strings._('gui_individual_file_download').format(path)) self.method_label = QtWidgets.QLabel("{} {}".format(self.method, self.path))
self.status_label = QtWidgets.QLabel()
# Progress bar
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setTextVisible(True)
self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(data['filesize'])
self.progress_bar.setValue(0)
self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
self.progress_bar.total_bytes = data['filesize']
# Text layout
labels_layout = QtWidgets.QHBoxLayout()
labels_layout.addWidget(self.timestamp_label)
labels_layout.addWidget(self.method_label)
labels_layout.addWidget(self.status_label)
# Layout # Layout
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.timestamp_label) layout.addLayout(labels_layout)
layout.addWidget(self.path_viewed_label) layout.addWidget(self.progress_bar)
self.setLayout(layout) self.setLayout(layout)
# All non-GET requests are error 405 Method Not Allowed
if self.method.lower() != 'get':
self.status_label.setText("405")
self.progress_bar.hide()
else:
# Start at 0
self.update(0)
def update(self): def update(self, downloaded_bytes):
self.label.setText(self.get_finished_label_text(self.started_dt)) self.downloaded_bytes = downloaded_bytes
self.status = HistoryItem.STATUS_FINISHED
self.progress_bar.setValue(downloaded_bytes)
if downloaded_bytes == self.progress_bar.total_bytes:
self.progress_bar.hide()
self.status = HistoryItem.STATUS_FINISHED
else:
elapsed = time.time() - self.started
if elapsed < 10:
# Wait a couple of seconds for the download rate to stabilize.
# This prevents a "Windows copy dialog"-esque experience at
# the beginning of the download.
pb_fmt = strings._('gui_all_modes_progress_starting').format(
self.common.human_readable_filesize(downloaded_bytes))
else:
pb_fmt = strings._('gui_all_modes_progress_eta').format(
self.common.human_readable_filesize(downloaded_bytes),
self.estimated_time_remaining)
self.progress_bar.setFormat(pb_fmt)
def cancel(self): def cancel(self):
self.progress_bar.setFormat(strings._('gui_canceled')) self.progress_bar.setFormat(strings._('gui_canceled'))
self.status = HistoryItem.STATUS_CANCELED self.status = HistoryItem.STATUS_CANCELED
class VisitHistoryItem(HistoryItem): @property
""" def estimated_time_remaining(self):
Download history item, for share mode return self.common.estimated_time_remaining(self.downloaded_bytes,
""" self.total_bytes,
def __init__(self, common, id, total_bytes): self.started)
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): class HistoryItemList(QtWidgets.QScrollArea):
""" """

View File

@ -225,21 +225,6 @@ class ShareMode(Mode):
""" """
self.primary_action.hide() self.primary_action.hide()
def handle_request_load(self, event):
"""
Handle REQUEST_LOAD event.
"""
self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message'))
if not self.common.settings.get('close_after_first_download') and not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/':
item = IndividualFileHistoryItem(self.common, event["path"])
self.history.add(0, item)
self.toggle_history.update_indicator(True)
self.history.completed_count += 1
self.history.update_completed()
self.system_tray.showMessage(strings._('systray_individual_file_downloaded_title'), strings._('systray_individual_file_downloaded_message').format(event["path"]))
def handle_request_started(self, event): def handle_request_started(self, event):
""" """
Handle REQUEST_STARTED event. Handle REQUEST_STARTED event.

View File

@ -30,7 +30,7 @@ from onionshare.web import Web
from ..file_selection import FileSelection from ..file_selection import FileSelection
from .. import Mode from .. import Mode
from ..history import History, ToggleHistory, VisitHistoryItem from ..history import History, ToggleHistory
from ...widgets import Alert from ...widgets import Alert
class WebsiteMode(Mode): class WebsiteMode(Mode):
@ -204,21 +204,6 @@ class WebsiteMode(Mode):
""" """
self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message')) 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): def on_reload_settings(self):
""" """
If there were some files listed for sharing, we should be ok to re-enable If there were some files listed for sharing, we should be ok to re-enable

View File

@ -470,6 +470,15 @@ class OnionShareGui(QtWidgets.QMainWindow):
elif event["type"] == Web.REQUEST_UPLOAD_CANCELED: elif event["type"] == Web.REQUEST_UPLOAD_CANCELED:
mode.handle_request_upload_canceled(event) mode.handle_request_upload_canceled(event)
elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_STARTED:
mode.handle_request_individual_file_started(event)
elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_PROGRESS:
mode.handle_request_individual_file_progress(event)
elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_CANCELED:
mode.handle_request_individual_file_canceled(event)
if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE:
Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))

View File

@ -178,7 +178,6 @@
"gui_receive_mode_no_files": "No Files Received Yet", "gui_receive_mode_no_files": "No Files Received Yet",
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving", "gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
"gui_visit_started": "Someone has visited your website {}", "gui_visit_started": "Someone has visited your website {}",
"gui_individual_file_download": "Viewed {}",
"receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_upload_starting": "Upload of total size {} is starting",
"days_first_letter": "d", "days_first_letter": "d",
"hours_first_letter": "h", "hours_first_letter": "h",

19
share/templates/405.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>OnionShare: 405 Method Not Allowed</title>
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
</head>
<body>
<div class="info-wrapper">
<div class="info">
<p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
<p class="info-header">405 Method Not Allowed</p>
</div>
</div>
</body>
</html>