bugfix: JSON-safe encoding of raw binary data/files

Encoding of the received files and/or body data was failing in case of raw
binary data being POSTed/PUT/etc. (The exact trigger was actually a non-UTF-8
data stream, since Flask's `jsonify`, i.e. `simplejson.dumps` assumes UTF-8.)

Binary data in a JSON response are now encoded as "data" URLs, according to
RFC 2397. "Data" URL scheme was chosen for its simplicity and clarity. MIME
type is included. Plain text is passed thru unmodified, as before.

Also, tests demonstrating the bug/fix added to `test_httpbin.py`.
This commit is contained in:
Radomir Stevanovic
2012-08-14 22:57:35 +02:00
parent 213d03a090
commit 9a56bb8dd0
3 changed files with 43 additions and 4 deletions
+2 -1
View File
@@ -14,4 +14,5 @@ Patches and Suggestions
- Andrey Petrov
- Lispython
- Kyle Conroy
- Flavio Percoco
- Flavio Percoco
- Radomir Stevanovic (http://github.com/randomir)
+25 -3
View File
@@ -7,7 +7,8 @@ httpbin.helpers
This module provides helper functions for httpbin.
"""
import json
import base64
import simplejson as json
from hashlib import md5
from werkzeug.http import parse_authorization_header
@@ -62,13 +63,34 @@ ANGRY_ASCII ="""
YOU SHOUDN'T BE HERE
"""
def json_safe(string, content_type='application/octet-stream'):
"""Returns JSON-safe version of `string`.
If `string` is a Unicode string or a valid UTF-8, it is returned unmodified,
as it can safely be encoded to JSON string.
If `string` contains raw/binary data, it is Base64-encoded, formatted and
returned according to "data" URL scheme (RFC2397). Since JSON is not
suitable for binary data, some additional encoding was necessary; "data"
URL scheme was chosen for its simplicity.
"""
try:
_encoded = json.dumps(string)
return string
except ValueError:
return ''.join(['data:%s;base64,' % content_type,
base64.b64encode(string)])
def get_files():
"""Returns files dict from request context."""
files = dict()
for k, v in request.files.items():
files[k] = v.read()
files[k] = json_safe(v.read(), request.files[k].content_type)
return files
@@ -120,7 +142,7 @@ def get_dict(*keys, **extras):
url=request.url,
args=request.args,
form=form,
data=data,
data=json_safe(data),
origin=request.remote_addr,
headers=get_headers(),
files=get_files(),
+16
View File
@@ -25,6 +25,22 @@ class HttpbinTestCase(unittest.TestCase):
content = response.data.decode('utf-8')
self.assertEquals(greeting, content)
def test_post_binary(self):
response = self.app.post('/post',
data='\x01\x02\x03\x81\x82\x83',
content_type='application/octet-stream')
self.assertEquals(response.status_code, 200)
def test_post_file_text(self):
with open('httpbin/core.py') as f:
response = self.app.post('/post', data={"file": f})
self.assertEquals(response.status_code, 200)
def test_post_file_binary(self):
with open('httpbin/core.pyc') as f:
response = self.app.post('/post', data={"file": f})
self.assertEquals(response.status_code, 200)
if __name__ == '__main__':
unittest.main()