events: Add support for transitional extensible file messages
According to MSC3551
This commit is contained in:
parent
0e11996545
commit
8673d0d3f6
@ -10,11 +10,26 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
message::MessageContent,
|
||||
room::{message::Relation, JsonWebKey},
|
||||
room::{
|
||||
message::{FileInfo, FileMessageEventContent, Relation},
|
||||
EncryptedFile, JsonWebKey, MediaSource,
|
||||
},
|
||||
};
|
||||
use crate::{serde::Base64, MxcUri};
|
||||
|
||||
/// The payload for an extensible text message.
|
||||
/// The payload for an extensible file message.
|
||||
///
|
||||
/// This is the new primary type introduced in [MSC3551] and should not be sent before the end of
|
||||
/// the transition period. See the documentation of the [`message`] module for more information.
|
||||
///
|
||||
/// `FileEventContent` can be converted to a [`RoomMessageEventContent`] with a
|
||||
/// [`MessageType::File`]. You can convert it back with
|
||||
/// [`FileEventContent::from_file_room_message()`].
|
||||
///
|
||||
/// [MSC3551]: https://github.com/matrix-org/matrix-spec-proposals/pull/3551
|
||||
/// [`message`]: super::message
|
||||
/// [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
|
||||
/// [`MessageType::File`]: super::room::message::MessageType::File
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.file", kind = MessageLike)]
|
||||
@ -84,6 +99,22 @@ impl FileEventContent {
|
||||
) -> Self {
|
||||
Self { message, file: FileContent::encrypted(url, encryption_info, info), relates_to: None }
|
||||
}
|
||||
|
||||
/// Create a new `FileEventContent` from the given `FileMessageEventContent` and optional
|
||||
/// relation.
|
||||
pub fn from_file_room_message(
|
||||
content: FileMessageEventContent,
|
||||
relates_to: Option<Relation>,
|
||||
) -> Self {
|
||||
let FileMessageEventContent { body, filename, source, info, message, file } = content;
|
||||
|
||||
let message = message.unwrap_or_else(|| MessageContent::plain(body));
|
||||
let file = file.unwrap_or_else(|| {
|
||||
FileContent::from_room_message_content(source, info.as_deref(), filename)
|
||||
});
|
||||
|
||||
Self { message, file, relates_to }
|
||||
}
|
||||
}
|
||||
|
||||
/// File content.
|
||||
@ -119,6 +150,21 @@ impl FileContent {
|
||||
Self { url, info, encryption_info: Some(Box::new(encryption_info)) }
|
||||
}
|
||||
|
||||
/// Create a new `FileContent` with the given media source, file info and filename.
|
||||
pub fn from_room_message_content(
|
||||
source: MediaSource,
|
||||
info: Option<impl Into<FileContentInfo>>,
|
||||
filename: Option<String>,
|
||||
) -> Self {
|
||||
let (url, encryption_info) = match source {
|
||||
MediaSource::Plain(url) => (url, None),
|
||||
MediaSource::Encrypted(file) => (file.url.to_owned(), Some(Box::new((&*file).into()))),
|
||||
};
|
||||
let info = FileContentInfo::from_room_message_content(info, filename).map(Box::new);
|
||||
|
||||
Self { url, encryption_info, info }
|
||||
}
|
||||
|
||||
/// Whether the file is encrypted.
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
self.encryption_info.is_some()
|
||||
@ -147,6 +193,29 @@ impl FileContentInfo {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new `FileContentInfo` with the given file info and filename.
|
||||
///
|
||||
/// Returns `None` if both parameters are `None`
|
||||
pub fn from_room_message_content(
|
||||
info: Option<impl Into<FileContentInfo>>,
|
||||
filename: Option<String>,
|
||||
) -> Option<Self> {
|
||||
if filename.is_none() && info.is_none() {
|
||||
None
|
||||
} else {
|
||||
let mut info: Self = info.map(Into::into).unwrap_or_default();
|
||||
info.name = filename;
|
||||
Some(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FileInfo> for FileContentInfo {
|
||||
fn from(info: &FileInfo) -> Self {
|
||||
let FileInfo { mimetype, size, .. } = info;
|
||||
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The encryption info of a file sent to a room with end-to-end encryption enabled.
|
||||
@ -203,3 +272,10 @@ impl From<EncryptedContentInit> for EncryptedContent {
|
||||
Self { key, iv, hashes, v }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncryptedFile> for EncryptedContent {
|
||||
fn from(encrypted: &EncryptedFile) -> Self {
|
||||
let EncryptedFile { key, iv, hashes, v, .. } = encrypted.to_owned();
|
||||
Self { key, iv, hashes, v }
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ use std::collections::BTreeMap;
|
||||
use js_int::UInt;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
use super::file::{EncryptedContent, FileContent};
|
||||
use crate::{
|
||||
serde::{base64::UrlSafe, Base64},
|
||||
MxcUri,
|
||||
@ -70,6 +72,18 @@ impl<'de> Deserialize<'de> for MediaSource {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
impl From<&FileContent> for MediaSource {
|
||||
fn from(content: &FileContent) -> Self {
|
||||
let FileContent { url, encryption_info, .. } = content;
|
||||
if let Some(encryption_info) = encryption_info.as_deref() {
|
||||
Self::Encrypted(Box::new(EncryptedFile::from_extensible_content(url, encryption_info)))
|
||||
} else {
|
||||
Self::Plain(url.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about an image.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
@ -173,6 +187,15 @@ pub struct EncryptedFile {
|
||||
pub v: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
impl EncryptedFile {
|
||||
/// Create an `EncryptedFile` from the given url and encryption info.
|
||||
pub fn from_extensible_content(url: &MxcUri, encryption_info: &EncryptedContent) -> Self {
|
||||
let EncryptedContent { key, iv, hashes, v } = encryption_info.to_owned();
|
||||
Self { url: url.to_owned(), key, iv, hashes, v }
|
||||
}
|
||||
}
|
||||
|
||||
/// Initial set of fields of `EncryptedFile`.
|
||||
///
|
||||
/// This struct will not be updated even if additional fields are added to `EncryptedFile` in a new
|
||||
|
@ -10,6 +10,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use super::{EncryptedFile, ImageInfo, MediaSource, ThumbnailInfo};
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
use crate::events::file::{FileContent, FileContentInfo, FileEventContent};
|
||||
#[cfg(feature = "unstable-msc1767")]
|
||||
use crate::events::{
|
||||
emote::EmoteEventContent,
|
||||
@ -197,6 +199,20 @@ impl From<EmoteEventContent> for RoomMessageEventContent {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
impl From<FileEventContent> for RoomMessageEventContent {
|
||||
fn from(content: FileEventContent) -> Self {
|
||||
let FileEventContent { message, file, relates_to } = content;
|
||||
|
||||
Self {
|
||||
msgtype: MessageType::File(FileMessageEventContent::from_extensible_content(
|
||||
message, file,
|
||||
)),
|
||||
relates_to,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc1767")]
|
||||
impl From<MessageEventContent> for RoomMessageEventContent {
|
||||
fn from(content: MessageEventContent) -> Self {
|
||||
@ -597,6 +613,10 @@ impl From<MessageContent> for EmoteMessageEventContent {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[serde(tag = "msgtype", rename = "m.file")]
|
||||
#[cfg_attr(
|
||||
feature = "unstable-msc3551",
|
||||
serde(from = "content_serde::FileMessageEventContentDeHelper")
|
||||
)]
|
||||
pub struct FileMessageEventContent {
|
||||
/// A human-readable description of the file.
|
||||
///
|
||||
@ -614,19 +634,69 @@ pub struct FileMessageEventContent {
|
||||
/// Metadata about the file referred to in `url`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub info: Option<Box<FileInfo>>,
|
||||
|
||||
/// Extensible-event text representation of the message.
|
||||
///
|
||||
/// If present, this should be preferred over the `body` field.
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<MessageContent>,
|
||||
|
||||
/// Extensible-event file content of the message.
|
||||
///
|
||||
/// If present, this should be preferred over the `source` and `info` fields.
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
#[serde(rename = "org.matrix.msc1767.file", skip_serializing_if = "Option::is_none")]
|
||||
pub file: Option<FileContent>,
|
||||
}
|
||||
|
||||
impl FileMessageEventContent {
|
||||
/// Creates a new non-encrypted `RoomFileMessageEventContent` with the given body, url and
|
||||
/// optional extra info.
|
||||
pub fn plain(body: String, url: Box<MxcUri>, info: Option<Box<FileInfo>>) -> Self {
|
||||
Self { body, filename: None, source: MediaSource::Plain(url), info }
|
||||
Self {
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
message: Some(MessageContent::plain(body.clone())),
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
file: Some(FileContent::plain(
|
||||
url.clone(),
|
||||
info.as_deref().map(|info| Box::new(info.into())),
|
||||
)),
|
||||
body,
|
||||
filename: None,
|
||||
source: MediaSource::Plain(url),
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new encrypted `RoomFileMessageEventContent` with the given body and encrypted
|
||||
/// file.
|
||||
pub fn encrypted(body: String, file: EncryptedFile) -> Self {
|
||||
Self { body, filename: None, source: MediaSource::Encrypted(Box::new(file)), info: None }
|
||||
Self {
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
message: Some(MessageContent::plain(body.clone())),
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
file: Some(FileContent::encrypted(file.url.clone(), (&file).into(), None)),
|
||||
body,
|
||||
filename: None,
|
||||
source: MediaSource::Encrypted(Box::new(file)),
|
||||
info: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `RoomFileMessageEventContent` with the given message and file info.
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
pub fn from_extensible_content(message: MessageContent, file: FileContent) -> Self {
|
||||
let body = if let Some(body) = message.find_plain() {
|
||||
body.to_owned()
|
||||
} else {
|
||||
message[0].body.clone()
|
||||
};
|
||||
let filename = file.info.as_deref().and_then(|info| info.name.clone());
|
||||
let info = file.info.as_deref().map(|info| Box::new(info.into()));
|
||||
let source = (&file).into();
|
||||
|
||||
Self { message: Some(message), file: Some(file), body, filename, source, info }
|
||||
}
|
||||
}
|
||||
|
||||
@ -662,6 +732,14 @@ impl FileInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
impl From<&FileContentInfo> for FileInfo {
|
||||
fn from(info: &FileContentInfo) -> Self {
|
||||
let FileContentInfo { mimetype, size, .. } = info;
|
||||
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The payload for an image message.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
|
@ -3,6 +3,8 @@
|
||||
use serde::{de, Deserialize};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
use super::{FileContent, FileInfo, FileMessageEventContent, MediaSource, MessageContent};
|
||||
use super::{MessageType, Relation, RoomMessageEventContent};
|
||||
use crate::serde::from_raw_json_value;
|
||||
|
||||
@ -50,3 +52,55 @@ impl<'de> Deserialize<'de> for MessageType {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for deserializing `FileMessageEventContent` with stable and unstable field names.
|
||||
///
|
||||
/// It's not possible to use the `alias` attribute of serde because of
|
||||
/// https://github.com/serde-rs/serde/issues/1504.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
pub struct FileMessageEventContentDeHelper {
|
||||
/// A human-readable description of the file.
|
||||
pub body: String,
|
||||
|
||||
/// The original filename of the uploaded file.
|
||||
pub filename: Option<String>,
|
||||
|
||||
/// The source of the file.
|
||||
#[serde(flatten)]
|
||||
pub source: MediaSource,
|
||||
|
||||
/// Metadata about the file referred to in `url`.
|
||||
pub info: Option<Box<FileInfo>>,
|
||||
|
||||
/// Extensible-event text representation of the message.
|
||||
#[serde(flatten)]
|
||||
pub message: Option<MessageContent>,
|
||||
|
||||
/// Extensible-event file content of the message, with stable name.
|
||||
#[serde(rename = "m.file")]
|
||||
pub file_stable: Option<FileContent>,
|
||||
|
||||
/// Extensible-event file content of the message, with unstable name.
|
||||
#[serde(rename = "org.matrix.msc1767.file")]
|
||||
pub file_unstable: Option<FileContent>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc3551")]
|
||||
impl From<FileMessageEventContentDeHelper> for FileMessageEventContent {
|
||||
fn from(helper: FileMessageEventContentDeHelper) -> Self {
|
||||
let FileMessageEventContentDeHelper {
|
||||
body,
|
||||
filename,
|
||||
source,
|
||||
info,
|
||||
message,
|
||||
file_stable,
|
||||
file_unstable,
|
||||
} = helper;
|
||||
|
||||
let file = file_stable.or(file_unstable);
|
||||
|
||||
Self { body, filename, source, info, message, file }
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ use ruma_common::{
|
||||
file::{EncryptedContentInit, FileContent, FileContentInfo, FileEventContent},
|
||||
message::MessageContent,
|
||||
room::{
|
||||
message::{InReplyTo, Relation},
|
||||
JsonWebKeyInit,
|
||||
message::{
|
||||
FileMessageEventContent, InReplyTo, MessageType, Relation, RoomMessageEventContent,
|
||||
},
|
||||
EncryptedFileInit, JsonWebKeyInit, MediaSource,
|
||||
},
|
||||
AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned,
|
||||
},
|
||||
@ -152,7 +154,7 @@ fn file_event_serialization() {
|
||||
#[test]
|
||||
fn plain_content_deserialization() {
|
||||
let json_data = json!({
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"m.text": "Upload: my_file.txt",
|
||||
"m.file": {
|
||||
"url": "mxc://notareal.hs/abcdef",
|
||||
}
|
||||
@ -171,7 +173,7 @@ fn plain_content_deserialization() {
|
||||
#[test]
|
||||
fn encrypted_content_deserialization() {
|
||||
let json_data = json!({
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"m.text": "Upload: my_file.txt",
|
||||
"m.file": {
|
||||
"url": "mxc://notareal.hs/abcdef",
|
||||
"key": {
|
||||
@ -204,7 +206,7 @@ fn encrypted_content_deserialization() {
|
||||
fn message_event_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": {
|
||||
"org.matrix.msc1767.message": [
|
||||
"m.message": [
|
||||
{ "body": "Upload: <strong>my_file.txt</strong>", "mimetype": "text/html"},
|
||||
{ "body": "Upload: my_file.txt", "mimetype": "text/plain"},
|
||||
],
|
||||
@ -252,3 +254,260 @@ fn message_event_deserialization() {
|
||||
&& unsigned.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_plain_content_serialization() {
|
||||
let message_event_content =
|
||||
RoomMessageEventContent::new(MessageType::File(FileMessageEventContent::plain(
|
||||
"Upload: my_file.txt".to_owned(),
|
||||
mxc_uri!("mxc://notareal.hs/file").to_owned(),
|
||||
None,
|
||||
)));
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&message_event_content).unwrap(),
|
||||
json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"msgtype": "m.file",
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"org.matrix.msc1767.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_encrypted_content_serialization() {
|
||||
let message_event_content =
|
||||
RoomMessageEventContent::new(MessageType::File(FileMessageEventContent::encrypted(
|
||||
"Upload: my_file.txt".to_owned(),
|
||||
EncryptedFileInit {
|
||||
url: mxc_uri!("mxc://notareal.hs/file").to_owned(),
|
||||
key: JsonWebKeyInit {
|
||||
kty: "oct".to_owned(),
|
||||
key_ops: vec!["encrypt".to_owned(), "decrypt".to_owned()],
|
||||
alg: "A256CTR".to_owned(),
|
||||
k: Base64::parse("TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A").unwrap(),
|
||||
ext: true,
|
||||
}
|
||||
.into(),
|
||||
iv: Base64::parse("S22dq3NAX8wAAAAAAAAAAA").unwrap(),
|
||||
hashes: [(
|
||||
"sha256".to_owned(),
|
||||
Base64::parse("aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q").unwrap(),
|
||||
)]
|
||||
.into(),
|
||||
v: "v2".to_owned(),
|
||||
}
|
||||
.into(),
|
||||
)));
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&message_event_content).unwrap(),
|
||||
json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
"msgtype": "m.file",
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"org.matrix.msc1767.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_plain_content_stable_deserialization() {
|
||||
let json_data = json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"msgtype": "m.file",
|
||||
"m.text": "Upload: my_file.txt",
|
||||
"m.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
},
|
||||
});
|
||||
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::File(_));
|
||||
if let MessageType::File(content) = event_content.msgtype {
|
||||
assert_eq!(content.body, "Upload: my_file.txt");
|
||||
assert_matches!(content.source, MediaSource::Plain(_));
|
||||
if let MediaSource::Plain(url) = content.source {
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
}
|
||||
let message = content.message.unwrap();
|
||||
assert_eq!(message.len(), 1);
|
||||
assert_eq!(message[0].body, "Upload: my_file.txt");
|
||||
let file = content.file.unwrap();
|
||||
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||
assert!(!file.is_encrypted());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_plain_content_unstable_deserialization() {
|
||||
let json_data = json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"msgtype": "m.file",
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"org.matrix.msc1767.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
},
|
||||
});
|
||||
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::File(_));
|
||||
if let MessageType::File(content) = event_content.msgtype {
|
||||
assert_eq!(content.body, "Upload: my_file.txt");
|
||||
assert_matches!(content.source, MediaSource::Plain(_));
|
||||
if let MediaSource::Plain(url) = content.source {
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
}
|
||||
let message = content.message.unwrap();
|
||||
assert_eq!(message.len(), 1);
|
||||
assert_eq!(message[0].body, "Upload: my_file.txt");
|
||||
let file = content.file.unwrap();
|
||||
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||
assert!(!file.is_encrypted());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_encrypted_content_stable_deserialization() {
|
||||
let json_data = json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
"msgtype": "m.file",
|
||||
"m.text": "Upload: my_file.txt",
|
||||
"m.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
});
|
||||
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::File(_));
|
||||
if let MessageType::File(content) = event_content.msgtype {
|
||||
assert_eq!(content.body, "Upload: my_file.txt");
|
||||
assert_matches!(content.source, MediaSource::Encrypted(_));
|
||||
if let MediaSource::Encrypted(encrypted_file) = content.source {
|
||||
assert_eq!(encrypted_file.url, "mxc://notareal.hs/file");
|
||||
}
|
||||
let message = content.message.unwrap();
|
||||
assert_eq!(message.len(), 1);
|
||||
assert_eq!(message[0].body, "Upload: my_file.txt");
|
||||
let file = content.file.unwrap();
|
||||
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||
assert!(file.is_encrypted());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_message_encrypted_content_unstable_deserialization() {
|
||||
let json_data = json!({
|
||||
"body": "Upload: my_file.txt",
|
||||
"file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
"msgtype": "m.file",
|
||||
"org.matrix.msc1767.text": "Upload: my_file.txt",
|
||||
"org.matrix.msc1767.file": {
|
||||
"url": "mxc://notareal.hs/file",
|
||||
"key": {
|
||||
"kty": "oct",
|
||||
"key_ops": ["encrypt", "decrypt"],
|
||||
"alg": "A256CTR",
|
||||
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
|
||||
"ext": true
|
||||
},
|
||||
"iv": "S22dq3NAX8wAAAAAAAAAAA",
|
||||
"hashes": {
|
||||
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
|
||||
},
|
||||
"v": "v2",
|
||||
},
|
||||
});
|
||||
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::File(_));
|
||||
if let MessageType::File(content) = event_content.msgtype {
|
||||
assert_eq!(content.body, "Upload: my_file.txt");
|
||||
assert_matches!(content.source, MediaSource::Encrypted(_));
|
||||
if let MediaSource::Encrypted(encrypted_file) = content.source {
|
||||
assert_eq!(encrypted_file.url, "mxc://notareal.hs/file");
|
||||
}
|
||||
let message = content.message.unwrap();
|
||||
assert_eq!(message.len(), 1);
|
||||
assert_eq!(message[0].body, "Upload: my_file.txt");
|
||||
let file = content.file.unwrap();
|
||||
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||
assert!(file.is_encrypted());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user