codekingpro/portable-devtools
115k
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 