Repository: aio-libs/aiohttp

Issue:
MultipartReader does not support multipart data with a preamble
## Long story short

I'm using the MultipartReader to read multi-part data from a 3rd party API.  It's response has an extra CRLF pair before the first boundary marker.  

It appears from reading the [RFC](https://www.ietf.org/rfc/rfc2046.txt) that it's valid to include a preamble before the first boundary, and it should just be ignored

I'm passing a response from this server through `aiohttp.MultipartReader.from_response` and then calling `.next()`
## Expected behaviour

aiohttp ignores the preamble and returns the first part.
## Actual behaviour

I get the exception:

> ValueError: Invalid boundary b'', expected b'------=_Part_125656_1263945784.1463662001461'
## Steps to reproduce

I've added a failing unit test here: https://github.com/obmarg/aiohttp/commit/872403569c0e735f3f35203f887d516679028764
## Your environment

Not sure any of it's relevant, but: MacOS, python 3.5.1, aiohttp 0.19.0 (though reproduced with master).



Discussion:

Resolving patch:
diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py
--- a/aiohttp/multipart.py
+++ b/aiohttp/multipart.py
@@ -510,6 +510,7 @@ def __init__(self, headers, content):
         self._content = content
         self._last_part = None
         self._at_eof = False
+        self._at_bof = True
         self._unread = []
 
     @asyncio.coroutine
@@ -544,10 +545,15 @@ def at_eof(self):
     @asyncio.coroutine
     def next(self):
         """Emits the next multipart body part."""
+        # So, if we're at BOF, we need to skip till the boundary.
         if self._at_eof:
             return
         yield from self._maybe_release_last_part()
-        yield from self._read_boundary()
+        if self._at_bof:
+            yield from self._read_until_first_boundary()
+            self._at_bof = False
+        else:
+            yield from self._read_boundary()
         if self._at_eof:  # we just read the last boundary, nothing to do there
             return
         self._last_part = yield from self.fetch_next_part()
@@ -605,6 +611,20 @@ def _readline(self):
             return self._unread.pop()
         return (yield from self._content.readline())
 
+    @asyncio.coroutine
+    def _read_until_first_boundary(self):
+        while True:
+            chunk = yield from self._readline()
+            if chunk == b'':
+                raise ValueError("Could not find starting boundary %r"
+                                 % (self._boundary))
+            chunk = chunk.rstrip()
+            if chunk == self._boundary:
+                return
+            elif chunk == self._boundary + b'--':
+                self._at_eof = True
+                return
+
     @asyncio.coroutine
     def _read_boundary(self):
         chunk = (yield from self._readline()).rstrip()


Test patch:
diff --git a/tests/test_multipart.py b/tests/test_multipart.py
--- a/tests/test_multipart.py
+++ b/tests/test_multipart.py
@@ -683,6 +683,24 @@ def test_read_chunk_from_stream_doesnt_breaks_reader(self):
             body_parts.append(read_part)
         self.assertListEqual(body_parts, [b'chunk', b'two_chunks'])
 
+    def test_reading_skips_prelude(self):
+        reader = aiohttp.multipart.MultipartReader(
+            {CONTENT_TYPE: 'multipart/related;boundary=":"'},
+            Stream(b'Multi-part data is not supported.\r\n'
+                   b'\r\n'
+                   b'--:\r\n'
+                   b'\r\n'
+                   b'test\r\n'
+                   b'--:\r\n'
+                   b'\r\n'
+                   b'passed\r\n'
+                   b'--:--'))
+        first = yield from reader.next()
+        self.assertIsInstance(first, aiohttp.multipart.BodyPartReader)
+        second = yield from reader.next()
+        self.assertTrue(first.at_eof())
+        self.assertFalse(second.at_eof())
+
 
 class BodyPartWriterTestCase(unittest.TestCase):