[#4988] gracefully handle attachments that are partial images
diff --git a/Allura/allura/model/attachments.py b/Allura/allura/model/attachments.py
index 99d43ab..d0869c9 100644
--- a/Allura/allura/model/attachments.py
+++ b/Allura/allura/model/attachments.py
@@ -56,8 +56,9 @@
             original_meta=original_meta)
         if orig is not None:
             return orig, thumbnail
-
-        # No, generic attachment
-        return cls.from_stream(
-            filename, fp, content_type=content_type,
-            **original_meta)
+        else:
+            # No, generic attachment
+            fp.seek(0)  # stream may have been partially consumed in a failed save_image attempt
+            return cls.from_stream(
+                filename, fp, content_type=content_type,
+                **original_meta)
diff --git a/Allura/allura/model/filesystem.py b/Allura/allura/model/filesystem.py
index e98aea1..36269e4 100644
--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -161,10 +161,15 @@
             original = cls(
                 filename=filename, content_type=content_type, **original_meta)
             with original.wfile() as fp_w:
-                if 'transparency' in image.info:
-                    image.save(fp_w, format, transparency=image.info['transparency'])
-                else:
-                    image.save(fp_w, format)
+                try:
+                    if 'transparency' in image.info:
+                        image.save(fp_w, format, transparency=image.info['transparency'])
+                    else:
+                        image.save(fp_w, format)
+                except Exception as e:
+                    session(original).expunge(original)
+                    log.error('Error saving image %s %s', filename, e)
+                    return None, None
         else:
             original = None
 
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index ca988bc..7ab99b5 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -2,10 +2,12 @@
 import os
 from unittest import TestCase
 from cStringIO import StringIO
+from io import BytesIO
 
 from pylons import response
+from pylons import tmpl_context as c
 from ming.orm import session, Mapper
-
+from nose.tools import assert_equal
 
 from allura import model as M
 from alluratest.controller import setup_unit_test
@@ -150,6 +152,17 @@
         assert f == None
         assert t == None
 
+    def test_partial_image_as_attachment(self):
+        path = os.path.join(os.path.dirname(__file__), '..', 'data', 'user.png')
+        fp = BytesIO(open(path, 'rb').read(500))
+        c.app.config._id = None
+        attachment = M.BaseAttachment.save_attachment('user.png', fp,
+                                                      save_original=True)
+        assert type(attachment) != tuple   # tuple is for (img, thumb) pairs
+        assert_equal(attachment.length, 500)
+        assert_equal(attachment.filename, 'user.png')
+
+
     def _assert_content(self, f, content):
         result = f.rfile().read()
         assert result == content, result