diff --git a/changelogs/client_server/newsfragments/1935.clarification b/changelogs/client_server/newsfragments/1935.clarification new file mode 100644 index 00000000..de90b5da --- /dev/null +++ b/changelogs/client_server/newsfragments/1935.clarification @@ -0,0 +1 @@ +Specify `Content-Type` and `Content-Disposition` usage in the media repo, as per [MSC2701](https://github.com/matrix-org/matrix-spec-proposals/pull/2701) and [MSC2702](https://github.com/matrix-org/matrix-spec-proposals/pull/2702). \ No newline at end of file diff --git a/content/client-server-api/modules/content_repo.md b/content/client-server-api/modules/content_repo.md index c9ac06d7..17715504 100644 --- a/content/client-server-api/modules/content_repo.md +++ b/content/client-server-api/modules/content_repo.md @@ -168,3 +168,50 @@ Homeservers have additional content-specific concerns: - Clients or remote homeservers may try to upload malicious files targeting vulnerabilities in either the homeserver thumbnailing or the client decoders. + +##### Serving inline content + +Clients with insecure configurations may be vulnerable to Cross-Site Scripting +attacks when served media with a `Content-Disposition` of `inline`. Clients +SHOULD NOT be hosted on the same domain as the media endpoints for the homeserver +to mitigate most of this risk. Servers SHOULD restrict `Content-Type` headers to +one of the following values when serving content with `Content-Disposition: inline`: + +* `text/css` +* `text/plain` +* `text/csv` +* `application/json` +* `application/ld+json` +* `image/jpeg` +* `image/gif` +* `image/png` +* `image/apng` +* `image/webp` +* `image/avif` +* `video/mp4` +* `video/webm` +* `video/ogg` +* `video/quicktime` +* `audio/mp4` +* `audio/webm` +* `audio/aac` +* `audio/mpeg` +* `audio/ogg` +* `audio/wave` +* `audio/wav` +* `audio/x-wav` +* `audio/x-pn-wav` +* `audio/flac` +* `audio/x-flac` + +These types are unlikely to cause Cross-Site Scripting issues when a `Content-Type` +header is provided, as clients will only try to render the data using that content +type. For example, if a HTML file is uploaded with a `Content-Type` of `image/png`, +clients will just assume that the image is corrupted, and won't render it as a +HTML page. Therefore, there is no risk in trusting the user-defined content type, +as long as the `Content-Disposition` is calculated based on that type. + +Clients SHOULD NOT rely on servers returning `inline` rather than `attachment` +on `/download`. Server implementations might decide out of an abundance of +caution that all downloads are responded to with `attachment`, regardless of +content type - clients should not be surprised by this behaviour. diff --git a/data/api/client-server/authed-content-repo.yaml b/data/api/client-server/authed-content-repo.yaml index 43cb5881..53416cf3 100644 --- a/data/api/client-server/authed-content-repo.yaml +++ b/data/api/client-server/authed-content-repo.yaml @@ -46,9 +46,29 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: The name of the file that was previously uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and SHOULD contain a file name. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + + If the upload was made with a `filename`, this header MUST + contain the same `filename`. Otherwise, `filename` is excluded + from the header. If the media being downloaded is remote, the + remote server's `filename` in the `Content-Disposition` header + is used as the `filename` instead. When the header is not + supplied, or does not supply a `filename`, the local download + response does not include a `filename`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -106,11 +126,21 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: |- - The `fileName` requested or the name of the file that was previously - uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and MUST contain the file name requested in the path. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -208,8 +238,24 @@ paths: "200": description: A thumbnail of the requested content. headers: + Content-Disposition: + x-addedInMatrixVersion: "1.12" + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. `thumbnail.png`). + + Servers should note the [Content-Type restrictions for serving inline content](/client-server-api/#serving-inline-content), + as these limitations imply which formats should be used for thumbnail generation. + required: true + schema: + type: string + example: "inline; filename=\"thumbnail.png\"" Content-Type: + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`. description: The content type of the thumbnail. + required: true schema: type: string enum: @@ -512,7 +558,29 @@ components: format: uri headers: downloadContentType: - description: The content type of the file that was previously uploaded. + description: | + The content type of the file that was previously uploaded. + + The server MUST return a `Content-Type` which is either exactly the same + as the original upload, or reasonably close. The bounds of "reasonable" + are: + + * Adding a charset to `text/*` content types. + * Detecting HTML and using `text/html` instead of `text/plain`. + * Using `application/octet-stream` when the server determines the + content type is obviously wrong. For example, an encrypted file being + claimed as `image/png`. + * Returning `application/octet-stream` when the media has an + unknown/unprovided `Content-Type`. For example, being uploaded before + the server tracked content types or when the remote server is + non-compliantly omitting the header entirely. + + Actions not in the spirit of the above are not considered "reasonable". + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`, + and the behaviour to compute its value was clarified. + required: true schema: type: string diff --git a/data/api/client-server/content-repo.yaml b/data/api/client-server/content-repo.yaml index 92ca6caa..f50850bd 100644 --- a/data/api/client-server/content-repo.yaml +++ b/data/api/client-server/content-repo.yaml @@ -248,9 +248,29 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: The name of the file that was previously uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and SHOULD contain a file name. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + + If the upload was made with a `filename`, this header MUST + contain the same `filename`. Otherwise, `filename` is excluded + from the header. If the media being downloaded is remote, the + remote server's `filename` in the `Content-Disposition` header + is used as the `filename` instead. When the header is not + supplied, or does not supply a `filename`, the local download + response does not include a `filename`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -309,11 +329,21 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: |- - The `fileName` requested or the name of the file that was previously - uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and MUST contain the file name requested in the path. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -412,8 +442,24 @@ paths: "200": description: A thumbnail of the requested content. headers: + Content-Disposition: + x-addedInMatrixVersion: "1.12" + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. `thumbnail.png`). + + Servers should note the [Content-Type restrictions for serving inline content](/client-server-api/#serving-inline-content), + as these limitations imply which formats should be used for thumbnail generation. + required: true + schema: + type: string + example: "inline; filename=\"thumbnail.png\"" Content-Type: + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`. description: The content type of the thumbnail. + required: true schema: type: string enum: @@ -639,7 +685,15 @@ components: contentType: in: header name: Content-Type - description: The content type of the file being uploaded + description: | + **Optional.** The content type of the file being uploaded. + + Clients SHOULD always supply this header. + + Defaults to `application/octet-stream` if it is not set. + x-changedInMatrixVersion: + "1.12": | + This header became explicitly optional with a default value. example: application/pdf schema: type: string @@ -782,7 +836,29 @@ components: format: uri headers: downloadContentType: - description: The content type of the file that was previously uploaded. + description: | + The content type of the file that was previously uploaded. + + The server MUST return a `Content-Type` which is either exactly the same + as the original upload, or reasonably close. The bounds of "reasonable" + are: + + * Adding a charset to `text/*` content types. + * Detecting HTML and using `text/html` instead of `text/plain`. + * Using `application/octet-stream` when the server determines the + content type is obviously wrong. For example, an encrypted file being + claimed as `image/png`. + * Returning `application/octet-stream` when the media has an + unknown/unprovided `Content-Type`. For example, being uploaded before + the server tracked content types or when the remote server is + non-compliantly omitting the header entirely. + + Actions not in the spirit of the above are not considered "reasonable". + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`, + and the behaviour to compute its value was clarified. + required: true schema: type: string