Skip to content

Commit

Permalink
Merge pull request psf#746 from jkbr/develop
Browse files Browse the repository at this point in the history
Fixed encoding of fields with the same name, multipart/form-data fixes.
  • Loading branch information
Kenneth Reitz committed Aug 11, 2012
2 parents 6464618 + 88c762e commit 27b55a7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 21 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Expand Up @@ -110,3 +110,4 @@ Patches and Suggestions
- Victoria Mo
- Leila Muhtasib
- Matthias Rahlf <matthias@webding.de>
- Jakub Roztocil <jakub@roztocil.name>
56 changes: 38 additions & 18 deletions requests/models.py
Expand Up @@ -10,6 +10,7 @@
import os
import socket
from datetime import datetime
from io import BytesIO

from .hooks import dispatch_hook, HOOKS
from .structures import CaseInsensitiveDict
Expand Down Expand Up @@ -344,36 +345,55 @@ def _encode_params(data):
return data

def _encode_files(self, files):
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
2-tuples. Order is retained if data is a list of 2-tuples but abritrary
if parameters are supplied as a dict.
"""
if (not files) or isinstance(self.data, str):
return None

try:
fields = self.data.copy()
except AttributeError:
fields = dict(self.data)
def tuples(obj):
"""Ensure 2-tuples. A dict or a 2-tuples list can be supplied."""
if isinstance(obj, dict):
return list(obj.items())
elif hasattr(obj, '__iter__'):
try:
dict(obj)
except ValueError:
pass
else:
return obj
raise ValueError('A dict or a list of 2-tuples required.')

# 2-tuples containing both file and data fields.
fields = []

for (k, v) in list(files.items()):
for k, v in tuples(files):
# support for explicit filename
if isinstance(v, (tuple, list)):
fn, fp = v
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (bytes, str)):
if isinstance(fp, str):
fp = StringIO(fp)
fields.update({k: (fn, fp.read())})

for field in fields:
if isinstance(fields[field], numeric_types):
fields[field] = str(fields[field])
if isinstance(fields[field], list):
newvalue = ', '.join(fields[field])
fields[field] = newvalue

(body, content_type) = encode_multipart_formdata(fields)

return (body, content_type)
if isinstance(fp, bytes):
fp = BytesIO(fp)
fields.append((k, (fn, fp.read())))

for k, vs in tuples(self.data):
if isinstance(vs, list):
for v in vs:
fields.append((k, str(v)))
else:
fields.append((k, str(vs)))

body, content_type = encode_multipart_formdata(fields)

return body, content_type

@property
def full_url(self):
Expand Down
41 changes: 38 additions & 3 deletions tests/test_requests.py
Expand Up @@ -7,7 +7,6 @@
import sys
import os
sys.path.insert(0, os.path.abspath('..'))

import json
import os
import unittest
Expand Down Expand Up @@ -981,10 +980,10 @@ def test_post_fields_with_multiple_values_and_files(self):
list for a value in the data argument."""

data = {'field': ['a', 'b']}
files = {'file': 'Garbled data'}
files = {'field': 'Garbled data'}
r = post(httpbin('post'), data=data, files=files)
t = json.loads(r.text)
self.assertEqual(t.get('form'), {'field': 'a, b'})
self.assertEqual(t.get('form'), {'field': ['a', 'b']})
self.assertEqual(t.get('files'), files)

def test_str_data_content_type(self):
Expand Down Expand Up @@ -1028,5 +1027,41 @@ def __str__(self):
r = get(URL())
self.assertEqual(r.status_code, 200)

def test_post_fields_with_multiple_values_and_files_as_tuples(self):
"""Test that it is possible to POST multiple data and file fields
with the same name.
https://github.com/kennethreitz/requests/pull/746
"""

fields = [
('__field__', '__value__'),
('__field__', '__value__'),
]

r = post(httpbin('post'), data=fields, files=fields)
t = json.loads(r.text)

self.assertEqual(t.get('form'), {
'__field__': [
'__value__',
'__value__',
]
})

# It's not currently possible to test for multiple file fields with
# the same name against httpbin so we need to inspect the encoded
# body manually.
request = r.request
body, content_type = request._encode_files(request.files)
file_field = (b'Content-Disposition: form-data;'
b' name="__field__"; filename="__field__"')
self.assertEqual(body.count(b'__value__'), 4)
self.assertEqual(body.count(file_field), 2)

def test_bytes_files(self):
"""Test that `bytes` can be used as the values of `files`."""
post(httpbin('post'), files={'test': b'test'})


if __name__ == '__main__':
unittest.main()

0 comments on commit 27b55a7

Please sign in to comment.