Commits

Ihar Niamilentsau committed 6c50ee3

Raise RuntimeError if emitters detect a recursive structure - fixes #164 (thanks to easel for the patch)

Comments (0)

Files changed (5)

piston/emitters.py

             """
             ret = None
 
+            # return anything we've already seen as a string only
+            # this prevents infinite recursion in the case of recursive 
+            # relationships
+
+            if thing in self.stack:
+                raise RuntimeError, (u'Circular reference detected while emitting '
+                                     'response')
+
+            self.stack.append(thing)
+
             if isinstance(thing, QuerySet):
                 ret = _qs(thing, fields)
             elif isinstance(thing, (tuple, list, set)):
             else:
                 ret = smart_unicode(thing, strings_only=True)
 
+            self.stack.pop()
+
             return ret
 
         def _fk(data, field):
             return dict([ (k, _any(v, fields)) for k, v in data.iteritems() ])
 
         # Kickstart the seralizin'.
+        self.stack = [];
         return _any(self.data, self.fields)
 
     def in_typemapper(self, model, anonymous):

tests/test_project/apps/testapp/handlers.py

 from piston.handler import BaseHandler
 from piston.utils import rc, validate
 
-from models import TestModel, ExpressiveTestModel, Comment, InheritedModel, PlainOldObject, Issue58Model, ListFieldsModel
+from models import TestModel, ExpressiveTestModel, Comment, InheritedModel, PlainOldObject, Issue58Model, ListFieldsModel, CircularA, CircularB, CircularC
 from forms import EchoForm, FormWithFileField
 from test_project.apps.testapp import signals
 
     def create(self, request):
         return {'chaff': request.form.cleaned_data['chaff'],
                 'file_size': request.form.cleaned_data['le_file'].size}
+
+class CircularAHandler(BaseHandler):
+    allowed_methods = ('GET',)
+    fields = ('name', 'link')
+    model = CircularA
+
+class CircularAHandler(BaseHandler):
+    allowed_methods = ('GET',)
+    fields = ('name', 'link')
+    model = CircularB
+
+class CircularAHandler(BaseHandler):
+    allowed_methods = ('GET',)
+    fields = ('name', 'link')
+    model = CircularC

tests/test_project/apps/testapp/models.py

 class Issue58Model(models.Model):
     read = models.BooleanField(default=False)
     model = models.CharField(max_length=1, blank=True, null=True)
+
+class CircularA(models.Model):
+    link = models.ForeignKey('testapp.CircularB', null=True)
+    name = models.CharField(max_length=15)
+
+class CircularB(models.Model):
+    link = models.ForeignKey('testapp.CircularC', null=True)
+    name = models.CharField(max_length=15)
+
+class CircularC(models.Model):
+    link = models.ForeignKey('testapp.CircularA', null=True)
+    name = models.CharField(max_length=15)

tests/test_project/apps/testapp/tests.py

 
 import urllib, base64, tempfile
 
-from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel, Issue58Model, ListFieldsModel
+from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel, Issue58Model, ListFieldsModel, CircularA, CircularB, CircularC
 from test_project.apps.testapp import signals
 
 
                                HTTP_ACCEPT='text/html')
         self.assertEquals(resp.status_code, 406)
 
+class CircularReferenceTest(MainTests):
+    def init_delegate(self):
+        self.a = CircularA.objects.create(name='foo')
+        self.b = CircularB.objects.create(name='bar')
+        self.c = CircularC.objects.create(name='baz')
+        self.a.link = self.b; self.a.save()
+        self.b.link = self.c; self.b.save()
+        self.c.link = self.a; self.c.save()
+
+    def test_circular_model_references(self):
+        self.assertRaises(
+            RuntimeError,
+            self.client.get,
+            '/api/circular_a/',
+            HTTP_AUTHORIZATION=self.auth_string)
+
         
+        

tests/test_project/apps/testapp/urls.py

 from piston.authentication import HttpBasicAuthentication, HttpBasicSimple
 from piston.authentication.oauth import OAuthAuthentication
 
-from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler, ListFieldsHandler, FileUploadHandler
+from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler, ListFieldsHandler, FileUploadHandler, CircularAHandler
 
 
 auth = HttpBasicAuthentication(realm='TestApplication')
 
     url(r'^multiauth/$', multiauth),
 
+    url(r'^circular_a/$', circular_a),
+
     # OAuth
     url(r'^oauth/', include('piston.authentication.oauth.urls')),
     url(r'^oauth/two_legged_api$', ouath_two_legged_api),