You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
4.1 KiB

4 years ago
  1. from libc.stdint cimport uint8_t, uint64_t
  2. from libc.string cimport memcpy
  3. from cpython.exc cimport PyErr_NoMemory
  4. from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
  5. from cpython.bytes cimport PyBytes_FromStringAndSize
  6. from cpython.object cimport PyObject_Str
  7. from multidict import istr
  8. DEF BUF_SIZE = 16 * 1024 # 16KiB
  9. cdef char BUFFER[BUF_SIZE]
  10. cdef object _istr = istr
  11. # ----------------- writer ---------------------------
  12. cdef struct Writer:
  13. char *buf
  14. Py_ssize_t size
  15. Py_ssize_t pos
  16. cdef inline void _init_writer(Writer* writer):
  17. writer.buf = &BUFFER[0]
  18. writer.size = BUF_SIZE
  19. writer.pos = 0
  20. cdef inline void _release_writer(Writer* writer):
  21. if writer.buf != BUFFER:
  22. PyMem_Free(writer.buf)
  23. cdef inline int _write_byte(Writer* writer, uint8_t ch):
  24. cdef char * buf
  25. cdef Py_ssize_t size
  26. if writer.pos == writer.size:
  27. # reallocate
  28. size = writer.size + BUF_SIZE
  29. if writer.buf == BUFFER:
  30. buf = <char*>PyMem_Malloc(size)
  31. if buf == NULL:
  32. PyErr_NoMemory()
  33. return -1
  34. memcpy(buf, writer.buf, writer.size)
  35. else:
  36. buf = <char*>PyMem_Realloc(writer.buf, size)
  37. if buf == NULL:
  38. PyErr_NoMemory()
  39. return -1
  40. writer.buf = buf
  41. writer.size = size
  42. writer.buf[writer.pos] = <char>ch
  43. writer.pos += 1
  44. return 0
  45. cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol):
  46. cdef uint64_t utf = <uint64_t> symbol
  47. if utf < 0x80:
  48. return _write_byte(writer, <uint8_t>utf)
  49. elif utf < 0x800:
  50. if _write_byte(writer, <uint8_t>(0xc0 | (utf >> 6))) < 0:
  51. return -1
  52. return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
  53. elif 0xD800 <= utf <= 0xDFFF:
  54. # surogate pair, ignored
  55. return 0
  56. elif utf < 0x10000:
  57. if _write_byte(writer, <uint8_t>(0xe0 | (utf >> 12))) < 0:
  58. return -1
  59. if _write_byte(writer, <uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:
  60. return -1
  61. return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
  62. elif utf > 0x10FFFF:
  63. # symbol is too large
  64. return 0
  65. else:
  66. if _write_byte(writer, <uint8_t>(0xf0 | (utf >> 18))) < 0:
  67. return -1
  68. if _write_byte(writer,
  69. <uint8_t>(0x80 | ((utf >> 12) & 0x3f))) < 0:
  70. return -1
  71. if _write_byte(writer,
  72. <uint8_t>(0x80 | ((utf >> 6) & 0x3f))) < 0:
  73. return -1
  74. return _write_byte(writer, <uint8_t>(0x80 | (utf & 0x3f)))
  75. cdef inline int _write_str(Writer* writer, str s):
  76. cdef Py_UCS4 ch
  77. for ch in s:
  78. if _write_utf8(writer, ch) < 0:
  79. return -1
  80. # --------------- _serialize_headers ----------------------
  81. cdef str to_str(object s):
  82. typ = type(s)
  83. if typ is str:
  84. return <str>s
  85. elif typ is _istr:
  86. return PyObject_Str(s)
  87. elif not isinstance(s, str):
  88. raise TypeError("Cannot serialize non-str key {!r}".format(s))
  89. else:
  90. return str(s)
  91. def _serialize_headers(str status_line, headers):
  92. cdef Writer writer
  93. cdef object key
  94. cdef object val
  95. cdef bytes ret
  96. _init_writer(&writer)
  97. try:
  98. if _write_str(&writer, status_line) < 0:
  99. raise
  100. if _write_byte(&writer, '\r') < 0:
  101. raise
  102. if _write_byte(&writer, '\n') < 0:
  103. raise
  104. for key, val in headers.items():
  105. if _write_str(&writer, to_str(key)) < 0:
  106. raise
  107. if _write_byte(&writer, ':') < 0:
  108. raise
  109. if _write_byte(&writer, ' ') < 0:
  110. raise
  111. if _write_str(&writer, to_str(val)) < 0:
  112. raise
  113. if _write_byte(&writer, '\r') < 0:
  114. raise
  115. if _write_byte(&writer, '\n') < 0:
  116. raise
  117. if _write_byte(&writer, '\r') < 0:
  118. raise
  119. if _write_byte(&writer, '\n') < 0:
  120. raise
  121. return PyBytes_FromStringAndSize(writer.buf, writer.pos)
  122. finally:
  123. _release_writer(&writer)