Commits

Olemis Lang committed 0bb7753

BH RPC : Probe RPC methods on TypeError to detect signature contract violations

Comments (0)

Files changed (4)

trunk/tracrpc/api.py

                 if p.name == method:
                     return p
         raise MethodNotFound('RPC method "%s" not found' % method)
-        
+
     # Exported methods
     def all_methods(self, req):
         """ List all methods exposed via RPC. Returns a list of Method objects. """

trunk/tracrpc/json_rpc.py

 
 from tracrpc.api import IRPCProtocol, XMLRPCSystem, Binary, \
         RPCError, MethodNotFound, ProtocolException, ServiceException
-from tracrpc.util import exception_to_unicode, empty, prepare_docs
+from tracrpc.util import exception_to_unicode, empty, prepare_docs, probe_method
 
 __all__ = ['JsonRpcProtocol']
 
             """Send a JSON-RPC fault message back to the caller. """
             rpcreq = req.rpc
             r_id = rpcreq.get('id')
-            response = json.dumps(self._json_error(e, r_id=r_id), \
+            response = json.dumps(self._json_error(e, r_id=r_id, req=req), \
                                       cls=TracRpcJSONEncoder)
             self._send_response(req, response + '\n', rpcreq['mimetype'])
 
             else :
                 return self._json_error(result, r_id=r_id)
 
-        def _json_error(self, e, c=None, r_id=None):
+        def _json_error(self, e, c=None, r_id=None, req=None):
             """ Makes a response dictionary that is an error. """
             if isinstance(e, ServiceException):
                 e = e._exc
                 c = -32400
             elif isinstance(e, NotImplementedError):
                 c = 501
+            elif isinstance(e, TypeError):
+                # bhnet:product:rpc:ticket:210
+                if req is None or probe_method(self.env, req) is not False:
+                    # Internal server error
+                    c = -32603
+                else:
+                    # Invalid arguments
+                    c = -32602
             else:
                 c = c or hasattr(e, 'code') and e.code or -32603
             return {'result': None, 'id': r_id, 'error': {

trunk/tracrpc/util.py

 (c) 2013      ::: Olemis Lang (olemis+trac@gmail.com)
 """
 
+import inspect
+import types
+
 from trac.util.compat import any
 
+from tracrpc.api import XMLRPCSystem
+
 try:
   from cStringIO import StringIO
 except ImportError:
     to_utimestamp = to_timestamp
     from_utimestamp = lambda x: to_datetime(x, utc)
 
+def same_sig_probe(f):
+    argspec = inspect.getargspec(f)
+    if isinstance(f, types.MethodType):
+        # Remove self
+        del argspec.args[0]
+    sig = inspect.formatargspec(*argspec)
+    return eval('lambda %s: None' % sig[1:-1])
+
+def probe_method(env, req, method=None, params=None):
+    """Probe RPC method signature
+
+    :return:    `True` on matching signature, `False` on mismatch,
+                `None` if a decision can't be made
+    """
+    # bhnet:product:rpc:ticket:210
+    if method is None:
+        rpcreq = getattr(req, 'rpc', None)
+        if rpcreq is None:
+            return None
+        method = rpcreq.get('method')
+        params = params or rpcreq.get('params')
+
+    rpcsys = XMLRPCSystem(env)
+    try:
+        method = rpcsys.get_method(method)
+        probe = same_sig_probe(method.callable)
+    except (TypeError, MethodNotFound):
+        return None
+    except:
+        rpcsys.log.exception('Error probing method signature')
+        return None
+    else:
+        try:
+            probe(req, params or ())
+        except TypeError:
+            return False
+        except:
+            rpcsys.log.exception('Unexpected probe error')
+            return None
+    return True
 
 #--------------------------------------------------
 # Imported from TracGVizPlugin

trunk/tracrpc/xml_rpc.py

 
 from tracrpc.api import XMLRPCSystem, IRPCProtocol, Binary, \
         RPCError, MethodNotFound, ProtocolException, ServiceException
-from tracrpc.util import empty, prepare_docs
+from tracrpc.util import empty, prepare_docs, probe_method
 
 __all__ = ['XmlRpcProtocol']
 
             fault = xmlrpclib.Fault(-32400, to_unicode(e))
         elif isinstance(e, NotImplementedError):
             fault = xmlrpclib.Fault(501, to_unicode(e))
+        elif isinstance(e, TypeError):
+            # Invalid args, see bhnet:product:rpc:ticket:210
+            if probe_method(self.env, req) is False:
+                fault = xmlrpclib.Fault(-32602, to_unicode(e))
 
         if fault is not None :
             self._send_response(req, xmlrpclib.dumps(fault), rpcreq['mimetype'])