diff --git a/aiosmtpd/smtp.py b/aiosmtpd/smtp.py index 45d5d92bc..3ccc80eda 100644 --- a/aiosmtpd/smtp.py +++ b/aiosmtpd/smtp.py @@ -583,10 +583,23 @@ def smtp_DATA(self, arg): yield from self.push('354 End data with .') data = [] num_bytes = 0 + unfinished_line = [] size_exceeded = False while True: - line = yield from self._reader.readline() - if line == b'.\r\n': + # Since eof_received cancels this coroutine, + # readuntil() can never raise asyncio.IncompleteReadError. + try: + line = yield from self._reader.readuntil() + assert line.endswith(b'\n') + except asyncio.LimitOverrunError as e: + # The line exceeds StreamReader's buffer. + # XXX Should we respond with 500 Line too long + # and set size_exceeded = True here? + # How is an SMTP implementation supposed to react? + line = yield from self._reader.read(e.consumed) + assert not line.endswith(b'\n') + # A lone dot in a line signals the end of DATA. + if not unfinished_line and line == b'.\r\n': if data: data[-1] = data[-1].rstrip(b'\r\n') break @@ -597,10 +610,18 @@ def smtp_DATA(self, arg): size_exceeded = True yield from self.push('552 Error: Too much mail data') if not size_exceeded: - data.append(line) + if line.endswith(b'\n'): + data.append(EMPTYBYTES.join(unfinished_line) + line) + del unfinished_line[:] + else: + unfinished_line.append(line) + elif line.endswith(b'\n'): + del unfinished_line[:] if size_exceeded: self._set_post_data_state() return + # If unfinished_line is non-empty, then the connection was closed. + assert not unfinished_line # Remove extraneous carriage returns and de-transparency # according to RFC 5321, Section 4.5.2. for i in range(len(data)):