17db96d56Sopenharmony_ci# -*- Mode: Python; tab-width: 4 -*-
27db96d56Sopenharmony_ci#       Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
37db96d56Sopenharmony_ci#       Author: Sam Rushing <rushing@nightmare.com>
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ci# ======================================================================
67db96d56Sopenharmony_ci# Copyright 1996 by Sam Rushing
77db96d56Sopenharmony_ci#
87db96d56Sopenharmony_ci#                         All Rights Reserved
97db96d56Sopenharmony_ci#
107db96d56Sopenharmony_ci# Permission to use, copy, modify, and distribute this software and
117db96d56Sopenharmony_ci# its documentation for any purpose and without fee is hereby
127db96d56Sopenharmony_ci# granted, provided that the above copyright notice appear in all
137db96d56Sopenharmony_ci# copies and that both that copyright notice and this permission
147db96d56Sopenharmony_ci# notice appear in supporting documentation, and that the name of Sam
157db96d56Sopenharmony_ci# Rushing not be used in advertising or publicity pertaining to
167db96d56Sopenharmony_ci# distribution of the software without specific, written prior
177db96d56Sopenharmony_ci# permission.
187db96d56Sopenharmony_ci#
197db96d56Sopenharmony_ci# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
207db96d56Sopenharmony_ci# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
217db96d56Sopenharmony_ci# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
227db96d56Sopenharmony_ci# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
237db96d56Sopenharmony_ci# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
247db96d56Sopenharmony_ci# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
257db96d56Sopenharmony_ci# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
267db96d56Sopenharmony_ci# ======================================================================
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_cir"""A class supporting chat-style (command/response) protocols.
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ciThis class adds support for 'chat' style protocols - where one side
317db96d56Sopenharmony_cisends a 'command', and the other sends a response (examples would be
327db96d56Sopenharmony_cithe common internet protocols - smtp, nntp, ftp, etc..).
337db96d56Sopenharmony_ci
347db96d56Sopenharmony_ciThe handle_read() method looks at the input stream for the current
357db96d56Sopenharmony_ci'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
367db96d56Sopenharmony_cifor multi-line output), calling self.found_terminator() on its
377db96d56Sopenharmony_cireceipt.
387db96d56Sopenharmony_ci
397db96d56Sopenharmony_cifor example:
407db96d56Sopenharmony_ciSay you build an async nntp client using this class.  At the start
417db96d56Sopenharmony_ciof the connection, you'll have self.terminator set to '\r\n', in
427db96d56Sopenharmony_ciorder to process the single-line greeting.  Just before issuing a
437db96d56Sopenharmony_ci'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST
447db96d56Sopenharmony_cicommand will be accumulated (using your own 'collect_incoming_data'
457db96d56Sopenharmony_cimethod) up to the terminator, and then control will be returned to
467db96d56Sopenharmony_ciyou - by calling your self.found_terminator() method.
477db96d56Sopenharmony_ci"""
487db96d56Sopenharmony_ciimport asyncore
497db96d56Sopenharmony_cifrom collections import deque
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_cifrom warnings import _deprecated
527db96d56Sopenharmony_ci
537db96d56Sopenharmony_ci_DEPRECATION_MSG = ('The {name} module is deprecated and will be removed in '
547db96d56Sopenharmony_ci                    'Python {remove}. The recommended replacement is asyncio')
557db96d56Sopenharmony_ci_deprecated(__name__, _DEPRECATION_MSG, remove=(3, 12))
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ci
597db96d56Sopenharmony_ciclass async_chat(asyncore.dispatcher):
607db96d56Sopenharmony_ci    """This is an abstract class.  You must derive from this class, and add
617db96d56Sopenharmony_ci    the two methods collect_incoming_data() and found_terminator()"""
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ci    # these are overridable defaults
647db96d56Sopenharmony_ci
657db96d56Sopenharmony_ci    ac_in_buffer_size = 65536
667db96d56Sopenharmony_ci    ac_out_buffer_size = 65536
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci    # we don't want to enable the use of encoding by default, because that is a
697db96d56Sopenharmony_ci    # sign of an application bug that we don't want to pass silently
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ci    use_encoding = 0
727db96d56Sopenharmony_ci    encoding = 'latin-1'
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci    def __init__(self, sock=None, map=None):
757db96d56Sopenharmony_ci        # for string terminator matching
767db96d56Sopenharmony_ci        self.ac_in_buffer = b''
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci        # we use a list here rather than io.BytesIO for a few reasons...
797db96d56Sopenharmony_ci        # del lst[:] is faster than bio.truncate(0)
807db96d56Sopenharmony_ci        # lst = [] is faster than bio.truncate(0)
817db96d56Sopenharmony_ci        self.incoming = []
827db96d56Sopenharmony_ci
837db96d56Sopenharmony_ci        # we toss the use of the "simple producer" and replace it with
847db96d56Sopenharmony_ci        # a pure deque, which the original fifo was a wrapping of
857db96d56Sopenharmony_ci        self.producer_fifo = deque()
867db96d56Sopenharmony_ci        asyncore.dispatcher.__init__(self, sock, map)
877db96d56Sopenharmony_ci
887db96d56Sopenharmony_ci    def collect_incoming_data(self, data):
897db96d56Sopenharmony_ci        raise NotImplementedError("must be implemented in subclass")
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci    def _collect_incoming_data(self, data):
927db96d56Sopenharmony_ci        self.incoming.append(data)
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ci    def _get_data(self):
957db96d56Sopenharmony_ci        d = b''.join(self.incoming)
967db96d56Sopenharmony_ci        del self.incoming[:]
977db96d56Sopenharmony_ci        return d
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci    def found_terminator(self):
1007db96d56Sopenharmony_ci        raise NotImplementedError("must be implemented in subclass")
1017db96d56Sopenharmony_ci
1027db96d56Sopenharmony_ci    def set_terminator(self, term):
1037db96d56Sopenharmony_ci        """Set the input delimiter.
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci        Can be a fixed string of any length, an integer, or None.
1067db96d56Sopenharmony_ci        """
1077db96d56Sopenharmony_ci        if isinstance(term, str) and self.use_encoding:
1087db96d56Sopenharmony_ci            term = bytes(term, self.encoding)
1097db96d56Sopenharmony_ci        elif isinstance(term, int) and term < 0:
1107db96d56Sopenharmony_ci            raise ValueError('the number of received bytes must be positive')
1117db96d56Sopenharmony_ci        self.terminator = term
1127db96d56Sopenharmony_ci
1137db96d56Sopenharmony_ci    def get_terminator(self):
1147db96d56Sopenharmony_ci        return self.terminator
1157db96d56Sopenharmony_ci
1167db96d56Sopenharmony_ci    # grab some more data from the socket,
1177db96d56Sopenharmony_ci    # throw it to the collector method,
1187db96d56Sopenharmony_ci    # check for the terminator,
1197db96d56Sopenharmony_ci    # if found, transition to the next state.
1207db96d56Sopenharmony_ci
1217db96d56Sopenharmony_ci    def handle_read(self):
1227db96d56Sopenharmony_ci
1237db96d56Sopenharmony_ci        try:
1247db96d56Sopenharmony_ci            data = self.recv(self.ac_in_buffer_size)
1257db96d56Sopenharmony_ci        except BlockingIOError:
1267db96d56Sopenharmony_ci            return
1277db96d56Sopenharmony_ci        except OSError:
1287db96d56Sopenharmony_ci            self.handle_error()
1297db96d56Sopenharmony_ci            return
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ci        if isinstance(data, str) and self.use_encoding:
1327db96d56Sopenharmony_ci            data = bytes(str, self.encoding)
1337db96d56Sopenharmony_ci        self.ac_in_buffer = self.ac_in_buffer + data
1347db96d56Sopenharmony_ci
1357db96d56Sopenharmony_ci        # Continue to search for self.terminator in self.ac_in_buffer,
1367db96d56Sopenharmony_ci        # while calling self.collect_incoming_data.  The while loop
1377db96d56Sopenharmony_ci        # is necessary because we might read several data+terminator
1387db96d56Sopenharmony_ci        # combos with a single recv(4096).
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ci        while self.ac_in_buffer:
1417db96d56Sopenharmony_ci            lb = len(self.ac_in_buffer)
1427db96d56Sopenharmony_ci            terminator = self.get_terminator()
1437db96d56Sopenharmony_ci            if not terminator:
1447db96d56Sopenharmony_ci                # no terminator, collect it all
1457db96d56Sopenharmony_ci                self.collect_incoming_data(self.ac_in_buffer)
1467db96d56Sopenharmony_ci                self.ac_in_buffer = b''
1477db96d56Sopenharmony_ci            elif isinstance(terminator, int):
1487db96d56Sopenharmony_ci                # numeric terminator
1497db96d56Sopenharmony_ci                n = terminator
1507db96d56Sopenharmony_ci                if lb < n:
1517db96d56Sopenharmony_ci                    self.collect_incoming_data(self.ac_in_buffer)
1527db96d56Sopenharmony_ci                    self.ac_in_buffer = b''
1537db96d56Sopenharmony_ci                    self.terminator = self.terminator - lb
1547db96d56Sopenharmony_ci                else:
1557db96d56Sopenharmony_ci                    self.collect_incoming_data(self.ac_in_buffer[:n])
1567db96d56Sopenharmony_ci                    self.ac_in_buffer = self.ac_in_buffer[n:]
1577db96d56Sopenharmony_ci                    self.terminator = 0
1587db96d56Sopenharmony_ci                    self.found_terminator()
1597db96d56Sopenharmony_ci            else:
1607db96d56Sopenharmony_ci                # 3 cases:
1617db96d56Sopenharmony_ci                # 1) end of buffer matches terminator exactly:
1627db96d56Sopenharmony_ci                #    collect data, transition
1637db96d56Sopenharmony_ci                # 2) end of buffer matches some prefix:
1647db96d56Sopenharmony_ci                #    collect data to the prefix
1657db96d56Sopenharmony_ci                # 3) end of buffer does not match any prefix:
1667db96d56Sopenharmony_ci                #    collect data
1677db96d56Sopenharmony_ci                terminator_len = len(terminator)
1687db96d56Sopenharmony_ci                index = self.ac_in_buffer.find(terminator)
1697db96d56Sopenharmony_ci                if index != -1:
1707db96d56Sopenharmony_ci                    # we found the terminator
1717db96d56Sopenharmony_ci                    if index > 0:
1727db96d56Sopenharmony_ci                        # don't bother reporting the empty string
1737db96d56Sopenharmony_ci                        # (source of subtle bugs)
1747db96d56Sopenharmony_ci                        self.collect_incoming_data(self.ac_in_buffer[:index])
1757db96d56Sopenharmony_ci                    self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
1767db96d56Sopenharmony_ci                    # This does the Right Thing if the terminator
1777db96d56Sopenharmony_ci                    # is changed here.
1787db96d56Sopenharmony_ci                    self.found_terminator()
1797db96d56Sopenharmony_ci                else:
1807db96d56Sopenharmony_ci                    # check for a prefix of the terminator
1817db96d56Sopenharmony_ci                    index = find_prefix_at_end(self.ac_in_buffer, terminator)
1827db96d56Sopenharmony_ci                    if index:
1837db96d56Sopenharmony_ci                        if index != lb:
1847db96d56Sopenharmony_ci                            # we found a prefix, collect up to the prefix
1857db96d56Sopenharmony_ci                            self.collect_incoming_data(self.ac_in_buffer[:-index])
1867db96d56Sopenharmony_ci                            self.ac_in_buffer = self.ac_in_buffer[-index:]
1877db96d56Sopenharmony_ci                        break
1887db96d56Sopenharmony_ci                    else:
1897db96d56Sopenharmony_ci                        # no prefix, collect it all
1907db96d56Sopenharmony_ci                        self.collect_incoming_data(self.ac_in_buffer)
1917db96d56Sopenharmony_ci                        self.ac_in_buffer = b''
1927db96d56Sopenharmony_ci
1937db96d56Sopenharmony_ci    def handle_write(self):
1947db96d56Sopenharmony_ci        self.initiate_send()
1957db96d56Sopenharmony_ci
1967db96d56Sopenharmony_ci    def handle_close(self):
1977db96d56Sopenharmony_ci        self.close()
1987db96d56Sopenharmony_ci
1997db96d56Sopenharmony_ci    def push(self, data):
2007db96d56Sopenharmony_ci        if not isinstance(data, (bytes, bytearray, memoryview)):
2017db96d56Sopenharmony_ci            raise TypeError('data argument must be byte-ish (%r)',
2027db96d56Sopenharmony_ci                            type(data))
2037db96d56Sopenharmony_ci        sabs = self.ac_out_buffer_size
2047db96d56Sopenharmony_ci        if len(data) > sabs:
2057db96d56Sopenharmony_ci            for i in range(0, len(data), sabs):
2067db96d56Sopenharmony_ci                self.producer_fifo.append(data[i:i+sabs])
2077db96d56Sopenharmony_ci        else:
2087db96d56Sopenharmony_ci            self.producer_fifo.append(data)
2097db96d56Sopenharmony_ci        self.initiate_send()
2107db96d56Sopenharmony_ci
2117db96d56Sopenharmony_ci    def push_with_producer(self, producer):
2127db96d56Sopenharmony_ci        self.producer_fifo.append(producer)
2137db96d56Sopenharmony_ci        self.initiate_send()
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ci    def readable(self):
2167db96d56Sopenharmony_ci        "predicate for inclusion in the readable for select()"
2177db96d56Sopenharmony_ci        # cannot use the old predicate, it violates the claim of the
2187db96d56Sopenharmony_ci        # set_terminator method.
2197db96d56Sopenharmony_ci
2207db96d56Sopenharmony_ci        # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
2217db96d56Sopenharmony_ci        return 1
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_ci    def writable(self):
2247db96d56Sopenharmony_ci        "predicate for inclusion in the writable for select()"
2257db96d56Sopenharmony_ci        return self.producer_fifo or (not self.connected)
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_ci    def close_when_done(self):
2287db96d56Sopenharmony_ci        "automatically close this channel once the outgoing queue is empty"
2297db96d56Sopenharmony_ci        self.producer_fifo.append(None)
2307db96d56Sopenharmony_ci
2317db96d56Sopenharmony_ci    def initiate_send(self):
2327db96d56Sopenharmony_ci        while self.producer_fifo and self.connected:
2337db96d56Sopenharmony_ci            first = self.producer_fifo[0]
2347db96d56Sopenharmony_ci            # handle empty string/buffer or None entry
2357db96d56Sopenharmony_ci            if not first:
2367db96d56Sopenharmony_ci                del self.producer_fifo[0]
2377db96d56Sopenharmony_ci                if first is None:
2387db96d56Sopenharmony_ci                    self.handle_close()
2397db96d56Sopenharmony_ci                    return
2407db96d56Sopenharmony_ci
2417db96d56Sopenharmony_ci            # handle classic producer behavior
2427db96d56Sopenharmony_ci            obs = self.ac_out_buffer_size
2437db96d56Sopenharmony_ci            try:
2447db96d56Sopenharmony_ci                data = first[:obs]
2457db96d56Sopenharmony_ci            except TypeError:
2467db96d56Sopenharmony_ci                data = first.more()
2477db96d56Sopenharmony_ci                if data:
2487db96d56Sopenharmony_ci                    self.producer_fifo.appendleft(data)
2497db96d56Sopenharmony_ci                else:
2507db96d56Sopenharmony_ci                    del self.producer_fifo[0]
2517db96d56Sopenharmony_ci                continue
2527db96d56Sopenharmony_ci
2537db96d56Sopenharmony_ci            if isinstance(data, str) and self.use_encoding:
2547db96d56Sopenharmony_ci                data = bytes(data, self.encoding)
2557db96d56Sopenharmony_ci
2567db96d56Sopenharmony_ci            # send the data
2577db96d56Sopenharmony_ci            try:
2587db96d56Sopenharmony_ci                num_sent = self.send(data)
2597db96d56Sopenharmony_ci            except OSError:
2607db96d56Sopenharmony_ci                self.handle_error()
2617db96d56Sopenharmony_ci                return
2627db96d56Sopenharmony_ci
2637db96d56Sopenharmony_ci            if num_sent:
2647db96d56Sopenharmony_ci                if num_sent < len(data) or obs < len(first):
2657db96d56Sopenharmony_ci                    self.producer_fifo[0] = first[num_sent:]
2667db96d56Sopenharmony_ci                else:
2677db96d56Sopenharmony_ci                    del self.producer_fifo[0]
2687db96d56Sopenharmony_ci            # we tried to send some actual data
2697db96d56Sopenharmony_ci            return
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_ci    def discard_buffers(self):
2727db96d56Sopenharmony_ci        # Emergencies only!
2737db96d56Sopenharmony_ci        self.ac_in_buffer = b''
2747db96d56Sopenharmony_ci        del self.incoming[:]
2757db96d56Sopenharmony_ci        self.producer_fifo.clear()
2767db96d56Sopenharmony_ci
2777db96d56Sopenharmony_ci
2787db96d56Sopenharmony_ciclass simple_producer:
2797db96d56Sopenharmony_ci
2807db96d56Sopenharmony_ci    def __init__(self, data, buffer_size=512):
2817db96d56Sopenharmony_ci        self.data = data
2827db96d56Sopenharmony_ci        self.buffer_size = buffer_size
2837db96d56Sopenharmony_ci
2847db96d56Sopenharmony_ci    def more(self):
2857db96d56Sopenharmony_ci        if len(self.data) > self.buffer_size:
2867db96d56Sopenharmony_ci            result = self.data[:self.buffer_size]
2877db96d56Sopenharmony_ci            self.data = self.data[self.buffer_size:]
2887db96d56Sopenharmony_ci            return result
2897db96d56Sopenharmony_ci        else:
2907db96d56Sopenharmony_ci            result = self.data
2917db96d56Sopenharmony_ci            self.data = b''
2927db96d56Sopenharmony_ci            return result
2937db96d56Sopenharmony_ci
2947db96d56Sopenharmony_ci
2957db96d56Sopenharmony_ci# Given 'haystack', see if any prefix of 'needle' is at its end.  This
2967db96d56Sopenharmony_ci# assumes an exact match has already been checked.  Return the number of
2977db96d56Sopenharmony_ci# characters matched.
2987db96d56Sopenharmony_ci# for example:
2997db96d56Sopenharmony_ci# f_p_a_e("qwerty\r", "\r\n") => 1
3007db96d56Sopenharmony_ci# f_p_a_e("qwertydkjf", "\r\n") => 0
3017db96d56Sopenharmony_ci# f_p_a_e("qwerty\r\n", "\r\n") => <undefined>
3027db96d56Sopenharmony_ci
3037db96d56Sopenharmony_ci# this could maybe be made faster with a computed regex?
3047db96d56Sopenharmony_ci# [answer: no; circa Python-2.0, Jan 2001]
3057db96d56Sopenharmony_ci# new python:   28961/s
3067db96d56Sopenharmony_ci# old python:   18307/s
3077db96d56Sopenharmony_ci# re:        12820/s
3087db96d56Sopenharmony_ci# regex:     14035/s
3097db96d56Sopenharmony_ci
3107db96d56Sopenharmony_cidef find_prefix_at_end(haystack, needle):
3117db96d56Sopenharmony_ci    l = len(needle) - 1
3127db96d56Sopenharmony_ci    while l and not haystack.endswith(needle[:l]):
3137db96d56Sopenharmony_ci        l -= 1
3147db96d56Sopenharmony_ci    return l
315