Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify Content-Type and Content-Disposition usage in the media repo #1935

Merged
merged 5 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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).
47 changes: 47 additions & 0 deletions content/client-server-api/modules/content_repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
78 changes: 73 additions & 5 deletions data/api/client-server/authed-content-repo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

88 changes: 82 additions & 6 deletions data/api/client-server/content-repo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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