Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
parser.pyx437 linesDownload Raw Back to parser
1#cython: language_level=3
2
3from __future__ import print_function
4from typing import Optional
5
6from cpython.mem cimport PyMem_Malloc, PyMem_Free
7from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
8                     Py_buffer, PyBytes_AsString
9
10from .python cimport PyMemoryView_Check, PyMemoryView_GET_BUFFER
11
12
13from .errors import (HttpParserError,
14                     HttpParserCallbackError,
15                     HttpParserInvalidStatusError,
16                     HttpParserInvalidMethodError,
17                     HttpParserInvalidURLError,
18                     HttpParserUpgrade)
19
20cimport cython
21from . cimport cparser
22
23
24__all__ = ('HttpRequestParser', 'HttpResponseParser')
25
26
27@cython.internal
28cdef class HttpParser:
29
30    cdef:
31        cparser.llhttp_t* _cparser
32        cparser.llhttp_settings_t* _csettings
33
34        bytes _current_header_name
35        bytes _current_header_value
36
37        _proto_on_url, _proto_on_status, _proto_on_body, \
38        _proto_on_header, _proto_on_headers_complete, \
39        _proto_on_message_complete, _proto_on_chunk_header, \
40        _proto_on_chunk_complete, _proto_on_message_begin
41
42        object _last_error
43
44        Py_buffer py_buf
45
46    def __cinit__(self):
47        self._cparser = <cparser.llhttp_t*> \
48                                PyMem_Malloc(sizeof(cparser.llhttp_t))
49        if self._cparser is NULL:
50            raise MemoryError()
51
52        self._csettings = <cparser.llhttp_settings_t*> \
53                                PyMem_Malloc(sizeof(cparser.llhttp_settings_t))
54        if self._csettings is NULL:
55            raise MemoryError()
56
57    def __dealloc__(self):
58        PyMem_Free(self._cparser)
59        PyMem_Free(self._csettings)
60
61    cdef _init(self, protocol, cparser.llhttp_type_t mode):
62        cparser.llhttp_settings_init(self._csettings)
63
64        cparser.llhttp_init(self._cparser, mode, self._csettings)
65        self._cparser.data = <void*>self
66
67        self._current_header_name = None
68        self._current_header_value = None
69
70        self._proto_on_header = getattr(protocol, 'on_header', None)
71        if self._proto_on_header is not None:
72            self._csettings.on_header_field = cb_on_header_field
73            self._csettings.on_header_value = cb_on_header_value
74        self._proto_on_headers_complete = getattr(
75            protocol, 'on_headers_complete', None)
76        self._csettings.on_headers_complete = cb_on_headers_complete
77
78        self._proto_on_body = getattr(protocol, 'on_body', None)
79        if self._proto_on_body is not None:
80            self._csettings.on_body = cb_on_body
81
82        self._proto_on_message_begin = getattr(
83            protocol, 'on_message_begin', None)
84        if self._proto_on_message_begin is not None:
85            self._csettings.on_message_begin = cb_on_message_begin
86
87        self._proto_on_message_complete = getattr(
88            protocol, 'on_message_complete', None)
89        if self._proto_on_message_complete is not None:
90            self._csettings.on_message_complete = cb_on_message_complete
91
92        self._proto_on_chunk_header = getattr(
93            protocol, 'on_chunk_header', None)
94        self._csettings.on_chunk_header = cb_on_chunk_header
95
96        self._proto_on_chunk_complete = getattr(
97            protocol, 'on_chunk_complete', None)
98        self._csettings.on_chunk_complete = cb_on_chunk_complete
99
100        self._last_error = None
101
102    cdef _maybe_call_on_header(self):
103        if self._current_header_value is not None:
104            current_header_name = self._current_header_name
105            current_header_value = self._current_header_value
106
107            self._current_header_name = self._current_header_value = None
108
109            if self._proto_on_header is not None:
110                self._proto_on_header(current_header_name,
111                                      current_header_value)
112
113    cdef _on_header_field(self, bytes field):
114        self._maybe_call_on_header()
115        if self._current_header_name is None:
116            self._current_header_name = field
117        else:
118            self._current_header_name += field
119
120    cdef _on_header_value(self, bytes val):
121        if self._current_header_value is None:
122            self._current_header_value = val
123        else:
124            # This is unlikely, as mostly HTTP headers are one-line
125            self._current_header_value += val
126
127    cdef _on_headers_complete(self):
128        self._maybe_call_on_header()
129
130        if self._proto_on_headers_complete is not None:
131            self._proto_on_headers_complete()
132
133    cdef _on_chunk_header(self):
134        if (self._current_header_value is not None or
135            self._current_header_name is not None):
136            raise HttpParserError('invalid headers state')
137
138        if self._proto_on_chunk_header is not None:
139            self._proto_on_chunk_header()
140
141    cdef _on_chunk_complete(self):
142        self._maybe_call_on_header()
143
144        if self._proto_on_chunk_complete is not None:
145            self._proto_on_chunk_complete()
146
147    ### Public API ###
148
149    def set_dangerous_leniencies(
150        self,
151        lenient_headers: Optional[bool] = None,
152        lenient_chunked_length: Optional[bool] = None,
153        lenient_keep_alive: Optional[bool] = None,
154        lenient_transfer_encoding: Optional[bool] = None,
155        lenient_version: Optional[bool] = None,
156        lenient_data_after_close: Optional[bool] = None,
157        lenient_optional_lf_after_cr: Optional[bool] = None,
158        lenient_optional_cr_before_lf: Optional[bool] = None,
159        lenient_optional_crlf_after_chunk: Optional[bool] = None,
160        lenient_spaces_after_chunk_size: Optional[bool] = None,
161    ):
162        cdef cparser.llhttp_t* parser = self._cparser
163        if lenient_headers is not None:
164            cparser.llhttp_set_lenient_headers(
165                parser, lenient_headers)
166        if lenient_chunked_length is not None:
167            cparser.llhttp_set_lenient_chunked_length(
168                parser, lenient_chunked_length)
169        if lenient_keep_alive is not None:
170            cparser.llhttp_set_lenient_keep_alive(
171                parser, lenient_keep_alive)
172        if lenient_transfer_encoding is not None:
173            cparser.llhttp_set_lenient_transfer_encoding(
174                parser, lenient_transfer_encoding)
175        if lenient_version is not None:
176            cparser.llhttp_set_lenient_version(
177                parser, lenient_version)
178        if lenient_data_after_close is not None:
179            cparser.llhttp_set_lenient_data_after_close(
180                parser, lenient_data_after_close)
181        if lenient_optional_lf_after_cr is not None:
182            cparser.llhttp_set_lenient_optional_lf_after_cr(
183                parser, lenient_optional_lf_after_cr)
184        if lenient_optional_cr_before_lf is not None:
185            cparser.llhttp_set_lenient_optional_cr_before_lf(
186                parser, lenient_optional_cr_before_lf)
187        if lenient_optional_crlf_after_chunk is not None:
188            cparser.llhttp_set_lenient_optional_crlf_after_chunk(
189                parser, lenient_optional_crlf_after_chunk)
190        if lenient_spaces_after_chunk_size is not None:
191            cparser.llhttp_set_lenient_spaces_after_chunk_size(
192                parser, lenient_spaces_after_chunk_size)
193
194    def get_http_version(self):
195        cdef cparser.llhttp_t* parser = self._cparser
196        return '{}.{}'.format(parser.http_major, parser.http_minor)
197
198    def should_keep_alive(self):
199        return bool(cparser.llhttp_should_keep_alive(self._cparser))
200
201    def should_upgrade(self):
202        cdef cparser.llhttp_t* parser = self._cparser
203        return bool(parser.upgrade)
204
205    def feed_data(self, data):
206        cdef:
207            size_t data_len
208            cparser.llhttp_errno_t err
209            Py_buffer *buf
210            bint owning_buf = False
211            const char* err_pos
212
213        if PyMemoryView_Check(data):
214            buf = PyMemoryView_GET_BUFFER(data)
215            data_len = <size_t>buf.len
216            err = cparser.llhttp_execute(
217                self._cparser,
218                <char*>buf.buf,
219                data_len)
220
221        else:
222            buf = &self.py_buf
223            PyObject_GetBuffer(data, buf, PyBUF_SIMPLE)
224            owning_buf = True
225            data_len = <size_t>buf.len
226
227            err = cparser.llhttp_execute(
228                self._cparser,
229                <char*>buf.buf,
230                data_len)
231
232        try:
233            if self._cparser.upgrade == 1 and err == cparser.HPE_PAUSED_UPGRADE:
234                err_pos = cparser.llhttp_get_error_pos(self._cparser)
235
236                # Immediately free the parser from "error" state, simulating
237                # http-parser behavior here because 1) we never had the API to
238                # allow users manually "resume after upgrade", and 2) the use
239                # case for resuming parsing is very rare.
240                cparser.llhttp_resume_after_upgrade(self._cparser)
241
242                # The err_pos here is specific for the input buf. So if we ever
243                # switch to the llhttp behavior (re-raise HttpParserUpgrade for
244                # successive calls to feed_data() until resume_after_upgrade is
245                # called), we have to store the result and keep our own state.
246                raise HttpParserUpgrade(err_pos - <char*>buf.buf)
247        finally:
248            if owning_buf:
249                PyBuffer_Release(buf)
250
251        if err != cparser.HPE_OK:
252            ex = parser_error_from_errno(
253                self._cparser,
254                <cparser.llhttp_errno_t> self._cparser.error)
255            if isinstance(ex, HttpParserCallbackError):
256                if self._last_error is not None:
257                    ex.__context__ = self._last_error
258                    self._last_error = None
259            raise ex
260
261
262cdef class HttpRequestParser(HttpParser):
263
264    def __init__(self, protocol):
265        self._init(protocol, cparser.HTTP_REQUEST)
266
267        self._proto_on_url = getattr(protocol, 'on_url', None)
268        if self._proto_on_url is not None:
269            self._csettings.on_url = cb_on_url
270
271    def get_method(self):
272        cdef cparser.llhttp_t* parser = self._cparser
273        return cparser.llhttp_method_name(<cparser.llhttp_method_t> parser.method)
274
275
276cdef class HttpResponseParser(HttpParser):
277
278    def __init__(self, protocol):
279        self._init(protocol, cparser.HTTP_RESPONSE)
280
281        self._proto_on_status = getattr(protocol, 'on_status', None)
282        if self._proto_on_status is not None:
283            self._csettings.on_status = cb_on_status
284
285    def get_status_code(self):
286        cdef cparser.llhttp_t* parser = self._cparser
287        return parser.status_code
288
289
290cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:
291    cdef HttpParser pyparser = <HttpParser>parser.data
292    try:
293        pyparser._proto_on_message_begin()
294    except BaseException as ex:
295        pyparser._last_error = ex
296        return -1
297    else:
298        return 0
299
300
301cdef int cb_on_url(cparser.llhttp_t* parser,
302                   const char *at, size_t length) except -1:
303    cdef HttpParser pyparser = <HttpParser>parser.data
304    try:
305        pyparser._proto_on_url(at[:length])
306    except BaseException as ex:
307        cparser.llhttp_set_error_reason(parser, "`on_url` callback error")
308        pyparser._last_error = ex
309        return cparser.HPE_USER
310    else:
311        return 0
312
313
314cdef int cb_on_status(cparser.llhttp_t* parser,
315                      const char *at, size_t length) except -1:
316    cdef HttpParser pyparser = <HttpParser>parser.data
317    try:
318        pyparser._proto_on_status(at[:length])
319    except BaseException as ex:
320        cparser.llhttp_set_error_reason(parser, "`on_status` callback error")
321        pyparser._last_error = ex
322        return cparser.HPE_USER
323    else:
324        return 0
325
326
327cdef int cb_on_header_field(cparser.llhttp_t* parser,
328                            const char *at, size_t length) except -1:
329    cdef HttpParser pyparser = <HttpParser>parser.data
330    try:
331        pyparser._on_header_field(at[:length])
332    except BaseException as ex:
333        cparser.llhttp_set_error_reason(parser, "`on_header_field` callback error")
334        pyparser._last_error = ex
335        return cparser.HPE_USER
336    else:
337        return 0
338
339
340cdef int cb_on_header_value(cparser.llhttp_t* parser,
341                            const char *at, size_t length) except -1:
342    cdef HttpParser pyparser = <HttpParser>parser.data
343    try:
344        pyparser._on_header_value(at[:length])
345    except BaseException as ex:
346        cparser.llhttp_set_error_reason(parser, "`on_header_value` callback error")
347        pyparser._last_error = ex
348        return cparser.HPE_USER
349    else:
350        return 0
351
352
353cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
354    cdef HttpParser pyparser = <HttpParser>parser.data
355    try:
356        pyparser._on_headers_complete()
357    except BaseException as ex:
358        pyparser._last_error = ex
359        return -1
360    else:
361        if pyparser._cparser.upgrade:
362            return 1
363        else:
364            return 0
365
366
367cdef int cb_on_body(cparser.llhttp_t* parser,
368                    const char *at, size_t length) except -1:
369    cdef HttpParser pyparser = <HttpParser>parser.data
370    try:
371        pyparser._proto_on_body(at[:length])
372    except BaseException as ex:
373        cparser.llhttp_set_error_reason(parser, "`on_body` callback error")
374        pyparser._last_error = ex
375        return cparser.HPE_USER
376    else:
377        return 0
378
379
380cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
381    cdef HttpParser pyparser = <HttpParser>parser.data
382    try:
383        pyparser._proto_on_message_complete()
384    except BaseException as ex:
385        pyparser._last_error = ex
386        return -1
387    else:
388        return 0
389
390
391cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:
392    cdef HttpParser pyparser = <HttpParser>parser.data
393    try:
394        pyparser._on_chunk_header()
395    except BaseException as ex:
396        pyparser._last_error = ex
397        return -1
398    else:
399        return 0
400
401
402cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:
403    cdef HttpParser pyparser = <HttpParser>parser.data
404    try:
405        pyparser._on_chunk_complete()
406    except BaseException as ex:
407        pyparser._last_error = ex
408        return -1
409    else:
410        return 0
411
412
413cdef parser_error_from_errno(cparser.llhttp_t* parser, cparser.llhttp_errno_t errno):
414    cdef bytes reason = cparser.llhttp_get_error_reason(parser)
415
416    if errno in (cparser.HPE_CB_MESSAGE_BEGIN,
417                 cparser.HPE_CB_HEADERS_COMPLETE,
418                 cparser.HPE_CB_MESSAGE_COMPLETE,
419                 cparser.HPE_CB_CHUNK_HEADER,
420                 cparser.HPE_CB_CHUNK_COMPLETE,
421                 cparser.HPE_USER):
422        cls = HttpParserCallbackError
423
424    elif errno == cparser.HPE_INVALID_STATUS:
425        cls = HttpParserInvalidStatusError
426
427    elif errno == cparser.HPE_INVALID_METHOD:
428        cls = HttpParserInvalidMethodError
429
430    elif errno == cparser.HPE_INVALID_URL:
431        cls = HttpParserInvalidURLError
432
433    else:
434        cls = HttpParserError
435
436    return cls(reason.decode('latin-1'))
437 
codekingpro/portable-devtools · Team Ai