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
filesize = os.path.getsize(filesystem_path)
# TODO: Tell GUI the download started
#self.web.add_request(self.web.REQUEST_STARTED, path, {
# 'id': download_id,
# 'use_gzip': use_gzip
#})
# Each download has a unique id
download_id = self.download_count
self.download_count += 1
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():
chunk_size = 102400 # 100kb
fp = open(file_to_download, 'rb')
done = False
canceled = False
while not done:
chunk = fp.read(chunk_size)
if chunk == b'':
@ -152,7 +162,7 @@ class SendBaseModeWeb:
try:
yield chunk
# TODO: Tell GUI the progress
# Tell GUI the progress
downloaded_bytes = fp.tell()
percent = (1.0 * downloaded_bytes / filesize) * 100
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))
sys.stdout.flush()
#self.web.add_request(self.web.REQUEST_PROGRESS, path, {
# 'id': download_id,
# 'bytes': downloaded_bytes
# })
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, {
'id': download_id,
'bytes': downloaded_bytes
})
done = False
except:
# Looks like the download was canceled
done = True
canceled = True
# TODO: Tell the GUI the download has canceled
#self.web.add_request(self.web.REQUEST_CANCELED, path, {
# 'id': download_id
#})
# Tell the GUI the individual file was canceled
self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, {
'id': download_id
})
fp.close()

View File

@ -37,15 +37,18 @@ class Web:
REQUEST_LOAD = 0
REQUEST_STARTED = 1
REQUEST_PROGRESS = 2
REQUEST_OTHER = 3
REQUEST_CANCELED = 4
REQUEST_RATE_LIMIT = 5
REQUEST_UPLOAD_FILE_RENAMED = 6
REQUEST_UPLOAD_SET_DIR = 7
REQUEST_UPLOAD_FINISHED = 8
REQUEST_UPLOAD_CANCELED = 9
REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10
REQUEST_INVALID_PASSWORD = 11
REQUEST_CANCELED = 3
REQUEST_RATE_LIMIT = 4
REQUEST_UPLOAD_FILE_RENAMED = 5
REQUEST_UPLOAD_SET_DIR = 6
REQUEST_UPLOAD_FINISHED = 7
REQUEST_UPLOAD_CANCELED = 8
REQUEST_INDIVIDUAL_FILE_STARTED = 9
REQUEST_INDIVIDUAL_FILE_PROGRESS = 10
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'):
self.common = common
@ -193,15 +196,18 @@ class Web:
r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401)
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):
self.add_request(Web.REQUEST_OTHER, request.path)
r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
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)
def error405(self):
r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405)
return self.add_security_headers(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.common import AutoStopTimer
from .history import IndividualFileHistoryItem
from ..server_status import ServerStatus
from ..threads import OnionThread
from ..threads import AutoStartTimer
@ -29,7 +31,7 @@ from ..widgets import Alert
class Mode(QtWidgets.QWidget):
"""
The class that ShareMode and ReceiveMode inherit from.
The class that all modes inherit from
"""
start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal()
@ -417,3 +419,46 @@ class Mode(QtWidgets.QWidget):
Handle REQUEST_UPLOAD_CANCELED event.
"""
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
"""
def __init__(self, common, path):
def __init__(self, common, data, path):
super(IndividualFileHistoryItem, self).__init__()
self.status = HistoryItem.STATUS_STARTED
self.common = common
self.visited = time.time()
self.visited_dt = datetime.fromtimestamp(self.visited)
self.id = id
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
self.timestamp_label = QtWidgets.QLabel(self.visited_dt.strftime("%b %d, %I:%M%p"))
self.path_viewed_label = QtWidgets.QLabel(strings._('gui_individual_file_download').format(path))
self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p"))
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 = QtWidgets.QVBoxLayout()
layout.addWidget(self.timestamp_label)
layout.addWidget(self.path_viewed_label)
layout.addLayout(labels_layout)
layout.addWidget(self.progress_bar)
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):
self.label.setText(self.get_finished_label_text(self.started_dt))
def update(self, downloaded_bytes):
self.downloaded_bytes = downloaded_bytes
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):
self.progress_bar.setFormat(strings._('gui_canceled'))
self.status = HistoryItem.STATUS_CANCELED
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
@property
def estimated_time_remaining(self):
return self.common.estimated_time_remaining(self.downloaded_bytes,
self.total_bytes,
self.started)
class HistoryItemList(QtWidgets.QScrollArea):
"""

View File

@ -225,21 +225,6 @@ class ShareMode(Mode):
"""
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):
"""
Handle REQUEST_STARTED event.

View File

@ -30,7 +30,7 @@ from onionshare.web import Web
from ..file_selection import FileSelection
from .. import Mode
from ..history import History, ToggleHistory, VisitHistoryItem
from ..history import History, ToggleHistory
from ...widgets import Alert
class WebsiteMode(Mode):
@ -204,21 +204,6 @@ class WebsiteMode(Mode):
"""
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

View File

@ -470,6 +470,15 @@ class OnionShareGui(QtWidgets.QMainWindow):
elif event["type"] == Web.REQUEST_UPLOAD_CANCELED:
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:
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_autostop_timer_waiting": "Waiting to finish receiving",
"gui_visit_started": "Someone has visited your website {}",
"gui_individual_file_download": "Viewed {}",
"receive_mode_upload_starting": "Upload of total size {} is starting",
"days_first_letter": "d",
"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>