Просмотр исходного кода

Merge branch 'master' of https://github.com/sh4nks/flaskbb

sh4nks 10 лет назад
Родитель
Сommit
fc917a177c
5 измененных файлов с 145 добавлено и 10 удалено
  1. 1 0
      .gitignore
  2. 32 0
      flaskbb/fixtures/settings.py
  3. 8 6
      flaskbb/user/forms.py
  4. 42 3
      flaskbb/utils/helpers.py
  5. 62 1
      tests/unit/utils/test_helpers.py

+ 1 - 0
.gitignore

@@ -48,3 +48,4 @@ flaskbb/configs/development.py
 *.rdb
 .cache
 *.mo
+.coverage

+ 32 - 0
flaskbb/fixtures/settings.py

@@ -21,6 +21,10 @@ def available_markups():
     return [('bbcode', 'BBCode'), ('markdown', 'Markdown')]
 
 
+def available_avatar_types():
+    return [("image/png", "PNG"), ("image/jpeg", "JPG"), ("image/gif", "GIF")]
+
+
 def available_languages():
     return [(locale.language, locale.display_name)
             for locale in babel.list_translations()]
@@ -98,6 +102,34 @@ fixture = (
                 'extra':        {'choices': available_markups},
                 'name':         "Post markup",
                 'description':  "Select post markup type."
+            }),
+            ('avatar_height', {
+                'value':        150,
+                'value_type':   "integer",
+                'extra':        {'min': 0},
+                'name':         "Avatar Height",
+                'description':  "The allowed height of an avatar in pixels."
+            }),
+            ('avatar_width', {
+                'value':        150,
+                'value_type':   "integer",
+                'extra':        {'min': 0},
+                'name':         "Avatar Width",
+                'description':  "The allowed width of an avatar in pixels."
+            }),
+            ('avatar_size', {
+                'value':        200,
+                'value_type':   "integer",
+                'extra':        {'min': 0},
+                'name':         "Avatar Size",
+                'description':  "The allowed size of the avatar in kilobytes."
+            }),
+            ('avatar_types', {
+                'value':        ["image/png", "image/jpeg", "image/gif"],
+                'value_type':   "selectmultiple",
+                'extra':        {"choices": available_avatar_types},
+                'name':         "Avatar Types",
+                'description':  "The allowed types of an avatar. Such as JPEG, GIF or PNG."
             })
         ),
     }),

+ 8 - 6
flaskbb/user/forms.py

@@ -20,12 +20,7 @@ from flaskbb.user.models import User, PrivateMessage
 from flaskbb.extensions import db
 from flaskbb.utils.widgets import SelectBirthdayWidget
 from flaskbb.utils.fields import BirthdayField
-
-
-IMG_RE = r'^[^/\\]\.(?:jpg|gif|png)'
-
-is_image = regexp(IMG_RE,
-                  message=_("Only jpg, jpeg, png and gifs are allowed!"))
+from flaskbb.utils.helpers import check_image
 
 
 class GeneralSettingsForm(Form):
@@ -113,6 +108,13 @@ class ChangeUserDetailsForm(Form):
         if field.data is None:
             return True
 
+    def validate_avatar(self, field):
+        if field.data is not None:
+            error, status = check_image(field.data)
+            if error is not None:
+                raise ValidationError(error)
+            return status
+
 
 class NewMessageForm(Form):
     to_user = StringField(_("To User"), validators=[

+ 42 - 3
flaskbb/utils/helpers.py

@@ -367,7 +367,6 @@ def get_image_info(url):
     width = -1
     content_type = ''
 
-    #image_size     size = file.headers.get("content-length")
     if size:
         size = int(size)
 
@@ -382,8 +381,8 @@ def get_image_info(url):
     # See PNG 2. Edition spec (http://www.w3.org/TR/PNG/)
     # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
     # and finally the 4-byte width, height
-    elif ((size >= 24) and data.startswith(b'\211PNG\r\n\032\n')
-          and (data[12:16] == b'IHDR')):
+    elif ((size >= 24) and data.startswith(b'\211PNG\r\n\032\n') and
+            (data[12:16] == b'IHDR')):
         content_type = 'image/png'
         w, h = struct.unpack(b">LL", data[16:24])
         width = int(w)
@@ -428,3 +427,43 @@ def get_image_info(url):
 
     return {"content-type": content_type, "size": image_size,
             "width": width, "height": height}
+
+
+def check_image(url):
+    """A little wrapper for the :func:`get_image_info` function.
+    If the image doesn't match the ``flaskbb_config`` settings it will
+    return a tuple with a the first value is the custom error message and
+    the second value ``False`` for not passing the check.
+    If the check is successful, it will return ``None`` for the error message
+    and ``True`` for the passed check.
+
+    :param url: The image url to be checked.
+    """
+    img_info = get_image_info(url)
+    error = None
+
+    if not img_info["content-type"] in flaskbb_config["AVATAR_TYPES"]:
+        error = "Image type is not allowed. Allowed types are: {}".format(
+            ", ".join(flaskbb_config["AVATAR_TYPES"])
+        )
+        return error, False
+
+    if img_info["width"] > flaskbb_config["AVATAR_WIDTH"]:
+        error = "Image is too wide! {}px width is allowed.".format(
+            flaskbb_config["AVATAR_WIDTH"]
+        )
+        return error, False
+
+    if img_info["height"] > flaskbb_config["AVATAR_HEIGHT"]:
+        error = "Image is too high! {}px height is allowed.".format(
+            flaskbb_config["AVATAR_HEIGHT"]
+        )
+        return error, False
+
+    if img_info["size"] > flaskbb_config["AVATAR_SIZE"]:
+        error = "Image is too big! {}kb are allowed.".format(
+            flaskbb_config["AVATAR_SIZE"]
+        )
+        return error, False
+
+    return error, True

+ 62 - 1
tests/unit/utils/test_helpers.py

@@ -1,7 +1,8 @@
 #-*- coding: utf-8 -*-
 import datetime
 from flaskbb.utils.helpers import slugify, forum_is_unread, topic_is_unread, \
-    crop_title, render_markup, is_online, format_date, format_quote
+    crop_title, render_markup, is_online, format_date, format_quote, \
+    get_image_info, check_image
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.forum.models import Forum
 
@@ -117,3 +118,63 @@ def test_format_quote(topic):
 
     flaskbb_config["MARKUP_TYPE"] = "markdown"
     assert format_quote(topic.first_post) == expected_markdown
+
+
+def test_get_image_info():
+    # some random jpg/gif/png images from my imgur account
+    jpg = "http://i.imgur.com/NgVIeRG.jpg"
+    gif = "http://i.imgur.com/l3Vmp4m.gif"
+    png = "http://i.imgur.com/JXzKxNs.png"
+
+    jpg_img = get_image_info(jpg)
+    assert jpg_img["content-type"] == "image/jpeg"
+    assert jpg_img["height"] == 1024
+    assert jpg_img["width"] == 1280
+    assert jpg_img["size"] == 209.06
+
+    gif_img = get_image_info(gif)
+    assert gif_img["content-type"] == "image/gif"
+    assert gif_img["height"] == 168
+    assert gif_img["width"] == 400
+    assert gif_img["size"] == 576.138
+
+    png_img = get_image_info(png)
+    assert png_img["content-type"] == "image/png"
+    assert png_img["height"] == 1080
+    assert png_img["width"] == 1920
+    assert png_img["size"] == 269.409
+
+
+def test_check_image(default_settings):
+    # test200x100.png
+    img_width = "http://i.imgur.com/4dAWAZI.png"
+    # test100x200.png
+    img_height = "http://i.imgur.com/I7GwF3D.png"
+    # test100x100.png
+    img_ok = "http://i.imgur.com/CYV6NzT.png"
+    # random too big image
+    img_size = "http://i.imgur.com/l3Vmp4m.gif"
+    # random image wrong type
+    img_type = "https://d11xdyzr0div58.cloudfront.net/static/logos/archlinux-logo-black-scalable.f931920e6cdb.svg"
+
+    data = check_image(img_width)
+    assert "wide" in data[0]
+    assert not data[1]
+
+    data = check_image(img_height)
+    assert "high" in data[0]
+    assert not data[1]
+
+    data = check_image(img_type)
+    assert "type" in data[0]
+    assert not data[1]
+
+    data = check_image(img_ok)
+    assert data[0] is None
+    assert data[1]
+
+    flaskbb_config["AVATAR_WIDTH"] = 1000
+    flaskbb_config["AVATAR_HEIGHT"] = 1000
+    data = check_image(img_size)
+    assert "big" in data[0]
+    assert not data[1]