url_parser.pyx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #cython: language_level=3
  2. from __future__ import print_function
  3. from cpython.mem cimport PyMem_Malloc, PyMem_Free
  4. from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
  5. Py_buffer
  6. from .errors import HttpParserInvalidURLError
  7. cimport cython
  8. from . cimport url_cparser as uparser
  9. __all__ = ('parse_url',)
  10. DEF MAX_URL_LENGTH = (1 << 16) - 1
  11. @cython.freelist(250)
  12. cdef class URL:
  13. cdef readonly bytes schema
  14. cdef readonly bytes host
  15. cdef readonly object port
  16. cdef readonly bytes path
  17. cdef readonly bytes query
  18. cdef readonly bytes fragment
  19. cdef readonly bytes userinfo
  20. def __cinit__(self, bytes schema, bytes host, object port, bytes path,
  21. bytes query, bytes fragment, bytes userinfo):
  22. self.schema = schema
  23. self.host = host
  24. self.port = port
  25. self.path = path
  26. self.query = query
  27. self.fragment = fragment
  28. self.userinfo = userinfo
  29. def __repr__(self):
  30. return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
  31. 'query: {!r}, fragment: {!r}, userinfo: {!r}>'
  32. .format(self.schema, self.host, self.port, self.path,
  33. self.query, self.fragment, self.userinfo))
  34. def parse_url(url):
  35. cdef:
  36. Py_buffer py_buf
  37. char* buf_data
  38. uparser.http_parser_url* parsed
  39. int res
  40. bytes schema = None
  41. bytes host = None
  42. object port = None
  43. bytes path = None
  44. bytes query = None
  45. bytes fragment = None
  46. bytes userinfo = None
  47. object result = None
  48. int off
  49. int ln
  50. parsed = <uparser.http_parser_url*> \
  51. PyMem_Malloc(sizeof(uparser.http_parser_url))
  52. uparser.http_parser_url_init(parsed)
  53. PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
  54. try:
  55. if py_buf.len > MAX_URL_LENGTH:
  56. # http_parser stores URL field offsets/lengths as uint16_t,
  57. # so URLs longer than this will cause silent truncation.
  58. # See https://github.com/MagicStack/httptools/issues/142
  59. raise HttpParserInvalidURLError(
  60. "url is too long: url length of {} bytes exceeds the "
  61. "maximum of {} bytes".format(py_buf.len, MAX_URL_LENGTH))
  62. buf_data = <char*>py_buf.buf
  63. res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
  64. if res == 0:
  65. if parsed.field_set & (1 << uparser.UF_SCHEMA):
  66. off = parsed.field_data[<int>uparser.UF_SCHEMA].off
  67. ln = parsed.field_data[<int>uparser.UF_SCHEMA].len
  68. schema = buf_data[off:off+ln]
  69. if parsed.field_set & (1 << uparser.UF_HOST):
  70. off = parsed.field_data[<int>uparser.UF_HOST].off
  71. ln = parsed.field_data[<int>uparser.UF_HOST].len
  72. host = buf_data[off:off+ln]
  73. if parsed.field_set & (1 << uparser.UF_PORT):
  74. port = parsed.port
  75. if parsed.field_set & (1 << uparser.UF_PATH):
  76. off = parsed.field_data[<int>uparser.UF_PATH].off
  77. ln = parsed.field_data[<int>uparser.UF_PATH].len
  78. path = buf_data[off:off+ln]
  79. if parsed.field_set & (1 << uparser.UF_QUERY):
  80. off = parsed.field_data[<int>uparser.UF_QUERY].off
  81. ln = parsed.field_data[<int>uparser.UF_QUERY].len
  82. query = buf_data[off:off+ln]
  83. if parsed.field_set & (1 << uparser.UF_FRAGMENT):
  84. off = parsed.field_data[<int>uparser.UF_FRAGMENT].off
  85. ln = parsed.field_data[<int>uparser.UF_FRAGMENT].len
  86. fragment = buf_data[off:off+ln]
  87. if parsed.field_set & (1 << uparser.UF_USERINFO):
  88. off = parsed.field_data[<int>uparser.UF_USERINFO].off
  89. ln = parsed.field_data[<int>uparser.UF_USERINFO].len
  90. userinfo = buf_data[off:off+ln]
  91. return URL(schema, host, port, path, query, fragment, userinfo)
  92. else:
  93. raise HttpParserInvalidURLError("invalid url {!r}".format(url))
  94. finally:
  95. PyBuffer_Release(&py_buf)
  96. PyMem_Free(parsed)