From 8962f0a383b4831a9196cc1d918a6e29563b4863 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 25 Apr 2023 03:00:41 +0300 Subject: [PATCH] MSC2246: Asynchronous media uploads (#2246) * Proposal for asynchronous media uploads Signed-off-by: Tulir Asokan * Add security consideration and mention possible /create request body Signed-off-by: Tulir Asokan * Expand on security consideration * Change error code for existing media PUT Co-Authored-By: Travis Ralston * Add spam prevention error, example response and auth requirements * Add query parameters to control downloading * Rename max_stall to max_stall_ms * Specify /create request body content * Update links, versions and prefixes * Add recommended maximum delay for starting upload * Explicitly deny uploading to other users' media IDs * Prevent spam by ratelimiting instead of limiting number of IDs * Remove streaming requirement It's complicated * Add unstable prefixes for new error codes * Add missing words * Change /create endpoint to use v1 Signed-off-by: Sumner Evans * Reorganize /upload spec and integrate feedback * Explicitly specify that M_NOT_FOUND should be used for expired media * Explicitly specify that M_FORBIDDEN should be used when a user other than the one who created the media ID tries to upload to it * Remove content-length failure note Signed-off-by: Sumner Evans * Rename max_stall_ms -> timeout_ms Signed-off-by: Sumner Evans * Mention that maximum value for timeout_ms should be imposed by the server Signed-off-by: Sumner Evans * Mention that the timeout_ms can be ignored if the media exists already Signed-off-by: Sumner Evans * Change M_NOT_YET_UPLOADED to use 504 status code Signed-off-by: Sumner Evans * Remove retry_after_ms optional field Signed-off-by: Sumner Evans * Make unused_expires_at the deadline for the upload to complete rather than start Signed-off-by: Sumner Evans * Add notes about suggested rate-limiting techniques Namely, allowing a limited number of concurrent uploads Signed-off-by: Sumner Evans * Recommend 24 hours instead of 1 minute Signed-off-by: Sumner Evans * Clarify that rate limiting can apply on /create and /upload Signed-off-by: Sumner Evans * Clarify that unused_expires_at is a POSIX millisecond timestamp Signed-off-by: Sumner Evans --------- Signed-off-by: Tulir Asokan Signed-off-by: Sumner Evans Co-authored-by: Travis Ralston Co-authored-by: Sumner Evans --- proposals/2246-asynchronous-uploads.md | 152 +++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 proposals/2246-asynchronous-uploads.md diff --git a/proposals/2246-asynchronous-uploads.md b/proposals/2246-asynchronous-uploads.md new file mode 100644 index 00000000..2e844eeb --- /dev/null +++ b/proposals/2246-asynchronous-uploads.md @@ -0,0 +1,152 @@ +# MSC2246: Asynchronous media uploads +Sending media to Matrix currently requires that clients first upload the media +to the content repository and then send the event. This is a problem for some +use cases, such as bridges that want to preserve message order, as reuploading +a large file would block all messages. + +## Proposal +This proposal proposes a way to send the event containing media before actually +uploading the media, which would make the aformentioned bridge message order +preservation possible without blocking all other messages behind a long upload. + +In the future, this new functionality could be used for streaming file +transfers, as requested in [matrix-spec#432]. + +### Content repository behavior +The proposal adds two new endpoints to the content repository API and modifies +the download and thumbnail endpoints. + +#### `POST /_matrix/media/v1/create` +Create a new MXC URI without content. Like `/upload`, this endpoint requires +auth, can be rate limited, and returns the `content_uri` that can be used in +events. + +The request body should be an empty JSON object. In the future, the body could +be used for metadata about the file, such as the mime type or access control +settings (related: [MSC701]). + +The server may optionally enforce a maximum age for unused media IDs to delete +media IDs when the client doesn't start the upload in time, or when the upload +was interrupted and not resumed in time. The server should include the maximum +POSIX millisecond timestamp to complete the upload in the `unused_expires_at` +field in the response JSON. The recommended default expiration is 24 hours which +should be enough time to accommodate users on poor connection who find a better +connection to complete the upload. + +##### Rate Limiting + +The server should rate limit requests to create media. + +The server should limit the number of concurrent *pending media uploads* a given +user can have. A pending media upload is a created MXC URI that (a) is not +expired (the `unused_expires_at` timestamp has not passed) and (b) the media has +not yet been uploaded for. + +In both cases, the server should respond with `M_LIMIT_EXCEEDED` optionally +providing details in the `error` field, but servers may wish to obscure the +exact limits that are used and not provide such details. + +##### Example response +```json +{ + "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw", + "unused_expires_at": 1647257217083 +} +``` + +#### `PUT /_matrix/media/v3/upload/{serverName}/{mediaId}` +Upload content to a MXC URI that was created earlier. This endpoint requires +auth. If the upload is successful, an empty JSON object and status code 200 is +returned. Rate limiting additionally can apply here. + +If the endpoint is called with a media ID that already has content, the request +should be rejected with the error code `M_CANNOT_OVERWRITE_MEDIA` and HTTP +status code 409. + +If the upload request comes from a user other than the one who created the media +ID, the request should be rejected with an `M_FORBIDDEN` error. + +If the serverName/mediaId combination is not known, not local, or expired, an +`M_NOT_FOUND` error is returned. + +If the MXC's `unused_expires_at` is reached before the upload completes, the +server may either respond immediately with `M_NOT_FOUND` or allow the upload to +continue. + +For other errors, such as file size, file type or user quota errors, the normal +`/upload` rules apply. + +#### Changes to the `/download` and `/thumbnail` endpoints +A new query parameter, `timeout_ms` is added to the endpoints that can +download media. It's an integer that specifies the maximum number of +milliseconds that the client is willing to wait to start receiving data. +The default value is 20000 (20 seconds). The content repository can and should +impose a maximum value for this parameter. The content repository can also +choose to respond before the timeout if it desires. + +If the media is available immediately (for example in the case of a +non-asynchronous upload), the content repository should ignore this parameter. + +If the MXC has expired, the content repository should respond with `M_NOT_FOUND` +and a HTTP 404 status code. + +If the data is not available when the server chooses to respond, the content +repository returns a `M_NOT_YET_UPLOADED` error with a HTTP 504 status code. + +For the `/download` endpoint, the server could also stream data directly as it +is being uploaded. However, streaming creates several implementation and spec +complications (e.g. how to stream if the media repo has multiple workers, what +to do if the upload is interrupted), so specifying exactly how streaming works +is left for another MSC. + +## Potential issues +Other clients may time out the download if the sender takes too long to upload +media. + +## Alternatives + +## Security considerations + +The primary attack vector that must be prevented is a malicious user creating a +large number of MXC URIs and sending them to a room without uploading the +corresponding media. Clients in that room would then attempt to download the +media, holding open connections to the server and potentially exhausting the +number of available connections. + +This attack vector is stopped in multiple ways: + +1. Limits on `/create` prevent users from creating MXC URIs too quickly and also + require them to finish uploading files (or let some of their MXCs expire) + before creating new MXC URIs. + +2. Servers are free to respond to `/download` and `/thumbnail` requests before + the `timeout_ms` has been reached and respond with `M_NOT_YET_UPLOADED`. For + example, if the server is under connection count pressure, it can choose to + respond to waiting download connections with `M_NOT_YET_UPLOADED` to free + connections in the pool. + +3. Once the media is expired, servers can respond immediately to `/download` and + `/thumbnail` requests with `M_NOT_FOUND`. + +## Future work + +Future MSCs might wish to address large file uploads. One approach would be to +add metadata to the `/create` call via a query parameter (for example +`?large_file_upload=true`. Servers would have the ability to impose restrictions +on how many such "large file" uploads a user can have concurrently. For such a +situation, the server would likely send a more generous `unused_expires_at` +timestamp to allow for a long-running upload. + +## Unstable prefix +While this MSC is not in a released version of the spec, implementations should +use `fi.mau.msc2246` as a prefix and as an `unstable_features` flag in the +`/versions` endpoint. + +* `POST /_matrix/media/unstable/fi.mau.msc2246/create` +* `PUT /_matrix/media/unstable/fi.mau.msc2246/upload/{serverName}/{mediaId}` +* `?fi.mau.msc2246.timeout_ms` +* `FI.MAU.MSC2246_NOT_YET_UPLOADED` +* `FI.MAU.MSC2246_CANNOT_OVERWRITE_MEDIA` + +[matrix-spec#432]: https://github.com/matrix-org/matrix-spec/issues/432 +[MSC701]: https://github.com/matrix-org/matrix-doc/issues/701