The main purpose of this test is to ensure that line return are removed, but all other special character are kept once the file is saved. Unfortunately, werkzeug versions have different strategies on how to quote the filename, removing less special character in latest versions like after https://github.com/pallets/werkzeug/commit/babfc93b3834bcbb22163442a9af70141bcc5a81 This also changes after the changes that removed werkzeug urls methods replacing them by urllib, making the behaviour different again. This commit makes the test more robust by checking that the filename correspond to the expeted one once unquoted, not comparing the quoted versions. Part-of: odoo/odoo#160842
155 lines
6.2 KiB
Python
155 lines
6.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import io
|
|
import base64
|
|
|
|
from PIL import Image
|
|
from werkzeug.urls import url_unquote_plus
|
|
|
|
from odoo.tests.common import HttpCase, tagged
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestImage(HttpCase):
|
|
def test_01_content_image_resize_placeholder(self):
|
|
"""The goal of this test is to make sure the placeholder image is
|
|
resized appropriately depending on the given URL parameters."""
|
|
|
|
# CASE: resize placeholder, given size but original ratio is always kept
|
|
response = self.url_open('/web/image/0/200x150')
|
|
response.raise_for_status()
|
|
image = Image.open(io.BytesIO(response.content))
|
|
self.assertEqual(image.size, (150, 150))
|
|
|
|
# CASE: resize placeholder to 128
|
|
response = self.url_open('/web/image/fake/0/image_128')
|
|
response.raise_for_status()
|
|
image = Image.open(io.BytesIO(response.content))
|
|
self.assertEqual(image.size, (128, 128))
|
|
|
|
# CASE: resize placeholder to 256
|
|
response = self.url_open('/web/image/fake/0/image_256')
|
|
response.raise_for_status()
|
|
image = Image.open(io.BytesIO(response.content))
|
|
self.assertEqual(image.size, (256, 256))
|
|
|
|
# CASE: resize placeholder to 1024 (but placeholder image is too small)
|
|
response = self.url_open('/web/image/fake/0/image_1024')
|
|
response.raise_for_status()
|
|
image = Image.open(io.BytesIO(response.content))
|
|
self.assertEqual(image.size, (256, 256))
|
|
|
|
# CASE: no size found, use placeholder original size
|
|
response = self.url_open('/web/image/fake/0/image_no_size')
|
|
response.raise_for_status()
|
|
image = Image.open(io.BytesIO(response.content))
|
|
self.assertEqual(image.size, (256, 256))
|
|
|
|
def test_02_content_image_Etag_304(self):
|
|
"""This test makes sure that the 304 response is properly returned if the ETag is properly set"""
|
|
|
|
attachment = self.env['ir.attachment'].create({
|
|
'datas': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
|
'name': 'testEtag.gif',
|
|
'public': True,
|
|
'mimetype': 'image/gif',
|
|
})
|
|
response = self.url_open('/web/image/%s' % attachment.id, timeout=None)
|
|
response.raise_for_status()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(base64.b64encode(response.content), attachment.datas)
|
|
|
|
etag = response.headers.get('ETag')
|
|
|
|
response2 = self.url_open('/web/image/%s' % attachment.id, headers={"If-None-Match": etag})
|
|
response2.raise_for_status()
|
|
self.assertEqual(response2.status_code, 304)
|
|
self.assertEqual(len(response2.content), 0)
|
|
|
|
def test_03_web_content_filename(self):
|
|
"""This test makes sure the Content-Disposition header matches the given filename"""
|
|
|
|
att = self.env['ir.attachment'].create({
|
|
'datas': b'R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=',
|
|
'name': 'testFilename.gif',
|
|
'public': True,
|
|
'mimetype': 'image/gif'
|
|
})
|
|
|
|
# CASE: no filename given
|
|
res = self.url_open('/web/image/%s/0x0/?download=true' % att.id)
|
|
res.raise_for_status()
|
|
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=testFilename.gif')
|
|
|
|
# CASE: given filename without extension
|
|
res = self.url_open('/web/image/%s/0x0/custom?download=true' % att.id)
|
|
res.raise_for_status()
|
|
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=custom.gif')
|
|
|
|
# CASE: given filename and extention
|
|
res = self.url_open('/web/image/%s/0x0/custom.png?download=true' % att.id)
|
|
res.raise_for_status()
|
|
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=custom.png')
|
|
|
|
def test_04_web_content_filename_secure(self):
|
|
"""This test makes sure the Content-Disposition header matches the given filename"""
|
|
|
|
att = self.env['ir.attachment'].create({
|
|
'datas': b'R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=',
|
|
'name': """fô☺o-l'éb \n a"!r".gif""",
|
|
'public': True,
|
|
'mimetype': 'image/gif',
|
|
})
|
|
|
|
def assert_filenames(
|
|
url,
|
|
expected_filename,
|
|
expected_filename_star='',
|
|
message=r"File that will be saved on disc should have the original filename without \n and \r",
|
|
):
|
|
res = self.url_open(url)
|
|
res.raise_for_status()
|
|
if expected_filename_star:
|
|
inline, filename, filename_star = res.headers['Content-Disposition'].split('; ')
|
|
else:
|
|
inline, filename = res.headers['Content-Disposition'].split('; ')
|
|
filename_star = ''
|
|
|
|
filename = filename.removeprefix("filename=").strip('"')
|
|
filename_star = url_unquote_plus(filename_star.removeprefix("filename*=UTF-8''").strip('"'))
|
|
|
|
self.assertEqual(inline, 'inline')
|
|
self.assertEqual(filename, expected_filename, message)
|
|
self.assertEqual(filename_star, expected_filename_star, message)
|
|
|
|
assert_filenames(f'/web/image/{att.id}',
|
|
r"""foo-l'eb _ a\"!r\".gif""",
|
|
r"""fô☺o-l'éb _ a"!r".gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/custom_invalid_name\nis-ok.gif',
|
|
r"""custom_invalid_name_is-ok.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/\r\n',
|
|
r"""__.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/你好',
|
|
r""".gif""",
|
|
r"""你好.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/%E9%9D%A2%E5%9B%BE.gif',
|
|
r""".gif""",
|
|
r"""面图.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/hindi_नमस्ते.gif',
|
|
r"""hindi_.gif""",
|
|
r"""hindi_नमस्ते.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/arabic_مرحبا',
|
|
r"""arabic_.gif""",
|
|
r"""arabic_مرحبا.gif""",
|
|
)
|
|
assert_filenames(f'/web/image/{att.id}/4wzb_!!63148-0-t1.jpg_360x1Q75.jpg_.webp',
|
|
r"""4wzb_!!63148-0-t1.jpg_360x1Q75.jpg_.webp""",
|
|
)
|