Eyes
This commit adds the concept of eyes. One "eyes" is one instance of a response to GET /stream.mp4. Currently the number of eyes clients can have is unbounded, but this is a DoS vector.
このコミットが含まれているのは:
コミット
84ad17f13d
|
@ -46,6 +46,10 @@ def generate_user(timestamp, token, broadcaster, presence):
|
||||||
},
|
},
|
||||||
'presence': presence,
|
'presence': presence,
|
||||||
'linespan': deque(),
|
'linespan': deque(),
|
||||||
|
'eyes': {
|
||||||
|
'total': 0,
|
||||||
|
'current': {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_default_name(user):
|
def get_default_name(user):
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort
|
from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort
|
||||||
|
|
||||||
from anonstream.captcha import get_captcha_image
|
from anonstream.captcha import get_captcha_image
|
||||||
from anonstream.segments import segments
|
from anonstream.segments import segments, StopSendingSegments
|
||||||
from anonstream.stream import is_online, get_stream_uptime
|
from anonstream.stream import is_online, get_stream_uptime
|
||||||
from anonstream.user import watched
|
from anonstream.user import watched, create_eyes, renew_eyes, ExpiredEyes
|
||||||
from anonstream.routes.wrappers import with_user_from, auth_required
|
from anonstream.routes.wrappers import with_user_from, auth_required
|
||||||
from anonstream.utils.security import generate_csp
|
from anonstream.utils.security import generate_csp
|
||||||
|
|
||||||
|
@ -25,8 +25,13 @@ async def stream(user):
|
||||||
if not is_online():
|
if not is_online():
|
||||||
return abort(404)
|
return abort(404)
|
||||||
|
|
||||||
|
eyes_id = create_eyes(user, dict(request.headers))
|
||||||
def segment_read_hook(uri):
|
def segment_read_hook(uri):
|
||||||
print(f'{uri}: {user["token"]}')
|
try:
|
||||||
|
renew_eyes(user, eyes_id)
|
||||||
|
except ExpiredEyes as e:
|
||||||
|
raise StopSendingSegments(f'eyes {eyes_id} expired: {e}') from e
|
||||||
|
print(f'{uri}: {eyes_id}~{user["token"]}')
|
||||||
watched(user)
|
watched(user)
|
||||||
|
|
||||||
generator = segments(segment_read_hook, token=user['token'])
|
generator = segments(segment_read_hook, token=user['token'])
|
||||||
|
|
|
@ -22,6 +22,9 @@ class Stale(Exception):
|
||||||
class UnsafePath(Exception):
|
class UnsafePath(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class StopSendingSegments(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def get_mtime():
|
def get_mtime():
|
||||||
try:
|
try:
|
||||||
mtime = os.path.getmtime(CONFIG['SEGMENT_PLAYLIST'])
|
mtime = os.path.getmtime(CONFIG['SEGMENT_PLAYLIST'])
|
||||||
|
@ -148,7 +151,15 @@ async def segments(segment_read_hook=lambda uri: None, token=None):
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
segment_read_hook(uri)
|
try:
|
||||||
|
segment_read_hook(uri)
|
||||||
|
except StopSendingSegments as e:
|
||||||
|
reason, *_ = e.args
|
||||||
|
print(
|
||||||
|
f'[debug @ {time.time():.3f}: {token=}] '
|
||||||
|
f'told to stop sending segments: {reason}'
|
||||||
|
)
|
||||||
|
break
|
||||||
try:
|
try:
|
||||||
async with aiofiles.open(path, 'rb') as fp:
|
async with aiofiles.open(path, 'rb') as fp:
|
||||||
while chunk := await fp.read(8192):
|
while chunk := await fp.read(8192):
|
||||||
|
|
|
@ -25,6 +25,9 @@ class BadAppearance(ValueError):
|
||||||
class BadCaptcha(ValueError):
|
class BadCaptcha(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ExpiredEyes(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def add_state(user, **state):
|
def add_state(user, **state):
|
||||||
state_id = time.time_ns() // 1_000_000
|
state_id = time.time_ns() // 1_000_000
|
||||||
user['state'][state_id] = state
|
user['state'][state_id] = state
|
||||||
|
@ -219,3 +222,28 @@ def get_users_by_presence(timestamp):
|
||||||
for user in get_users_and_update_presence(timestamp):
|
for user in get_users_and_update_presence(timestamp):
|
||||||
users_by_presence[user['presence']].append(user)
|
users_by_presence[user['presence']].append(user)
|
||||||
return users_by_presence
|
return users_by_presence
|
||||||
|
|
||||||
|
@with_timestamp
|
||||||
|
def create_eyes(timestamp, user, headers):
|
||||||
|
eyes_id = user['eyes']['total']
|
||||||
|
user['eyes']['total'] += 1
|
||||||
|
user['eyes']['current'][eyes_id] = {
|
||||||
|
'id': eyes_id,
|
||||||
|
'token': user['token'],
|
||||||
|
'n_segments': 0,
|
||||||
|
'headers': headers,
|
||||||
|
'created': timestamp,
|
||||||
|
'renewed': timestamp,
|
||||||
|
}
|
||||||
|
return eyes_id
|
||||||
|
|
||||||
|
@with_timestamp
|
||||||
|
def renew_eyes(timestamp, user, eyes_id):
|
||||||
|
try:
|
||||||
|
eyes = user['eyes']['current'][eyes_id]
|
||||||
|
except KeyError:
|
||||||
|
raise ExpiredEyes(None)
|
||||||
|
if timestamp - eyes['renewed'] >= 20.0: # TODO remove magic number
|
||||||
|
user['eyes']['current'].pop(eyes_id)
|
||||||
|
raise ExpiredEyes(eyes)
|
||||||
|
eyes['renewed'] = timestamp
|
||||||
|
|
読み込み中…
新しいイシューから参照