2021-01-15 10:57:37 -05:00
# Copyright 2014-2016 OpenMarket Ltd
# Copyright 2020-2021 The Matrix.org Foundation C.I.C.
2014-12-10 09:46:55 -05:00
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2018-05-10 07:10:27 -04:00
import logging
2021-08-25 05:51:08 -04:00
from typing import TYPE_CHECKING , Any , Dict , List , Optional , Tuple
2021-01-15 10:57:37 -05:00
Provide more info why we don't have any thumbnails to serve (#13038)
Fix https://github.com/matrix-org/synapse/issues/13016
## New error code and status
### Before
Previously, we returned a `404` for `/thumbnail` which isn't even in the spec.
```json
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'hs1', b'tefQeZhmVxoiBfuFQUKRzJxc']"
}
```
### After
What does the spec say?
> 400: The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
>
> *-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixmediav3thumbnailservernamemediaid*
Now with this PR, we respond with a `400` when we don't have thumbnails to serve and we explain why we might not have any thumbnails.
```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
}
```
> Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)
---
We still respond with a 404 in many other places. But we can iterate on those later and maybe keep some in some specific places after spec updates/clarification: https://github.com/matrix-org/matrix-spec/issues/1122
We can also iterate on the bugs where Synapse doesn't thumbnail when it should in other issues/PRs.
2022-07-15 12:42:21 -04:00
from synapse . api . errors import Codes , SynapseError , cs_error
from synapse . config . repository import THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP
2022-06-27 09:44:05 -04:00
from synapse . http . server import (
DirectServeJsonResource ,
Provide more info why we don't have any thumbnails to serve (#13038)
Fix https://github.com/matrix-org/synapse/issues/13016
## New error code and status
### Before
Previously, we returned a `404` for `/thumbnail` which isn't even in the spec.
```json
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'hs1', b'tefQeZhmVxoiBfuFQUKRzJxc']"
}
```
### After
What does the spec say?
> 400: The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
>
> *-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixmediav3thumbnailservernamemediaid*
Now with this PR, we respond with a `400` when we don't have thumbnails to serve and we explain why we might not have any thumbnails.
```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
}
```
> Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)
---
We still respond with a 404 in many other places. But we can iterate on those later and maybe keep some in some specific places after spec updates/clarification: https://github.com/matrix-org/matrix-spec/issues/1122
We can also iterate on the bugs where Synapse doesn't thumbnail when it should in other issues/PRs.
2022-07-15 12:42:21 -04:00
respond_with_json ,
2022-06-27 09:44:05 -04:00
set_corp_headers ,
set_cors_headers ,
)
2018-05-10 07:10:27 -04:00
from synapse . http . servlet import parse_integer , parse_string
2021-09-24 06:01:25 -04:00
from synapse . http . site import SynapseRequest
2023-02-27 08:26:05 -05:00
from synapse . media . _base import (
2018-07-09 02:09:20 -04:00
FileInfo ,
2021-09-14 07:09:38 -04:00
ThumbnailInfo ,
2018-07-09 02:09:20 -04:00
parse_media_id ,
respond_404 ,
respond_with_file ,
2018-05-10 07:10:27 -04:00
respond_with_responder ,
)
2023-02-27 08:26:05 -05:00
from synapse . media . media_storage import MediaStorage
2014-12-10 09:46:55 -05:00
2021-01-15 10:57:37 -05:00
if TYPE_CHECKING :
2023-02-27 08:26:05 -05:00
from synapse . media . media_repository import MediaRepository
2021-03-23 07:12:48 -04:00
from synapse . server import HomeServer
2021-01-15 10:57:37 -05:00
2014-12-10 09:46:55 -05:00
logger = logging . getLogger ( __name__ )
2020-07-03 14:02:19 -04:00
class ThumbnailResource ( DirectServeJsonResource ) :
2014-12-10 09:46:55 -05:00
isLeaf = True
2021-01-15 10:57:37 -05:00
def __init__ (
self ,
hs : " HomeServer " ,
media_repo : " MediaRepository " ,
media_storage : MediaStorage ,
) :
2019-06-29 03:06:55 -04:00
super ( ) . __init__ ( )
2016-04-19 06:24:59 -04:00
2022-02-23 06:04:02 -05:00
self . store = hs . get_datastores ( ) . main
2016-04-19 06:24:59 -04:00
self . media_repo = media_repo
2018-01-09 06:08:46 -05:00
self . media_storage = media_storage
2021-09-24 07:25:21 -04:00
self . dynamic_thumbnails = hs . config . media . dynamic_thumbnails
2023-05-05 10:06:22 -04:00
self . _is_mine_server_name = hs . is_mine_server_name
2023-05-09 14:08:51 -04:00
self . prevent_media_downloads_from = hs . config . media . prevent_media_downloads_from
2016-04-19 06:24:59 -04:00
2021-09-24 06:01:25 -04:00
async def _async_render_GET ( self , request : SynapseRequest ) - > None :
2016-11-02 07:29:25 -04:00
set_cors_headers ( request )
2022-06-27 09:44:05 -04:00
set_corp_headers ( request )
2015-07-03 06:24:55 -04:00
server_name , media_id , _ = parse_media_id ( request )
2019-05-29 09:27:41 -04:00
width = parse_integer ( request , " width " , required = True )
height = parse_integer ( request , " height " , required = True )
2015-04-21 11:35:53 -04:00
method = parse_string ( request , " method " , " scale " )
2023-02-23 16:07:46 -05:00
# TODO Parse the Accept header to get an prioritised list of thumbnail types.
m_type = " image/png "
2014-12-10 09:46:55 -05:00
2023-05-05 10:06:22 -04:00
if self . _is_mine_server_name ( server_name ) :
2015-08-12 05:54:38 -04:00
if self . dynamic_thumbnails :
2019-06-29 03:06:55 -04:00
await self . _select_or_generate_local_thumbnail (
2015-08-12 05:54:38 -04:00
request , media_id , width , height , method , m_type
)
else :
2019-06-29 03:06:55 -04:00
await self . _respond_local_thumbnail (
2015-08-12 05:54:38 -04:00
request , media_id , width , height , method , m_type
)
2018-01-18 10:06:24 -05:00
self . media_repo . mark_recently_accessed ( None , media_id )
2014-12-10 09:46:55 -05:00
else :
2023-05-09 14:08:51 -04:00
# Don't let users download media from configured domains, even if it
# is already downloaded. This is Trust & Safety tooling to make some
# media inaccessible to local users.
# See `prevent_media_downloads_from` config docs for more info.
if server_name in self . prevent_media_downloads_from :
respond_404 ( request )
return
2015-08-12 05:54:38 -04:00
if self . dynamic_thumbnails :
2019-06-29 03:06:55 -04:00
await self . _select_or_generate_remote_thumbnail (
2015-08-12 05:54:38 -04:00
request , server_name , media_id , width , height , method , m_type
)
else :
2019-06-29 03:06:55 -04:00
await self . _respond_remote_thumbnail (
2015-08-12 05:54:38 -04:00
request , server_name , media_id , width , height , method , m_type
)
2018-01-18 10:06:24 -05:00
self . media_repo . mark_recently_accessed ( server_name , media_id )
2014-12-10 09:46:55 -05:00
2020-03-20 07:20:02 -04:00
async def _respond_local_thumbnail (
2021-01-15 10:57:37 -05:00
self ,
2021-09-24 06:01:25 -04:00
request : SynapseRequest ,
2021-01-15 10:57:37 -05:00
media_id : str ,
width : int ,
height : int ,
method : str ,
m_type : str ,
) - > None :
2020-03-20 07:20:02 -04:00
media_info = await self . store . get_local_media ( media_id )
2014-12-10 09:46:55 -05:00
2018-01-16 09:32:56 -05:00
if not media_info :
respond_404 ( request )
return
if media_info [ " quarantined_by " ] :
logger . info ( " Media is quarantined " )
2016-04-19 06:24:59 -04:00
respond_404 ( request )
2014-12-10 09:46:55 -05:00
return
2020-03-20 07:20:02 -04:00
thumbnail_infos = await self . store . get_local_media_thumbnails ( media_id )
2021-01-21 14:53:58 -05:00
await self . _select_and_respond_with_thumbnail (
request ,
width ,
height ,
method ,
m_type ,
thumbnail_infos ,
media_id ,
2021-02-18 11:22:21 -05:00
media_id ,
2021-09-14 07:09:38 -04:00
url_cache = bool ( media_info [ " url_cache " ] ) ,
2021-01-21 14:53:58 -05:00
server_name = None ,
)
2014-12-10 09:46:55 -05:00
2020-03-20 07:20:02 -04:00
async def _select_or_generate_local_thumbnail (
2015-07-23 09:12:49 -04:00
self ,
2021-09-24 06:01:25 -04:00
request : SynapseRequest ,
2021-01-15 10:57:37 -05:00
media_id : str ,
desired_width : int ,
desired_height : int ,
desired_method : str ,
desired_type : str ,
) - > None :
2020-03-20 07:20:02 -04:00
media_info = await self . store . get_local_media ( media_id )
2015-07-23 09:12:49 -04:00
2018-01-16 09:32:56 -05:00
if not media_info :
respond_404 ( request )
return
if media_info [ " quarantined_by " ] :
logger . info ( " Media is quarantined " )
2016-04-19 06:24:59 -04:00
respond_404 ( request )
2015-07-23 09:12:49 -04:00
return
2020-03-20 07:20:02 -04:00
thumbnail_infos = await self . store . get_local_media_thumbnails ( media_id )
2015-07-23 09:12:49 -04:00
for info in thumbnail_infos :
t_w = info [ " thumbnail_width " ] == desired_width
t_h = info [ " thumbnail_height " ] == desired_height
t_method = info [ " thumbnail_method " ] == desired_method
t_type = info [ " thumbnail_type " ] == desired_type
if t_w and t_h and t_method and t_type :
2018-01-09 06:08:46 -05:00
file_info = FileInfo (
server_name = None ,
file_id = media_id ,
url_cache = media_info [ " url_cache " ] ,
2021-09-14 07:09:38 -04:00
thumbnail = ThumbnailInfo (
width = info [ " thumbnail_width " ] ,
height = info [ " thumbnail_height " ] ,
type = info [ " thumbnail_type " ] ,
method = info [ " thumbnail_method " ] ,
) ,
2018-01-09 06:08:46 -05:00
)
t_type = file_info . thumbnail_type
t_length = info [ " thumbnail_length " ]
2020-03-20 07:20:02 -04:00
responder = await self . media_storage . fetch_media ( file_info )
2018-01-09 06:08:46 -05:00
if responder :
2020-03-20 07:20:02 -04:00
await respond_with_responder ( request , responder , t_type , t_length )
2018-01-09 06:08:46 -05:00
return
2015-07-23 09:12:49 -04:00
2018-01-16 08:53:43 -05:00
logger . debug ( " We don ' t have a thumbnail of that size. Generating " )
2015-07-23 09:12:49 -04:00
# Okay, so we generate one.
2020-03-20 07:20:02 -04:00
file_path = await self . media_repo . generate_local_exact_thumbnail (
2018-01-16 05:52:32 -05:00
media_id ,
desired_width ,
desired_height ,
desired_method ,
desired_type ,
2021-09-14 07:09:38 -04:00
url_cache = bool ( media_info [ " url_cache " ] ) ,
2015-07-23 09:12:49 -04:00
)
if file_path :
2020-03-20 07:20:02 -04:00
await respond_with_file ( request , desired_type , file_path )
2015-07-23 09:12:49 -04:00
else :
2019-10-31 06:23:24 -04:00
logger . warning ( " Failed to generate thumbnail " )
2020-09-09 12:59:41 -04:00
raise SynapseError ( 400 , " Failed to generate thumbnail. " )
2015-07-23 09:12:49 -04:00
2020-03-20 07:20:02 -04:00
async def _select_or_generate_remote_thumbnail (
2015-07-23 09:12:49 -04:00
self ,
2021-09-24 06:01:25 -04:00
request : SynapseRequest ,
2021-01-15 10:57:37 -05:00
server_name : str ,
media_id : str ,
desired_width : int ,
desired_height : int ,
desired_method : str ,
desired_type : str ,
) - > None :
2020-03-20 07:20:02 -04:00
media_info = await self . media_repo . get_remote_media_info ( server_name , media_id )
2015-07-23 09:12:49 -04:00
2020-03-20 07:20:02 -04:00
thumbnail_infos = await self . store . get_remote_media_thumbnails (
2015-07-23 09:12:49 -04:00
server_name , media_id
)
2015-07-23 09:24:21 -04:00
file_id = media_info [ " filesystem_id " ]
2015-07-23 09:12:49 -04:00
for info in thumbnail_infos :
t_w = info [ " thumbnail_width " ] == desired_width
t_h = info [ " thumbnail_height " ] == desired_height
t_method = info [ " thumbnail_method " ] == desired_method
t_type = info [ " thumbnail_type " ] == desired_type
if t_w and t_h and t_method and t_type :
2018-01-09 06:08:46 -05:00
file_info = FileInfo (
2018-01-16 07:02:06 -05:00
server_name = server_name ,
file_id = media_info [ " filesystem_id " ] ,
2021-09-14 07:09:38 -04:00
thumbnail = ThumbnailInfo (
width = info [ " thumbnail_width " ] ,
height = info [ " thumbnail_height " ] ,
type = info [ " thumbnail_type " ] ,
method = info [ " thumbnail_method " ] ,
) ,
2015-07-23 09:12:49 -04:00
)
2018-01-09 06:08:46 -05:00
t_type = file_info . thumbnail_type
t_length = info [ " thumbnail_length " ]
2020-03-20 07:20:02 -04:00
responder = await self . media_storage . fetch_media ( file_info )
2018-01-09 06:08:46 -05:00
if responder :
2020-03-20 07:20:02 -04:00
await respond_with_responder ( request , responder , t_type , t_length )
2018-01-09 06:08:46 -05:00
return
2015-07-23 09:12:49 -04:00
2018-01-16 08:53:43 -05:00
logger . debug ( " We don ' t have a thumbnail of that size. Generating " )
2015-07-23 09:12:49 -04:00
# Okay, so we generate one.
2020-03-20 07:20:02 -04:00
file_path = await self . media_repo . generate_remote_exact_thumbnail (
2015-07-23 09:24:21 -04:00
server_name ,
file_id ,
media_id ,
desired_width ,
desired_height ,
desired_method ,
desired_type ,
2015-07-23 09:12:49 -04:00
)
2015-07-23 09:24:21 -04:00
if file_path :
2020-03-20 07:20:02 -04:00
await respond_with_file ( request , desired_type , file_path )
2015-07-23 09:12:49 -04:00
else :
2019-10-31 06:23:24 -04:00
logger . warning ( " Failed to generate thumbnail " )
2020-09-09 12:59:41 -04:00
raise SynapseError ( 400 , " Failed to generate thumbnail. " )
2015-07-23 09:12:49 -04:00
2020-03-20 07:20:02 -04:00
async def _respond_remote_thumbnail (
2021-01-15 10:57:37 -05:00
self ,
2021-09-24 06:01:25 -04:00
request : SynapseRequest ,
2021-01-15 10:57:37 -05:00
server_name : str ,
media_id : str ,
width : int ,
height : int ,
method : str ,
m_type : str ,
) - > None :
2018-01-12 10:38:06 -05:00
# TODO: Don't download the whole remote file
# We should proxy the thumbnail from the remote server instead of
# downloading the remote file and generating our own thumbnails.
2020-03-20 07:20:02 -04:00
media_info = await self . media_repo . get_remote_media_info ( server_name , media_id )
2018-01-12 10:38:06 -05:00
2020-03-20 07:20:02 -04:00
thumbnail_infos = await self . store . get_remote_media_thumbnails (
2014-12-10 09:46:55 -05:00
server_name , media_id
)
2021-01-21 14:53:58 -05:00
await self . _select_and_respond_with_thumbnail (
request ,
width ,
height ,
method ,
m_type ,
thumbnail_infos ,
2021-02-18 11:22:21 -05:00
media_id ,
2021-01-21 14:53:58 -05:00
media_info [ " filesystem_id " ] ,
2021-09-14 07:09:38 -04:00
url_cache = False ,
2021-01-21 14:53:58 -05:00
server_name = server_name ,
)
2014-12-10 09:46:55 -05:00
2021-01-21 14:53:58 -05:00
async def _select_and_respond_with_thumbnail (
self ,
2021-09-24 06:01:25 -04:00
request : SynapseRequest ,
2021-01-21 14:53:58 -05:00
desired_width : int ,
desired_height : int ,
desired_method : str ,
desired_type : str ,
thumbnail_infos : List [ Dict [ str , Any ] ] ,
2021-02-18 11:22:21 -05:00
media_id : str ,
2021-01-21 14:53:58 -05:00
file_id : str ,
2021-09-14 07:09:38 -04:00
url_cache : bool ,
2021-01-21 14:53:58 -05:00
server_name : Optional [ str ] = None ,
) - > None :
"""
Respond to a request with an appropriate thumbnail from the previously generated thumbnails .
Args :
request : The incoming request .
desired_width : The desired width , the returned thumbnail may be larger than this .
desired_height : The desired height , the returned thumbnail may be larger than this .
desired_method : The desired method used to generate the thumbnail .
desired_type : The desired content - type of the thumbnail .
thumbnail_infos : A list of dictionaries of candidate thumbnails .
file_id : The ID of the media that a thumbnail is being requested for .
2021-09-14 07:09:38 -04:00
url_cache : True if this is from a URL cache .
2021-01-21 14:53:58 -05:00
server_name : The server name , if this is a remote thumbnail .
"""
Provide more info why we don't have any thumbnails to serve (#13038)
Fix https://github.com/matrix-org/synapse/issues/13016
## New error code and status
### Before
Previously, we returned a `404` for `/thumbnail` which isn't even in the spec.
```json
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'hs1', b'tefQeZhmVxoiBfuFQUKRzJxc']"
}
```
### After
What does the spec say?
> 400: The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
>
> *-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixmediav3thumbnailservernamemediaid*
Now with this PR, we respond with a `400` when we don't have thumbnails to serve and we explain why we might not have any thumbnails.
```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
}
```
> Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)
---
We still respond with a 404 in many other places. But we can iterate on those later and maybe keep some in some specific places after spec updates/clarification: https://github.com/matrix-org/matrix-spec/issues/1122
We can also iterate on the bugs where Synapse doesn't thumbnail when it should in other issues/PRs.
2022-07-15 12:42:21 -04:00
logger . debug (
" _select_and_respond_with_thumbnail: media_id= %s desired= %s x %s ( %s ) thumbnail_infos= %s " ,
media_id ,
desired_width ,
desired_height ,
desired_method ,
thumbnail_infos ,
)
# If `dynamic_thumbnails` is enabled, we expect Synapse to go down a
# different code path to handle it.
assert not self . dynamic_thumbnails
2014-12-10 09:46:55 -05:00
if thumbnail_infos :
2021-01-21 14:53:58 -05:00
file_info = self . _select_thumbnail (
desired_width ,
desired_height ,
desired_method ,
desired_type ,
thumbnail_infos ,
file_id ,
url_cache ,
server_name ,
2014-12-10 09:46:55 -05:00
)
2021-01-21 14:53:58 -05:00
if not file_info :
logger . info ( " Couldn ' t find a thumbnail matching the desired inputs " )
respond_404 ( request )
return
2014-12-10 09:46:55 -05:00
2021-09-14 07:09:38 -04:00
# The thumbnail property must exist.
assert file_info . thumbnail is not None
2021-02-18 11:22:21 -05:00
responder = await self . media_storage . fetch_media ( file_info )
if responder :
await respond_with_responder (
request ,
responder ,
2021-09-14 07:09:38 -04:00
file_info . thumbnail . type ,
file_info . thumbnail . length ,
2021-02-18 11:22:21 -05:00
)
return
# If we can't find the thumbnail we regenerate it. This can happen
# if e.g. we've deleted the thumbnails but still have the original
# image somewhere.
#
# Since we have an entry for the thumbnail in the DB we a) know we
# have have successfully generated the thumbnail in the past (so we
# don't need to worry about repeatedly failing to generate
# thumbnails), and b) have already calculated that appropriate
# width/height/method so we can just call the "generate exact"
# methods.
2021-02-19 05:46:18 -05:00
# First let's check that we do actually have the original image
# still. This will throw a 404 if we don't.
# TODO: We should refetch the thumbnails for remote media.
await self . media_storage . ensure_media_is_in_local_cache (
FileInfo ( server_name , file_id , url_cache = url_cache )
)
2021-02-18 11:22:21 -05:00
if server_name :
await self . media_repo . generate_remote_exact_thumbnail (
server_name ,
file_id = file_id ,
media_id = media_id ,
2021-09-14 07:09:38 -04:00
t_width = file_info . thumbnail . width ,
t_height = file_info . thumbnail . height ,
t_method = file_info . thumbnail . method ,
t_type = file_info . thumbnail . type ,
2021-02-18 11:22:21 -05:00
)
else :
await self . media_repo . generate_local_exact_thumbnail (
media_id = media_id ,
2021-09-14 07:09:38 -04:00
t_width = file_info . thumbnail . width ,
t_height = file_info . thumbnail . height ,
t_method = file_info . thumbnail . method ,
t_type = file_info . thumbnail . type ,
2021-02-18 11:22:21 -05:00
url_cache = url_cache ,
)
2020-03-20 07:20:02 -04:00
responder = await self . media_storage . fetch_media ( file_info )
2021-01-21 14:53:58 -05:00
await respond_with_responder (
2021-02-18 11:22:21 -05:00
request ,
responder ,
2021-09-14 07:09:38 -04:00
file_info . thumbnail . type ,
file_info . thumbnail . length ,
2021-01-21 14:53:58 -05:00
)
2014-12-10 09:46:55 -05:00
else :
Provide more info why we don't have any thumbnails to serve (#13038)
Fix https://github.com/matrix-org/synapse/issues/13016
## New error code and status
### Before
Previously, we returned a `404` for `/thumbnail` which isn't even in the spec.
```json
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'hs1', b'tefQeZhmVxoiBfuFQUKRzJxc']"
}
```
### After
What does the spec say?
> 400: The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
>
> *-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixmediav3thumbnailservernamemediaid*
Now with this PR, we respond with a `400` when we don't have thumbnails to serve and we explain why we might not have any thumbnails.
```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
}
```
> Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)
---
We still respond with a 404 in many other places. But we can iterate on those later and maybe keep some in some specific places after spec updates/clarification: https://github.com/matrix-org/matrix-spec/issues/1122
We can also iterate on the bugs where Synapse doesn't thumbnail when it should in other issues/PRs.
2022-07-15 12:42:21 -04:00
# This might be because:
# 1. We can't create thumbnails for the given media (corrupted or
# unsupported file type), or
# 2. The thumbnailing process never ran or errored out initially
# when the media was first uploaded (these bugs should be
# reported and fixed).
# Note that we don't attempt to generate a thumbnail now because
# `dynamic_thumbnails` is disabled.
2018-01-16 07:01:40 -05:00
logger . info ( " Failed to find any generated thumbnails " )
Provide more info why we don't have any thumbnails to serve (#13038)
Fix https://github.com/matrix-org/synapse/issues/13016
## New error code and status
### Before
Previously, we returned a `404` for `/thumbnail` which isn't even in the spec.
```json
{
"errcode": "M_NOT_FOUND",
"error": "Not found [b'hs1', b'tefQeZhmVxoiBfuFQUKRzJxc']"
}
```
### After
What does the spec say?
> 400: The request does not make sense to the server, or the server cannot thumbnail the content. For example, the client requested non-integer dimensions or asked for negatively-sized images.
>
> *-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixmediav3thumbnailservernamemediaid*
Now with this PR, we respond with a `400` when we don't have thumbnails to serve and we explain why we might not have any thumbnails.
```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)",
}
```
> Cannot find any thumbnails for the requested media ([b'example.com', b'12345']). This might mean the media is not a supported_media_format=(image/jpeg, image/jpg, image/webp, image/gif, image/png) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.)
---
We still respond with a 404 in many other places. But we can iterate on those later and maybe keep some in some specific places after spec updates/clarification: https://github.com/matrix-org/matrix-spec/issues/1122
We can also iterate on the bugs where Synapse doesn't thumbnail when it should in other issues/PRs.
2022-07-15 12:42:21 -04:00
respond_with_json (
request ,
400 ,
cs_error (
" Cannot find any thumbnails for the requested media ( %r ). This might mean the media is not a supported_media_format=( %s ) or that thumbnailing failed for some other reason. (Dynamic thumbnails are disabled on this server.) "
% (
request . postpath ,
" , " . join ( THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP . keys ( ) ) ,
) ,
code = Codes . UNKNOWN ,
) ,
send_cors = True ,
)
2014-12-10 09:46:55 -05:00
def _select_thumbnail (
self ,
2021-01-15 10:57:37 -05:00
desired_width : int ,
desired_height : int ,
desired_method : str ,
desired_type : str ,
2021-01-21 14:53:58 -05:00
thumbnail_infos : List [ Dict [ str , Any ] ] ,
file_id : str ,
2021-09-14 07:09:38 -04:00
url_cache : bool ,
2021-01-21 14:53:58 -05:00
server_name : Optional [ str ] ,
) - > Optional [ FileInfo ] :
"""
Choose an appropriate thumbnail from the previously generated thumbnails .
Args :
desired_width : The desired width , the returned thumbnail may be larger than this .
desired_height : The desired height , the returned thumbnail may be larger than this .
desired_method : The desired method used to generate the thumbnail .
desired_type : The desired content - type of the thumbnail .
thumbnail_infos : A list of dictionaries of candidate thumbnails .
file_id : The ID of the media that a thumbnail is being requested for .
2021-09-14 07:09:38 -04:00
url_cache : True if this is from a URL cache .
2021-01-21 14:53:58 -05:00
server_name : The server name , if this is a remote thumbnail .
Returns :
The thumbnail which best matches the desired parameters .
"""
desired_method = desired_method . lower ( )
# The chosen thumbnail.
thumbnail_info = None
2014-12-10 09:46:55 -05:00
d_w = desired_width
d_h = desired_height
2021-01-21 14:53:58 -05:00
if desired_method == " crop " :
# Thumbnails that match equal or larger sizes of desired width/height.
2021-08-25 05:51:08 -04:00
crop_info_list : List [ Tuple [ int , int , int , bool , int , Dict [ str , Any ] ] ] = [ ]
2021-01-21 14:53:58 -05:00
# Other thumbnails.
2021-08-25 05:51:08 -04:00
crop_info_list2 : List [ Tuple [ int , int , int , bool , int , Dict [ str , Any ] ] ] = [ ]
2014-12-10 09:46:55 -05:00
for info in thumbnail_infos :
2021-01-21 14:53:58 -05:00
# Skip thumbnails generated with different methods.
if info [ " thumbnail_method " ] != " crop " :
continue
2014-12-10 09:46:55 -05:00
t_w = info [ " thumbnail_width " ]
t_h = info [ " thumbnail_height " ]
2021-01-21 14:53:58 -05:00
aspect_quality = abs ( d_w * t_h - d_h * t_w )
min_quality = 0 if d_w < = t_w and d_h < = t_h else 1
size_quality = abs ( ( d_w - t_w ) * ( d_h - t_h ) )
type_quality = desired_type != info [ " thumbnail_type " ]
length_quality = info [ " thumbnail_length " ]
if t_w > = d_w or t_h > = d_h :
crop_info_list . append (
(
aspect_quality ,
min_quality ,
size_quality ,
type_quality ,
length_quality ,
info ,
2019-06-20 05:32:02 -04:00
)
2021-01-21 14:53:58 -05:00
)
else :
crop_info_list2 . append (
(
aspect_quality ,
min_quality ,
size_quality ,
type_quality ,
length_quality ,
info ,
2019-06-20 05:32:02 -04:00
)
2021-01-21 14:53:58 -05:00
)
2021-08-25 05:51:08 -04:00
# Pick the most appropriate thumbnail. Some values of `desired_width` and
# `desired_height` may result in a tie, in which case we avoid comparing on
# the thumbnail info dictionary and pick the thumbnail that appears earlier
# in the list of candidates.
2020-01-20 12:38:21 -05:00
if crop_info_list :
2021-08-25 05:51:08 -04:00
thumbnail_info = min ( crop_info_list , key = lambda t : t [ : - 1 ] ) [ - 1 ]
2021-01-21 14:53:58 -05:00
elif crop_info_list2 :
2021-08-25 05:51:08 -04:00
thumbnail_info = min ( crop_info_list2 , key = lambda t : t [ : - 1 ] ) [ - 1 ]
2021-01-21 14:53:58 -05:00
elif desired_method == " scale " :
# Thumbnails that match equal or larger sizes of desired width/height.
2021-08-25 05:51:08 -04:00
info_list : List [ Tuple [ int , bool , int , Dict [ str , Any ] ] ] = [ ]
2021-01-21 14:53:58 -05:00
# Other thumbnails.
2021-08-25 05:51:08 -04:00
info_list2 : List [ Tuple [ int , bool , int , Dict [ str , Any ] ] ] = [ ]
2021-01-21 14:53:58 -05:00
2014-12-10 09:46:55 -05:00
for info in thumbnail_infos :
2021-01-21 14:53:58 -05:00
# Skip thumbnails generated with different methods.
if info [ " thumbnail_method " ] != " scale " :
continue
2014-12-10 09:46:55 -05:00
t_w = info [ " thumbnail_width " ]
t_h = info [ " thumbnail_height " ]
2014-12-19 07:05:26 -05:00
size_quality = abs ( ( d_w - t_w ) * ( d_h - t_h ) )
type_quality = desired_type != info [ " thumbnail_type " ]
length_quality = info [ " thumbnail_length " ]
2021-01-21 14:53:58 -05:00
if t_w > = d_w or t_h > = d_h :
2014-12-10 09:46:55 -05:00
info_list . append ( ( size_quality , type_quality , length_quality , info ) )
2021-01-21 14:53:58 -05:00
else :
2014-12-19 07:05:26 -05:00
info_list2 . append (
( size_quality , type_quality , length_quality , info )
)
2021-08-25 05:51:08 -04:00
# Pick the most appropriate thumbnail. Some values of `desired_width` and
# `desired_height` may result in a tie, in which case we avoid comparing on
# the thumbnail info dictionary and pick the thumbnail that appears earlier
# in the list of candidates.
2014-12-19 07:05:26 -05:00
if info_list :
2021-08-25 05:51:08 -04:00
thumbnail_info = min ( info_list , key = lambda t : t [ : - 1 ] ) [ - 1 ]
2021-01-21 14:53:58 -05:00
elif info_list2 :
2021-08-25 05:51:08 -04:00
thumbnail_info = min ( info_list2 , key = lambda t : t [ : - 1 ] ) [ - 1 ]
2021-01-21 14:53:58 -05:00
if thumbnail_info :
return FileInfo (
file_id = file_id ,
url_cache = url_cache ,
server_name = server_name ,
2021-09-14 07:09:38 -04:00
thumbnail = ThumbnailInfo (
width = thumbnail_info [ " thumbnail_width " ] ,
height = thumbnail_info [ " thumbnail_height " ] ,
type = thumbnail_info [ " thumbnail_type " ] ,
method = thumbnail_info [ " thumbnail_method " ] ,
length = thumbnail_info [ " thumbnail_length " ] ,
) ,
2021-01-21 14:53:58 -05:00
)
# No matching thumbnail was found.
return None