kevin1024/vcrpy — pull request #29 by fatuhoku (2013-09-16 16:43:43+00:00)
Title: Multipart boundary string agnosticism

This will fix #28. I.e. recording and playing back `requests.post(file=...)` multipart requests will now work properly. Hurrah!

The current implementation is basic: it looks through the request, picks up and remembers declarations of boundaries, then replaces all their occurrences with a constant one.

**When recording...**

So let's say VCR.py observes the following multipart message being sent out by `requests.post(file=...)` (snippet from http://www.ietf.org/rfc/rfc2183.txt):
```
Content-Type: multipart/mixed; boundary=070e0bf3cf9042e7970199c939e24ea5
Content-Description: multipart-1

--070e0bf3cf9042e7970199c939e24ea5
Content-Type: text/plain
Content-Disposition: inline
Content-Description: text-part-1

Some text goes here

--070e0bf3cf9042e7970199c939e24ea5
Content-Type: multipart/mixed; boundary=3f38b2baea134e97842b3b41793f1a4b
Content-Disposition: attachment
Content-Description: multipart-2

--3f38b2baea134e97842b3b41793f1a4b
Content-Type: text/plain
Content-Disposition: inline
Content-Description: text-part-2

Some more text here.

--3f38b2baea134e97842b3b41793f1a4b
Content-Type: image/jpeg
Content-Disposition: attachment
Content-Description: jpeg-1

--3f38b2baea134e97842b3b41793f1a4b--
--070e0bf3cf9042e7970199c939e24ea5--
```

VCR.py will replace the two randomly generated boundary strings with deterministic boundary strings derived from a constant GUID:

`boundary=070e0bf3cf9042e7970199c939e24ea5` -> `boundary=ffeac16b6e044eec98d4176a95e68663-0`
`boundary=3f38b2baea134e97842b3b41793f1a4b` -> `boundary=ffeac16b6e044eec98d4176a95e68663-1`

The resulting, modified, request is then saved into the cassette.

**When playing back...**

Exactly the same boundary `requests.post()` generates, it will be stripped before being stored in the cassette, and replaced with a deterministic, constant boundary.

The constant is based on a generated GUID, which should not be replicable anywhere else. Currently its value is `ffeac16b6e044eec98d4176a95e68663-{0}` where `{0}` is a cardinal number representing which index . The outermost boundary declared in the header will always have index 0.

The current approach first looks at the header to see whether a `boundary=...` was declared in the `Content-Type` header field.

If so, we remember this boundary string, and update the `Content-Type... boundary=...` declaration with the constant. We want to remember the boundary string because it may occur somewhere in the actual body.

**Caveats...**

Of course, this new implementation means that NO multipart request submitted via `requests.post()` is actually faithful. This is a problem if existing code expects randomly generated boundaries.