0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-19 19:42:19 +02:00

backup: Validate quoted attachments

This commit is contained in:
Jordan Rose 2024-07-26 16:20:54 -07:00
parent f2b9ad59d5
commit 03b71fea23

View File

@ -4,6 +4,7 @@
//
use crate::backup::chat::text::{MessageText, TextError};
use crate::backup::file::{MessageAttachment, MessageAttachmentError};
use crate::backup::frame::RecipientId;
use crate::backup::method::Contains;
use crate::backup::time::Timestamp;
@ -17,6 +18,7 @@ pub struct Quote {
pub author: RecipientId,
pub quote_type: QuoteType,
pub target_sent_timestamp: Option<Timestamp>,
pub attachments: Vec<QuotedAttachment>,
pub text: Option<MessageText>,
#[serde(skip)]
_limit_construction_to_module: (),
@ -29,6 +31,16 @@ pub enum QuoteType {
GiftBadge,
}
#[derive(Debug, serde::Serialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct QuotedAttachment {
pub content_type: Option<String>,
pub file_name: Option<String>,
pub thumbnail: Option<MessageAttachment>,
#[serde(skip)]
_limit_construction_to_module: (),
}
#[derive(Debug, displaydoc::Display, thiserror::Error)]
#[cfg_attr(test, derive(PartialEq))]
pub enum QuoteError {
@ -40,6 +52,10 @@ pub enum QuoteError {
BodyRangesWithoutText,
/// text error: {0}
Text(#[from] TextError),
/// attachment thumbnail: {0}
AttachmentThumbnail(#[from] MessageAttachmentError),
/// attachment thumbnail cannot have flag {0:?}
AttachmentThumbnailWrongFlag(proto::message_attachment::Flag),
}
impl<R: Contains<RecipientId>> TryFromWith<proto::Quote, R> for Quote {
@ -52,8 +68,7 @@ impl<R: Contains<RecipientId>> TryFromWith<proto::Quote, R> for Quote {
targetSentTimestamp,
text,
bodyRanges,
// TODO validate these fields
attachments: _,
attachments,
special_fields: _,
} = item;
@ -82,22 +97,112 @@ impl<R: Contains<RecipientId>> TryFromWith<proto::Quote, R> for Quote {
.try_into()?,
),
};
let attachments = attachments
.into_iter()
.map(QuotedAttachment::try_from)
.collect::<Result<_, _>>()?;
Ok(Self {
author,
quote_type,
target_sent_timestamp,
attachments,
text,
_limit_construction_to_module: (),
})
}
}
impl TryFrom<proto::quote::QuotedAttachment> for QuotedAttachment {
type Error = QuoteError;
fn try_from(value: proto::quote::QuotedAttachment) -> Result<Self, Self::Error> {
let proto::quote::QuotedAttachment {
contentType,
fileName,
thumbnail,
special_fields: _,
} = value;
let thumbnail = thumbnail
.into_option()
.map(MessageAttachment::try_from)
.transpose()?;
if let Some(thumbnail) = &thumbnail {
match thumbnail.flag {
proto::message_attachment::Flag::NONE
| proto::message_attachment::Flag::BORDERLESS
| proto::message_attachment::Flag::GIF => {}
proto::message_attachment::Flag::VOICE_MESSAGE => {
return Err(QuoteError::AttachmentThumbnailWrongFlag(thumbnail.flag));
}
}
}
Ok(Self {
content_type: contentType,
file_name: fileName,
thumbnail,
_limit_construction_to_module: (),
})
}
}
#[cfg(test)]
mod test {
use test_case::test_case;
use crate::backup::time::testutil::MillisecondsSinceEpoch;
use super::*;
impl proto::quote::QuotedAttachment {
fn test_data() -> Self {
Self {
contentType: Some("video/mpeg".into()),
fileName: Some("test.mpg".into()),
thumbnail: Some(proto::MessageAttachment::test_data()).into(),
..Default::default()
}
}
}
impl QuotedAttachment {
fn from_proto_test_data() -> Self {
Self {
content_type: Some("video/mpeg".into()),
file_name: Some("test.mpg".into()),
thumbnail: Some(MessageAttachment::from_proto_test_data()),
_limit_construction_to_module: (),
}
}
}
#[test_case(|_| {} => Ok(()); "valid")]
#[test_case(|x| x.contentType = None => Ok(()); "no contentType")]
#[test_case(|x| x.contentType = Some("".into()) => Ok(()); "empty contentType")]
#[test_case(|x| x.fileName = None => Ok(()); "no fileName")]
#[test_case(|x| x.fileName = Some("".into()) => Ok(()); "empty fileName")]
#[test_case(|x| x.thumbnail = None.into() => Ok(()); "no thumbnail")]
#[test_case(|x| x.thumbnail = Some(proto::MessageAttachment::default()).into() => Err(QuoteError::AttachmentThumbnail(MessageAttachmentError::NoFilePointer)); "invalid thumbnail")]
#[test_case(|x| x.thumbnail = Some(proto::MessageAttachment {
flag: proto::message_attachment::Flag::BORDERLESS.into(),
..proto::MessageAttachment::test_data()
}).into() => Ok(()); "borderless thumbnail")]
#[test_case(|x| x.thumbnail = Some(proto::MessageAttachment {
flag: proto::message_attachment::Flag::VOICE_MESSAGE.into(),
..proto::MessageAttachment::test_data()
}).into() => Err(QuoteError::AttachmentThumbnailWrongFlag(proto::message_attachment::Flag::VOICE_MESSAGE)); "voice message thumbnail")]
fn attachment(
modifier: impl FnOnce(&mut proto::quote::QuotedAttachment),
) -> Result<(), QuoteError> {
let mut attachment = proto::quote::QuotedAttachment::test_data();
modifier(&mut attachment);
QuotedAttachment::try_from(attachment).map(|_| ())
}
impl proto::Quote {
pub(crate) fn test_data() -> Self {
let proto::Text {
@ -110,6 +215,7 @@ mod test {
authorId: proto::Recipient::TEST_ID,
type_: proto::quote::Type::NORMAL.into(),
targetSentTimestamp: Some(MillisecondsSinceEpoch::TEST_VALUE.0),
attachments: vec![proto::quote::QuotedAttachment::test_data()],
text: Some(body),
bodyRanges,
..Default::default()
@ -124,6 +230,7 @@ mod test {
author: RecipientId(proto::Recipient::TEST_ID),
quote_type: QuoteType::Normal,
target_sent_timestamp: Some(Timestamp::test_value()),
attachments: vec![QuotedAttachment::from_proto_test_data()],
_limit_construction_to_module: (),
}
}