refactor: Remove unused blob functions (#6563)

This commit is contained in:
Hocuri
2025-02-24 11:02:26 +01:00
committed by GitHub
parent b916937a7a
commit fbf3ff0112
14 changed files with 88 additions and 411 deletions

View File

@@ -14,8 +14,7 @@ use image::codecs::jpeg::JpegEncoder;
use image::ImageReader;
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat, Pixel, Rgba};
use num_traits::FromPrimitive;
use tokio::io::AsyncWriteExt;
use tokio::{fs, io, task};
use tokio::{fs, task};
use tokio_stream::wrappers::ReadDirStream;
use crate::config::Config;
@@ -48,73 +47,6 @@ enum ImageOutputFormat {
}
impl<'a> BlobObject<'a> {
/// Creates a new file, returning a tuple of the name and the handle.
async fn create_new_file(
context: &Context,
dir: &Path,
stem: &str,
ext: &str,
) -> Result<(String, fs::File)> {
const MAX_ATTEMPT: u32 = 16;
let mut attempt = 0;
let mut name = format!("{stem}{ext}");
loop {
attempt += 1;
let path = dir.join(&name);
match fs::OpenOptions::new()
// Using `create_new(true)` in order to avoid race conditions
// when creating multiple files with the same name.
.create_new(true)
.write(true)
.open(&path)
.await
{
Ok(file) => return Ok((name, file)),
Err(err) => {
if attempt >= MAX_ATTEMPT {
return Err(err).context("failed to create file");
} else if attempt == 1 && !dir.exists() {
fs::create_dir_all(dir).await.log_err(context).ok();
} else {
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
}
}
}
}
}
/// Creates a new blob object with unique name by copying an existing file.
///
/// This creates a new blob
/// and copies an existing file into it. This is done in a
/// in way which avoids race-conditions when multiple files are
/// concurrently created.
pub async fn create_and_copy(context: &'a Context, src: &Path) -> Result<BlobObject<'a>> {
let mut src_file = fs::File::open(src)
.await
.with_context(|| format!("failed to open file {}", src.display()))?;
let (stem, ext) = BlobObject::sanitize_name_and_split_extension(&src.to_string_lossy());
let (name, mut dst_file) =
BlobObject::create_new_file(context, context.get_blobdir(), &stem, &ext).await?;
let name_for_err = name.clone();
if let Err(err) = io::copy(&mut src_file, &mut dst_file).await {
// Attempt to remove the failed file, swallow errors resulting from that.
let path = context.get_blobdir().join(&name_for_err);
fs::remove_file(path).await.ok();
return Err(err).context("failed to copy file");
}
// Ensure that all buffered bytes are written
dst_file.flush().await?;
let blob = BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{name}"),
};
context.emit_event(EventType::NewBlobFile(blob.as_name().to_string()));
Ok(blob)
}
/// Creates a blob object by copying or renaming an existing file.
/// If the source file is already in the blobdir, it will be renamed,
/// otherwise it will be copied to the blobdir first.
@@ -209,27 +141,6 @@ impl<'a> BlobObject<'a> {
})
}
/// Creates a blob from a file, possibly copying it to the blobdir.
///
/// If the source file is not a path to into the blob directory
/// the file will be copied into the blob directory first. If the
/// source file is already in the blobdir it will not be copied
/// and only be created if it is a valid blobname, that is no
/// subdirectory is used and [BlobObject::sanitize_name_and_split_extension] does not
/// modify the filename.
///
/// Paths into the blob directory may be either defined by an absolute path
/// or by the relative prefix `$BLOBDIR`.
pub async fn new_from_path(context: &'a Context, src: &Path) -> Result<BlobObject<'a>> {
if src.starts_with(context.get_blobdir()) {
BlobObject::from_path(context, src)
} else if src.starts_with("$BLOBDIR/") {
BlobObject::from_name(context, src.to_str().unwrap_or_default().to_string())
} else {
BlobObject::create_and_copy(context, src).await
}
}
/// Returns a [BlobObject] for an existing blob from a path.
///
/// The path must designate a file directly in the blobdir and
@@ -301,50 +212,6 @@ impl<'a> BlobObject<'a> {
}
}
/// The name is returned as a tuple, the first part
/// being the stem or basename and the second being an extension,
/// including the dot. E.g. "foo.txt" is returned as `("foo",
/// ".txt")` while "bar" is returned as `("bar", "")`.
///
/// The extension part will always be lowercased.
fn sanitize_name_and_split_extension(name: &str) -> (String, String) {
let name = sanitize_filename(name);
// Let's take a tricky filename,
// "file.with_lots_of_characters_behind_point_and_double_ending.tar.gz" as an example.
// Assume that the extension is 32 chars maximum.
let ext: String = name
.chars()
.rev()
.take_while(|c| {
(!c.is_ascii_punctuation() || *c == '.') && !c.is_whitespace() && !c.is_control()
})
.take(33)
.collect::<Vec<_>>()
.iter()
.rev()
.collect();
// ext == "nd_point_and_double_ending.tar.gz"
// Split it into "nd_point_and_double_ending" and "tar.gz":
let mut iter = ext.splitn(2, '.');
iter.next();
let ext = iter.next().unwrap_or_default();
let ext = if ext.is_empty() {
String::new()
} else {
format!(".{ext}")
// ".tar.gz"
};
let stem = name
.strip_suffix(&ext)
.unwrap_or_default()
.chars()
.take(64)
.collect();
(stem, ext.to_lowercase())
}
/// Checks whether a name is a valid blob name.
///
/// This is slightly less strict than stanitise_name, presumably

View File

@@ -104,55 +104,15 @@ async fn test_create_long_names() {
assert!(blobname.len() < 70);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_and_copy() {
let t = TestContext::new().await;
let src = t.dir.path().join("src");
fs::write(&src, b"boo").await.unwrap();
let blob = BlobObject::create_and_copy(&t, src.as_ref()).await.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/src");
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
let whoops = t.dir.path().join("whoops");
assert!(BlobObject::create_and_copy(&t, whoops.as_ref())
.await
.is_err());
let whoops = t.get_blobdir().join("whoops");
assert!(!whoops.exists());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_from_path() {
let t = TestContext::new().await;
let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t, src_ext.as_ref())
.await
.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/external");
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
let src_int = t.get_blobdir().join("internal");
fs::write(&src_int, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t, &src_int).await.unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/internal");
let data = fs::read(blob.to_abs_path()).await.unwrap();
assert_eq!(data, b"boo");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_from_name_long() {
let t = TestContext::new().await;
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").await.unwrap();
let blob = BlobObject::new_from_path(&t, src_ext.as_ref())
.await
.unwrap();
let blob = BlobObject::create_and_deduplicate(&t, &src_ext, &src_ext).unwrap();
assert_eq!(
blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html"
"$BLOBDIR/06f010b24d1efe57ffab44a8ad20c54.html"
);
}
@@ -166,64 +126,6 @@ fn test_is_blob_name() {
assert!(!BlobObject::is_acceptible_blob_name("foo\x00bar"));
}
#[test]
fn test_sanitise_name() {
let (stem, ext) = BlobObject::sanitize_name_and_split_extension(
"Я ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ.txt",
);
assert_eq!(ext, ".txt");
assert!(!stem.is_empty());
// the extensions are kept together as between stem and extension a number may be added -
// and `foo.tar.gz` should become `foo-1234.tar.gz` and not `foo.tar-1234.gz`
let (stem, ext) = BlobObject::sanitize_name_and_split_extension("wot.tar.gz");
assert_eq!(stem, "wot");
assert_eq!(ext, ".tar.gz");
let (stem, ext) = BlobObject::sanitize_name_and_split_extension(".foo.bar");
assert_eq!(stem, "file");
assert_eq!(ext, ".foo.bar");
let (stem, ext) = BlobObject::sanitize_name_and_split_extension("foo?.bar");
assert!(stem.contains("foo"));
assert!(!stem.contains('?'));
assert_eq!(ext, ".bar");
let (stem, ext) = BlobObject::sanitize_name_and_split_extension("no-extension");
assert_eq!(stem, "no-extension");
assert_eq!(ext, "");
let (stem, ext) =
BlobObject::sanitize_name_and_split_extension("path/ignored\\this: is* forbidden?.c");
assert_eq!(ext, ".c");
assert!(!stem.contains("path"));
assert!(!stem.contains("ignored"));
assert!(stem.contains("this"));
assert!(stem.contains("forbidden"));
assert!(!stem.contains('/'));
assert!(!stem.contains('\\'));
assert!(!stem.contains(':'));
assert!(!stem.contains('*'));
assert!(!stem.contains('?'));
let (stem, ext) = BlobObject::sanitize_name_and_split_extension(
"file.with_lots_of_characters_behind_point_and_double_ending.tar.gz",
);
assert_eq!(
stem,
"file.with_lots_of_characters_behind_point_and_double_ending"
);
assert_eq!(ext, ".tar.gz");
let (stem, ext) = BlobObject::sanitize_name_and_split_extension("a. tar.tar.gz");
assert_eq!(stem, "a. tar");
assert_eq!(ext, ".tar.gz");
let (stem, ext) = BlobObject::sanitize_name_and_split_extension("Guia_uso_GNB (v0.8).pdf");
assert_eq!(stem, "Guia_uso_GNB (v0.8)");
assert_eq!(ext, ".pdf");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_add_white_bg() {
let t = TestContext::new().await;
@@ -236,7 +138,7 @@ async fn test_add_white_bg() {
let avatar_src = t.dir.path().join("avatar.png");
fs::write(&avatar_src, bytes).await.unwrap();
let mut blob = BlobObject::new_from_path(&t, &avatar_src).await.unwrap();
let mut blob = BlobObject::create_and_deduplicate(&t, &avatar_src, &avatar_src).unwrap();
let img_wh = 128;
let maybe_sticker = &mut false;
let strict_limits = true;
@@ -285,7 +187,7 @@ async fn test_selfavatar_outside_blobdir() {
constants::BALANCED_AVATAR_SIZE,
);
let mut blob = BlobObject::new_from_path(&t, avatar_path).await.unwrap();
let mut blob = BlobObject::create_and_deduplicate(&t, avatar_path, avatar_path).unwrap();
let maybe_sticker = &mut false;
let strict_limits = true;
blob.recode_to_size(&t, None, maybe_sticker, 1000, 3000, strict_limits)
@@ -643,9 +545,9 @@ impl SendImageCheckMediaquality<'_> {
check_image_size(file_saved, compressed_width, compressed_height);
if original_width == compressed_width {
assert_extension(&alice, alice_msg, extension).await;
assert_extension(&alice, alice_msg, extension);
} else {
assert_extension(&alice, alice_msg, "jpg").await;
assert_extension(&alice, alice_msg, "jpg");
}
let bob_msg = bob.recv_msg(&sent).await;
@@ -668,16 +570,16 @@ impl SendImageCheckMediaquality<'_> {
let img = check_image_size(file_saved, compressed_width, compressed_height);
if original_width == compressed_width {
assert_extension(&bob, bob_msg, extension).await;
assert_extension(&bob, bob_msg, extension);
} else {
assert_extension(&bob, bob_msg, "jpg").await;
assert_extension(&bob, bob_msg, "jpg");
}
Ok(img)
}
}
async fn assert_extension(context: &TestContext, msg: Message, extension: &str) {
fn assert_extension(context: &TestContext, msg: Message, extension: &str) {
assert!(msg
.param
.get(Param::File)
@@ -703,8 +605,7 @@ async fn assert_extension(context: &TestContext, msg: Message, extension: &str)
);
assert_eq!(
msg.param
.get_blob(Param::File, context)
.await
.get_file_blob(context)
.unwrap()
.unwrap()
.suffix()

View File

@@ -904,8 +904,7 @@ impl ChatId {
if msg.viewtype == Viewtype::Vcard {
let blob = msg
.param
.get_blob(Param::File, context)
.await?
.get_file_blob(context)?
.context("no file stored in params")?;
msg.try_set_vcard(context, &blob.to_abs_path()).await?;
}
@@ -2751,8 +2750,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
} else if msg.viewtype.has_file() {
let mut blob = msg
.param
.get_blob(Param::File, context)
.await?
.get_file_blob(context)?
.with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
let send_as_is = msg.viewtype == Viewtype::File;

View File

@@ -9,7 +9,6 @@ use crate::tools::time;
use crate::webxdc::StatusUpdateItem;
use async_channel::{self as channel, Receiver, Sender};
use serde_json::json;
use std::path::PathBuf;
use tokio::task;
#[derive(Debug)]
@@ -100,9 +99,7 @@ pub async fn maybe_set_logging_xdc(
context,
msg.get_viewtype(),
chat_id,
msg.param
.get_path(Param::Filename, context)
.unwrap_or_default(),
msg.param.get(Param::Filename),
msg.get_id(),
)
.await?;
@@ -115,18 +112,16 @@ pub async fn maybe_set_logging_xdc_inner(
context: &Context,
viewtype: Viewtype,
chat_id: ChatId,
filename: Option<PathBuf>,
filename: Option<&str>,
msg_id: MsgId,
) -> anyhow::Result<()> {
if viewtype == Viewtype::Webxdc {
if let Some(file) = filename {
if let Some(file_name) = file.file_name().and_then(|name| name.to_str()) {
if file_name.starts_with("debug_logging")
&& file_name.ends_with(".xdc")
&& chat_id.is_self_talk(context).await?
{
set_debug_logging_xdc(context, Some(msg_id)).await?;
}
if let Some(filename) = filename {
if filename.starts_with("debug_logging")
&& filename.ends_with(".xdc")
&& chat_id.is_self_talk(context).await?
{
set_debug_logging_xdc(context, Some(msg_id)).await?;
}
}
}

View File

@@ -636,7 +636,7 @@ impl Message {
/// Returns the full path to the file associated with a message.
pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
self.param.get_path(Param::File, context).unwrap_or(None)
self.param.get_file_path(context).unwrap_or(None)
}
/// Returns vector of vcards if the file has a vCard attachment.
@@ -669,7 +669,7 @@ impl Message {
/// If message is an image or gif, set Param::Width and Param::Height
pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
if self.viewtype.has_file() {
let file_param = self.param.get_path(Param::File, context)?;
let file_param = self.param.get_file_path(context)?;
if let Some(path_and_filename) = file_param {
if (self.viewtype == Viewtype::Image || self.viewtype == Viewtype::Gif)
&& !self.param.exists(Param::Width)
@@ -817,7 +817,7 @@ impl Message {
/// Returns the size of the file in bytes, if applicable.
pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
if let Some(path) = self.param.get_path(Param::File, context)? {
if let Some(path) = self.param.get_file_path(context)? {
Ok(Some(get_filebytes(context, &path).await.with_context(
|| format!("failed to get {} size in bytes", path.display()),
)?))

View File

@@ -768,7 +768,7 @@ async fn test_sanitize_filename_message() -> Result<()> {
msg.set_file_from_bytes(t, "/\\:ee.tx*T ", b"hallo", None)?;
assert_eq!(msg.get_filename().unwrap(), "ee.txT");
let blob = msg.param.get_blob(Param::File, t).await?.unwrap();
let blob = msg.param.get_file_blob(t)?.unwrap();
assert_eq!(blob.suffix().unwrap(), "txt");
// The filename shouldn't be empty if there were only illegal characters:

View File

@@ -1628,8 +1628,7 @@ async fn build_body_file(context: &Context, msg: &Message) -> Result<MimePart<'s
let blob = msg
.param
.get_blob(Param::File, context)
.await?
.get_file_blob(context)?
.context("msg has no file")?;
// Get file name to use for sending. For privacy purposes, we do

View File

@@ -785,7 +785,7 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg==
// Make sure the file is there even though the html is wrong:
let param = &message.parts[0].param;
let blob: BlobObject = param.get_blob(Param::File, &t).await.unwrap().unwrap();
let blob: BlobObject = param.get_file_blob(&t).unwrap().unwrap();
let f = tokio::fs::File::open(blob.to_abs_path()).await.unwrap();
let size = f.metadata().await.unwrap().len();
assert_eq!(size, 154);
@@ -1871,8 +1871,7 @@ This is the epilogue. It is also to be ignored.";
assert_eq!(mimeparser.parts[0].typ, Viewtype::File);
let blob: BlobObject = mimeparser.parts[0]
.param
.get_blob(Param::File, &context)
.await
.get_file_blob(&context)
.unwrap()
.unwrap();
assert_eq!(
@@ -1883,8 +1882,7 @@ This is the epilogue. It is also to be ignored.";
assert_eq!(mimeparser.parts[1].typ, Viewtype::File);
let blob: BlobObject = mimeparser.parts[1]
.param
.get_blob(Param::File, &context)
.await
.get_file_blob(&context)
.unwrap()
.unwrap();
assert_eq!(

View File

@@ -3,6 +3,7 @@ use std::fmt;
use std::path::PathBuf;
use std::str;
use anyhow::ensure;
use anyhow::{bail, Error, Result};
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
@@ -295,6 +296,9 @@ impl Params {
/// Set the given key to the passed in value.
pub fn set(&mut self, key: Param, value: impl ToString) -> &mut Self {
if key == Param::File {
debug_assert!(value.to_string().starts_with("$BLOBDIR/"));
}
self.inner.insert(key, value.to_string());
self
}
@@ -357,59 +361,20 @@ impl Params {
self.get(key).and_then(|s| s.parse().ok())
}
/// Gets the given parameter and parse as [ParamsFile].
///
/// See also [Params::get_blob] and [Params::get_path] which may
/// be more convenient.
pub fn get_file<'a>(&self, key: Param, context: &'a Context) -> Result<Option<ParamsFile<'a>>> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
ParamsFile::from_param(context, val).map(Some)
}
/// Gets the parameter and returns a [BlobObject] for it.
///
/// This parses the parameter value as a [ParamsFile] and than
/// tries to return a [BlobObject] for that file. If the file is
/// not yet a valid blob, one will be created by copying the file.
///
/// Note that in the [ParamsFile::FsPath] case the blob can be
/// created without copying if the path already refers to a valid
/// blob. If so a [BlobObject] will be returned.
pub async fn get_blob<'a>(
&self,
key: Param,
context: &'a Context,
) -> Result<Option<BlobObject<'a>>> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
let file = ParamsFile::from_param(context, val)?;
let blob = match file {
ParamsFile::FsPath(path) => BlobObject::new_from_path(context, &path).await?,
ParamsFile::Blob(blob) => blob,
/// Returns a [BlobObject] for the [Param::File] parameter.
pub fn get_file_blob<'a>(&self, context: &'a Context) -> Result<Option<BlobObject<'a>>> {
let Some(val) = self.get(Param::File) else {
return Ok(None);
};
ensure!(val.starts_with("$BLOBDIR/"));
let blob = BlobObject::from_name(context, val.to_string())?;
Ok(Some(blob))
}
/// Gets the parameter and returns a [PathBuf] for it.
///
/// This parses the parameter value as a [ParamsFile] and returns
/// a [PathBuf] to the file.
pub fn get_path(&self, key: Param, context: &Context) -> Result<Option<PathBuf>> {
let val = match self.get(key) {
Some(val) => val,
None => return Ok(None),
};
let file = ParamsFile::from_param(context, val)?;
let path = match file {
ParamsFile::FsPath(path) => path,
ParamsFile::Blob(blob) => blob.to_abs_path(),
};
Ok(Some(path))
/// Returns a [PathBuf] for the [Param::File] parameter.
pub fn get_file_path(&self, context: &Context) -> Result<Option<PathBuf>> {
let blob = self.get_file_blob(context)?;
Ok(blob.map(|p| p.to_abs_path()))
}
/// Set the given parameter to the passed in `i32`.
@@ -431,48 +396,18 @@ impl Params {
}
}
/// The value contained in [Param::File].
///
/// Because the only way to construct this object is from a valid
/// UTF-8 string it is always safe to convert the value contained
/// within the [ParamsFile::FsPath] back to a [String] or [&str].
/// Despite the type itself does not guarantee this.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParamsFile<'a> {
FsPath(PathBuf),
Blob(BlobObject<'a>),
}
impl<'a> ParamsFile<'a> {
/// Parse the [Param::File] value into an object.
///
/// If the value was stored into the [Params] correctly this
/// should not fail.
pub fn from_param(context: &'a Context, src: &str) -> Result<ParamsFile<'a>> {
let param = match src.starts_with("$BLOBDIR/") {
true => ParamsFile::Blob(BlobObject::from_name(context, src.to_string())?),
false => ParamsFile::FsPath(PathBuf::from(src)),
};
Ok(param)
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use std::str::FromStr;
use tokio::fs;
use super::*;
use crate::test_utils::TestContext;
#[test]
fn test_dc_param() {
let mut p1: Params = "a=1\nf=2\nc=3".parse().unwrap();
let mut p1: Params = "a=1\nw=2\nc=3".parse().unwrap();
assert_eq!(p1.get_int(Param::Forwarded), Some(1));
assert_eq!(p1.get_int(Param::File), Some(2));
assert_eq!(p1.get_int(Param::Width), Some(2));
assert_eq!(p1.get_int(Param::Height), None);
assert!(!p1.exists(Param::Height));
@@ -483,13 +418,13 @@ mod tests {
let mut p1 = Params::new();
p1.set(Param::Forwarded, "foo")
.set_int(Param::File, 2)
.set_int(Param::Width, 2)
.remove(Param::GuaranteeE2ee)
.set_int(Param::Duration, 4);
assert_eq!(p1.to_string(), "a=foo\nd=4\nf=2");
assert_eq!(p1.to_string(), "a=foo\nd=4\nw=2");
p1.remove(Param::File);
p1.remove(Param::Width);
assert_eq!(p1.to_string(), "a=foo\nd=4",);
assert_eq!(p1.len(), 2);
@@ -511,56 +446,6 @@ mod tests {
assert_eq!(params.to_string().parse::<Params>().unwrap(), params);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_params_file_fs_path() {
let t = TestContext::new().await;
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t, "/foo/bar/baz").unwrap() {
assert_eq!(p, Path::new("/foo/bar/baz"));
} else {
panic!("Wrong enum variant");
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_params_file_blob() {
let t = TestContext::new().await;
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else {
panic!("Wrong enum variant");
}
}
// Tests for Params::get_file(), Params::get_path() and Params::get_blob().
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_params_get_fileparam() {
let t = TestContext::new().await;
let fname = t.dir.path().join("foo");
let mut p = Params::new();
p.set(Param::File, fname.to_str().unwrap());
let file = p.get_file(Param::File, &t).unwrap().unwrap();
assert_eq!(file, ParamsFile::FsPath(fname.clone()));
let path: PathBuf = p.get_path(Param::File, &t).unwrap().unwrap();
assert_eq!(path, fname);
fs::write(fname, b"boo").await.unwrap();
let blob = p.get_blob(Param::File, &t).await.unwrap().unwrap();
assert!(blob.as_name().starts_with("$BLOBDIR/foo"));
// Blob in blobdir, expect blob.
let bar_path = t.get_blobdir().join("bar");
p.set(Param::File, bar_path.to_str().unwrap());
let blob = p.get_blob(Param::File, &t).await.unwrap().unwrap();
assert_eq!(blob, BlobObject::from_name(&t, "bar".to_string()).unwrap());
p.remove(Param::File);
assert!(p.get_file(Param::File, &t).unwrap().is_none());
assert!(p.get_path(Param::File, &t).unwrap().is_none());
assert!(p.get_blob(Param::File, &t).await.unwrap().is_none());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_params_unknown_key() -> Result<()> {
// 'Z' is used as a key that is known to be unused; these keys should be ignored silently by definition.

View File

@@ -1727,9 +1727,7 @@ RETURNING id
context,
part.typ,
chat_id,
part.param
.get_path(Param::File, context)
.unwrap_or_default(),
part.param.get(Param::Filename),
*msg_id,
)
.await?;

View File

@@ -5612,7 +5612,7 @@ PGh0bWw+PGJvZHk+dGV4dDwvYm9keT5kYXRh
assert_eq!(msg.get_filename().unwrap(), "test.HTML");
let blob = msg.param.get_blob(Param::File, alice).await?.unwrap();
let blob = msg.param.get_file_blob(alice)?.unwrap();
assert_eq!(blob.suffix().unwrap(), "html");
Ok(())

View File

@@ -251,7 +251,7 @@ impl Sql {
if recode_avatar {
if let Some(avatar) = context.get_config(Config::Selfavatar).await? {
let mut blob = BlobObject::new_from_path(context, avatar.as_ref()).await?;
let mut blob = BlobObject::from_name(context, avatar)?;
match blob.recode_to_avatar_size(context).await {
Ok(()) => {
if let Some(path) = blob.to_abs_path().to_str() {

View File

@@ -469,7 +469,8 @@ mod tests {
let mut msg = Message::new(Viewtype::File);
msg.set_text(some_text.clone());
msg.param.set(Param::File, "foo.bar");
msg.set_file_from_bytes(ctx, "foo.bar", b"data", None)
.unwrap();
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_summary_texts(&msg, ctx, "Autocrypt Setup Message").await; // file name is not added for autocrypt setup messages
}

View File

@@ -560,3 +560,38 @@ fn test_parse_mailto() {
reps
);
}
#[test]
fn test_sanitize_filename() {
let name = sanitize_filename("Я ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ.txt");
assert!(!name.is_empty());
let name = sanitize_filename("wot.tar.gz");
assert_eq!(name, "wot.tar.gz");
let name = sanitize_filename(".foo.bar");
assert_eq!(name, "file.foo.bar");
let name = sanitize_filename("foo?.bar");
assert_eq!(name, "foo.bar");
assert!(!name.contains('?'));
let name = sanitize_filename("no-extension");
assert_eq!(name, "no-extension");
let name = sanitize_filename("path/ignored\\this: is* forbidden?.c");
assert_eq!(name, "this is forbidden.c");
let name =
sanitize_filename("file.with_lots_of_characters_behind_point_and_double_ending.tar.gz");
assert_eq!(
name,
"file.with_lots_of_characters_behind_point_and_double_ending.tar.gz"
);
let name = sanitize_filename("a. tar.tar.gz");
assert_eq!(name, "a. tar.tar.gz");
let name = sanitize_filename("Guia_uso_GNB (v0.8).pdf");
assert_eq!(name, "Guia_uso_GNB (v0.8).pdf");
}