summaryrefslogtreecommitdiffstats
path: root/ipaserver/rpcserver.py
diff options
context:
space:
mode:
authorJohn Dennis <jdennis@redhat.com>2010-03-01 18:55:39 -0500
committerRob Crittenden <rcritten@redhat.com>2010-03-04 15:30:16 -0500
commit1289285d491caa67edaeab623e9fbcaaf1a253ff (patch)
tree305eaf94a8c0fbb0c54f15ea0cb6b3e2e65aa548 /ipaserver/rpcserver.py
parent06e5b8bd6b4648516b34a5c5ae51dbee65946263 (diff)
downloadfreeipa-1289285d491caa67edaeab623e9fbcaaf1a253ff.tar.gz
freeipa-1289285d491caa67edaeab623e9fbcaaf1a253ff.tar.xz
freeipa-1289285d491caa67edaeab623e9fbcaaf1a253ff.zip
Fix JSON binary encode and decode errors
Traverse the objects passed to JSON for encoding and decoding. When binary data is seen during encode replace the binary data with a dict {'__base64__' : base64_encoding_of_binary_value}. On decode if a dict is seen whose single key is '__base64__' replace that dict with the base64 decoded value of the key's value.
Diffstat (limited to 'ipaserver/rpcserver.py')
-rw-r--r--ipaserver/rpcserver.py101
1 files changed, 99 insertions, 2 deletions
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index ad402cdf8..967ee333c 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -33,7 +33,7 @@ from ipalib.rpc import xml_dumps, xml_loads
from ipalib.util import make_repr
from ipalib.compat import json
from wsgiref.util import shift_path_info
-
+import base64
_not_found_template = """<html>
<head>
@@ -60,7 +60,6 @@ def not_found(environ, start_response):
)
return [output]
-
def read_input(environ):
"""
Read the request body from environ['wsgi.input'].
@@ -300,6 +299,102 @@ class xmlserver(WSGIExecutioner):
return xml_dumps(response, methodresponse=True)
+def json_encode_binary(val):
+ '''
+ JSON cannot encode binary values. We encode binary values in Python str
+ objects and text in Python unicode objects. In order to allow a binary
+ object to be passed through JSON we base64 encode it thus converting it to
+ text which JSON can transport. To assure we recognize the value is a base64
+ encoded representation of the original binary value and not confuse it with
+ other text we convert the binary value to a dict in this form:
+
+ {'__base64__' : base64_encoding_of_binary_value}
+
+ This modification of the original input value cannot be done "in place" as
+ one might first assume (e.g. replacing any binary items in a container
+ (e.g. list, tuple, dict) with the base64 dict because the container might be
+ an immutable object (i.e. a tuple). Therefore this function returns a copy
+ of any container objects it encounters with tuples replaced by lists. This
+ is O.K. because the JSON encoding will map both lists and tuples to JSON
+ arrays.
+ '''
+
+ if isinstance(val, dict):
+ new_dict = {}
+ for k,v in val.items():
+ if isinstance(v, str):
+ new_dict[k] = {'__base64__' : base64.b64encode(v)}
+ else:
+ new_dict[k] = json_encode_binary(v)
+ del val
+ return new_dict
+ elif isinstance(val, (list, tuple)):
+ new_list = []
+ n = len(val)
+ i = 0
+ while i < n:
+ v = val[i]
+ if isinstance(v, str):
+ new_list.append({'__base64__' : base64.b64encode(v)})
+ else:
+ new_list.append(json_encode_binary(v))
+ i += 1
+ del val
+ return new_list
+ elif isinstance(val, str):
+ return {'__base64__' : base64.b64encode(val)}
+ else:
+ return val
+
+def json_decode_binary(val):
+ '''
+ JSON cannot transport binary data. In order to transport binary data we
+ convert binary data to a form like this:
+
+ {'__base64__' : base64_encoding_of_binary_value}
+
+ see json_encode_binary()
+
+ After JSON had decoded the JSON stream back into a Python object we must
+ recursively scan the object looking for any dicts which might represent
+ binary values and replace the dict containing the base64 encoding of the
+ binary value with the decoded binary value. Unlike the encoding problem
+ where the input might consist of immutable object, all JSON decoded
+ container are mutable so the conversion could be done in place. However we
+ don't modify objects in place because of side effects which may be
+ dangerous. Thus we elect to spend a few more cycles and avoid the
+ possibility of unintended side effects in favor of robustness.
+ '''
+
+ if isinstance(val, dict):
+ if val.has_key('__base64__'):
+ return base64.b64decode(val['__base64__'])
+ else:
+ new_dict = {}
+ for k,v in val.items():
+ if isinstance(v, dict) and v.has_key('__base64__'):
+ new_dict[k] = base64.b64decode(v['__base64__'])
+ else:
+ new_dict[k] = json_decode_binary(v)
+ del val
+ return new_dict
+ elif isinstance(val, list):
+ new_list = []
+ n = len(val)
+ i = 0
+ while i < n:
+ v = val[i]
+ if isinstance(v, dict) and v.has_key('__base64__'):
+ binary_val = base64.b64decode(v['__base64__'])
+ new_list.append(binary_val)
+ else:
+ new_list.append(json_decode_binary(v))
+ i += 1
+ del val
+ return new_list
+ else:
+ return val
+
class jsonserver(WSGIExecutioner):
"""
JSON RPC server.
@@ -326,6 +421,7 @@ class jsonserver(WSGIExecutioner):
error=error,
id=_id,
)
+ response = json_encode_binary(response)
return json.dumps(response, sort_keys=True, indent=4)
def unmarshal(self, data):
@@ -339,6 +435,7 @@ class jsonserver(WSGIExecutioner):
raise JSONError(error='Request is missing "method"')
if 'params' not in d:
raise JSONError(error='Request is missing "params"')
+ d = json_decode_binary(d)
method = d['method']
params = d['params']
_id = d.get('id')