[FIX] http,web,website_*: return the correct Content-Type for images

* = website_profile, website_slides

Before this commit, the returned Content-Type was not always correct, for
example if the image tool was changing the format, which happens when given
a BMP (converted to PNG), or other types being converted to JPEG.

The previous method `force_contenttype` was too specific and it wasn't available
in every controller. It didn't even need to be a controller method because it
didn't use self.

Now we create a generic helper to ease updating headers.
The Content-Type is only updated when it is safe to do. It is especially unsafe
for example for SVG files.

task-1958000
PR: #31811
This commit is contained in:
Sébastien Theys
2019-04-29 13:45:34 +00:00
parent 09ff05ba45
commit 7d3fe51100
4 changed files with 38 additions and 9 deletions
+1 -7
View File
@@ -971,11 +971,6 @@ class Binary(http.Controller):
addons_path = http.addons_manifest['web']['addons_path']
return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
def force_contenttype(self, headers, contenttype='image/png'):
dictheaders = dict(headers)
dictheaders['Content-Type'] = contenttype
return list(dictheaders.items())
@http.route(['/web/content',
'/web/content/<string:xmlid>',
'/web/content/<string:xmlid>/<string:filename>',
@@ -1034,14 +1029,13 @@ class Binary(http.Controller):
return request.env['ir.http']._response_by_status(status, headers, image_base64)
if not image_base64:
image_base64 = base64.b64encode(self.placeholder(image=placeholder))
headers = self.force_contenttype(headers, contenttype='image/png')
if not (width or height):
width, height = odoo.tools.image_guess_size_from_field_name(field)
image_base64 = image_process(image_base64, (width, height), crop=crop)
content = base64.b64decode(image_base64)
headers.append(('Content-Length', len(content)))
headers = http.set_safe_image_headers(headers, content)
response = request.make_response(content, headers)
response.status_code = status
return response
+1 -1
View File
@@ -98,7 +98,7 @@ class WebsiteProfile(http.Controller):
image_base64 = tools.image_process(image_base64, (width, height), crop=crop)
content = base64.b64decode(image_base64)
headers.append(('Content-Length', len(content)))
headers = http.set_safe_image_headers(headers, content)
response = request.make_response(content, headers)
response.status_code = status
return response
+1 -1
View File
@@ -531,7 +531,7 @@ class WebsiteSlides(WebsiteProfile):
image_base64 = tools.image_process(image_base64, (width, height), crop=crop)
content = base64.b64decode(image_base64)
headers.append(('Content-Length', len(content)))
headers = http.set_safe_image_headers(headers, content)
response = request.make_response(content, headers)
response.status_code = status
return response
+35
View File
@@ -50,6 +50,7 @@ from .service.server import memory_info
from .service import security, model as service_model
from .tools.func import lazy_property
from .tools import ustr, consteq, frozendict, pycompat, unique, date_utils
from .tools.mimetypes import guess_mimetype
from .modules.module import module_manifest
@@ -1617,5 +1618,39 @@ def content_disposition(filename):
return "attachment; filename*=UTF-8''%s" % escaped
def set_safe_image_headers(headers, content):
"""Return new headers based on `headers` but with `Content-Length` and
`Content-Type` set appropriately depending on the given `content` only if it
is safe to do."""
content_type = guess_mimetype(content)
safe_types = ['image/jpeg', 'image/png', 'image/gif', 'image/x-icon']
if content_type in safe_types:
headers = set_header_field(headers, 'Content-Type', content_type)
set_header_field(headers, 'Content-Length', len(content))
return headers
def set_header_field(headers, name, value):
""" Return new headers based on `headers` but with `value` set for the
header field `name`.
:param headers: the existing headers
:type headers: list of tuples (name, value)
:param name: the header field name
:type name: string
:param value: the value to set for the `name` header
:type value: string
:return: the updated headers
:rtype: list of tuples (name, value)
"""
dictheaders = dict(headers)
dictheaders[name] = value
return list(dictheaders.items())
# main wsgi handler
root = Root()