Compare commits
32 commits
restricted
...
next
Author | SHA1 | Date | |
---|---|---|---|
|
3c93c81204 | ||
|
6767ca8bc8 | ||
|
f8d7ef04e6 | ||
|
892fb8846a | ||
|
bca8d1f70f | ||
|
65fe6b0ab5 | ||
|
fea85b0894 | ||
|
a7405cddc0 | ||
|
3df21e8257 | ||
|
e4d6202840 | ||
|
c4810a3a08 | ||
|
73d0536cd3 | ||
|
a6797ca0a2 | ||
|
cdd03dfec0 | ||
|
2bab8869d0 | ||
|
cbd3b07ca7 | ||
|
27d6d94355 | ||
|
a3716a7d5a | ||
|
a9c3867287 | ||
|
423b0928d5 | ||
|
44dd21f432 | ||
|
75a0f68349 | ||
|
8abab8c8a0 | ||
|
324e1beabf | ||
|
00c9ef7b56 | ||
|
6455e918be | ||
|
ea3e7045b4 | ||
|
b8a1b4fee5 | ||
|
d95345377b | ||
|
75322af8c7 | ||
|
11187b3fad | ||
|
35ed731a46 |
19 changed files with 668 additions and 235 deletions
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -487,7 +487,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "conduit"
|
name = "conduit"
|
||||||
version = "0.9.0-alpha"
|
version = "0.10.0-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum 0.7.5",
|
"axum 0.7.5",
|
||||||
|
@ -1203,9 +1203,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
|
@ -2232,7 +2232,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
@ -2253,7 +2253,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2265,7 +2265,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
|
@ -2288,7 +2288,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -2318,7 +2318,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
|
@ -2334,15 +2334,22 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"web-time",
|
||||||
"wildmatch",
|
"wildmatch",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http 1.1.0",
|
||||||
|
"httparse",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"rand",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"ruma-events",
|
"ruma-events",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2352,7 +2359,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -2361,7 +2368,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identity-service-api"
|
name = "ruma-identity-service-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2371,8 +2378,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -2386,7 +2394,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2398,7 +2406,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-server-util"
|
name = "ruma-server-util"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"headers",
|
"headers",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
|
@ -2411,7 +2419,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
@ -2427,7 +2435,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-state-res"
|
name = "ruma-state-res"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/ruma/ruma#fec2152d879a6c6c2bccce984d4b8f424f460cb2"
|
source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
|
|
@ -16,7 +16,7 @@ license = "Apache-2.0"
|
||||||
name = "conduit"
|
name = "conduit"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://gitlab.com/famedly/conduit"
|
repository = "https://gitlab.com/famedly/conduit"
|
||||||
version = "0.9.0-alpha"
|
version = "0.10.0-alpha"
|
||||||
|
|
||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
rust-version = "1.79.0"
|
rust-version = "1.79.0"
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
> **Note:** If you update the configuration file, you must restart Conduit for the changes to take effect
|
> **Note:** If you update the configuration file, you must restart Conduit for the changes to take effect
|
||||||
|
|
||||||
|
> **Note:** You can also configure Conduit by using `CONDUIT_{field_name}` environment variables. To set values inside a table, use `CONDUIT_{table_name}__{field_name}`. Example: `CONDUIT_SERVER_NAME="example.org"`
|
||||||
|
|
||||||
Conduit's configuration file is divided into the following sections:
|
Conduit's configuration file is divided into the following sections:
|
||||||
|
|
||||||
- [Global](#global)
|
- [Global](#global)
|
||||||
|
@ -56,7 +58,8 @@ The `global` section contains the following fields:
|
||||||
| `turn_secret` | `string` | The TURN secret | `""` |
|
| `turn_secret` | `string` | The TURN secret | `""` |
|
||||||
| `turn_ttl` | `integer` | The TURN TTL in seconds | `86400` |
|
| `turn_ttl` | `integer` | The TURN TTL in seconds | `86400` |
|
||||||
| `emergency_password` | `string` | Set a password to login as the `conduit` user in case of emergency | N/A |
|
| `emergency_password` | `string` | Set a password to login as the `conduit` user in case of emergency | N/A |
|
||||||
| `well_known` | `table` | Used for [delegation](delegation.md) | See [delegation](delegation.md) |
|
| `well_known_client` | `string` | Used for [delegation](delegation.md) | See [delegation](delegation.md) |
|
||||||
|
| `well_known_server` | `string` | Used for [delegation](delegation.md) | See [delegation](delegation.md) |
|
||||||
|
|
||||||
|
|
||||||
### TLS
|
### TLS
|
||||||
|
|
|
@ -16,18 +16,18 @@ are connected to the server running Conduit using something like a VPN.
|
||||||
|
|
||||||
> **Note**: this will automatically allow you to use [sliding sync][0] without any extra configuration
|
> **Note**: this will automatically allow you to use [sliding sync][0] without any extra configuration
|
||||||
|
|
||||||
To configure it, use the following options in the `global.well_known` table:
|
To configure it, use the following options:
|
||||||
| Field | Type | Description | Default |
|
| Field | Type | Description | Default |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `client` | `String` | The URL that clients should use to connect to Conduit | `https://<server_name>` |
|
| `well_known_client` | `String` | The URL that clients should use to connect to Conduit | `https://<server_name>` |
|
||||||
| `server` | `String` | The hostname and port servers should use to connect to Conduit | `<server_name>:443` |
|
| `well_known_server` | `String` | The hostname and port servers should use to connect to Conduit | `<server_name>:443` |
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[global.well_known]
|
[global]
|
||||||
client = "https://matrix.example.org"
|
well_known_client = "https://matrix.example.org"
|
||||||
server = "matrix.example.org:443"
|
well_known_server = "matrix.example.org:443"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual
|
## Manual
|
||||||
|
|
|
@ -64,6 +64,7 @@ docker run -d -p 8448:6167 \
|
||||||
-e CONDUIT_MAX_REQUEST_SIZE="20000000" \
|
-e CONDUIT_MAX_REQUEST_SIZE="20000000" \
|
||||||
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
||||||
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
||||||
|
-e CONDUIT_PORT="6167" \
|
||||||
--name conduit <link>
|
--name conduit <link>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## General instructions
|
## General instructions
|
||||||
|
|
||||||
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md).
|
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/element-hq/synapse/blob/develop/docs/turn-howto.md).
|
||||||
|
|
||||||
## Edit/Add a few settings to your existing conduit.toml
|
## Edit/Add a few settings to your existing conduit.toml
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,21 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
||||||
use ruma::api::client::{
|
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
|
||||||
error::ErrorKind,
|
use ruma::{
|
||||||
media::{
|
api::{
|
||||||
create_content, get_content, get_content_as_filename, get_content_thumbnail,
|
client::{
|
||||||
get_media_config,
|
authenticated_media::{
|
||||||
|
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
|
||||||
},
|
},
|
||||||
|
error::ErrorKind,
|
||||||
|
media::{self, create_content},
|
||||||
|
},
|
||||||
|
federation::authenticated_media::{self as federation_media, FileOrLocation},
|
||||||
|
},
|
||||||
|
http_headers::{ContentDisposition, ContentDispositionType},
|
||||||
|
media::Method,
|
||||||
|
ServerName, UInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MXC_LENGTH: usize = 32;
|
const MXC_LENGTH: usize = 32;
|
||||||
|
@ -18,9 +27,20 @@ const MXC_LENGTH: usize = 32;
|
||||||
///
|
///
|
||||||
/// Returns max upload size.
|
/// Returns max upload size.
|
||||||
pub async fn get_media_config_route(
|
pub async fn get_media_config_route(
|
||||||
_body: Ruma<get_media_config::v3::Request>,
|
_body: Ruma<media::get_media_config::v3::Request>,
|
||||||
) -> Result<get_media_config::v3::Response> {
|
) -> Result<media::get_media_config::v3::Response> {
|
||||||
Ok(get_media_config::v3::Response {
|
Ok(media::get_media_config::v3::Response {
|
||||||
|
upload_size: services().globals.max_request_size().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v1/media/config`
|
||||||
|
///
|
||||||
|
/// Returns max upload size.
|
||||||
|
pub async fn get_media_config_auth_route(
|
||||||
|
_body: Ruma<get_media_config::v1::Request>,
|
||||||
|
) -> Result<get_media_config::v1::Response> {
|
||||||
|
Ok(get_media_config::v1::Response {
|
||||||
upload_size: services().globals.max_request_size().into(),
|
upload_size: services().globals.max_request_size().into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -44,10 +64,10 @@ pub async fn create_content_route(
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
mxc.clone(),
|
mxc.clone(),
|
||||||
body.filename
|
Some(
|
||||||
.as_ref()
|
ContentDisposition::new(ContentDispositionType::Inline)
|
||||||
.map(|filename| "inline; filename=".to_owned() + filename)
|
.with_filename(body.filename.clone()),
|
||||||
.as_deref(),
|
),
|
||||||
body.content_type.as_deref(),
|
body.content_type.as_deref(),
|
||||||
&body.file,
|
&body.file,
|
||||||
)
|
)
|
||||||
|
@ -61,28 +81,67 @@ pub async fn create_content_route(
|
||||||
|
|
||||||
pub async fn get_remote_content(
|
pub async fn get_remote_content(
|
||||||
mxc: &str,
|
mxc: &str,
|
||||||
server_name: &ruma::ServerName,
|
server_name: &ServerName,
|
||||||
media_id: String,
|
media_id: String,
|
||||||
) -> Result<get_content::v3::Response, Error> {
|
) -> Result<get_content::v1::Response, Error> {
|
||||||
let content_response = services()
|
let content_response = match services()
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
server_name,
|
server_name,
|
||||||
get_content::v3::Request {
|
federation_media::get_content::v1::Request {
|
||||||
allow_remote: false,
|
media_id: media_id.clone(),
|
||||||
|
timeout_ms: Duration::from_secs(20),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(federation_media::get_content::v1::Response {
|
||||||
|
metadata: _,
|
||||||
|
content: FileOrLocation::File(content),
|
||||||
|
}) => get_content::v1::Response {
|
||||||
|
file: content.file,
|
||||||
|
content_type: content.content_type,
|
||||||
|
content_disposition: content.content_disposition,
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(federation_media::get_content::v1::Response {
|
||||||
|
metadata: _,
|
||||||
|
content: FileOrLocation::Location(url),
|
||||||
|
}) => get_location_content(url).await?,
|
||||||
|
Err(Error::BadRequest(ErrorKind::Unrecognized, _)) => {
|
||||||
|
let media::get_content::v3::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
|
..
|
||||||
|
} = services()
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
server_name,
|
||||||
|
media::get_content::v3::Request {
|
||||||
server_name: server_name.to_owned(),
|
server_name: server_name.to_owned(),
|
||||||
media_id,
|
media_id,
|
||||||
timeout_ms: Duration::from_secs(20),
|
timeout_ms: Duration::from_secs(20),
|
||||||
allow_redirect: false,
|
allow_remote: false,
|
||||||
|
allow_redirect: true,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
get_content::v1::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
services()
|
services()
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
mxc.to_owned(),
|
mxc.to_owned(),
|
||||||
content_response.content_disposition.as_deref(),
|
content_response.content_disposition.clone(),
|
||||||
content_response.content_type.as_deref(),
|
content_response.content_type.as_deref(),
|
||||||
&content_response.file,
|
&content_response.file,
|
||||||
)
|
)
|
||||||
|
@ -97,31 +156,57 @@ pub async fn get_remote_content(
|
||||||
///
|
///
|
||||||
/// - Only allows federation if `allow_remote` is true
|
/// - Only allows federation if `allow_remote` is true
|
||||||
pub async fn get_content_route(
|
pub async fn get_content_route(
|
||||||
body: Ruma<get_content::v3::Request>,
|
body: Ruma<media::get_content::v3::Request>,
|
||||||
) -> Result<get_content::v3::Response> {
|
) -> Result<media::get_content::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let get_content::v1::Response {
|
||||||
|
file,
|
||||||
if let Some(FileMeta {
|
|
||||||
content_disposition,
|
content_disposition,
|
||||||
content_type,
|
content_type,
|
||||||
file,
|
} = get_content(&body.server_name, body.media_id.clone(), body.allow_remote).await?;
|
||||||
}) = services().media.get(mxc.clone()).await?
|
|
||||||
{
|
Ok(media::get_content::v3::Response {
|
||||||
Ok(get_content::v3::Response {
|
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition,
|
content_disposition,
|
||||||
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
})
|
})
|
||||||
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
}
|
||||||
let remote_content_response =
|
|
||||||
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
|
||||||
|
|
||||||
Ok(get_content::v3::Response {
|
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}`
|
||||||
|
///
|
||||||
|
/// Load media from our server or over federation.
|
||||||
|
pub async fn get_content_auth_route(
|
||||||
|
body: Ruma<get_content::v1::Request>,
|
||||||
|
) -> Result<get_content::v1::Response> {
|
||||||
|
get_content(&body.server_name, body.media_id.clone(), true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_content(
|
||||||
|
server_name: &ServerName,
|
||||||
|
media_id: String,
|
||||||
|
allow_remote: bool,
|
||||||
|
) -> Result<get_content::v1::Response, Error> {
|
||||||
|
let mxc = format!("mxc://{}/{}", server_name, media_id);
|
||||||
|
|
||||||
|
if let Ok(Some(FileMeta {
|
||||||
|
content_disposition,
|
||||||
|
content_type,
|
||||||
|
file,
|
||||||
|
})) = services().media.get(mxc.clone()).await
|
||||||
|
{
|
||||||
|
Ok(get_content::v1::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: Some(content_disposition),
|
||||||
|
})
|
||||||
|
} else if server_name != services().globals.server_name() && allow_remote {
|
||||||
|
let remote_content_response =
|
||||||
|
get_remote_content(&mxc, server_name, media_id.clone()).await?;
|
||||||
|
|
||||||
|
Ok(get_content::v1::Response {
|
||||||
content_disposition: remote_content_response.content_disposition,
|
content_disposition: remote_content_response.content_disposition,
|
||||||
content_type: remote_content_response.content_type,
|
content_type: remote_content_response.content_type,
|
||||||
file: remote_content_response.file,
|
file: remote_content_response.file,
|
||||||
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
@ -134,29 +219,74 @@ pub async fn get_content_route(
|
||||||
///
|
///
|
||||||
/// - Only allows federation if `allow_remote` is true
|
/// - Only allows federation if `allow_remote` is true
|
||||||
pub async fn get_content_as_filename_route(
|
pub async fn get_content_as_filename_route(
|
||||||
body: Ruma<get_content_as_filename::v3::Request>,
|
body: Ruma<media::get_content_as_filename::v3::Request>,
|
||||||
) -> Result<get_content_as_filename::v3::Response> {
|
) -> Result<media::get_content_as_filename::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let get_content_as_filename::v1::Response {
|
||||||
|
|
||||||
if let Some(FileMeta {
|
|
||||||
file, content_type, ..
|
|
||||||
}) = services().media.get(mxc.clone()).await?
|
|
||||||
{
|
|
||||||
Ok(get_content_as_filename::v3::Response {
|
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition: Some(format!("inline; filename={}", body.filename)),
|
content_disposition,
|
||||||
|
} = get_content_as_filename(
|
||||||
|
&body.server_name,
|
||||||
|
body.media_id.clone(),
|
||||||
|
body.filename.clone(),
|
||||||
|
body.allow_remote,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(media::get_content_as_filename::v3::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
})
|
})
|
||||||
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
}
|
||||||
let remote_content_response =
|
|
||||||
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
|
||||||
|
|
||||||
Ok(get_content_as_filename::v3::Response {
|
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}/{fileName}`
|
||||||
content_disposition: Some(format!("inline: filename={}", body.filename)),
|
///
|
||||||
|
/// Load media from our server or over federation, permitting desired filename.
|
||||||
|
pub async fn get_content_as_filename_auth_route(
|
||||||
|
body: Ruma<get_content_as_filename::v1::Request>,
|
||||||
|
) -> Result<get_content_as_filename::v1::Response, Error> {
|
||||||
|
get_content_as_filename(
|
||||||
|
&body.server_name,
|
||||||
|
body.media_id.clone(),
|
||||||
|
body.filename.clone(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_content_as_filename(
|
||||||
|
server_name: &ServerName,
|
||||||
|
media_id: String,
|
||||||
|
filename: String,
|
||||||
|
allow_remote: bool,
|
||||||
|
) -> Result<get_content_as_filename::v1::Response, Error> {
|
||||||
|
let mxc = format!("mxc://{}/{}", server_name, media_id);
|
||||||
|
|
||||||
|
if let Ok(Some(FileMeta {
|
||||||
|
file, content_type, ..
|
||||||
|
})) = services().media.get(mxc.clone()).await
|
||||||
|
{
|
||||||
|
Ok(get_content_as_filename::v1::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: Some(
|
||||||
|
ContentDisposition::new(ContentDispositionType::Inline)
|
||||||
|
.with_filename(Some(filename.clone())),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} else if server_name != services().globals.server_name() && allow_remote {
|
||||||
|
let remote_content_response =
|
||||||
|
get_remote_content(&mxc, server_name, media_id.clone()).await?;
|
||||||
|
|
||||||
|
Ok(get_content_as_filename::v1::Response {
|
||||||
|
content_disposition: Some(
|
||||||
|
ContentDisposition::new(ContentDispositionType::Inline)
|
||||||
|
.with_filename(Some(filename.clone())),
|
||||||
|
),
|
||||||
content_type: remote_content_response.content_type,
|
content_type: remote_content_response.content_type,
|
||||||
file: remote_content_response.file,
|
file: remote_content_response.file,
|
||||||
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
@ -169,63 +299,169 @@ pub async fn get_content_as_filename_route(
|
||||||
///
|
///
|
||||||
/// - Only allows federation if `allow_remote` is true
|
/// - Only allows federation if `allow_remote` is true
|
||||||
pub async fn get_content_thumbnail_route(
|
pub async fn get_content_thumbnail_route(
|
||||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
body: Ruma<media::get_content_thumbnail::v3::Request>,
|
||||||
) -> Result<get_content_thumbnail::v3::Response> {
|
) -> Result<media::get_content_thumbnail::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let get_content_thumbnail::v1::Response { file, content_type } = get_content_thumbnail(
|
||||||
|
&body.server_name,
|
||||||
if let Some(FileMeta {
|
body.media_id.clone(),
|
||||||
file, content_type, ..
|
body.height,
|
||||||
}) = services()
|
body.width,
|
||||||
.media
|
body.method.clone(),
|
||||||
.get_thumbnail(
|
body.animated,
|
||||||
mxc.clone(),
|
body.allow_remote,
|
||||||
body.width
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
|
||||||
body.height
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
{
|
|
||||||
Ok(get_content_thumbnail::v3::Response {
|
Ok(media::get_content_thumbnail::v3::Response {
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
})
|
})
|
||||||
} else if body.server_name != services().globals.server_name() && body.allow_remote {
|
}
|
||||||
let get_thumbnail_response = services()
|
|
||||||
|
/// # `GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`
|
||||||
|
///
|
||||||
|
/// Load media thumbnail from our server or over federation.
|
||||||
|
pub async fn get_content_thumbnail_auth_route(
|
||||||
|
body: Ruma<get_content_thumbnail::v1::Request>,
|
||||||
|
) -> Result<get_content_thumbnail::v1::Response> {
|
||||||
|
get_content_thumbnail(
|
||||||
|
&body.server_name,
|
||||||
|
body.media_id.clone(),
|
||||||
|
body.height,
|
||||||
|
body.width,
|
||||||
|
body.method.clone(),
|
||||||
|
body.animated,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_content_thumbnail(
|
||||||
|
server_name: &ServerName,
|
||||||
|
media_id: String,
|
||||||
|
height: UInt,
|
||||||
|
width: UInt,
|
||||||
|
method: Option<Method>,
|
||||||
|
animated: Option<bool>,
|
||||||
|
allow_remote: bool,
|
||||||
|
) -> Result<get_content_thumbnail::v1::Response, Error> {
|
||||||
|
let mxc = format!("mxc://{}/{}", server_name, media_id);
|
||||||
|
|
||||||
|
if let Ok(Some(FileMeta {
|
||||||
|
file, content_type, ..
|
||||||
|
})) = services()
|
||||||
|
.media
|
||||||
|
.get_thumbnail(
|
||||||
|
mxc.clone(),
|
||||||
|
width
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||||
|
height
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(get_content_thumbnail::v1::Response { file, content_type })
|
||||||
|
} else if server_name != services().globals.server_name() && allow_remote {
|
||||||
|
let thumbnail_response = match services()
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
&body.server_name,
|
server_name,
|
||||||
get_content_thumbnail::v3::Request {
|
federation_media::get_content_thumbnail::v1::Request {
|
||||||
allow_remote: false,
|
height,
|
||||||
height: body.height,
|
width,
|
||||||
width: body.width,
|
method: method.clone(),
|
||||||
method: body.method.clone(),
|
media_id: media_id.clone(),
|
||||||
server_name: body.server_name.clone(),
|
timeout_ms: Duration::from_secs(20),
|
||||||
media_id: body.media_id.clone(),
|
animated,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(federation_media::get_content_thumbnail::v1::Response {
|
||||||
|
metadata: _,
|
||||||
|
content: FileOrLocation::File(content),
|
||||||
|
}) => get_content_thumbnail::v1::Response {
|
||||||
|
file: content.file,
|
||||||
|
content_type: content.content_type,
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(federation_media::get_content_thumbnail::v1::Response {
|
||||||
|
metadata: _,
|
||||||
|
content: FileOrLocation::Location(url),
|
||||||
|
}) => {
|
||||||
|
let get_content::v1::Response {
|
||||||
|
file, content_type, ..
|
||||||
|
} = get_location_content(url).await?;
|
||||||
|
|
||||||
|
get_content_thumbnail::v1::Response { file, content_type }
|
||||||
|
}
|
||||||
|
Err(Error::BadRequest(ErrorKind::Unrecognized, _)) => {
|
||||||
|
let media::get_content_thumbnail::v3::Response {
|
||||||
|
file, content_type, ..
|
||||||
|
} = services()
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
server_name,
|
||||||
|
media::get_content_thumbnail::v3::Request {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
method: method.clone(),
|
||||||
|
server_name: server_name.to_owned(),
|
||||||
|
media_id: media_id.clone(),
|
||||||
timeout_ms: Duration::from_secs(20),
|
timeout_ms: Duration::from_secs(20),
|
||||||
allow_redirect: false,
|
allow_redirect: false,
|
||||||
animated: body.animated,
|
animated,
|
||||||
|
allow_remote: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
get_content_thumbnail::v1::Response { file, content_type }
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
services()
|
services()
|
||||||
.media
|
.media
|
||||||
.upload_thumbnail(
|
.upload_thumbnail(
|
||||||
mxc,
|
mxc,
|
||||||
None,
|
thumbnail_response.content_type.as_deref(),
|
||||||
get_thumbnail_response.content_type.as_deref(),
|
width.try_into().expect("all UInts are valid u32s"),
|
||||||
body.width.try_into().expect("all UInts are valid u32s"),
|
height.try_into().expect("all UInts are valid u32s"),
|
||||||
body.height.try_into().expect("all UInts are valid u32s"),
|
&thumbnail_response.file,
|
||||||
&get_thumbnail_response.file,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(get_thumbnail_response)
|
Ok(thumbnail_response)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_location_content(url: String) -> Result<get_content::v1::Response, Error> {
|
||||||
|
let client = services().globals.default_client();
|
||||||
|
let response = client.get(url).send().await?;
|
||||||
|
let headers = response.headers();
|
||||||
|
|
||||||
|
let content_type = headers
|
||||||
|
.get(CONTENT_TYPE)
|
||||||
|
.and_then(|header| header.to_str().ok())
|
||||||
|
.map(ToOwned::to_owned);
|
||||||
|
|
||||||
|
let content_disposition = headers
|
||||||
|
.get(CONTENT_DISPOSITION)
|
||||||
|
.map(|header| header.as_bytes())
|
||||||
|
.map(TryFrom::try_from)
|
||||||
|
.and_then(Result::ok);
|
||||||
|
|
||||||
|
let file = response.bytes().await?.to_vec();
|
||||||
|
|
||||||
|
Ok(get_content::v1::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub async fn join_room_by_id_or_alias_route(
|
||||||
|
|
||||||
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
|
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
|
||||||
Ok(room_id) => {
|
Ok(room_id) => {
|
||||||
let mut servers = body.server_name.clone();
|
let mut servers = body.via.clone();
|
||||||
servers.extend(
|
servers.extend(
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -627,7 +627,7 @@ async fn join_room_by_id_helper(
|
||||||
let event_id = format!(
|
let event_id = format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&join_event_stub, &room_version_id)
|
ruma::signatures::reference_hash(&join_event_stub, &room_version_id)
|
||||||
.expect("ruma can calculate reference hashes")
|
.expect("Event format validated when event was hashed")
|
||||||
);
|
);
|
||||||
let event_id = <&EventId>::try_from(event_id.as_str())
|
let event_id = <&EventId>::try_from(event_id.as_str())
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
@ -1145,7 +1145,7 @@ async fn validate_and_add_event_id(
|
||||||
let event_id = EventId::parse(format!(
|
let event_id = EventId::parse(format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&value, room_version)
|
ruma::signatures::reference_hash(&value, room_version)
|
||||||
.expect("ruma can calculate reference hashes")
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid PDU format"))?
|
||||||
))
|
))
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
||||||
|
@ -1614,7 +1614,7 @@ async fn remote_leave_room(user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
||||||
let event_id = EventId::parse(format!(
|
let event_id = EventId::parse(format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&leave_event_stub, &room_version_id)
|
ruma::signatures::reference_hash(&leave_event_stub, &room_version_id)
|
||||||
.expect("ruma can calculate reference hashes")
|
.expect("Event format validated when event was hashed")
|
||||||
))
|
))
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ pub async fn get_supported_versions_route(
|
||||||
"v1.4".to_owned(),
|
"v1.4".to_owned(),
|
||||||
"v1.5".to_owned(),
|
"v1.5".to_owned(),
|
||||||
],
|
],
|
||||||
unstable_features: BTreeMap::from_iter([("org.matrix.e2e_cross_signing".to_owned(), true)]),
|
unstable_features: BTreeMap::from_iter([
|
||||||
|
("org.matrix.e2e_cross_signing".to_owned(), true),
|
||||||
|
("org.matrix.msc3916.stable".to_owned(), true),
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||||
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
service::{
|
||||||
|
globals::SigningKeys,
|
||||||
|
media::FileMeta,
|
||||||
|
pdu::{gen_event_id_canonical_json, PduBuilder},
|
||||||
|
},
|
||||||
services, utils, Error, PduEvent, Result, Ruma,
|
services, utils, Error, PduEvent, Result, Ruma,
|
||||||
};
|
};
|
||||||
use axum::{response::IntoResponse, Json};
|
use axum::{response::IntoResponse, Json};
|
||||||
use axum_extra::headers::{authorization::Credentials, CacheControl, Header};
|
use axum_extra::headers::{CacheControl, Header};
|
||||||
use get_profile_information::v1::ProfileField;
|
use get_profile_information::v1::ProfileField;
|
||||||
use http::header::AUTHORIZATION;
|
use http::header::AUTHORIZATION;
|
||||||
|
|
||||||
|
@ -14,6 +18,9 @@ use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::error::{Error as RumaError, ErrorKind},
|
client::error::{Error as RumaError, ErrorKind},
|
||||||
federation::{
|
federation::{
|
||||||
|
authenticated_media::{
|
||||||
|
get_content, get_content_thumbnail, Content, ContentMetadata, FileOrLocation,
|
||||||
|
},
|
||||||
authorization::get_event_authorization,
|
authorization::get_event_authorization,
|
||||||
backfill::get_backfill,
|
backfill::get_backfill,
|
||||||
device::get_devices::{self, v1::UserDevice},
|
device::get_devices::{self, v1::UserDevice},
|
||||||
|
@ -45,7 +52,6 @@ use ruma::{
|
||||||
StateEventType, TimelineEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
serde::{Base64, JsonObject, Raw},
|
serde::{Base64, JsonObject, Raw},
|
||||||
server_util::authorization::XMatrix,
|
|
||||||
to_device::DeviceIdOrAllDevices,
|
to_device::DeviceIdOrAllDevices,
|
||||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||||
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId,
|
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId,
|
||||||
|
@ -204,7 +210,7 @@ where
|
||||||
.try_into_http_request::<Vec<u8>>(
|
.try_into_http_request::<Vec<u8>>(
|
||||||
&actual_destination_str,
|
&actual_destination_str,
|
||||||
SendAccessToken::IfRequired(""),
|
SendAccessToken::IfRequired(""),
|
||||||
&[MatrixVersion::V1_4],
|
&[MatrixVersion::V1_11],
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!(
|
warn!(
|
||||||
|
@ -268,15 +274,15 @@ where
|
||||||
for s in signature_server {
|
for s in signature_server {
|
||||||
http_request.headers_mut().insert(
|
http_request.headers_mut().insert(
|
||||||
AUTHORIZATION,
|
AUTHORIZATION,
|
||||||
XMatrix::parse(&format!(
|
format!(
|
||||||
"X-Matrix origin=\"{}\",destination=\"{}\",key=\"{}\",sig=\"{}\"",
|
"X-Matrix origin=\"{}\",destination=\"{}\",key=\"{}\",sig=\"{}\"",
|
||||||
services().globals.server_name(),
|
services().globals.server_name(),
|
||||||
destination,
|
destination,
|
||||||
s.0,
|
s.0,
|
||||||
s.1
|
s.1
|
||||||
))
|
)
|
||||||
.expect("When Ruma signs JSON, it produces a valid base64 signature. All other types are valid ServerNames or OwnedKeyId")
|
.try_into()
|
||||||
.encode(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,7 +342,7 @@ where
|
||||||
|
|
||||||
response.map_err(|e| {
|
response.map_err(|e| {
|
||||||
warn!(
|
warn!(
|
||||||
"Invalid 200 response from {} on: {} {}",
|
"Invalid 200 response from {} on: {} {:?}",
|
||||||
&destination, url, e
|
&destination, url, e
|
||||||
);
|
);
|
||||||
Error::BadServerResponse("Server returned bad 200 response.")
|
Error::BadServerResponse("Server returned bad 200 response.")
|
||||||
|
@ -564,6 +570,13 @@ async fn get_srv_destination(delegated_hostname: String) -> (FedDest, Option<Ins
|
||||||
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
|
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Removing in case there was previously a SRV record
|
||||||
|
services()
|
||||||
|
.globals
|
||||||
|
.tls_name_override
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&delegated_hostname);
|
||||||
warn!("Using SRV record, but could not resolve to IP");
|
warn!("Using SRV record, but could not resolve to IP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,6 +589,13 @@ async fn get_srv_destination(delegated_hostname: String) -> (FedDest, Option<Ins
|
||||||
(add_port_to_hostname(&delegated_hostname), Some(timestamp))
|
(add_port_to_hostname(&delegated_hostname), Some(timestamp))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Removing in case there was previously a SRV record
|
||||||
|
services()
|
||||||
|
.globals
|
||||||
|
.tls_name_override
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&delegated_hostname);
|
||||||
debug!("No SRV records found");
|
debug!("No SRV records found");
|
||||||
(add_port_to_hostname(&delegated_hostname), None)
|
(add_port_to_hostname(&delegated_hostname), None)
|
||||||
}
|
}
|
||||||
|
@ -793,17 +813,78 @@ pub fn parse_incoming_pdu(
|
||||||
|
|
||||||
let (event_id, value) = match gen_event_id_canonical_json(pdu, &room_version_id) {
|
let (event_id, value) = match gen_event_id_canonical_json(pdu, &room_version_id) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
// Event could not be converted to canonical json
|
// Event could not be converted to canonical json
|
||||||
return Err(Error::BadRequest(
|
return Err(e);
|
||||||
ErrorKind::InvalidParam,
|
|
||||||
"Could not convert event to canonical json.",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((event_id, value, room_id))
|
Ok((event_id, value, room_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to parse and append PDU to timeline.
|
||||||
|
/// If no event ID is returned, then the PDU was failed to be parsed.
|
||||||
|
/// If the Ok(()) is returned, then the PDU was successfully appended to the timeline.
|
||||||
|
async fn handle_pdu_in_transaction(
|
||||||
|
origin: &ServerName,
|
||||||
|
pub_key_map: &RwLock<BTreeMap<String, SigningKeys>>,
|
||||||
|
pdu: &RawJsonValue,
|
||||||
|
) -> (Option<OwnedEventId>, Result<()>) {
|
||||||
|
let (event_id, value, room_id) = match parse_incoming_pdu(pdu) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Could not parse PDU: {e}");
|
||||||
|
warn!("Full PDU: {:?}", &pdu);
|
||||||
|
return (None, Err(Error::BadServerResponse("Could not parse PDU")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Makes use of the m.room.create event. If we cannot fetch this event,
|
||||||
|
// we must have never been in that room.
|
||||||
|
if services().rooms.state.get_room_version(&room_id).is_err() {
|
||||||
|
debug!("Room {room_id} is not known to this server");
|
||||||
|
return (
|
||||||
|
Some(event_id),
|
||||||
|
Err(Error::BadServerResponse("Room is not known to this server")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||||
|
|
||||||
|
let mutex = Arc::clone(
|
||||||
|
services()
|
||||||
|
.globals
|
||||||
|
.roomid_mutex_federation
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.entry(room_id.to_owned())
|
||||||
|
.or_default(),
|
||||||
|
);
|
||||||
|
let mutex_lock = mutex.lock().await;
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
if let Err(e) = services()
|
||||||
|
.rooms
|
||||||
|
.event_handler
|
||||||
|
.handle_incoming_pdu(origin, &event_id, &room_id, value, true, pub_key_map)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("Error appending PDU to timeline: {}: {:?}", e, pdu);
|
||||||
|
return (Some(event_id), Err(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(mutex_lock);
|
||||||
|
|
||||||
|
let elapsed = start_time.elapsed();
|
||||||
|
debug!(
|
||||||
|
"Handling transaction of event {} took {}m{}s",
|
||||||
|
event_id,
|
||||||
|
elapsed.as_secs() / 60,
|
||||||
|
elapsed.as_secs() % 60
|
||||||
|
);
|
||||||
|
|
||||||
|
(Some(event_id), Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
/// # `PUT /_matrix/federation/v1/send/{txnId}`
|
/// # `PUT /_matrix/federation/v1/send/{txnId}`
|
||||||
///
|
///
|
||||||
/// Push EDUs and PDUs to this server.
|
/// Push EDUs and PDUs to this server.
|
||||||
|
@ -828,77 +909,11 @@ pub async fn send_transaction_message_route(
|
||||||
// let mut auth_cache = EventMap::new();
|
// let mut auth_cache = EventMap::new();
|
||||||
|
|
||||||
for pdu in &body.pdus {
|
for pdu in &body.pdus {
|
||||||
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
|
let (event_id, result) =
|
||||||
warn!("Error parsing incoming event {:?}: {:?}", pdu, e);
|
handle_pdu_in_transaction(sender_servername, &pub_key_map, pdu).await;
|
||||||
Error::BadServerResponse("Invalid PDU in server response")
|
|
||||||
})?;
|
|
||||||
let room_id: OwnedRoomId = value
|
|
||||||
.get("room_id")
|
|
||||||
.and_then(|id| RoomId::parse(id.as_str()?).ok())
|
|
||||||
.ok_or(Error::BadRequest(
|
|
||||||
ErrorKind::InvalidParam,
|
|
||||||
"Invalid room id in pdu",
|
|
||||||
))?;
|
|
||||||
|
|
||||||
if services().rooms.state.get_room_version(&room_id).is_err() {
|
if let Some(event_id) = event_id {
|
||||||
debug!("Server is not in room {room_id}");
|
resolved_map.insert(event_id.clone(), result.map_err(|e| e.sanitized_error()));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = parse_incoming_pdu(pdu);
|
|
||||||
let (event_id, value, room_id) = match r {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Could not parse PDU: {e}");
|
|
||||||
warn!("Full PDU: {:?}", &pdu);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
|
||||||
|
|
||||||
let mutex = Arc::clone(
|
|
||||||
services()
|
|
||||||
.globals
|
|
||||||
.roomid_mutex_federation
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.entry(room_id.to_owned())
|
|
||||||
.or_default(),
|
|
||||||
);
|
|
||||||
let mutex_lock = mutex.lock().await;
|
|
||||||
let start_time = Instant::now();
|
|
||||||
resolved_map.insert(
|
|
||||||
event_id.clone(),
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.event_handler
|
|
||||||
.handle_incoming_pdu(
|
|
||||||
sender_servername,
|
|
||||||
&event_id,
|
|
||||||
&room_id,
|
|
||||||
value,
|
|
||||||
true,
|
|
||||||
&pub_key_map,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|_| ()),
|
|
||||||
);
|
|
||||||
drop(mutex_lock);
|
|
||||||
|
|
||||||
let elapsed = start_time.elapsed();
|
|
||||||
debug!(
|
|
||||||
"Handling transaction of event {} took {}m{}s",
|
|
||||||
event_id,
|
|
||||||
elapsed.as_secs() / 60,
|
|
||||||
elapsed.as_secs() % 60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for pdu in &resolved_map {
|
|
||||||
if let Err(e) = pdu.1 {
|
|
||||||
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
|
|
||||||
warn!("Incoming PDU failed {:?}", pdu);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,12 +1082,7 @@ pub async fn send_transaction_message_route(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(send_transaction_message::v1::Response {
|
Ok(send_transaction_message::v1::Response { pdus: resolved_map })
|
||||||
pdus: resolved_map
|
|
||||||
.into_iter()
|
|
||||||
.map(|(e, r)| (e, r.map_err(|e| e.sanitized_error())))
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/federation/v1/event/{eventId}`
|
/// # `GET /_matrix/federation/v1/event/{eventId}`
|
||||||
|
@ -1815,7 +1825,7 @@ pub async fn create_invite_route(
|
||||||
let event_id = EventId::parse(format!(
|
let event_id = EventId::parse(format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&signed_event, &body.room_version)
|
ruma::signatures::reference_hash(&signed_event, &body.room_version)
|
||||||
.expect("ruma can calculate reference hashes")
|
.expect("Event format validated when event was hashed")
|
||||||
))
|
))
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
||||||
|
@ -1884,6 +1894,90 @@ pub async fn create_invite_route(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/federation/v1/media/download/{serverName}/{mediaId}`
|
||||||
|
///
|
||||||
|
/// Load media from our server.
|
||||||
|
pub async fn get_content_route(
|
||||||
|
body: Ruma<get_content::v1::Request>,
|
||||||
|
) -> Result<get_content::v1::Response> {
|
||||||
|
let mxc = format!(
|
||||||
|
"mxc://{}/{}",
|
||||||
|
services().globals.server_name(),
|
||||||
|
body.media_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(FileMeta {
|
||||||
|
content_disposition,
|
||||||
|
content_type,
|
||||||
|
file,
|
||||||
|
}) = services().media.get(mxc.clone()).await?
|
||||||
|
{
|
||||||
|
Ok(get_content::v1::Response::new(
|
||||||
|
ContentMetadata::new(),
|
||||||
|
FileOrLocation::File(Content {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: Some(content_disposition),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/federation/v1/media/thumbnail/{serverName}/{mediaId}`
|
||||||
|
///
|
||||||
|
/// Load media thumbnail from our server or over federation.
|
||||||
|
pub async fn get_content_thumbnail_route(
|
||||||
|
body: Ruma<get_content_thumbnail::v1::Request>,
|
||||||
|
) -> Result<get_content_thumbnail::v1::Response> {
|
||||||
|
let mxc = format!(
|
||||||
|
"mxc://{}/{}",
|
||||||
|
services().globals.server_name(),
|
||||||
|
body.media_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let Some(FileMeta {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
|
}) = services()
|
||||||
|
.media
|
||||||
|
.get_thumbnail(
|
||||||
|
mxc.clone(),
|
||||||
|
body.width
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||||
|
body.height
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
|
||||||
|
};
|
||||||
|
|
||||||
|
services()
|
||||||
|
.media
|
||||||
|
.upload_thumbnail(
|
||||||
|
mxc,
|
||||||
|
content_type.as_deref(),
|
||||||
|
body.width.try_into().expect("all UInts are valid u32s"),
|
||||||
|
body.height.try_into().expect("all UInts are valid u32s"),
|
||||||
|
&file,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(get_content_thumbnail::v1::Response::new(
|
||||||
|
ContentMetadata::new(),
|
||||||
|
FileOrLocation::File(Content {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: Some(content_disposition),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/federation/v1/user/devices/{userId}`
|
/// # `GET /_matrix/federation/v1/user/devices/{userId}`
|
||||||
///
|
///
|
||||||
/// Gets information on all devices of the user.
|
/// Gets information on all devices of the user.
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub struct Config {
|
||||||
pub allow_unstable_room_versions: bool,
|
pub allow_unstable_room_versions: bool,
|
||||||
#[serde(default = "default_default_room_version")]
|
#[serde(default = "default_default_room_version")]
|
||||||
pub default_room_version: RoomVersionId,
|
pub default_room_version: RoomVersionId,
|
||||||
#[serde(default)]
|
#[serde(default, flatten)]
|
||||||
pub well_known: WellKnownConfig,
|
pub well_known: WellKnownConfig,
|
||||||
#[serde(default = "false_fn")]
|
#[serde(default = "false_fn")]
|
||||||
pub allow_jaeger: bool,
|
pub allow_jaeger: bool,
|
||||||
|
@ -97,7 +97,9 @@ pub struct TlsConfig {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
pub struct WellKnownConfig {
|
pub struct WellKnownConfig {
|
||||||
|
#[serde(rename = "well_known_client")]
|
||||||
pub client: Option<Url>,
|
pub client: Option<Url>,
|
||||||
|
#[serde(rename = "well_known_server")]
|
||||||
pub server: Option<OwnedServerName>,
|
pub server: Option<OwnedServerName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ruma::api::client::error::ErrorKind;
|
use ruma::{api::client::error::ErrorKind, http_headers::ContentDisposition};
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, utils, Error, Result};
|
use crate::{database::KeyValueDatabase, service, utils, Error, Result};
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ impl service::media::Data for KeyValueDatabase {
|
||||||
mxc: String,
|
mxc: String,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
content_disposition: Option<&str>,
|
content_disposition: &ContentDisposition,
|
||||||
content_type: Option<&str>,
|
content_type: Option<&str>,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let mut key = mxc.as_bytes().to_vec();
|
let mut key = mxc.as_bytes().to_vec();
|
||||||
|
@ -16,12 +16,7 @@ impl service::media::Data for KeyValueDatabase {
|
||||||
key.extend_from_slice(&width.to_be_bytes());
|
key.extend_from_slice(&width.to_be_bytes());
|
||||||
key.extend_from_slice(&height.to_be_bytes());
|
key.extend_from_slice(&height.to_be_bytes());
|
||||||
key.push(0xff);
|
key.push(0xff);
|
||||||
key.extend_from_slice(
|
key.extend_from_slice(content_disposition.to_string().as_bytes());
|
||||||
content_disposition
|
|
||||||
.as_ref()
|
|
||||||
.map(|f| f.as_bytes())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
key.push(0xff);
|
key.push(0xff);
|
||||||
key.extend_from_slice(
|
key.extend_from_slice(
|
||||||
content_type
|
content_type
|
||||||
|
@ -40,7 +35,7 @@ impl service::media::Data for KeyValueDatabase {
|
||||||
mxc: String,
|
mxc: String,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<(Option<String>, Option<String>, Vec<u8>)> {
|
) -> Result<(ContentDisposition, Option<String>, Vec<u8>)> {
|
||||||
let mut prefix = mxc.as_bytes().to_vec();
|
let mut prefix = mxc.as_bytes().to_vec();
|
||||||
prefix.push(0xff);
|
prefix.push(0xff);
|
||||||
prefix.extend_from_slice(&width.to_be_bytes());
|
prefix.extend_from_slice(&width.to_be_bytes());
|
||||||
|
@ -68,15 +63,9 @@ impl service::media::Data for KeyValueDatabase {
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
||||||
|
|
||||||
let content_disposition = if content_disposition_bytes.is_empty() {
|
let content_disposition = content_disposition_bytes.try_into().unwrap_or_else(|_| {
|
||||||
None
|
ContentDisposition::new(ruma::http_headers::ContentDispositionType::Inline)
|
||||||
} else {
|
});
|
||||||
Some(
|
|
||||||
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
|
|
||||||
Error::bad_database("Content Disposition in mediaid_file is invalid unicode.")
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
Ok((content_disposition, content_type, key))
|
Ok((content_disposition, content_type, key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
SERVICES,
|
SERVICES,
|
||||||
};
|
};
|
||||||
use abstraction::{KeyValueDatabaseEngine, KvTree};
|
use abstraction::{KeyValueDatabaseEngine, KvTree};
|
||||||
|
use base64::{engine::general_purpose, Engine};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
|
|
||||||
|
@ -424,7 +425,7 @@ impl KeyValueDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the database has any data, perform data migrations before starting
|
// If the database has any data, perform data migrations before starting
|
||||||
let latest_database_version = 13;
|
let latest_database_version = 16;
|
||||||
|
|
||||||
if services().users.count()? > 0 {
|
if services().users.count()? > 0 {
|
||||||
// MIGRATIONS
|
// MIGRATIONS
|
||||||
|
@ -941,6 +942,86 @@ impl KeyValueDatabase {
|
||||||
warn!("Migration: 12 -> 13 finished");
|
warn!("Migration: 12 -> 13 finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if services().globals.database_version()? < 16 {
|
||||||
|
// Reconstruct all media using the filesystem
|
||||||
|
db.mediaid_file.clear().unwrap();
|
||||||
|
|
||||||
|
for file in fs::read_dir(services().globals.get_media_folder()).unwrap() {
|
||||||
|
let file = file.unwrap();
|
||||||
|
let mediaid = general_purpose::URL_SAFE_NO_PAD
|
||||||
|
.decode(file.file_name().into_string().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut parts = mediaid.rsplit(|&b| b == 0xff);
|
||||||
|
|
||||||
|
let mut removed_bytes = 0;
|
||||||
|
|
||||||
|
let content_type_bytes = parts.next().unwrap();
|
||||||
|
removed_bytes += content_type_bytes.len() + 1;
|
||||||
|
|
||||||
|
let content_disposition_bytes = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
|
||||||
|
removed_bytes += content_disposition_bytes.len();
|
||||||
|
|
||||||
|
let mut content_disposition =
|
||||||
|
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
|
||||||
|
Error::bad_database("Content Disposition in mediaid_file is invalid.")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if content_disposition.contains("filename=")
|
||||||
|
&& !content_disposition.contains("filename=\"")
|
||||||
|
{
|
||||||
|
println!("{}", &content_disposition);
|
||||||
|
content_disposition =
|
||||||
|
content_disposition.replacen("filename=", "filename=\"", 1);
|
||||||
|
content_disposition.push('"');
|
||||||
|
println!("{}", &content_disposition);
|
||||||
|
|
||||||
|
let mut new_key = mediaid[..(mediaid.len() - removed_bytes)].to_vec();
|
||||||
|
assert!(*new_key.last().unwrap() == 0xff);
|
||||||
|
|
||||||
|
let mut shorter_key = new_key.clone();
|
||||||
|
shorter_key.extend(
|
||||||
|
ruma::http_headers::ContentDisposition::new(
|
||||||
|
ruma::http_headers::ContentDispositionType::Inline,
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
shorter_key.push(0xff);
|
||||||
|
shorter_key.extend_from_slice(content_type_bytes);
|
||||||
|
|
||||||
|
new_key.extend_from_slice(content_disposition.to_string().as_bytes());
|
||||||
|
new_key.push(0xff);
|
||||||
|
new_key.extend_from_slice(content_type_bytes);
|
||||||
|
|
||||||
|
// Some file names are too long. Ignore those.
|
||||||
|
match fs::rename(
|
||||||
|
services().globals.get_media_file(&mediaid),
|
||||||
|
services().globals.get_media_file(&new_key),
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
db.mediaid_file.insert(&new_key, &[])?;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
fs::rename(
|
||||||
|
services().globals.get_media_file(&mediaid),
|
||||||
|
services().globals.get_media_file(&shorter_key),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
db.mediaid_file.insert(&shorter_key, &[])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
db.mediaid_file.insert(&mediaid, &[])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
services().globals.bump_database_version(16)?;
|
||||||
|
|
||||||
|
warn!("Migration: 13 -> 16 finished");
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
services().globals.database_version().unwrap(),
|
services().globals.database_version().unwrap(),
|
||||||
latest_database_version
|
latest_database_version
|
||||||
|
|
|
@ -57,7 +57,7 @@ async fn main() {
|
||||||
))
|
))
|
||||||
.nested(),
|
.nested(),
|
||||||
)
|
)
|
||||||
.merge(Env::prefixed("CONDUIT_").global());
|
.merge(Env::prefixed("CONDUIT_").global().split("__"));
|
||||||
|
|
||||||
let config = match raw_config.extract::<Config>() {
|
let config = match raw_config.extract::<Config>() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
@ -379,10 +379,14 @@ fn routes(config: &Config) -> Router {
|
||||||
.ruma_route(client_server::turn_server_route)
|
.ruma_route(client_server::turn_server_route)
|
||||||
.ruma_route(client_server::send_event_to_device_route)
|
.ruma_route(client_server::send_event_to_device_route)
|
||||||
.ruma_route(client_server::get_media_config_route)
|
.ruma_route(client_server::get_media_config_route)
|
||||||
|
.ruma_route(client_server::get_media_config_auth_route)
|
||||||
.ruma_route(client_server::create_content_route)
|
.ruma_route(client_server::create_content_route)
|
||||||
.ruma_route(client_server::get_content_route)
|
.ruma_route(client_server::get_content_route)
|
||||||
|
.ruma_route(client_server::get_content_auth_route)
|
||||||
.ruma_route(client_server::get_content_as_filename_route)
|
.ruma_route(client_server::get_content_as_filename_route)
|
||||||
|
.ruma_route(client_server::get_content_as_filename_auth_route)
|
||||||
.ruma_route(client_server::get_content_thumbnail_route)
|
.ruma_route(client_server::get_content_thumbnail_route)
|
||||||
|
.ruma_route(client_server::get_content_thumbnail_auth_route)
|
||||||
.ruma_route(client_server::get_devices_route)
|
.ruma_route(client_server::get_devices_route)
|
||||||
.ruma_route(client_server::get_device_route)
|
.ruma_route(client_server::get_device_route)
|
||||||
.ruma_route(client_server::update_device_route)
|
.ruma_route(client_server::update_device_route)
|
||||||
|
@ -440,6 +444,8 @@ fn routes(config: &Config) -> Router {
|
||||||
.ruma_route(server_server::create_join_event_v2_route)
|
.ruma_route(server_server::create_join_event_v2_route)
|
||||||
.ruma_route(server_server::create_invite_route)
|
.ruma_route(server_server::create_invite_route)
|
||||||
.ruma_route(server_server::get_devices_route)
|
.ruma_route(server_server::get_devices_route)
|
||||||
|
.ruma_route(server_server::get_content_route)
|
||||||
|
.ruma_route(server_server::get_content_thumbnail_route)
|
||||||
.ruma_route(server_server::get_room_information_route)
|
.ruma_route(server_server::get_room_information_route)
|
||||||
.ruma_route(server_server::get_profile_information_route)
|
.ruma_route(server_server::get_profile_information_route)
|
||||||
.ruma_route(server_server::get_keys_route)
|
.ruma_route(server_server::get_keys_route)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use ruma::http_headers::ContentDisposition;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
|
@ -6,7 +8,7 @@ pub trait Data: Send + Sync {
|
||||||
mxc: String,
|
mxc: String,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
content_disposition: Option<&str>,
|
content_disposition: &ContentDisposition,
|
||||||
content_type: Option<&str>,
|
content_type: Option<&str>,
|
||||||
) -> Result<Vec<u8>>;
|
) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
|
@ -16,5 +18,5 @@ pub trait Data: Send + Sync {
|
||||||
mxc: String,
|
mxc: String,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<(Option<String>, Option<String>, Vec<u8>)>;
|
) -> Result<(ContentDisposition, Option<String>, Vec<u8>)>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod data;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
|
use ruma::http_headers::{ContentDisposition, ContentDispositionType};
|
||||||
|
|
||||||
use crate::{services, Result};
|
use crate::{services, Result};
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
|
@ -12,7 +13,7 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FileMeta {
|
pub struct FileMeta {
|
||||||
pub content_disposition: Option<String>,
|
pub content_disposition: ContentDisposition,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub file: Vec<u8>,
|
pub file: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
@ -26,14 +27,17 @@ impl Service {
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: String,
|
||||||
content_disposition: Option<&str>,
|
content_disposition: Option<ContentDisposition>,
|
||||||
content_type: Option<&str>,
|
content_type: Option<&str>,
|
||||||
file: &[u8],
|
file: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let content_disposition =
|
||||||
|
content_disposition.unwrap_or(ContentDisposition::new(ContentDispositionType::Inline));
|
||||||
|
|
||||||
// Width, Height = 0 if it's not a thumbnail
|
// Width, Height = 0 if it's not a thumbnail
|
||||||
let key = self
|
let key = self
|
||||||
.db
|
.db
|
||||||
.create_file_metadata(mxc, 0, 0, content_disposition, content_type)?;
|
.create_file_metadata(mxc, 0, 0, &content_disposition, content_type)?;
|
||||||
|
|
||||||
let path = services().globals.get_media_file(&key);
|
let path = services().globals.get_media_file(&key);
|
||||||
let mut f = File::create(path).await?;
|
let mut f = File::create(path).await?;
|
||||||
|
@ -46,15 +50,18 @@ impl Service {
|
||||||
pub async fn upload_thumbnail(
|
pub async fn upload_thumbnail(
|
||||||
&self,
|
&self,
|
||||||
mxc: String,
|
mxc: String,
|
||||||
content_disposition: Option<&str>,
|
|
||||||
content_type: Option<&str>,
|
content_type: Option<&str>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
file: &[u8],
|
file: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let key =
|
let key = self.db.create_file_metadata(
|
||||||
self.db
|
mxc,
|
||||||
.create_file_metadata(mxc, width, height, content_disposition, content_type)?;
|
width,
|
||||||
|
height,
|
||||||
|
&ContentDisposition::new(ContentDispositionType::Inline),
|
||||||
|
content_type,
|
||||||
|
)?;
|
||||||
|
|
||||||
let path = services().globals.get_media_file(&key);
|
let path = services().globals.get_media_file(&key);
|
||||||
let mut f = File::create(path).await?;
|
let mut f = File::create(path).await?;
|
||||||
|
@ -198,7 +205,7 @@ impl Service {
|
||||||
mxc,
|
mxc,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
content_disposition.as_deref(),
|
&content_disposition,
|
||||||
content_type.as_deref(),
|
content_type.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
api::client::error::ErrorKind,
|
||||||
canonical_json::redact_content_in_place,
|
canonical_json::redact_content_in_place,
|
||||||
events::{
|
events::{
|
||||||
room::{member::RoomMemberEventContent, redaction::RoomRedactionEventContent},
|
room::{member::RoomMemberEventContent, redaction::RoomRedactionEventContent},
|
||||||
|
@ -443,7 +444,7 @@ pub(crate) fn gen_event_id_canonical_json(
|
||||||
"${}",
|
"${}",
|
||||||
// Anything higher than version3 behaves the same
|
// Anything higher than version3 behaves the same
|
||||||
ruma::signatures::reference_hash(&value, room_version_id)
|
ruma::signatures::reference_hash(&value, room_version_id)
|
||||||
.expect("ruma can calculate reference hashes")
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid PDU format"))?
|
||||||
)
|
)
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
|
@ -1477,7 +1477,7 @@ impl Service {
|
||||||
let event_id = format!(
|
let event_id = format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&value, room_version)
|
ruma::signatures::reference_hash(&value, room_version)
|
||||||
.expect("ruma can calculate reference hashes")
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid PDU format"))?
|
||||||
);
|
);
|
||||||
let event_id = <&EventId>::try_from(event_id.as_str())
|
let event_id = <&EventId>::try_from(event_id.as_str())
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
|
@ -815,7 +815,7 @@ impl Service {
|
||||||
pdu.event_id = EventId::parse_arc(format!(
|
pdu.event_id = EventId::parse_arc(format!(
|
||||||
"${}",
|
"${}",
|
||||||
ruma::signatures::reference_hash(&pdu_json, &room_version_id)
|
ruma::signatures::reference_hash(&pdu_json, &room_version_id)
|
||||||
.expect("ruma can calculate reference hashes")
|
.expect("Event format validated when event was hashed")
|
||||||
))
|
))
|
||||||
.expect("ruma's reference hashes are valid event ids");
|
.expect("ruma's reference hashes are valid event ids");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue