Merge branch 'ros-fixes' into release-2.5

This commit is contained in:
Micah Lee 2022-01-17 09:51:25 -08:00
commit c8ea770223
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73
6 changed files with 99 additions and 81 deletions

View File

@ -55,12 +55,8 @@ class ModeSettings:
"disable_text": False, "disable_text": False,
"disable_files": False, "disable_files": False,
}, },
"website": { "website": {"disable_csp": False, "custom_csp": None, "filenames": []},
"disable_csp": False, "chat": {},
"custom_csp": None,
"filenames": []
},
"chat": {"room": "default"},
} }
self._settings = {} self._settings = {}

View File

@ -9,31 +9,25 @@ $(function () {
); );
// Store current username received from app context // Store current username received from app context
var current_username = $('#username').val(); var current_username = $('#username').val().trim();
// On browser connect, emit a socket event to be added to
// room and assigned random username
socket.on('connect', function () {
socket.emit('joined', {});
});
// Triggered on any status change by any user, such as some // Triggered on any status change by any user, such as some
// user joined, or changed username, or left, etc. // user joined, or changed username, or left, etc.
socket.on('status', function (data) { socket.on('status', function (data) {
addMessageToRoom(data, current_username, 'status'); addMessageToPanel(data, current_username, 'status');
console.log(data, current_username); console.log(data, current_username);
}); });
// Triggered when message is received from a user. Even when sent // Triggered when message is received from a user. Even when sent
// by self, it get triggered after the server sends back the emit. // by self, it get triggered after the server sends back the emit.
socket.on('message', function (data) { socket.on('chat_message', function (data) {
addMessageToRoom(data, current_username, 'chat'); addMessageToPanel(data, current_username, 'chat');
console.log(data, current_username); console.log(data, current_username);
}); });
// Triggered when disconnected either by server stop or timeout // Triggered when disconnected either by server stop or timeout
socket.on('disconnect', function (data) { socket.on('disconnect', function (data) {
addMessageToRoom({ 'msg': 'The chat server is disconnected.' }, current_username, 'status'); addMessageToPanel({ 'msg': 'The chat server is disconnected.' }, current_username, 'status');
}) })
socket.on('connect_error', function (error) { socket.on('connect_error', function (error) {
console.log("error"); console.log("error");
@ -66,7 +60,7 @@ $(function () {
}); });
}); });
var addMessageToRoom = function (data, current_username, messageType) { var addMessageToPanel = function (data, current_username, messageType) {
var scrollDiff = getScrollDiffBefore(); var scrollDiff = getScrollDiffBefore();
if (messageType === 'status') { if (messageType === 'status') {
addStatusMessage(data.msg); addStatusMessage(data.msg);
@ -88,7 +82,7 @@ var emitMessage = function (socket) {
var updateUsername = function (socket) { var updateUsername = function (socket) {
var username = $('#username').val(); var username = $('#username').val();
if (!checkUsernameExists(username) && !checkUsernameTooLong(username)) { if (!checkUsernameExists(username) && !checkUsernameTooLong(username) && !checkUsernameAscii(username)) {
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: `http://${document.domain}:${location.port}/update-session-username`, url: `http://${document.domain}:${location.port}/update-session-username`,
@ -99,6 +93,8 @@ var updateUsername = function (socket) {
console.log(response); console.log(response);
if (response.success && response.username == username) { if (response.success && response.username == username) {
socket.emit('update_username', { username: username }); socket.emit('update_username', { username: username });
} else {
addStatusMessage("Failed to update username.")
} }
}); });
return username; return username;
@ -121,6 +117,16 @@ var createUserListHTML = function (connected_users, current_user) {
return userListHTML; return userListHTML;
} }
var checkUsernameAscii = function (username) {
// ASCII characters have code points in the range U+0000-U+007F.
$('#username-error').text('');
if (!/^[\u0000-\u007f]*$/.test(username)) {
$('#username-error').text('Non-ASCII usernames are not supported.');
return true;
}
return false;
}
var checkUsernameExists = function (username) { var checkUsernameExists = function (username) {
$('#username-error').text(''); $('#username-error').text('');
var userMatches = $('#user-list li').filter(function () { var userMatches = $('#user-list li').filter(function () {

View File

@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from flask import request, render_template, make_response, jsonify, session from flask import request, render_template, make_response, jsonify, session
from flask_socketio import emit, join_room, leave_room from flask_socketio import emit, ConnectionRefusedError
class ChatModeWeb: class ChatModeWeb:
@ -33,7 +33,7 @@ class ChatModeWeb:
self.web = web self.web = web
# This tracks users in the room # This tracks users in the server
self.connected_users = [] self.connected_users = []
# This tracks the history id # This tracks the history id
@ -47,6 +47,15 @@ class ChatModeWeb:
self.define_routes() self.define_routes()
def validate_username(self, username):
username = username.strip()
return (
username
and username.isascii()
and username not in self.connected_users
and len(username) < 128
)
def define_routes(self): def define_routes(self):
""" """
The web app routes for chatting The web app routes for chatting
@ -61,7 +70,6 @@ class ChatModeWeb:
if session.get("name") if session.get("name")
else self.common.build_username() else self.common.build_username()
) )
session["room"] = self.web.settings.default_settings["chat"]["room"]
self.web.add_request( self.web.add_request(
request.path, request.path,
{"id": history_id, "status_code": 200}, {"id": history_id, "status_code": 200},
@ -79,12 +87,9 @@ class ChatModeWeb:
def update_session_username(): def update_session_username():
history_id = self.cur_history_id history_id = self.cur_history_id
data = request.get_json() data = request.get_json()
if ( username = data.get("username", session.get("name")).strip()
data.get("username", "") if self.validate_username(username):
and data.get("username", "") not in self.connected_users session["name"] = username
and len(data.get("username", "")) < 128
):
session["name"] = data.get("username", session.get("name"))
self.web.add_request( self.web.add_request(
request.path, request.path,
{"id": history_id, "status_code": 200}, {"id": history_id, "status_code": 200},
@ -111,12 +116,12 @@ class ChatModeWeb:
) )
return r return r
@self.web.socketio.on("joined", namespace="/chat") @self.web.socketio.on("connect", namespace="/chat")
def joined(message): def server_connect():
"""Sent by clients when they enter a room. """Sent by clients when they enter a room.
A status message is broadcast to all people in the room.""" A status message is broadcast to all people in the room."""
if self.validate_username(session.get("name")):
self.connected_users.append(session.get("name")) self.connected_users.append(session.get("name"))
join_room(session.get("room"))
emit( emit(
"status", "status",
{ {
@ -125,26 +130,29 @@ class ChatModeWeb:
"connected_users": self.connected_users, "connected_users": self.connected_users,
"user": session.get("name"), "user": session.get("name"),
}, },
room=session.get("room"), broadcast=True,
) )
else:
raise ConnectionRefusedError('You are active from another session!')
@self.web.socketio.on("text", namespace="/chat") @self.web.socketio.on("text", namespace="/chat")
def text(message): def text(message):
"""Sent by a client when the user entered a new message. """Sent by a client when the user entered a new message.
The message is sent to all people in the room.""" The message is sent to all people in the server."""
emit( emit(
"message", "chat_message",
{"username": session.get("name"), "msg": message["msg"]}, {"username": session.get("name"), "msg": message["msg"]},
room=session.get("room"), broadcast=True,
) )
@self.web.socketio.on("update_username", namespace="/chat") @self.web.socketio.on("update_username", namespace="/chat")
def update_username(message): def update_username(message):
"""Sent by a client when the user updates their username. """Sent by a client when the user updates their username.
The message is sent to all people in the room.""" The message is sent to all people in the server."""
current_name = session.get("name") current_name = session.get("name")
if message.get("username", ""): new_name = message.get("username", "").strip()
session["name"] = message["username"] if self.validate_username(new_name):
session["name"] = new_name
self.connected_users[ self.connected_users[
self.connected_users.index(current_name) self.connected_users.index(current_name)
] = session.get("name") ] = session.get("name")
@ -158,20 +166,25 @@ class ChatModeWeb:
"old_name": current_name, "old_name": current_name,
"new_name": session.get("name"), "new_name": session.get("name"),
}, },
room=session.get("room"), broadcast=True,
)
else:
emit(
"status",
{"msg": "Failed to update username."},
) )
@self.web.socketio.on("disconnect", namespace="/chat") @self.web.socketio.on("disconnect", namespace="/chat")
def disconnect(): def disconnect():
"""Sent by clients when they disconnect from a room. """Sent by clients when they disconnect.
A status message is broadcast to all people in the room.""" A status message is broadcast to all people in the server."""
if session.get("name") in self.connected_users:
self.connected_users.remove(session.get("name")) self.connected_users.remove(session.get("name"))
leave_room(session.get("room"))
emit( emit(
"status", "status",
{ {
"msg": "{} has left the room.".format(session.get("name")), "msg": "{} has left the room.".format(session.get("name")),
"connected_users": self.connected_users, "connected_users": self.connected_users,
}, },
room=session.get("room"), broadcast=True,
) )

View File

@ -378,7 +378,7 @@ class ReceiveModeRequest(Request):
# Figure out what files should be saved # Figure out what files should be saved
now = datetime.now() now = datetime.now()
date_dir = now.strftime("%Y-%m-%d") date_dir = now.strftime("%Y-%m-%d")
time_dir = now.strftime("%H%M%S") time_dir = now.strftime("%H%M%S%f")
self.receive_mode_dir = os.path.join( self.receive_mode_dir = os.path.join(
self.web.settings.get("receive", "data_dir"), date_dir, time_dir self.web.settings.get("receive", "data_dir"), date_dir, time_dir
) )

View File

@ -481,6 +481,7 @@ class IndividualFileHistoryItem(HistoryItem):
self.common.gui.css["history_individual_file_timestamp_label"] self.common.gui.css["history_individual_file_timestamp_label"]
) )
self.path_label = QtWidgets.QLabel(self.path) self.path_label = QtWidgets.QLabel(self.path)
self.path_label.setTextFormat(QtCore.Qt.PlainText)
self.path_label.setStyleSheet(self.common.gui.css["history_default_label"]) self.path_label.setStyleSheet(self.common.gui.css["history_default_label"])
self.status_code_label = QtWidgets.QLabel() self.status_code_label = QtWidgets.QLabel()

View File

@ -1,3 +1,4 @@
import glob
import pytest import pytest
import os import os
import requests import requests
@ -35,15 +36,15 @@ class TestReceive(GuiBaseTest):
now = datetime.now() now = datetime.now()
for _ in range(10): for _ in range(10):
date_dir = now.strftime("%Y-%m-%d") date_dir = now.strftime("%Y-%m-%d")
if identical_files_at_once:
time_dir = now.strftime("%H%M%S-1")
else:
time_dir = now.strftime("%H%M%S") time_dir = now.strftime("%H%M%S")
receive_mode_dir = os.path.join( receive_mode_dir = os.path.join(
tab.settings.get("receive", "data_dir"), date_dir, time_dir tab.settings.get("receive", "data_dir"), date_dir, time_dir
) )
expected_filename = os.path.join(receive_mode_dir, expected_basename) # The directories have microseconds in the name, so we need
if os.path.exists(expected_filename): # to use globbing against directory names containing the same
# second in order to try to find the file.
for path in glob.glob(receive_mode_dir + "*"):
if os.path.exists(os.path.join(path, expected_basename)):
exists = True exists = True
break break
now = now - timedelta(seconds=1) now = now - timedelta(seconds=1)
@ -83,13 +84,14 @@ class TestReceive(GuiBaseTest):
for _ in range(10): for _ in range(10):
date_dir = now.strftime("%Y-%m-%d") date_dir = now.strftime("%Y-%m-%d")
time_dir = now.strftime("%H%M%S") time_dir = now.strftime("%H%M%S")
expected_filename = os.path.join( expected_estimated_filename = os.path.join(
tab.settings.get("receive", "data_dir"), tab.settings.get("receive", "data_dir"),
date_dir, date_dir,
f"{time_dir}-message.txt", f"{time_dir}*-message.txt",
) )
if os.path.exists(expected_filename): for path in glob.glob(expected_estimated_filename):
with open(expected_filename) as f: if os.path.exists(path):
with open(path) as f:
assert f.read() == message assert f.read() == message
exists = True exists = True