fix: do not ignore I/O errors in BlobObject::store_from_base64

This commit is contained in:
link2xt
2025-11-08 00:17:44 +00:00
committed by l
parent fdea6c8af3
commit 1cb0a25e16
4 changed files with 28 additions and 24 deletions

View File

@@ -234,8 +234,13 @@ impl<'a> BlobObject<'a> {
/// If `data` represents an image of known format, this adds the corresponding extension. /// If `data` represents an image of known format, this adds the corresponding extension.
/// ///
/// Even though this function is not async, it's OK to call it from an async context. /// Even though this function is not async, it's OK to call it from an async context.
pub(crate) fn store_from_base64(context: &Context, data: &str) -> Result<String> { ///
let buf = base64::engine::general_purpose::STANDARD.decode(data)?; /// Returns an error if there is an I/O problem,
/// but in case of a failure to decode base64 returns `Ok(None)`.
pub(crate) fn store_from_base64(context: &Context, data: &str) -> Result<Option<String>> {
let Ok(buf) = base64::engine::general_purpose::STANDARD.decode(data) else {
return Ok(None);
};
let name = if let Ok(format) = image::guess_format(&buf) { let name = if let Ok(format) = image::guess_format(&buf) {
if let Some(ext) = format.extensions_str().first() { if let Some(ext) = format.extensions_str().first() {
format!("file.{ext}") format!("file.{ext}")
@@ -246,7 +251,7 @@ impl<'a> BlobObject<'a> {
String::new() String::new()
}; };
let blob = BlobObject::create_and_deduplicate_from_bytes(context, &buf, &name)?; let blob = BlobObject::create_and_deduplicate_from_bytes(context, &buf, &name)?;
Ok(blob.as_name().to_string()) Ok(Some(blob.as_name().to_string()))
} }
/// Recode image to avatar size. /// Recode image to avatar size.

View File

@@ -665,7 +665,7 @@ impl Context {
Config::Selfavatar if value.is_empty() => None, Config::Selfavatar if value.is_empty() => None,
Config::Selfavatar => { Config::Selfavatar => {
config_value = BlobObject::store_from_base64(self, value)?; config_value = BlobObject::store_from_base64(self, value)?;
Some(config_value.as_str()) config_value.as_deref()
} }
_ => Some(value), _ => Some(value),
}; };

View File

@@ -369,16 +369,15 @@ async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Resu
return Ok(id); return Ok(id);
} }
let path = match &contact.profile_image { let path = match &contact.profile_image {
Some(image) => match BlobObject::store_from_base64(context, image) { Some(image) => match BlobObject::store_from_base64(context, image)? {
Err(e) => { None => {
warn!( warn!(
context, context,
"import_vcard_contact: Could not decode and save avatar for {}: {e:#}.", "import_vcard_contact: Could not decode avatar for {}.", contact.addr
contact.addr
); );
None None
} }
Ok(path) => Some(path), Some(path) => Some(path),
}, },
None => None, None => None,
}; };

View File

@@ -714,14 +714,16 @@ impl MimeMessage {
} }
/// Parses avatar action headers. /// Parses avatar action headers.
fn parse_avatar_headers(&mut self, context: &Context) { fn parse_avatar_headers(&mut self, context: &Context) -> Result<()> {
if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) { if let Some(header_value) = self.get_header(HeaderDef::ChatGroupAvatar) {
self.group_avatar = self.avatar_action_from_header(context, header_value.to_string()); self.group_avatar =
self.avatar_action_from_header(context, header_value.to_string())?;
} }
if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) { if let Some(header_value) = self.get_header(HeaderDef::ChatUserAvatar) {
self.user_avatar = self.avatar_action_from_header(context, header_value.to_string()); self.user_avatar = self.avatar_action_from_header(context, header_value.to_string())?;
} }
Ok(())
} }
fn parse_videochat_headers(&mut self) { fn parse_videochat_headers(&mut self) {
@@ -828,7 +830,7 @@ impl MimeMessage {
async fn parse_headers(&mut self, context: &Context) -> Result<()> { async fn parse_headers(&mut self, context: &Context) -> Result<()> {
self.parse_system_message_headers(context); self.parse_system_message_headers(context);
self.parse_avatar_headers(context); self.parse_avatar_headers(context)?;
self.parse_videochat_headers(); self.parse_videochat_headers();
if self.delivery_report.is_none() { if self.delivery_report.is_none() {
self.squash_attachment_parts(); self.squash_attachment_parts();
@@ -930,21 +932,18 @@ impl MimeMessage {
&mut self, &mut self,
context: &Context, context: &Context,
header_value: String, header_value: String,
) -> Option<AvatarAction> { ) -> Result<Option<AvatarAction>> {
if header_value == "0" { let res = if header_value == "0" {
Some(AvatarAction::Delete) Some(AvatarAction::Delete)
} else if let Some(base64) = header_value } else if let Some(base64) = header_value
.split_ascii_whitespace() .split_ascii_whitespace()
.collect::<String>() .collect::<String>()
.strip_prefix("base64:") .strip_prefix("base64:")
{ {
match BlobObject::store_from_base64(context, base64) { match BlobObject::store_from_base64(context, base64)? {
Ok(path) => Some(AvatarAction::Change(path)), Some(path) => Some(AvatarAction::Change(path)),
Err(err) => { None => {
warn!( warn!(context, "Could not decode avatar base64");
context,
"Could not decode and save avatar to blob file: {:#}", err,
);
None None
} }
} }
@@ -958,7 +957,7 @@ impl MimeMessage {
if let Some(blob) = part.param.get(Param::File) { if let Some(blob) = part.param.get(Param::File) {
let res = Some(AvatarAction::Change(blob.to_string())); let res = Some(AvatarAction::Change(blob.to_string()));
self.parts.remove(i); self.parts.remove(i);
return res; return Ok(res);
} }
break; break;
} }
@@ -966,7 +965,8 @@ impl MimeMessage {
i += 1; i += 1;
} }
None None
} };
Ok(res)
} }
/// Returns true if the message was encrypted as defined in /// Returns true if the message was encrypted as defined in