second commit

This commit is contained in:
2024-12-27 22:31:23 +09:00
parent 2353324570
commit 10a0f110ca
8819 changed files with 1307198 additions and 28 deletions

View File

@ -0,0 +1,6 @@
from . import parser
from .parser import * # NOQA
from ._version import __version__ # NOQA
__all__ = parser.__all__ + ('__version__',) # NOQA

View File

@ -0,0 +1,13 @@
# This file MUST NOT contain anything but the __version__ assignment.
#
# When making a release, change the value of __version__
# to an appropriate value, and open a pull request against
# the correct branch (master if making a new feature release).
# The commit message MUST contain a properly formatted release
# log, and the commit must be signed.
#
# The release automation will: build and test the packages for the
# supported platforms, publish the packages on PyPI, merge the PR
# to the target branch, create a Git tag pointing to the commit.
__version__ = '0.6.4'

View File

@ -0,0 +1,5 @@
from .parser import * # NoQA
from .errors import * # NoQA
from .url_parser import * # NoQA
__all__ = parser.__all__ + errors.__all__ + url_parser.__all__ # NoQA

View File

@ -0,0 +1,167 @@
from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t
cdef extern from "llhttp.h":
struct llhttp__internal_s:
int32_t _index
void *_span_pos0
void *_span_cb0
int32_t error
const char *reason
const char *error_pos
void *data
void *_current
uint64_t content_length
uint8_t type
uint8_t method
uint8_t http_major
uint8_t http_minor
uint8_t header_state
uint16_t flags
uint8_t upgrade
uint16_t status_code
uint8_t finish
void *settings
ctypedef llhttp__internal_s llhttp__internal_t
ctypedef llhttp__internal_t llhttp_t
ctypedef int (*llhttp_data_cb) (llhttp_t*,
const char *at,
size_t length) except -1
ctypedef int (*llhttp_cb) (llhttp_t*) except -1
struct llhttp_settings_s:
llhttp_cb on_message_begin
llhttp_data_cb on_url
llhttp_data_cb on_status
llhttp_data_cb on_header_field
llhttp_data_cb on_header_value
llhttp_cb on_headers_complete
llhttp_data_cb on_body
llhttp_cb on_message_complete
llhttp_cb on_chunk_header
llhttp_cb on_chunk_complete
ctypedef llhttp_settings_s llhttp_settings_t
enum llhttp_type:
HTTP_BOTH,
HTTP_REQUEST,
HTTP_RESPONSE
ctypedef llhttp_type llhttp_type_t
enum llhttp_errno:
HPE_OK,
HPE_INTERNAL,
HPE_STRICT,
HPE_LF_EXPECTED,
HPE_UNEXPECTED_CONTENT_LENGTH,
HPE_CLOSED_CONNECTION,
HPE_INVALID_METHOD,
HPE_INVALID_URL,
HPE_INVALID_CONSTANT,
HPE_INVALID_VERSION,
HPE_INVALID_HEADER_TOKEN,
HPE_INVALID_CONTENT_LENGTH,
HPE_INVALID_CHUNK_SIZE,
HPE_INVALID_STATUS,
HPE_INVALID_EOF_STATE,
HPE_INVALID_TRANSFER_ENCODING,
HPE_CB_MESSAGE_BEGIN,
HPE_CB_HEADERS_COMPLETE,
HPE_CB_MESSAGE_COMPLETE,
HPE_CB_CHUNK_HEADER,
HPE_CB_CHUNK_COMPLETE,
HPE_PAUSED,
HPE_PAUSED_UPGRADE,
HPE_USER
ctypedef llhttp_errno llhttp_errno_t
enum llhttp_flags:
F_CONNECTION_KEEP_ALIVE,
F_CONNECTION_CLOSE,
F_CONNECTION_UPGRADE,
F_CHUNKED,
F_UPGRADE,
F_CONTENT_LENGTH,
F_SKIPBODY,
F_TRAILING,
F_LENIENT,
F_TRANSFER_ENCODING
ctypedef llhttp_flags llhttp_flags_t
enum llhttp_method:
HTTP_DELETE,
HTTP_GET,
HTTP_HEAD,
HTTP_POST,
HTTP_PUT,
HTTP_CONNECT,
HTTP_OPTIONS,
HTTP_TRACE,
HTTP_COPY,
HTTP_LOCK,
HTTP_MKCOL,
HTTP_MOVE,
HTTP_PROPFIND,
HTTP_PROPPATCH,
HTTP_SEARCH,
HTTP_UNLOCK,
HTTP_BIND,
HTTP_REBIND,
HTTP_UNBIND,
HTTP_ACL,
HTTP_REPORT,
HTTP_MKACTIVITY,
HTTP_CHECKOUT,
HTTP_MERGE,
HTTP_MSEARCH,
HTTP_NOTIFY,
HTTP_SUBSCRIBE,
HTTP_UNSUBSCRIBE,
HTTP_PATCH,
HTTP_PURGE,
HTTP_MKCALENDAR,
HTTP_LINK,
HTTP_UNLINK,
HTTP_SOURCE,
HTTP_PRI,
HTTP_DESCRIBE,
HTTP_ANNOUNCE,
HTTP_SETUP,
HTTP_PLAY,
HTTP_PAUSE,
HTTP_TEARDOWN,
HTTP_GET_PARAMETER,
HTTP_SET_PARAMETER,
HTTP_REDIRECT,
HTTP_RECORD,
HTTP_FLUSH
ctypedef llhttp_method llhttp_method_t
void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)
void llhttp_settings_init(llhttp_settings_t* settings)
llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)
void llhttp_resume_after_upgrade(llhttp_t* parser)
int llhttp_should_keep_alive(const llhttp_t* parser)
const char* llhttp_get_error_pos(const llhttp_t* parser)
const char* llhttp_get_error_reason(const llhttp_t* parser)
const char* llhttp_method_name(llhttp_method_t method)
void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
void llhttp_set_lenient_headers(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_chunked_length(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_keep_alive(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_version(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_data_after_close(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, bint enabled);

View File

@ -0,0 +1,30 @@
__all__ = ('HttpParserError',
'HttpParserCallbackError',
'HttpParserInvalidStatusError',
'HttpParserInvalidMethodError',
'HttpParserInvalidURLError',
'HttpParserUpgrade')
class HttpParserError(Exception):
pass
class HttpParserCallbackError(HttpParserError):
pass
class HttpParserInvalidStatusError(HttpParserError):
pass
class HttpParserInvalidMethodError(HttpParserError):
pass
class HttpParserInvalidURLError(HttpParserError):
pass
class HttpParserUpgrade(Exception):
pass

View File

@ -0,0 +1,436 @@
#cython: language_level=3
from __future__ import print_function
from typing import Optional
from cpython.mem cimport PyMem_Malloc, PyMem_Free
from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
Py_buffer, PyBytes_AsString
from .python cimport PyMemoryView_Check, PyMemoryView_GET_BUFFER
from .errors import (HttpParserError,
HttpParserCallbackError,
HttpParserInvalidStatusError,
HttpParserInvalidMethodError,
HttpParserInvalidURLError,
HttpParserUpgrade)
cimport cython
from . cimport cparser
__all__ = ('HttpRequestParser', 'HttpResponseParser')
@cython.internal
cdef class HttpParser:
cdef:
cparser.llhttp_t* _cparser
cparser.llhttp_settings_t* _csettings
bytes _current_header_name
bytes _current_header_value
_proto_on_url, _proto_on_status, _proto_on_body, \
_proto_on_header, _proto_on_headers_complete, \
_proto_on_message_complete, _proto_on_chunk_header, \
_proto_on_chunk_complete, _proto_on_message_begin
object _last_error
Py_buffer py_buf
def __cinit__(self):
self._cparser = <cparser.llhttp_t*> \
PyMem_Malloc(sizeof(cparser.llhttp_t))
if self._cparser is NULL:
raise MemoryError()
self._csettings = <cparser.llhttp_settings_t*> \
PyMem_Malloc(sizeof(cparser.llhttp_settings_t))
if self._csettings is NULL:
raise MemoryError()
def __dealloc__(self):
PyMem_Free(self._cparser)
PyMem_Free(self._csettings)
cdef _init(self, protocol, cparser.llhttp_type_t mode):
cparser.llhttp_settings_init(self._csettings)
cparser.llhttp_init(self._cparser, mode, self._csettings)
self._cparser.data = <void*>self
self._current_header_name = None
self._current_header_value = None
self._proto_on_header = getattr(protocol, 'on_header', None)
if self._proto_on_header is not None:
self._csettings.on_header_field = cb_on_header_field
self._csettings.on_header_value = cb_on_header_value
self._proto_on_headers_complete = getattr(
protocol, 'on_headers_complete', None)
self._csettings.on_headers_complete = cb_on_headers_complete
self._proto_on_body = getattr(protocol, 'on_body', None)
if self._proto_on_body is not None:
self._csettings.on_body = cb_on_body
self._proto_on_message_begin = getattr(
protocol, 'on_message_begin', None)
if self._proto_on_message_begin is not None:
self._csettings.on_message_begin = cb_on_message_begin
self._proto_on_message_complete = getattr(
protocol, 'on_message_complete', None)
if self._proto_on_message_complete is not None:
self._csettings.on_message_complete = cb_on_message_complete
self._proto_on_chunk_header = getattr(
protocol, 'on_chunk_header', None)
self._csettings.on_chunk_header = cb_on_chunk_header
self._proto_on_chunk_complete = getattr(
protocol, 'on_chunk_complete', None)
self._csettings.on_chunk_complete = cb_on_chunk_complete
self._last_error = None
cdef _maybe_call_on_header(self):
if self._current_header_value is not None:
current_header_name = self._current_header_name
current_header_value = self._current_header_value
self._current_header_name = self._current_header_value = None
if self._proto_on_header is not None:
self._proto_on_header(current_header_name,
current_header_value)
cdef _on_header_field(self, bytes field):
self._maybe_call_on_header()
if self._current_header_name is None:
self._current_header_name = field
else:
self._current_header_name += field
cdef _on_header_value(self, bytes val):
if self._current_header_value is None:
self._current_header_value = val
else:
# This is unlikely, as mostly HTTP headers are one-line
self._current_header_value += val
cdef _on_headers_complete(self):
self._maybe_call_on_header()
if self._proto_on_headers_complete is not None:
self._proto_on_headers_complete()
cdef _on_chunk_header(self):
if (self._current_header_value is not None or
self._current_header_name is not None):
raise HttpParserError('invalid headers state')
if self._proto_on_chunk_header is not None:
self._proto_on_chunk_header()
cdef _on_chunk_complete(self):
self._maybe_call_on_header()
if self._proto_on_chunk_complete is not None:
self._proto_on_chunk_complete()
### Public API ###
def set_dangerous_leniencies(
self,
lenient_headers: Optional[bool] = None,
lenient_chunked_length: Optional[bool] = None,
lenient_keep_alive: Optional[bool] = None,
lenient_transfer_encoding: Optional[bool] = None,
lenient_version: Optional[bool] = None,
lenient_data_after_close: Optional[bool] = None,
lenient_optional_lf_after_cr: Optional[bool] = None,
lenient_optional_cr_before_lf: Optional[bool] = None,
lenient_optional_crlf_after_chunk: Optional[bool] = None,
lenient_spaces_after_chunk_size: Optional[bool] = None,
):
cdef cparser.llhttp_t* parser = self._cparser
if lenient_headers is not None:
cparser.llhttp_set_lenient_headers(
parser, lenient_headers)
if lenient_chunked_length is not None:
cparser.llhttp_set_lenient_chunked_length(
parser, lenient_chunked_length)
if lenient_keep_alive is not None:
cparser.llhttp_set_lenient_keep_alive(
parser, lenient_keep_alive)
if lenient_transfer_encoding is not None:
cparser.llhttp_set_lenient_transfer_encoding(
parser, lenient_transfer_encoding)
if lenient_version is not None:
cparser.llhttp_set_lenient_version(
parser, lenient_version)
if lenient_data_after_close is not None:
cparser.llhttp_set_lenient_data_after_close(
parser, lenient_data_after_close)
if lenient_optional_lf_after_cr is not None:
cparser.llhttp_set_lenient_optional_lf_after_cr(
parser, lenient_optional_lf_after_cr)
if lenient_optional_cr_before_lf is not None:
cparser.llhttp_set_lenient_optional_cr_before_lf(
parser, lenient_optional_cr_before_lf)
if lenient_optional_crlf_after_chunk is not None:
cparser.llhttp_set_lenient_optional_crlf_after_chunk(
parser, lenient_optional_crlf_after_chunk)
if lenient_spaces_after_chunk_size is not None:
cparser.llhttp_set_lenient_spaces_after_chunk_size(
parser, lenient_spaces_after_chunk_size)
def get_http_version(self):
cdef cparser.llhttp_t* parser = self._cparser
return '{}.{}'.format(parser.http_major, parser.http_minor)
def should_keep_alive(self):
return bool(cparser.llhttp_should_keep_alive(self._cparser))
def should_upgrade(self):
cdef cparser.llhttp_t* parser = self._cparser
return bool(parser.upgrade)
def feed_data(self, data):
cdef:
size_t data_len
cparser.llhttp_errno_t err
Py_buffer *buf
bint owning_buf = False
const char* err_pos
if PyMemoryView_Check(data):
buf = PyMemoryView_GET_BUFFER(data)
data_len = <size_t>buf.len
err = cparser.llhttp_execute(
self._cparser,
<char*>buf.buf,
data_len)
else:
buf = &self.py_buf
PyObject_GetBuffer(data, buf, PyBUF_SIMPLE)
owning_buf = True
data_len = <size_t>buf.len
err = cparser.llhttp_execute(
self._cparser,
<char*>buf.buf,
data_len)
try:
if self._cparser.upgrade == 1 and err == cparser.HPE_PAUSED_UPGRADE:
err_pos = cparser.llhttp_get_error_pos(self._cparser)
# Immediately free the parser from "error" state, simulating
# http-parser behavior here because 1) we never had the API to
# allow users manually "resume after upgrade", and 2) the use
# case for resuming parsing is very rare.
cparser.llhttp_resume_after_upgrade(self._cparser)
# The err_pos here is specific for the input buf. So if we ever
# switch to the llhttp behavior (re-raise HttpParserUpgrade for
# successive calls to feed_data() until resume_after_upgrade is
# called), we have to store the result and keep our own state.
raise HttpParserUpgrade(err_pos - <char*>buf.buf)
finally:
if owning_buf:
PyBuffer_Release(buf)
if err != cparser.HPE_OK:
ex = parser_error_from_errno(
self._cparser,
<cparser.llhttp_errno_t> self._cparser.error)
if isinstance(ex, HttpParserCallbackError):
if self._last_error is not None:
ex.__context__ = self._last_error
self._last_error = None
raise ex
cdef class HttpRequestParser(HttpParser):
def __init__(self, protocol):
self._init(protocol, cparser.HTTP_REQUEST)
self._proto_on_url = getattr(protocol, 'on_url', None)
if self._proto_on_url is not None:
self._csettings.on_url = cb_on_url
def get_method(self):
cdef cparser.llhttp_t* parser = self._cparser
return cparser.llhttp_method_name(<cparser.llhttp_method_t> parser.method)
cdef class HttpResponseParser(HttpParser):
def __init__(self, protocol):
self._init(protocol, cparser.HTTP_RESPONSE)
self._proto_on_status = getattr(protocol, 'on_status', None)
if self._proto_on_status is not None:
self._csettings.on_status = cb_on_status
def get_status_code(self):
cdef cparser.llhttp_t* parser = self._cparser
return parser.status_code
cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._proto_on_message_begin()
except BaseException as ex:
pyparser._last_error = ex
return -1
else:
return 0
cdef int cb_on_url(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._proto_on_url(at[:length])
except BaseException as ex:
cparser.llhttp_set_error_reason(parser, "`on_url` callback error")
pyparser._last_error = ex
return cparser.HPE_USER
else:
return 0
cdef int cb_on_status(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._proto_on_status(at[:length])
except BaseException as ex:
cparser.llhttp_set_error_reason(parser, "`on_status` callback error")
pyparser._last_error = ex
return cparser.HPE_USER
else:
return 0
cdef int cb_on_header_field(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._on_header_field(at[:length])
except BaseException as ex:
cparser.llhttp_set_error_reason(parser, "`on_header_field` callback error")
pyparser._last_error = ex
return cparser.HPE_USER
else:
return 0
cdef int cb_on_header_value(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._on_header_value(at[:length])
except BaseException as ex:
cparser.llhttp_set_error_reason(parser, "`on_header_value` callback error")
pyparser._last_error = ex
return cparser.HPE_USER
else:
return 0
cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._on_headers_complete()
except BaseException as ex:
pyparser._last_error = ex
return -1
else:
if pyparser._cparser.upgrade:
return 1
else:
return 0
cdef int cb_on_body(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._proto_on_body(at[:length])
except BaseException as ex:
cparser.llhttp_set_error_reason(parser, "`on_body` callback error")
pyparser._last_error = ex
return cparser.HPE_USER
else:
return 0
cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._proto_on_message_complete()
except BaseException as ex:
pyparser._last_error = ex
return -1
else:
return 0
cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._on_chunk_header()
except BaseException as ex:
pyparser._last_error = ex
return -1
else:
return 0
cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
try:
pyparser._on_chunk_complete()
except BaseException as ex:
pyparser._last_error = ex
return -1
else:
return 0
cdef parser_error_from_errno(cparser.llhttp_t* parser, cparser.llhttp_errno_t errno):
cdef bytes reason = cparser.llhttp_get_error_reason(parser)
if errno in (cparser.HPE_CB_MESSAGE_BEGIN,
cparser.HPE_CB_HEADERS_COMPLETE,
cparser.HPE_CB_MESSAGE_COMPLETE,
cparser.HPE_CB_CHUNK_HEADER,
cparser.HPE_CB_CHUNK_COMPLETE,
cparser.HPE_USER):
cls = HttpParserCallbackError
elif errno == cparser.HPE_INVALID_STATUS:
cls = HttpParserInvalidStatusError
elif errno == cparser.HPE_INVALID_METHOD:
cls = HttpParserInvalidMethodError
elif errno == cparser.HPE_INVALID_URL:
cls = HttpParserInvalidURLError
else:
cls = HttpParserError
return cls(reason.decode('latin-1'))

View File

@ -0,0 +1,6 @@
cimport cpython
cdef extern from "Python.h":
cpython.Py_buffer* PyMemoryView_GET_BUFFER(object)
bint PyMemoryView_Check(object)

View File

@ -0,0 +1,31 @@
from libc.stdint cimport uint16_t
cdef extern from "http_parser.h":
# URL Parser
enum http_parser_url_fields:
UF_SCHEMA = 0,
UF_HOST = 1,
UF_PORT = 2,
UF_PATH = 3,
UF_QUERY = 4,
UF_FRAGMENT = 5,
UF_USERINFO = 6,
UF_MAX = 7
struct http_parser_url_field_data:
uint16_t off
uint16_t len
struct http_parser_url:
uint16_t field_set
uint16_t port
http_parser_url_field_data[<int>UF_MAX] field_data
void http_parser_url_init(http_parser_url *u)
int http_parser_parse_url(const char *buf,
size_t buflen,
int is_connect,
http_parser_url *u)

View File

@ -0,0 +1,108 @@
#cython: language_level=3
from __future__ import print_function
from cpython.mem cimport PyMem_Malloc, PyMem_Free
from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
Py_buffer
from .errors import HttpParserInvalidURLError
cimport cython
from . cimport url_cparser as uparser
__all__ = ('parse_url',)
@cython.freelist(250)
cdef class URL:
cdef readonly bytes schema
cdef readonly bytes host
cdef readonly object port
cdef readonly bytes path
cdef readonly bytes query
cdef readonly bytes fragment
cdef readonly bytes userinfo
def __cinit__(self, bytes schema, bytes host, object port, bytes path,
bytes query, bytes fragment, bytes userinfo):
self.schema = schema
self.host = host
self.port = port
self.path = path
self.query = query
self.fragment = fragment
self.userinfo = userinfo
def __repr__(self):
return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
'query: {!r}, fragment: {!r}, userinfo: {!r}>'
.format(self.schema, self.host, self.port, self.path,
self.query, self.fragment, self.userinfo))
def parse_url(url):
cdef:
Py_buffer py_buf
char* buf_data
uparser.http_parser_url* parsed
int res
bytes schema = None
bytes host = None
object port = None
bytes path = None
bytes query = None
bytes fragment = None
bytes userinfo = None
object result = None
int off
int ln
parsed = <uparser.http_parser_url*> \
PyMem_Malloc(sizeof(uparser.http_parser_url))
uparser.http_parser_url_init(parsed)
PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
try:
buf_data = <char*>py_buf.buf
res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
if res == 0:
if parsed.field_set & (1 << uparser.UF_SCHEMA):
off = parsed.field_data[<int>uparser.UF_SCHEMA].off
ln = parsed.field_data[<int>uparser.UF_SCHEMA].len
schema = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_HOST):
off = parsed.field_data[<int>uparser.UF_HOST].off
ln = parsed.field_data[<int>uparser.UF_HOST].len
host = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_PORT):
port = parsed.port
if parsed.field_set & (1 << uparser.UF_PATH):
off = parsed.field_data[<int>uparser.UF_PATH].off
ln = parsed.field_data[<int>uparser.UF_PATH].len
path = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_QUERY):
off = parsed.field_data[<int>uparser.UF_QUERY].off
ln = parsed.field_data[<int>uparser.UF_QUERY].len
query = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_FRAGMENT):
off = parsed.field_data[<int>uparser.UF_FRAGMENT].off
ln = parsed.field_data[<int>uparser.UF_FRAGMENT].len
fragment = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_USERINFO):
off = parsed.field_data[<int>uparser.UF_USERINFO].off
ln = parsed.field_data[<int>uparser.UF_USERINFO].len
userinfo = buf_data[off:off+ln]
return URL(schema, host, port, path, query, fragment, userinfo)
else:
raise HttpParserInvalidURLError("invalid url {!r}".format(url))
finally:
PyBuffer_Release(&py_buf)
PyMem_Free(parsed)