_http_writer.pyx 4.6 KB

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