{"id":"2f7e854212ecab2a","repo":"aio-libs/aiohttp","slug":"bad-line-ending-expected-crlf","errorCode":null,"errorMessage":"Bad line ending, expected CRLF","messagePattern":"Bad line ending, expected CRLF","errorType":"http","errorClass":"BadHttpMessage","httpStatus":400,"severity":"error","filePath":"aiohttp/http_parser.py","lineNumber":521,"sourceCode":"                        elif upgraded:\n                            # No body to read, so the connection switches to\n                            # the upgraded protocol immediately.\n                            self._upgraded = True\n                            payload = EMPTY_PAYLOAD\n                        else:\n                            payload = EMPTY_PAYLOAD\n\n                        messages.append((msg, payload))\n                        if self._max_msg_queue_size:\n                            self._msg_in_flight += 1\n                        should_close = msg.should_close\n                else:\n                    self._tail = data[start_pos:]\n                    # A bare LF here means CRLF was required:\n                    # reject instead of buffering, else a following request's\n                    # bytes get appended to this line and leak in the error.\n                    if b\"\\n\" in self._tail:\n                        raise BadHttpMessage(\"Bad line ending, expected CRLF\")\n                    if len(self._tail) > self.max_line_size:\n                        raise LineTooLong(self._tail[:100] + b\"...\", self.max_line_size)\n                    data = EMPTY\n                    break\n\n            # no parser, just store\n            elif self._payload_parser is None and self._upgraded:\n                assert not self._lines\n                break\n\n            # feed payload\n            else:\n                assert not self._lines\n                assert self._payload_parser is not None\n                try:\n                    payload_state, data = self._payload_parser.feed_data(\n                        data[start_pos:], SEP\n                    )","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/http_parser.py#L503-L539","documentation":"Raised by HttpParser.feed_data (aiohttp/http_parser.py:521) while buffering an incomplete line: if the pending tail contains a bare '\\n' the parser concludes the peer is using LF instead of the CRLF that RFC 9112 section 2.2 mandates, and rejects it. Rejecting (rather than tolerating LF) prevents a following request's bytes from being appended to the current line and leaking into error text. The lax response parser uses '\\n' as its separator, so responses tolerate bare LF; this fires in the strict request parser.","triggerScenarios":"A client/server that terminates HTTP lines with '\\n' instead of '\\r\\n' in a request line or header line. E.g. a raw socket sending 'GET / HTTP/1.0\\n\\n'. Strict request parsing refuses this; lax response parsing accepts it.","commonSituations":"Raw netcat/nc scripts, telnet sessions, embedded clients that build HTTP by hand with platform line endings (\\n on Unix), text-mode transfers that corrupt CRLF into LF, or HTTP/1.0-only embedded stacks.","solutions":["Terminate every HTTP line with CRLF ('\\r\\n'), including the blank line that ends the header block.","If writing a raw client, send '\\r\\n\\r\\n' explicitly - do not rely on platform line endings or print()'s newline.","Use a real HTTP library (like aiohttp) instead of hand-crafted sockets."],"exampleFix":"# before - bare LF line endings\nsock.send(b'GET / HTTP/1.1\\nHost: x\\n\\n')\n# after - proper CRLF\nsock.send(b'GET / HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n')","handlingStrategy":"validation","validationCode":"def crlf_terminated(raw: bytes) -> bool:\n    # every line must end with \\r\\n; no bare \\n\n    return b'\\n' in raw and all(\n        seg.endswith(b'\\r') or i == len(parts) - 1 and seg == b''\n        for i, seg in enumerate((raw.split(b'\\n')))\n    )\n# simpler: assert no '\\n' that is not preceded by '\\r'\ndef no_bare_lf(raw: bytes) -> bool:\n    return b'\\r\\n'.join(raw.split(b'\\n')) == raw\nassert no_bare_lf(outgoing)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always terminate HTTP lines with CRLF (\\r\\n)","Use an HTTP library rather than raw sockets","Never rely on platform line endings for HTTP"],"tags":["http","protocol","parser","line-endings","request"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}