|
|
- #cython: language_level=3
- #
- # Based on https://github.com/MagicStack/httptools
- #
- from __future__ import absolute_import, print_function
- from cpython.mem cimport PyMem_Malloc, PyMem_Free
- from libc.string cimport memcpy
- from cpython cimport (PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE,
- Py_buffer, PyBytes_AsString, PyBytes_AsStringAndSize)
-
- from multidict import (CIMultiDict as _CIMultiDict,
- CIMultiDictProxy as _CIMultiDictProxy)
- from yarl import URL as _URL
-
- from aiohttp import hdrs
- from .http_exceptions import (
- BadHttpMessage, BadStatusLine, InvalidHeader, LineTooLong, InvalidURLError,
- PayloadEncodingError, ContentLengthError, TransferEncodingError)
- from .http_writer import (HttpVersion as _HttpVersion,
- HttpVersion10 as _HttpVersion10,
- HttpVersion11 as _HttpVersion11)
- from .http_parser import DeflateBuffer as _DeflateBuffer
- from .streams import (EMPTY_PAYLOAD as _EMPTY_PAYLOAD,
- StreamReader as _StreamReader)
-
- cimport cython
- from aiohttp cimport _cparser as cparser
-
- include "_headers.pxi"
-
- from aiohttp cimport _find_header
-
- DEF DEFAULT_FREELIST_SIZE = 250
-
- cdef extern from "Python.h":
- int PyByteArray_Resize(object, Py_ssize_t) except -1
- Py_ssize_t PyByteArray_Size(object) except -1
- char* PyByteArray_AsString(object)
-
- __all__ = ('HttpRequestParser', 'HttpResponseParser',
- 'RawRequestMessage', 'RawResponseMessage')
-
- cdef object URL = _URL
- cdef object URL_build = URL.build
- cdef object CIMultiDict = _CIMultiDict
- cdef object CIMultiDictProxy = _CIMultiDictProxy
- cdef object HttpVersion = _HttpVersion
- cdef object HttpVersion10 = _HttpVersion10
- cdef object HttpVersion11 = _HttpVersion11
- cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1
- cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING
- cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD
- cdef object StreamReader = _StreamReader
- cdef object DeflateBuffer = _DeflateBuffer
-
-
- cdef inline object extend(object buf, const char* at, size_t length):
- cdef Py_ssize_t s
- cdef char* ptr
- s = PyByteArray_Size(buf)
- PyByteArray_Resize(buf, s + length)
- ptr = PyByteArray_AsString(buf)
- memcpy(ptr + s, at, length)
-
-
- DEF METHODS_COUNT = 34;
-
- cdef list _http_method = []
-
- for i in range(METHODS_COUNT):
- _http_method.append(
- cparser.http_method_str(<cparser.http_method> i).decode('ascii'))
-
-
- cdef inline str http_method_str(int i):
- if i < METHODS_COUNT:
- return <str>_http_method[i]
- else:
- return "<unknown>"
-
- cdef inline object find_header(bytes raw_header):
- cdef Py_ssize_t size
- cdef char *buf
- cdef int idx
- PyBytes_AsStringAndSize(raw_header, &buf, &size)
- idx = _find_header.find_header(buf, size)
- if idx == -1:
- return raw_header.decode('utf-8', 'surrogateescape')
- return headers[idx]
-
-
- @cython.freelist(DEFAULT_FREELIST_SIZE)
- cdef class RawRequestMessage:
- cdef readonly str method
- cdef readonly str path
- cdef readonly object version # HttpVersion
- cdef readonly object headers # CIMultiDict
- cdef readonly object raw_headers # tuple
- cdef readonly object should_close
- cdef readonly object compression
- cdef readonly object upgrade
- cdef readonly object chunked
- cdef readonly object url # yarl.URL
-
- def __init__(self, method, path, version, headers, raw_headers,
- should_close, compression, upgrade, chunked, url):
- self.method = method
- self.path = path
- self.version = version
- self.headers = headers
- self.raw_headers = raw_headers
- self.should_close = should_close
- self.compression = compression
- self.upgrade = upgrade
- self.chunked = chunked
- self.url = url
-
- def __repr__(self):
- info = []
- info.append(("method", self.method))
- info.append(("path", self.path))
- info.append(("version", self.version))
- info.append(("headers", self.headers))
- info.append(("raw_headers", self.raw_headers))
- info.append(("should_close", self.should_close))
- info.append(("compression", self.compression))
- info.append(("upgrade", self.upgrade))
- info.append(("chunked", self.chunked))
- info.append(("url", self.url))
- sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
- return '<RawRequestMessage(' + sinfo + ')>'
-
- def _replace(self, **dct):
- cdef RawRequestMessage ret
- ret = _new_request_message(self.method,
- self.path,
- self.version,
- self.headers,
- self.raw_headers,
- self.should_close,
- self.compression,
- self.upgrade,
- self.chunked,
- self.url)
- if "method" in dct:
- ret.method = dct["method"]
- if "path" in dct:
- ret.path = dct["path"]
- if "version" in dct:
- ret.version = dct["version"]
- if "headers" in dct:
- ret.headers = dct["headers"]
- if "raw_headers" in dct:
- ret.raw_headers = dct["raw_headers"]
- if "should_close" in dct:
- ret.should_close = dct["should_close"]
- if "compression" in dct:
- ret.compression = dct["compression"]
- if "upgrade" in dct:
- ret.upgrade = dct["upgrade"]
- if "chunked" in dct:
- ret.chunked = dct["chunked"]
- if "url" in dct:
- ret.url = dct["url"]
- return ret
-
- cdef _new_request_message(str method,
- str path,
- object version,
- object headers,
- object raw_headers,
- bint should_close,
- object compression,
- bint upgrade,
- bint chunked,
- object url):
- cdef RawRequestMessage ret
- ret = RawRequestMessage.__new__(RawRequestMessage)
- ret.method = method
- ret.path = path
- ret.version = version
- ret.headers = headers
- ret.raw_headers = raw_headers
- ret.should_close = should_close
- ret.compression = compression
- ret.upgrade = upgrade
- ret.chunked = chunked
- ret.url = url
- return ret
-
-
- @cython.freelist(DEFAULT_FREELIST_SIZE)
- cdef class RawResponseMessage:
- cdef readonly object version # HttpVersion
- cdef readonly int code
- cdef readonly str reason
- cdef readonly object headers # CIMultiDict
- cdef readonly object raw_headers # tuple
- cdef readonly object should_close
- cdef readonly object compression
- cdef readonly object upgrade
- cdef readonly object chunked
-
- def __init__(self, version, code, reason, headers, raw_headers,
- should_close, compression, upgrade, chunked):
- self.version = version
- self.code = code
- self.reason = reason
- self.headers = headers
- self.raw_headers = raw_headers
- self.should_close = should_close
- self.compression = compression
- self.upgrade = upgrade
- self.chunked = chunked
-
- def __repr__(self):
- info = []
- info.append(("version", self.version))
- info.append(("code", self.code))
- info.append(("reason", self.reason))
- info.append(("headers", self.headers))
- info.append(("raw_headers", self.raw_headers))
- info.append(("should_close", self.should_close))
- info.append(("compression", self.compression))
- info.append(("upgrade", self.upgrade))
- info.append(("chunked", self.chunked))
- sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
- return '<RawResponseMessage(' + sinfo + ')>'
-
-
- cdef _new_response_message(object version,
- int code,
- str reason,
- object headers,
- object raw_headers,
- bint should_close,
- object compression,
- bint upgrade,
- bint chunked):
- cdef RawResponseMessage ret
- ret = RawResponseMessage.__new__(RawResponseMessage)
- ret.version = version
- ret.code = code
- ret.reason = reason
- ret.headers = headers
- ret.raw_headers = raw_headers
- ret.should_close = should_close
- ret.compression = compression
- ret.upgrade = upgrade
- ret.chunked = chunked
- return ret
-
-
- @cython.internal
- cdef class HttpParser:
-
- cdef:
- cparser.http_parser* _cparser
- cparser.http_parser_settings* _csettings
-
- bytearray _raw_name
- bytearray _raw_value
- bint _has_value
-
- object _protocol
- object _loop
- object _timer
-
- size_t _max_line_size
- size_t _max_field_size
- size_t _max_headers
- bint _response_with_body
-
- bint _started
- object _url
- bytearray _buf
- str _path
- str _reason
- object _headers
- list _raw_headers
- bint _upgraded
- list _messages
- object _payload
- bint _payload_error
- object _payload_exception
- object _last_error
- bint _auto_decompress
-
- str _content_encoding
-
- Py_buffer py_buf
-
- def __cinit__(self):
- self._cparser = <cparser.http_parser*> \
- PyMem_Malloc(sizeof(cparser.http_parser))
- if self._cparser is NULL:
- raise MemoryError()
-
- self._csettings = <cparser.http_parser_settings*> \
- PyMem_Malloc(sizeof(cparser.http_parser_settings))
- if self._csettings is NULL:
- raise MemoryError()
-
- def __dealloc__(self):
- PyMem_Free(self._cparser)
- PyMem_Free(self._csettings)
-
- cdef _init(self, cparser.http_parser_type mode,
- object protocol, object loop, object timer=None,
- size_t max_line_size=8190, size_t max_headers=32768,
- size_t max_field_size=8190, payload_exception=None,
- bint response_with_body=True, bint auto_decompress=True):
- cparser.http_parser_init(self._cparser, mode)
- self._cparser.data = <void*>self
- self._cparser.content_length = 0
-
- cparser.http_parser_settings_init(self._csettings)
-
- self._protocol = protocol
- self._loop = loop
- self._timer = timer
-
- self._buf = bytearray()
- self._payload = None
- self._payload_error = 0
- self._payload_exception = payload_exception
- self._messages = []
-
- self._raw_name = bytearray()
- self._raw_value = bytearray()
- self._has_value = False
-
- self._max_line_size = max_line_size
- self._max_headers = max_headers
- self._max_field_size = max_field_size
- self._response_with_body = response_with_body
- self._upgraded = False
- self._auto_decompress = auto_decompress
- self._content_encoding = None
-
- self._csettings.on_url = cb_on_url
- self._csettings.on_status = cb_on_status
- self._csettings.on_header_field = cb_on_header_field
- self._csettings.on_header_value = cb_on_header_value
- self._csettings.on_headers_complete = cb_on_headers_complete
- self._csettings.on_body = cb_on_body
- self._csettings.on_message_begin = cb_on_message_begin
- self._csettings.on_message_complete = cb_on_message_complete
- self._csettings.on_chunk_header = cb_on_chunk_header
- self._csettings.on_chunk_complete = cb_on_chunk_complete
-
- self._last_error = None
-
- cdef _process_header(self):
- if self._raw_name:
- raw_name = bytes(self._raw_name)
- raw_value = bytes(self._raw_value)
-
- name = find_header(raw_name)
- value = raw_value.decode('utf-8', 'surrogateescape')
-
- self._headers.add(name, value)
-
- if name is CONTENT_ENCODING:
- self._content_encoding = value
-
- PyByteArray_Resize(self._raw_name, 0)
- PyByteArray_Resize(self._raw_value, 0)
- self._has_value = False
- self._raw_headers.append((raw_name, raw_value))
-
- cdef _on_header_field(self, char* at, size_t length):
- cdef Py_ssize_t size
- cdef char *buf
- if self._has_value:
- self._process_header()
-
- size = PyByteArray_Size(self._raw_name)
- PyByteArray_Resize(self._raw_name, size + length)
- buf = PyByteArray_AsString(self._raw_name)
- memcpy(buf + size, at, length)
-
- cdef _on_header_value(self, char* at, size_t length):
- cdef Py_ssize_t size
- cdef char *buf
-
- size = PyByteArray_Size(self._raw_value)
- PyByteArray_Resize(self._raw_value, size + length)
- buf = PyByteArray_AsString(self._raw_value)
- memcpy(buf + size, at, length)
- self._has_value = True
-
- cdef _on_headers_complete(self):
- self._process_header()
-
- method = http_method_str(self._cparser.method)
- should_close = not cparser.http_should_keep_alive(self._cparser)
- upgrade = self._cparser.upgrade
- chunked = self._cparser.flags & cparser.F_CHUNKED
-
- raw_headers = tuple(self._raw_headers)
- headers = CIMultiDictProxy(self._headers)
-
- if upgrade or self._cparser.method == 5: # cparser.CONNECT:
- self._upgraded = True
-
- # do not support old websocket spec
- if SEC_WEBSOCKET_KEY1 in headers:
- raise InvalidHeader(SEC_WEBSOCKET_KEY1)
-
- encoding = None
- enc = self._content_encoding
- if enc is not None:
- self._content_encoding = None
- enc = enc.lower()
- if enc in ('gzip', 'deflate', 'br'):
- encoding = enc
-
- if self._cparser.type == cparser.HTTP_REQUEST:
- msg = _new_request_message(
- method, self._path,
- self.http_version(), headers, raw_headers,
- should_close, encoding, upgrade, chunked, self._url)
- else:
- msg = _new_response_message(
- self.http_version(), self._cparser.status_code, self._reason,
- headers, raw_headers, should_close, encoding,
- upgrade, chunked)
-
- if (self._cparser.content_length > 0 or chunked or
- self._cparser.method == 5): # CONNECT: 5
- payload = StreamReader(
- self._protocol, timer=self._timer, loop=self._loop)
- else:
- payload = EMPTY_PAYLOAD
-
- self._payload = payload
- if encoding is not None and self._auto_decompress:
- self._payload = DeflateBuffer(payload, encoding)
-
- if not self._response_with_body:
- payload = EMPTY_PAYLOAD
-
- self._messages.append((msg, payload))
-
- cdef _on_message_complete(self):
- self._payload.feed_eof()
- self._payload = None
-
- cdef _on_chunk_header(self):
- self._payload.begin_http_chunk_receiving()
-
- cdef _on_chunk_complete(self):
- self._payload.end_http_chunk_receiving()
-
- cdef object _on_status_complete(self):
- pass
-
- cdef inline http_version(self):
- cdef cparser.http_parser* parser = self._cparser
-
- if parser.http_major == 1:
- if parser.http_minor == 0:
- return HttpVersion10
- elif parser.http_minor == 1:
- return HttpVersion11
-
- return HttpVersion(parser.http_major, parser.http_minor)
-
- ### Public API ###
-
- def feed_eof(self):
- cdef bytes desc
-
- if self._payload is not None:
- if self._cparser.flags & cparser.F_CHUNKED:
- raise TransferEncodingError(
- "Not enough data for satisfy transfer length header.")
- elif self._cparser.flags & cparser.F_CONTENTLENGTH:
- raise ContentLengthError(
- "Not enough data for satisfy content length header.")
- elif self._cparser.http_errno != cparser.HPE_OK:
- desc = cparser.http_errno_description(
- <cparser.http_errno> self._cparser.http_errno)
- raise PayloadEncodingError(desc.decode('latin-1'))
- else:
- self._payload.feed_eof()
- elif self._started:
- self._on_headers_complete()
- if self._messages:
- return self._messages[-1][0]
-
- def feed_data(self, data):
- cdef:
- size_t data_len
- size_t nb
-
- PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE)
- data_len = <size_t>self.py_buf.len
-
- nb = cparser.http_parser_execute(
- self._cparser,
- self._csettings,
- <char*>self.py_buf.buf,
- data_len)
-
- PyBuffer_Release(&self.py_buf)
-
- # i am not sure about cparser.HPE_INVALID_METHOD,
- # seems get err for valid request
- # test_client_functional.py::test_post_data_with_bytesio_file
- if (self._cparser.http_errno != cparser.HPE_OK and
- (self._cparser.http_errno != cparser.HPE_INVALID_METHOD or
- self._cparser.method == 0)):
- if self._payload_error == 0:
- if self._last_error is not None:
- ex = self._last_error
- self._last_error = None
- else:
- ex = parser_error_from_errno(
- <cparser.http_errno> self._cparser.http_errno)
- self._payload = None
- raise ex
-
- if self._messages:
- messages = self._messages
- self._messages = []
- else:
- messages = ()
-
- if self._upgraded:
- return messages, True, data[nb:]
- else:
- return messages, False, b''
-
-
- cdef class HttpRequestParser(HttpParser):
-
- def __init__(self, protocol, loop, timer=None,
- size_t max_line_size=8190, size_t max_headers=32768,
- size_t max_field_size=8190, payload_exception=None,
- bint response_with_body=True, bint read_until_eof=False):
- self._init(cparser.HTTP_REQUEST, protocol, loop, timer,
- max_line_size, max_headers, max_field_size,
- payload_exception, response_with_body)
-
- cdef object _on_status_complete(self):
- cdef Py_buffer py_buf
- if not self._buf:
- return
- self._path = self._buf.decode('utf-8', 'surrogateescape')
- if self._cparser.method == 5: # CONNECT
- self._url = URL(self._path)
- else:
- PyObject_GetBuffer(self._buf, &py_buf, PyBUF_SIMPLE)
- try:
- self._url = _parse_url(<char*>py_buf.buf,
- py_buf.len)
- finally:
- PyBuffer_Release(&py_buf)
- PyByteArray_Resize(self._buf, 0)
-
-
- cdef class HttpResponseParser(HttpParser):
-
- def __init__(self, protocol, loop, timer=None,
- size_t max_line_size=8190, size_t max_headers=32768,
- size_t max_field_size=8190, payload_exception=None,
- bint response_with_body=True, bint read_until_eof=False,
- bint auto_decompress=True):
- self._init(cparser.HTTP_RESPONSE, protocol, loop, timer,
- max_line_size, max_headers, max_field_size,
- payload_exception, response_with_body, auto_decompress)
-
- cdef object _on_status_complete(self):
- if self._buf:
- self._reason = self._buf.decode('utf-8', 'surrogateescape')
- PyByteArray_Resize(self._buf, 0)
-
-
- cdef int cb_on_message_begin(cparser.http_parser* parser) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
-
- pyparser._started = True
- pyparser._headers = CIMultiDict()
- pyparser._raw_headers = []
- PyByteArray_Resize(pyparser._buf, 0)
- pyparser._path = None
- pyparser._reason = None
- return 0
-
-
- cdef int cb_on_url(cparser.http_parser* parser,
- const char *at, size_t length) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- try:
- if length > pyparser._max_line_size:
- raise LineTooLong(
- 'Status line is too long', pyparser._max_line_size, length)
- extend(pyparser._buf, at, length)
- except BaseException as ex:
- pyparser._last_error = ex
- return -1
- else:
- return 0
-
-
- cdef int cb_on_status(cparser.http_parser* parser,
- const char *at, size_t length) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- cdef str reason
- try:
- if length > pyparser._max_line_size:
- raise LineTooLong(
- 'Status line is too long', pyparser._max_line_size, length)
- extend(pyparser._buf, at, length)
- except BaseException as ex:
- pyparser._last_error = ex
- return -1
- else:
- return 0
-
-
- cdef int cb_on_header_field(cparser.http_parser* parser,
- const char *at, size_t length) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- cdef Py_ssize_t size
- try:
- pyparser._on_status_complete()
- size = len(pyparser._raw_name) + length
- if size > pyparser._max_field_size:
- raise LineTooLong(
- 'Header name is too long', pyparser._max_field_size, size)
- pyparser._on_header_field(at, length)
- except BaseException as ex:
- pyparser._last_error = ex
- return -1
- else:
- return 0
-
-
- cdef int cb_on_header_value(cparser.http_parser* parser,
- const char *at, size_t length) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- cdef Py_ssize_t size
- try:
- size = len(pyparser._raw_value) + length
- if size > pyparser._max_field_size:
- raise LineTooLong(
- 'Header value is too long', pyparser._max_field_size, size)
- pyparser._on_header_value(at, length)
- except BaseException as ex:
- pyparser._last_error = ex
- return -1
- else:
- return 0
-
-
- cdef int cb_on_headers_complete(cparser.http_parser* parser) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- try:
- pyparser._on_status_complete()
- pyparser._on_headers_complete()
- except BaseException as exc:
- pyparser._last_error = exc
- return -1
- else:
- if pyparser._cparser.upgrade or pyparser._cparser.method == 5: # CONNECT
- return 2
- else:
- return 0
-
-
- cdef int cb_on_body(cparser.http_parser* parser,
- const char *at, size_t length) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- cdef bytes body = at[:length]
- try:
- pyparser._payload.feed_data(body, length)
- except BaseException as exc:
- if pyparser._payload_exception is not None:
- pyparser._payload.set_exception(pyparser._payload_exception(str(exc)))
- else:
- pyparser._payload.set_exception(exc)
- pyparser._payload_error = 1
- return -1
- else:
- return 0
-
-
- cdef int cb_on_message_complete(cparser.http_parser* parser) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- try:
- pyparser._started = False
- pyparser._on_message_complete()
- except BaseException as exc:
- pyparser._last_error = exc
- return -1
- else:
- return 0
-
-
- cdef int cb_on_chunk_header(cparser.http_parser* parser) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- try:
- pyparser._on_chunk_header()
- except BaseException as exc:
- pyparser._last_error = exc
- return -1
- else:
- return 0
-
-
- cdef int cb_on_chunk_complete(cparser.http_parser* parser) except -1:
- cdef HttpParser pyparser = <HttpParser>parser.data
- try:
- pyparser._on_chunk_complete()
- except BaseException as exc:
- pyparser._last_error = exc
- return -1
- else:
- return 0
-
-
- cdef parser_error_from_errno(cparser.http_errno errno):
- cdef bytes desc = cparser.http_errno_description(errno)
-
- if errno in (cparser.HPE_CB_message_begin,
- cparser.HPE_CB_url,
- cparser.HPE_CB_header_field,
- cparser.HPE_CB_header_value,
- cparser.HPE_CB_headers_complete,
- cparser.HPE_CB_body,
- cparser.HPE_CB_message_complete,
- cparser.HPE_CB_status,
- cparser.HPE_CB_chunk_header,
- cparser.HPE_CB_chunk_complete):
- cls = BadHttpMessage
-
- elif errno == cparser.HPE_INVALID_STATUS:
- cls = BadStatusLine
-
- elif errno == cparser.HPE_INVALID_METHOD:
- cls = BadStatusLine
-
- elif errno == cparser.HPE_INVALID_URL:
- cls = InvalidURLError
-
- else:
- cls = BadHttpMessage
-
- return cls(desc.decode('latin-1'))
-
-
- def parse_url(url):
- cdef:
- Py_buffer py_buf
- char* buf_data
-
- PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
- try:
- buf_data = <char*>py_buf.buf
- return _parse_url(buf_data, py_buf.len)
- finally:
- PyBuffer_Release(&py_buf)
-
-
- cdef _parse_url(char* buf_data, size_t length):
- cdef:
- cparser.http_parser_url* parsed
- int res
- str schema = None
- str host = None
- object port = None
- str path = None
- str query = None
- str fragment = None
- str user = None
- str password = None
- str userinfo = None
- object result = None
- int off
- int ln
-
- parsed = <cparser.http_parser_url*> \
- PyMem_Malloc(sizeof(cparser.http_parser_url))
- if parsed is NULL:
- raise MemoryError()
- cparser.http_parser_url_init(parsed)
- try:
- res = cparser.http_parser_parse_url(buf_data, length, 0, parsed)
-
- if res == 0:
- if parsed.field_set & (1 << cparser.UF_SCHEMA):
- off = parsed.field_data[<int>cparser.UF_SCHEMA].off
- ln = parsed.field_data[<int>cparser.UF_SCHEMA].len
- schema = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
- else:
- schema = ''
-
- if parsed.field_set & (1 << cparser.UF_HOST):
- off = parsed.field_data[<int>cparser.UF_HOST].off
- ln = parsed.field_data[<int>cparser.UF_HOST].len
- host = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
- else:
- host = ''
-
- if parsed.field_set & (1 << cparser.UF_PORT):
- port = parsed.port
-
- if parsed.field_set & (1 << cparser.UF_PATH):
- off = parsed.field_data[<int>cparser.UF_PATH].off
- ln = parsed.field_data[<int>cparser.UF_PATH].len
- path = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
- else:
- path = ''
-
- if parsed.field_set & (1 << cparser.UF_QUERY):
- off = parsed.field_data[<int>cparser.UF_QUERY].off
- ln = parsed.field_data[<int>cparser.UF_QUERY].len
- query = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
- else:
- query = ''
-
- if parsed.field_set & (1 << cparser.UF_FRAGMENT):
- off = parsed.field_data[<int>cparser.UF_FRAGMENT].off
- ln = parsed.field_data[<int>cparser.UF_FRAGMENT].len
- fragment = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
- else:
- fragment = ''
-
- if parsed.field_set & (1 << cparser.UF_USERINFO):
- off = parsed.field_data[<int>cparser.UF_USERINFO].off
- ln = parsed.field_data[<int>cparser.UF_USERINFO].len
- userinfo = buf_data[off:off+ln].decode('utf-8', 'surrogateescape')
-
- user, sep, password = userinfo.partition(':')
-
- return URL_build(scheme=schema,
- user=user, password=password, host=host, port=port,
- path=path, query=query, fragment=fragment)
- else:
- raise InvalidURLError("invalid url {!r}".format(buf_data))
- finally:
- PyMem_Free(parsed)
|