1414# limitations under the License.
1515
1616import logging
17- from typing import IO , TYPE_CHECKING , Dict , List , Optional
17+ from typing import IO , TYPE_CHECKING , Dict , List , Optional , Tuple
1818
1919from synapse .api .errors import Codes , SynapseError
2020from synapse .http .server import DirectServeJsonResource , respond_with_json
2121from synapse .http .servlet import parse_bytes_from_args
2222from synapse .http .site import SynapseRequest
2323from synapse .media .media_storage import SpamMediaException
2424
25+ from synapse .media ._base import parse_media_id , respond_404
26+
2527if TYPE_CHECKING :
2628 from synapse .media .media_repository import MediaRepository
2729 from synapse .server import HomeServer
2830
2931logger = logging .getLogger (__name__ )
3032
33+ # The name of the lock to use when uploading media.
34+ _UPLOAD_MEDIA_LOCK_NAME = "upload_media"
35+
3136
3237class UploadResource (DirectServeJsonResource ):
3338 isLeaf = True
@@ -38,16 +43,13 @@ def __init__(self, hs: "HomeServer", media_repo: "MediaRepository"):
3843 self .media_repo = media_repo
3944 self .filepaths = media_repo .filepaths
4045 self .store = hs .get_datastores ().main
41- self .clock = hs .get_clock ()
4246 self .server_name = hs .hostname
4347 self .auth = hs .get_auth ()
4448 self .max_upload_size = hs .config .media .max_upload_size
4549
46- async def _async_render_OPTIONS (self , request : SynapseRequest ) -> None :
47- respond_with_json (request , 200 , {}, send_cors = True )
48-
49- async def _async_render_POST (self , request : SynapseRequest ) -> None :
50- requester = await self .auth .get_user_by_req (request )
50+ def _get_file_metadata (
51+ self , request : SynapseRequest
52+ ) -> Tuple [int , Optional [str ], str ]:
5153 raw_content_length = request .getHeader ("Content-Length" )
5254 if raw_content_length is None :
5355 raise SynapseError (msg = "Request must specify a Content-Length" , code = 400 )
@@ -90,6 +92,15 @@ async def _async_render_POST(self, request: SynapseRequest) -> None:
9092 # disposition = headers.getRawHeaders(b"Content-Disposition")[0]
9193 # TODO(markjh): parse content-dispostion
9294
95+ return content_length , upload_name , media_type
96+
97+ async def _async_render_OPTIONS (self , request : SynapseRequest ) -> None :
98+ respond_with_json (request , 200 , {}, send_cors = True )
99+
100+ async def _async_render_POST (self , request : SynapseRequest ) -> None :
101+ requester = await self .auth .get_user_by_req (request )
102+ content_length , upload_name , media_type = self ._get_file_metadata (request )
103+
93104 try :
94105 content : IO = request .content # type: ignore
95106 content_uri = await self .media_repo .create_content (
@@ -105,3 +116,44 @@ async def _async_render_POST(self, request: SynapseRequest) -> None:
105116 respond_with_json (
106117 request , 200 , {"content_uri" : str (content_uri )}, send_cors = True
107118 )
119+
120+ async def _async_render_PUT (self , request : SynapseRequest ) -> None :
121+ requester = await self .auth .get_user_by_req (request )
122+ server_name , media_id , _ = parse_media_id (request )
123+
124+ if server_name != self .server_name :
125+ raise SynapseError (
126+ 404 ,
127+ "Non-local server name specified" ,
128+ errcode = Codes .NOT_FOUND ,
129+ )
130+
131+ lock = await self .store .try_acquire_lock (_UPLOAD_MEDIA_LOCK_NAME , media_id )
132+ if not lock :
133+ raise SynapseError (
134+ 409 ,
135+ "Media ID is is locked and cannot be uploaded to" ,
136+ errcode = "M_CANNOT_OVERWRITE_MEDIA" ,
137+ )
138+
139+ async with lock :
140+ await self .media_repo .verify_can_upload (media_id , requester .user )
141+ content_length , upload_name , media_type = self ._get_file_metadata (request )
142+
143+ try :
144+ content : IO = request .content # type: ignore
145+ await self .media_repo .update_content (
146+ media_id ,
147+ media_type ,
148+ upload_name ,
149+ content ,
150+ content_length ,
151+ requester .user ,
152+ )
153+ except SpamMediaException :
154+ # For uploading of media we want to respond with a 400, instead of
155+ # the default 404, as that would just be confusing.
156+ raise SynapseError (400 , "Bad content" )
157+
158+ logger .info ("Uploaded content to URI %r" , media_id )
159+ respond_with_json (request , 200 , {}, send_cors = True )
0 commit comments