diff --git a/anonstream/__init__.py b/anonstream/__init__.py index 4ba49ac..8d2fbff 100644 --- a/anonstream/__init__.py +++ b/anonstream/__init__.py @@ -31,16 +31,19 @@ async def create_app(): 'MAX_CHAT_SCROLLBACK': config['memory']['chat_scrollback'], 'CHECKUP_PERIOD_USER': config['ratelimits']['user_absence'], 'CHECKUP_PERIOD_CAPTCHA': config['ratelimits']['captcha_expiry'], - 'THRESHOLD_IDLE': config['thresholds']['idle'], - 'THRESHOLD_ABSENT': config['thresholds']['absent'], + 'THRESHOLD_USER_IDLE': config['thresholds']['user_idle'], + 'THRESHOLD_USER_ABSENT': config['thresholds']['user_absent'], + 'THRESHOLD_NOJS_CHAT_TIMEOUT': config['thresholds']['nojs_chat_timeout'], 'CHAT_COMMENT_MAX_LENGTH': config['chat']['max_name_length'], 'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'], 'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'], 'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']), }) + assert app.config['MAX_NOTICES'] >= 0 + assert app.config['MAX_CHAT_SCROLLBACK'] >= 0 assert app.config['MAX_CHAT_MESSAGES'] >= app.config['MAX_CHAT_SCROLLBACK'] - assert app.config['THRESHOLD_ABSENT'] >= app.config['THRESHOLD_IDLE'] + assert app.config['THRESHOLD_USER_ABSENT'] >= app.config['THRESHOLD_USER_IDLE'] app.messages_by_id = OrderedDict() app.users_by_token = {} diff --git a/anonstream/chat.py b/anonstream/chat.py index 529c3be..05b3792 100644 --- a/anonstream/chat.py +++ b/anonstream/chat.py @@ -3,9 +3,10 @@ from datetime import datetime from quart import current_app, escape -from anonstream.helpers.chat import generate_nonce_hash +from anonstream.helpers.chat import generate_nonce_hash, get_scrollback from anonstream.utils.chat import message_for_websocket +CONFIG = current_app.config MESSAGES_BY_ID = current_app.messages_by_id MESSAGES = current_app.messages USERS_BY_TOKEN = current_app.users_by_token @@ -25,7 +26,7 @@ def messages_for_websocket(): user=USERS_BY_TOKEN[message['token']], message=message, ), - MESSAGES, + get_scrollback(MESSAGES), )) async def add_chat_message(user, nonce, comment): @@ -35,6 +36,8 @@ async def add_chat_message(user, nonce, comment): raise Rejected('Discarded suspected duplicate message') if len(comment) == 0: raise Rejected('Message was empty') + if len(comment) > 512: + raise Rejected('Message exceeded 512 chars') # add message timestamp_ms = time.time_ns() // 1_000_000 @@ -62,6 +65,9 @@ async def add_chat_message(user, nonce, comment): 'markup': markup, } + while len(MESSAGES_BY_ID) > CONFIG['MAX_CHAT_MESSAGES']: + MESSAGES_BY_ID.pop(last=False) + # broadcast message to websockets await broadcast( USERS, diff --git a/anonstream/helpers/chat.py b/anonstream/helpers/chat.py index 807218f..45fad4f 100644 --- a/anonstream/helpers/chat.py +++ b/anonstream/helpers/chat.py @@ -7,3 +7,9 @@ CONFIG = current_app.config def generate_nonce_hash(nonce): parts = CONFIG['SECRET_KEY'] + b'nonce-hash\0' + nonce.encode() return hashlib.sha256(parts).digest() + +def get_scrollback(messages): + n = CONFIG['MAX_CHAT_SCROLLBACK'] + if len(messages) < n: + return messages + return list(messages)[-n:] diff --git a/anonstream/helpers/user.py b/anonstream/helpers/user.py index 74e488b..b262393 100644 --- a/anonstream/helpers/user.py +++ b/anonstream/helpers/user.py @@ -44,14 +44,14 @@ def get_default_name(user): ) def is_watching(timestamp, user): - return user['watching_last'] >= timestamp - CONFIG['THRESHOLD_IDLE'] + return user['watching_last'] >= timestamp - CONFIG['THRESHOLD_USER_IDLE'] def is_idle(timestamp, user): return is_present(timestamp, user) and not is_watching(timestamp, user) def is_present(timestamp, user): return ( - user['seen']['last'] >= timestamp - CONFIG['THRESHOLD_ABSENT'] + user['seen']['last'] >= timestamp - CONFIG['THRESHOLD_USER_ABSENT'] or len(user['websockets']) > 0 ) diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index 43c6845..725ce72 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -3,8 +3,9 @@ from quart import current_app, request, render_template, redirect, url_for, esca from anonstream.stream import get_stream_title from anonstream.user import add_notice, pop_notice, try_change_appearance from anonstream.chat import add_chat_message, Rejected -from anonstream.routes.wrappers import with_user_from +from anonstream.routes.wrappers import with_user_from, render_template_with_etag from anonstream.helpers.user import get_default_name +from anonstream.helpers.chat import get_scrollback from anonstream.utils.chat import generate_nonce from anonstream.utils.user import concatenate_for_notice @@ -20,14 +21,20 @@ async def nojs_info(user): @current_app.route('/chat/messages.html') @with_user_from(request) async def nojs_chat(user): - return await render_template( + return await render_template_with_etag( 'nojs_chat.html', user=user, users_by_token=current_app.users_by_token, - messages=current_app.messages, + messages=get_scrollback(current_app.messages), + timeout=current_app.config['THRESHOLD_NOJS_CHAT_TIMEOUT'], get_default_name=get_default_name, ) +@current_app.route('/chat/redirect') +@with_user_from(request) +async def nojs_chat_redirect(user): + return redirect(url_for('nojs_chat', _anchor='end')) + @current_app.route('/chat/form.html') @with_user_from(request) async def nojs_form(user): diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index b50dbec..651d73c 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -1,7 +1,8 @@ +import hashlib import time from functools import wraps -from quart import current_app, request, abort, make_response +from quart import current_app, request, abort, make_response, render_template, request from werkzeug.security import check_password_hash from anonstream.user import sunset, user_for_websocket @@ -97,3 +98,12 @@ def with_user_from(context): return wrapper return with_user_from_context + +async def render_template_with_etag(*args, **kwargs): + rendered_template = await render_template(*args, **kwargs) + tag = hashlib.sha256(rendered_template.encode()).hexdigest() + etag = f'W/"{tag}"' + if request.if_none_match.contains_weak(tag): + return '', 304, {'ETag': etag} + else: + return rendered_template, {'ETag': etag} diff --git a/anonstream/static/anonstream.js b/anonstream/static/anonstream.js index 3e5a66d..bc691a3 100644 --- a/anonstream/static/anonstream.js +++ b/anonstream/static/anonstream.js @@ -1,5 +1,5 @@ /* token */ -const token = document.querySelector("body").dataset.token; +const token = document.body.dataset.token; /* insert js-only markup */ const jsmarkup_style_color = '' @@ -7,7 +7,7 @@ const jsmarkup_style_tripcode_display = '' const jsmarkup_info = '
'; const jsmarkup_info_title = '