Skip to content

Commit c80c416

Browse files
cdelerpgjones
authored andcommitted
Fixed PR remarks from #115 (comment)
1. added new tests to test_io.py 2. introduced ReceiveBuffer::_extract 3. added a newsfragment
1 parent 15947cd commit c80c416

File tree

3 files changed

+64
-28
lines changed

3 files changed

+64
-28
lines changed

h11/_receivebuffer.py

+24-28
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ def __bytes__(self):
6868
__str__ = __bytes__
6969
__nonzero__ = __bool__
7070

71+
def _extract(self, count):
72+
# extracting an initial slice of the data buffer and return it
73+
out = self._data[:count]
74+
del self._data[:count]
75+
76+
self._next_line_search = 0
77+
self._multiple_lines_search = 0
78+
79+
return out
80+
7181
def maybe_extract_at_most(self, count):
7282
"""
7383
Extract a fixed number of bytes from the buffer.
@@ -76,65 +86,51 @@ def maybe_extract_at_most(self, count):
7686
if not out:
7787
return None
7888

79-
self._data[:count] = b""
80-
self._next_line_search = 0
81-
self._multiple_lines_search = 0
82-
return out
89+
return self._extract(count)
8390

8491
def maybe_extract_next_line(self):
8592
"""
8693
Extract the first line, if it is completed in the buffer.
8794
"""
8895
# Only search in buffer space that we've not already looked at.
8996
search_start_index = max(0, self._next_line_search - 1)
90-
partial_buffer = self._data[search_start_index:]
91-
partial_idx = partial_buffer.find(b"\r\n")
97+
partial_idx = self._data.find(b"\r\n", search_start_index)
98+
9299
if partial_idx == -1:
93100
self._next_line_search = len(self._data)
94101
return None
95102

96-
# Truncate the buffer and return it.
97103
# + 2 is to compensate len(b"\r\n")
98-
idx = search_start_index + partial_idx + 2
99-
out = self._data[:idx]
100-
self._data[:idx] = b""
101-
self._next_line_search = 0
102-
self._multiple_lines_search = 0
103-
return out
104+
idx = partial_idx + 2
105+
106+
return self._extract(idx)
104107

105108
def maybe_extract_lines(self):
106109
"""
107110
Extract everything up to the first blank line, and return a list of lines.
108111
"""
109112
# Handle the case where we have an immediate empty line.
110113
if self._data[:1] == b"\n":
111-
self._data[:1] = b""
112-
self._next_line_search = 0
113-
self._multiple_lines_search = 0
114+
self._extract(1)
114115
return []
115116

116117
if self._data[:2] == b"\r\n":
117-
self._data[:2] = b""
118-
self._next_line_search = 0
119-
self._multiple_lines_search = 0
118+
self._extract(2)
120119
return []
121120

122121
# Only search in buffer space that we've not already looked at.
123-
partial_buffer = self._data[self._multiple_lines_search :]
124-
match = blank_line_regex.search(partial_buffer)
122+
match = blank_line_regex.search(self._data, self._multiple_lines_search)
125123
if match is None:
126124
self._multiple_lines_search = max(0, len(self._data) - 2)
127125
return None
128126

129127
# Truncate the buffer and return it.
130-
idx = self._multiple_lines_search + match.span(0)[-1]
131-
out = self._data[:idx]
128+
idx = match.span(0)[-1]
129+
out = self._extract(idx)
132130
lines = [line.rstrip(b"\r") for line in out.split(b"\n")]
133131

134-
self._data[:idx] = b""
135-
self._next_line_search = 0
136-
self._multiple_lines_search = 0
137-
138132
assert lines[-2] == lines[-1] == b""
139133

140-
return lines[:-2]
134+
del lines[-2:]
135+
136+
return lines

h11/tests/test_io.py

+37
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,43 @@ def test_readers_unusual():
215215
),
216216
)
217217

218+
# Tolerate headers line endings (\r\n and \n)
219+
# \n\r\b between headers and body
220+
tr(
221+
READERS[SERVER, SEND_RESPONSE],
222+
b"HTTP/1.1 200 OK\r\nSomeHeader: val\n\r\n",
223+
Response(
224+
status_code=200,
225+
headers=[("SomeHeader", "val")],
226+
http_version="1.1",
227+
reason="OK",
228+
),
229+
)
230+
231+
# delimited only with \n
232+
tr(
233+
READERS[SERVER, SEND_RESPONSE],
234+
b"HTTP/1.1 200 OK\nSomeHeader1: val1\nSomeHeader2: val2\n\n",
235+
Response(
236+
status_code=200,
237+
headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")],
238+
http_version="1.1",
239+
reason="OK",
240+
),
241+
)
242+
243+
# mixed \r\n and \n
244+
tr(
245+
READERS[SERVER, SEND_RESPONSE],
246+
b"HTTP/1.1 200 OK\r\nSomeHeader1: val1\nSomeHeader2: val2\n\r\n",
247+
Response(
248+
status_code=200,
249+
headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")],
250+
http_version="1.1",
251+
reason="OK",
252+
),
253+
)
254+
218255
# obsolete line folding
219256
tr(
220257
READERS[CLIENT, IDLE],

newsfragments/7.feature.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added support for servers with broken line endings.
2+
3+
After this change `h11` accepts both `\r\n` and `\n` as a headers delimiter

0 commit comments

Comments
 (0)