~liberachat/curite

ref: c9456147e4571cd75d7d5bf73a37eaa05eabe814 curite/curite/httpd.py -rw-r--r-- 3.5 KiB
c9456147 — Jess Porter travis mypy (#3) 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import asyncio, traceback
from argparse     import ArgumentParser
from asyncio      import StreamReader, StreamWriter
from typing       import List
from urllib.parse import unquote as url_unquote

from async_timeout import timeout as timeout_

from irctokens import build
from ircrobots import Bot
from ircrobots.matching   import Response, Nick, Folded, Formatless, SELF
from ircrobots.formatting import strip as format_strip

from .config import Config

async def _headers(
        reader: StreamReader
        ) -> List[bytes]:
    headers: List[bytes] = []
    buffer = b""

    while True:
        data = await reader.read(2048)
        if data:
            buffer += data
            headers.extend(buffer.split(b"\r\n"))
            buffer  = headers.pop(-1)
            if b"" in headers:
                break
        else:
            raise ConnectionError("client closed connection")

    return headers

NICKSERV = Nick("NickServ")
async def _verify(
        bot:     Bot,
        account: str,
        token:   str) -> bool:

    success = f"{account} has now been verified."

    server  = list(bot.servers.values())[0]
    async with server.read_lock:
        await server.send(build("PRIVMSG", ["NickServ", f"VERIFY REGISTER {account} {token}"]))
        verify_line = await server.wait_for({
            Response("NOTICE", [SELF, Formatless(Folded(f"{account} is not awaiting verification."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(f"verification failed. invalid key for {account}."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(f"{account} is not registered."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(success))], source=NICKSERV)
        })

        verify_msg = format_strip(verify_line.params[1])
        return server.casefold_equals(success, verify_msg)

async def run(
        bot:    Bot,
        config: Config):

    async def _client(
            reader: StreamReader,
            writer: StreamWriter
            ):

        try:
            async with timeout_(10):
                headers = await _headers(reader)
        except asyncio.TimeoutError:
            print("! header timeout")
            return
        except ConnectionError as e:
            print(f"! header error {str(e)}")
            return

        method, path, _   = headers[0].decode("ascii").split(" ", 2)
        if not method == "POST":
            return

        path_match = config.path_pattern.search(path)
        if not path_match:
            return

        account = url_unquote(path_match.group("account"))
        token   = path_match.group("token")

        try:
            async with timeout_(5):
                verified = await _verify(bot, account, token)
        except asyncio.TimeoutError:
            print("! verify timeout")
            return

        if verified:
            url = config.url_success
        else:
            url = config.url_failure

        data = "\r\n".join([
            "HTTP/1.1 302 Moved",
            f"Location: {url}"
        ]).encode("utf8")
        # HTTP headers end with an empty line
        data += b"\r\n\r\n"

        try:
            async with timeout_(5):
                writer.write(data)
                await writer.drain()
                writer.close()
                await writer.wait_closed()
        except Exception as e:
            traceback.print_exc()
            return

    server = await asyncio.start_server(_client, "", config.httpd_port)
    async with server:
        await server.serve_forever()