xenu_nntp/lib/nntp/tiny/session.py

782 lines
22 KiB
Python
Raw Normal View History

2024-11-23 22:40:06 -05:00
import re
2024-11-20 21:17:03 -05:00
import enum
import socket
2024-11-23 22:40:06 -05:00
import datetime
import fnmatch
2024-11-25 20:27:14 -05:00
import traceback
2024-11-20 21:17:03 -05:00
2024-11-22 23:57:14 -05:00
from typing import Optional
2024-11-23 22:40:06 -05:00
from nntp.tiny.buffer import LineBuffer, BufferOverflow
2024-11-23 00:20:06 -05:00
from nntp.tiny.db import Database
2024-11-22 23:57:14 -05:00
from nntp.tiny.server import Server, ServerCapability
from nntp.tiny.response import Response, ResponseCode
from nntp.tiny.newsgroup import Newsgroup
from nntp.tiny.message import Message
2024-11-20 21:17:03 -05:00
class SessionState(enum.Flag):
2024-11-26 10:27:03 -05:00
ACTIVE = 1
2024-11-20 21:17:03 -05:00
AUTH_OK = enum.auto()
AUTH_POST = enum.auto()
2024-11-26 10:44:21 -05:00
class SessionMode(enum.Enum):
READER = 1
2024-11-25 20:27:55 -05:00
class MessagePart(enum.Enum):
HEAD = 1
BODY = enum.auto()
WHOLE = enum.auto()
2024-11-22 23:57:14 -05:00
class MessageRange():
__slots__ = 'id', 'min', 'max',
2024-11-25 00:48:56 -05:00
RE_NUM = re.compile(r'^(\d+)$')
RE_RANGE = re.compile(r'^(\d+)-(\d+)$')
RE_RANGE_LOWER = re.compile(r'^(\d+$)-$')
RE_RANGE_UPPER = re.compile(r'^-(\d+$)$')
2024-11-22 23:57:14 -05:00
def __init__(self):
self.id: int = None
self.min: int = None
self.max: int = None
def __str__(self):
if self.id is not None:
return str(self.id)
if self.min is not None and self.max is None:
return "%d-" % (self.min)
elif self.min is not None and self.max is not None:
return "%d-%d" % (self.min, self.max)
elif self.min is None and self.max is not None:
return "-%d" % (self.max)
return "?"
2024-11-23 00:20:06 -05:00
def where(self):
2024-11-22 23:57:14 -05:00
if self.id is not None:
2024-11-23 00:20:06 -05:00
return "id = %d" % (self.id)
2024-11-22 23:57:14 -05:00
if self.min is not None and self.max is None:
2024-11-23 00:20:06 -05:00
return "id >= %d" % (self.min)
2024-11-22 23:57:14 -05:00
elif self.min is not None and self.max is not None:
2024-11-23 00:20:06 -05:00
return "id >= %d and id <= %d" % (self.min, self.max)
2024-11-22 23:57:14 -05:00
elif self.min is None and self.max is not None:
2024-11-23 00:20:06 -05:00
return "id <= %d" % (self.max)
2024-11-22 23:57:14 -05:00
@staticmethod
def parse(r: str):
match = __class__.RE_NUM.match(r)
if match:
obj = __class__()
2024-11-25 21:52:07 -05:00
obj.id = int(match[1])
2024-11-22 23:57:14 -05:00
return obj
match = __class__.RE_RANGE.match(r)
if match:
obj = __class__()
2024-11-25 21:52:07 -05:00
obj.min = int(match[1])
obj.max = int(match[2])
2024-11-22 23:57:14 -05:00
return obj
match = __class__.RE_RANGE_LOWER.match(r)
if match:
obj = __class__()
2024-11-25 21:52:07 -05:00
obj.min = int(match[1])
2024-11-22 23:57:14 -05:00
return obj
match = __class__.RE_RANGE_UPPER.match(r)
if match:
obj = __class__()
2024-11-25 21:52:07 -05:00
obj.max = int(match[1])
2024-11-22 23:57:14 -05:00
return obj
2024-11-20 21:17:03 -05:00
class Session():
2024-11-25 17:17:08 -05:00
NNTP_VERSION = 2
2024-11-20 21:17:03 -05:00
NNTP_CAPABILITIES = [
2024-11-25 00:16:15 -05:00
'VERSION %d' % (NNTP_VERSION),
2024-11-20 21:17:03 -05:00
'READER',
'HDR',
'NEWNEWS',
'LIST ACTIVE NEWSGROUP OVERVIEW.FMT SUBSCRIPTIONS',
'OVER MSGID'
]
2024-11-25 17:17:08 -05:00
RE_SPLIT = re.compile(r'\s+')
2024-11-20 21:17:03 -05:00
def __init__(self, server: Server, sock: socket.socket):
self.server: Server = server
2024-11-23 00:20:06 -05:00
self.db: Database = server.db
2024-11-20 21:17:03 -05:00
self.sock: socket.socket = sock
self.buf: LineBuffer = LineBuffer()
2024-11-26 10:44:21 -05:00
self.state: SessionState = SessionState.ACTIVE
self.mode: SessionMode = SessionMode.READER
2024-11-20 21:17:03 -05:00
2024-11-25 20:27:55 -05:00
self.newsgroup: Optional[Newsgroup] = None
self.article_id: Optional[int] = None
2024-11-22 23:57:14 -05:00
2024-11-20 21:17:03 -05:00
def readline(self):
return self.buf.readline(self.sock)
def print(self, text: str, end: str="\r\n"):
2024-11-25 15:57:20 -05:00
return self.sock.send(bytes(text + end, 'utf-8'))
2024-11-20 21:17:03 -05:00
def end(self):
return self.print('.')
def respond(self, code: ResponseCode, message: str=None, body=None):
response = Response(code, message, body)
return self.print(str(response))
def _cmd_capabilities(self, *args):
self.respond(ResponseCode.NNTP_CAPABILITIES_FOLLOW)
if self.state & SessionState.AUTH_POST:
self.print('POST')
if self.state & SessionState.AUTH_OK:
self.print('AUTHUSER INFO')
for item in self.NNTP_CAPABILITIES:
self.print(item)
self.end()
2024-11-26 10:44:21 -05:00
def _cmd_mode(self, *args):
if len(args) != 1 or args[0] != 'READER':
return self.respond(ResponseCode.NNTP_SYNTAX_ERROR)
self.mode = SessionMode.READER
return self.respond(ResponseCode.NNTP_POST_PROHIBITED)
2024-11-22 23:57:14 -05:00
def _cmd_group(self, name: str):
if name not in self.server.newsgroups:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_FOUND)
newsgroup = self.server.newsgroups[name]
sql = """
select
count(id),
min(id),
max(id)
from
newsgroup_message
where
newsgroup_id = ?
"""
2024-11-25 00:49:24 -05:00
cr = self.db.execute(sql, (newsgroup.id,))
2024-11-22 23:57:14 -05:00
row = cr.fetchone()
if row is None:
text = "%d %d %d %s" % (
0, 0, 0, newsgroup.name
)
self.article_id = None
else:
text = "%d %d %d %s" % (
row[0],
row[1],
row[2],
newsgroup.name
)
2024-11-22 23:57:14 -05:00
self.article_id = row[1]
2024-11-22 23:57:14 -05:00
self.newsgroup = newsgroup
2024-11-22 23:57:14 -05:00
return self.respond(ResponseCode.NNTP_GROUP_LISTING, text)
2024-11-22 23:57:14 -05:00
2024-11-26 12:08:56 -05:00
def _cmd_last(self):
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
if self.article_id is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
sql = """
select
max(id)
from
newsgroup_message
where
newsgroup_id = ?
and id < ?
"""
cr = self.db.execute(sql, (self.newsgroup.id, self.article_id))
row = cr.fetchone()
if row is None or row[0] is None:
return self.respond(ResponseCode.NNTP_ARTICLE_NO_PREVIOUS)
self.article_id = row[0]
return self.respond(ResponseCode.NNTP_ARTICLE_STAT_RESPONSE)
def _cmd_next(self):
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
if self.article_id is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
sql = """
select
min(id)
from
newsgroup_message
where
newsgroup_id = ?
and id > ?
"""
cr = self.db.execute(sql, (self.newsgroup.id, self.article_id))
row = cr.fetchone()
if row is None or row[0] is None:
return self.respond(ResponseCode.NNTP_ARTICLE_NO_NEXT)
self.article_id = row[0]
return self.respond(ResponseCode.NNTP_ARTICLE_STAT_RESPONSE)
2024-11-23 00:20:06 -05:00
def _newsgroup_summary(self, newsgroup: Newsgroup) -> str:
sql = """
select
count(id),
min(id),
max(id)
from
newsgroup_message
where
newsgroup_id = ?
"""
cr = self.db.execute(sql, (newsgroup.id))
row = cr.fetchone()
return "%d %d %d %s" % (
row[0],
row[1],
row[2],
newsgroup.name
)
2024-11-22 23:57:14 -05:00
def _cmd_listgroup(self, *args):
newsgroup = self.newsgroup
2024-11-25 17:18:22 -05:00
2024-11-22 23:57:14 -05:00
if len(args) == 0 and newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
elif len(args) > 0:
newsgroup = self.server.newsgroups.get(args[0])
if newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_FOUND)
2024-11-23 00:20:06 -05:00
sql = """
select
id
from
newsgroup_message
where
newsgroup_id = ?
"""
2024-11-22 23:57:14 -05:00
if len(args) > 1:
2024-11-23 00:20:06 -05:00
msgrange = MessageRange.parse(args[1])
2024-11-25 21:52:27 -05:00
sql += " and " + msgrange.where()
2024-11-22 23:57:14 -05:00
2024-11-23 00:22:24 -05:00
summary = self._newsgroup_summary(newsgroup)
cr = self.db.execute(sql, (newsgroup.id))
2024-11-22 23:57:14 -05:00
2024-11-23 00:22:24 -05:00
self.respond(ResponseCode.NNTP_GROUP_LISTING, summary)
2024-11-22 23:57:14 -05:00
for message in cr.each():
self.print(str(message.id))
return self.end()
2024-11-25 15:53:32 -05:00
def _newsgroup_summary(self, newsgroup: Newsgroup):
sql = """
select
min(id),
max(id)
from
newsgroup_message
where
newsgroup_id = ?
"""
cr = self.db.execute(sql, (newsgroup.id,))
row = cr.fetchone()
return {
'low': row[0],
'high': row[1],
'perms': 'n'
}
def print_newsgroup(self, newsgroup: Newsgroup):
summary = self._newsgroup_summary(newsgroup)
return self.print("%s %d %d %s" % (
newsgroup.name,
summary['low'],
summary['high'],
summary['perms']
))
def _cmd_list_newsgroups(self):
self.respond(ResponseCode.NNTP_INFORMATION_FOLLOWS)
for name in self.server.newsgroups:
newsgroup = self.server.newsgroups[name]
self.print_newsgroup(newsgroup)
return self.end()
2024-11-25 17:18:38 -05:00
def _newsgroup_last_active(self, newsgroup: Newsgroup):
sql = """
select
max(created_on)
from
newsgroup_message
where
newsgroup_id = ?
"""
cr = self.db.execute(sql, (newsgroup.id,))
row = cr.fetchone()
if row is None:
return
return datetime.datetime.fromisoformat(row[0])
def _cmd_list_active(self):
now = datetime.datetime.now(datetime.UTC)
self.respond(ResponseCode.NNTP_INFORMATION_FOLLOWS)
for name in self.server.newsgroups:
newsgroup = self.server.newsgroups[name]
last_active = self._newsgroup_last_active(newsgroup)
if now - last_active < datetime.timedelta(days=1):
self.print_newsgroup(newsgroup)
return self.end()
2024-11-26 14:05:34 -05:00
def _cmd_list_active_times(self):
self.respond(ResponseCode.NNTP_INFORMATION_FOLLOWS)
for name in self.server.newsgroups:
newsgroup = self.server.newsgroups[name]
self.print("%s %d %s" % (
name,
newsgroup.created_on.timestamp(),
newsgroup.created_by
))
return self.end()
2024-11-26 13:28:33 -05:00
OVERVIEW_FMT_HEADERS = [
'Subject',
'From',
'Date',
'Message-ID',
'References',
'Bytes',
'Lines',
]
def _cmd_list_overview_fmt(self):
self.respond(ResponseCode.NNTP_INFORMATION_FOLLOWS, "Order of fields in overview database")
for header in self.OVERVIEW_FMT_HEADERS:
self.print("%s:" % (header,))
return self.end()
2024-11-25 15:53:32 -05:00
LIST_SUBCOMMANDS = {
2024-11-26 13:28:33 -05:00
'NEWSGROUPS': _cmd_list_newsgroups,
'ACTIVE': _cmd_list_active,
2024-11-26 14:05:34 -05:00
'ACTIVE.TIMES': _cmd_list_active_times,
2024-11-26 13:28:33 -05:00
'OVERVIEW.FMT': _cmd_list_overview_fmt,
2024-11-25 15:53:32 -05:00
}
2024-11-23 22:40:06 -05:00
def _cmd_list(self, *args):
2024-11-25 15:53:32 -05:00
if len(args) == 0:
return self.respond(ResponseCode.NNTP_SYNTAX_ERROR, "No subcommand provided")
subcmd, *subargs = args
fn = self.LIST_SUBCOMMANDS.get(subcmd.upper())
if fn is None:
return self.respond(ResponseCode.NNTP_COMMAND_UNKNOWN)
return fn(self, *subargs)
2024-11-23 22:40:06 -05:00
RE_DATE_SHORT = re.compile(r'^(\d{2})(\d{2})(\d{2})$')
RE_DATE_LONG = re.compile(r'^(\d{4})(\d{2})(\d{2})$')
RE_TIME = re.compile(r'^(\d{2})(\d{2})(\d{2})$')
def _parse_date_time(self, datestr: str, timestr: str):
yyyy, mm, dd = None, None, None,
hh, MM, ss = None, None, None
2024-11-25 17:18:22 -05:00
2024-11-23 22:40:06 -05:00
match = self.RE_DATE_SHORT.match(datestr)
if match:
yy, mm, dd = map(int, match[1:3])
if yy >= 70:
yyyy = 1900 + yy
else:
yyyy = 2000 + yy
match = self.RE_DATE_LONG.match(datestr)
if match:
yyyy, mm, dd = map(int, match[1:3])
if yyyy is None:
return
match = self.RE_TIME.match(timestr)
if match is None:
return
hh, mm, ss = map(int, match[1:3])
return datetime.datetime(yyyy, mm, dd, hh, MM, ss)
def _cmd_newnews(self, wildmat, datestr, timestr, *args):
gmt = False
if len(args) == 1:
if args[0] == "GMT":
gmt = True
else:
return self.send_response(ResponseCode.NNTP_SYNTAX_ERROR, "Only optional 'GMT' allowed")
elif len(args) > 1:
return self.send_response(ResponseCode.NNTP_SYNTAX_ERROR, "Too many arguments")
timestamp = self._parse_date_time(datestr, timestr)
if timestamp is None:
return self.send_response(ResponseCode.NNTP_SYNTAX_ERROR, "Invalid date or time")
self.respond(ResponseCode.NNTP_ARTICLE_LISTING_ID_FOLLOWS)
sql = """
2024-11-26 11:25:40 -05:00
select
message_id
from
newsgroup_message
where
newsgroup_id = ?
and created_on >= ?
2024-11-23 22:40:06 -05:00
"""
for name in self.server.newsgroups:
if fnmatch.fnmatch(name, wildmat):
newsgroup = self.server.newsgroups[name]
cr = self.db.execute(sql, (newsgroup.id, timestamp.isoformat()))
for row in cr.each():
self.print(row[0])
return self.end()
2024-11-25 00:49:53 -05:00
def _cmd_newgroups(self, wildmat, datestr, timestr, *args):
gmt = False
if len(args) == 1:
if args[0] == "GMT":
gmt = True
else:
2024-11-25 14:25:55 -05:00
return self.respond(ResponseCode.NNTP_SYNTAX_ERROR, "Only optional 'GMT' allowed")
2024-11-25 00:49:53 -05:00
elif len(args) > 1:
2024-11-25 14:25:55 -05:00
return self.respond(ResponseCode.NNTP_SYNTAX_ERROR, "Too many arguments")
2024-11-25 00:49:53 -05:00
2024-11-25 14:25:55 -05:00
self.respond(ResponseCode.NNTP_GROUPS_NEW_FOLLOW)
for name in self.server.newsgroups:
if fnmatch.fnmatch(name, wildmat):
newsgroup = self.server.newsgroups[name]
2024-11-25 15:53:32 -05:00
self.print_newsgroup(newsgroup)
2024-11-25 14:25:55 -05:00
return self.end()
2024-11-25 00:49:53 -05:00
2024-11-25 20:27:55 -05:00
def _message_by_id(self, identifier: str):
if identifier[0] == '<':
2024-11-25 20:34:26 -05:00
return self.db.get(Message, {
2024-11-25 20:27:55 -05:00
'newsgroup_id': self.newsgroup.id,
'message_id': identifier
})
else:
2024-11-25 20:34:26 -05:00
return self.db.get(Message, {
2024-11-25 20:27:55 -05:00
'newsgroup_id': self.newsgroup.id,
'id': int(identifier)
})
def _send_message_headers(self, message: Message):
for name in message.headers:
self.print("%s: %s" % (
name, message.headers[name]
))
2024-11-25 20:34:26 -05:00
def _serve_message(self, part: MessagePart, identifier: Optional[str]=None):
2024-11-25 20:27:55 -05:00
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
message = None
if self.article_id is None:
if identifier is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
else:
message = self._message_by_id(identifier)
self.article_id = message.id
else:
if identifier is None:
2024-11-25 20:34:26 -05:00
message = self._message_by_id(str(self.article_id))
2024-11-25 20:27:55 -05:00
else:
message = self._message_by_id(identifier)
self.article_id = message.id
text = "%d %s" % (
message.id,
message.message_id
)
self.respond(ResponseCode.NNTP_ARTICLE_LISTING, text)
if part is MessagePart.HEAD or part is MessagePart.WHOLE:
self._send_message_headers(message)
if part is MessagePart.WHOLE:
self.print('')
if part is MessagePart.BODY or part is MessagePart.WHOLE:
self.print(message.body)
return self.end()
2024-11-25 20:34:26 -05:00
def _cmd_head(self, identifier: Optional[str]=None):
2024-11-25 20:27:55 -05:00
return self._serve_message(MessagePart.HEAD, identifier)
2024-11-25 20:34:26 -05:00
def _cmd_body(self, identifier: Optional[str]=None):
2024-11-25 20:27:55 -05:00
return self._serve_message(MessagePart.BODY, identifier)
2024-11-25 20:34:26 -05:00
def _cmd_article(self, identifier: Optional[str]=None):
2024-11-25 20:27:55 -05:00
return self._serve_message(MessagePart.WHOLE, identifier)
2024-11-25 21:53:08 -05:00
def _send_message_header(self, message: Message, name: str):
return self.print("%d %s" % (
message.id, message.headers.get(name, '')
))
def _cmd_hdr(self, name: str, msg: Optional[str]=None):
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
if msg is None:
if self.article_id is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
message = self.db.get(Message, self.article_id)
self.respond(ResponseCode.NNTP_HEADERS_FOLLOW)
self._send_message_header(message, name)
else:
msgrange = MessageRange.parse(msg)
sql = f"select * from {Message.name} where "
sql += " newsgroup_id = ? and " + msgrange.where()
cr = self.db.query_sql(Message, sql, (self.newsgroup.id,))
2024-11-26 13:03:15 -05:00
first = True
2024-11-25 21:53:08 -05:00
for message in cr.each():
2024-11-26 13:03:15 -05:00
if first:
first = False
self.respond(ResponseCode.NNTP_HEADERS_FOLLOW)
2024-11-25 21:53:08 -05:00
self._send_message_header(message, name)
2024-11-26 13:03:15 -05:00
if first:
return self.respond(ResponseCode.NNTP_ARTICLE_NOT_FOUND_RANGE)
2024-11-25 21:53:08 -05:00
return self.end()
2024-11-26 13:12:42 -05:00
def _message_overview(self, message: Message) -> dict:
return map(lambda s: s.replace('\t', ' '), [
str(message.id),
message.subject,
message.sender,
message.created_on.isoformat(),
message.message_id,
message.parent_id or '',
str(len(message.body)),
str(message.body.count('\n') + 1),
])
def _cmd_over(self, identifier: Optional[str]=None):
if identifier is None:
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
if self.article_id is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
message = self.db.get(Message, self.article_id)
if message is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
elif identifier[0] == '<':
message = self.db.query(Message, {
'message_id': identifier
}).fetchone()
if message is None:
return self.respond(ResponseCode.NNTP_ARTICLE_NOT_FOUND_ID)
else:
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
msgrange = MessageRange.parse(identifier)
sql = f"select * from {Message.name} where "
sql += " newsgroup_id = ? and " + msgrange.where()
cr = self.db.query_sql(Message, sql, (self.newsgroup.id,))
first = True
for message in cr.each():
if first:
first = False
self.respond(ResponseCode.NNTP_OVERVIEW_FOLLOWS)
overview = self._message_overview(message)
self.print('|'.join(overview))
if first:
return self.respond(ResponseCode.NNTP_ARTICLE_NOT_FOUND_RANGE)
2024-11-25 22:01:49 -05:00
def _cmd_stat(self, identifier: Optional[str]=None):
if self.newsgroup is None:
return self.respond(ResponseCode.NNTP_NEWSGROUP_NOT_SELECTED)
sql = """
select
id, message_id
from
newsgroup_message
where
newsgroup_id = ?
"""
row = None
if identifier is None:
if self.article_id is None:
return self.respond(ResponseCode.NNTP_ARTICLE_INVALID_NUMBER)
identifier = str(self.article_id)
if identifier[0] == '<':
cr = self.db.execute(sql + " and message_id = ?",
(self.newsgroup.id, identifier))
row = cr.fetchone()
if row is None:
return self.respond(ResponseCode.NNTP_ARTICLE_NOT_FOUND_ID)
else:
cr = self.db.execute(sql + " and id = ?",
(self.newsgroup.id, int(identifier)))
row = cr.fetchone()
if row is None:
2024-11-26 13:02:19 -05:00
return self.respond(ResponseCode.NNTP_ARTICLE_NOT_FOUND_RANGE)
2024-11-25 22:01:49 -05:00
text = "%d %s" % (row[0], row[1])
if self.article_id is None:
self.article_id = int(row[0])
return self.respond(ResponseCode.NNTP_ARTICLE_STAT_RESPONSE, text)
2024-11-26 10:19:02 -05:00
def _cmd_date(self):
timestamp = datetime.datetime.now(datetime.UTC)
return self.respond(ResponseCode.NNTP_DATE,
timestamp.strftime("%Y%m%d%H%M%S"))
2024-11-26 10:27:03 -05:00
def _cmd_quit(self):
self.state &= ~SessionState.ACTIVE
return self.respond(ResponseCode.NNTP_CONNECTION_CLOSING)
2024-11-25 00:49:53 -05:00
COMMANDS = {
'CAPABILITIES': _cmd_capabilities,
2024-11-26 10:44:21 -05:00
'MODE': _cmd_mode,
2024-11-25 00:49:53 -05:00
'GROUP': _cmd_group,
2024-11-26 12:08:56 -05:00
'LAST': _cmd_last,
'NEXT': _cmd_next,
2024-11-25 00:49:53 -05:00
'LISTGROUP': _cmd_listgroup,
'LIST': _cmd_list,
'NEWNEWS': _cmd_newnews,
2024-11-25 14:25:55 -05:00
'NEWGROUPS': _cmd_newgroups,
2024-11-25 20:27:55 -05:00
'HEAD': _cmd_head,
'BODY': _cmd_body,
2024-11-25 21:53:08 -05:00
'ARTICLE': _cmd_article,
'HDR': _cmd_hdr,
2024-11-26 12:15:56 -05:00
'XHDR': _cmd_hdr,
2024-11-26 13:12:42 -05:00
'OVER': _cmd_over,
2024-11-25 22:01:49 -05:00
'STAT': _cmd_stat,
2024-11-26 10:19:02 -05:00
'DATE': _cmd_date,
2024-11-26 10:27:03 -05:00
'QUIT': _cmd_quit,
2024-11-25 00:49:53 -05:00
}
def greet(self):
return self.respond(ResponseCode.NNTP_SERVICE_READY_POST_PROHIBITED)
2024-11-20 21:17:03 -05:00
def handle(self):
line = self.readline()
if line == '':
return
2024-11-25 00:49:53 -05:00
tokens = self.RE_SPLIT.split(line.rstrip())
command, *args = tokens
fn = self.COMMANDS.get(command.upper())
if fn is None:
return self.respond(ResponseCode.NNTP_COMMAND_UNKNOWN)
2024-11-25 14:27:58 -05:00
try:
return fn(self, *args)
except TypeError as e:
2024-11-25 20:27:14 -05:00
traceback.print_exception(e)
2024-11-25 14:27:58 -05:00
return self.respond(ResponseCode.NNTP_SYNTAX_ERROR)
except Exception as e:
2024-11-25 20:27:14 -05:00
traceback.print_exception(e)
2024-11-25 14:27:58 -05:00
return self.respond(ResponseCode.NNTP_COMMAND_UNAVAILABLE)