Merge branch 'content-disposition-type' into 'next'
chore: Use Content-Disposition type for HTTP header Closes #463 See merge request famedly/conduit!713
This commit is contained in:
commit
a9c3867287
5 changed files with 70 additions and 57 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2371,7 +2378,7 @@ 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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
|
@ -2386,7 +2393,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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2398,7 +2405,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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"headers",
|
"headers",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
|
@ -2411,7 +2418,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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
@ -2427,7 +2434,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#82417e394076440089cd8ada87485d9a44cc4ba0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
|
|
@ -4,12 +4,15 @@
|
||||||
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 ruma::{
|
||||||
|
api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
media::{
|
media::{
|
||||||
create_content, get_content, get_content_as_filename, get_content_thumbnail,
|
create_content, get_content, get_content_as_filename, get_content_thumbnail,
|
||||||
get_media_config,
|
get_media_config,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
http_headers::{ContentDisposition, ContentDispositionType},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MXC_LENGTH: usize = 32;
|
const MXC_LENGTH: usize = 32;
|
||||||
|
@ -44,10 +47,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,
|
||||||
)
|
)
|
||||||
|
@ -82,7 +85,7 @@ pub async fn get_remote_content(
|
||||||
.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,
|
||||||
)
|
)
|
||||||
|
@ -110,7 +113,7 @@ pub async fn get_content_route(
|
||||||
Ok(get_content::v3::Response {
|
Ok(get_content::v3::Response {
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition,
|
content_disposition: Some(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 {
|
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||||
|
@ -145,7 +148,10 @@ pub async fn get_content_as_filename_route(
|
||||||
Ok(get_content_as_filename::v3::Response {
|
Ok(get_content_as_filename::v3::Response {
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition: Some(format!("inline; filename={}", body.filename)),
|
content_disposition: Some(
|
||||||
|
ContentDisposition::new(ContentDispositionType::Inline)
|
||||||
|
.with_filename(Some(body.filename.clone())),
|
||||||
|
),
|
||||||
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 {
|
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||||
|
@ -153,7 +159,10 @@ pub async fn get_content_as_filename_route(
|
||||||
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
||||||
|
|
||||||
Ok(get_content_as_filename::v3::Response {
|
Ok(get_content_as_filename::v3::Response {
|
||||||
content_disposition: Some(format!("inline: filename={}", body.filename)),
|
content_disposition: Some(
|
||||||
|
ContentDisposition::new(ContentDispositionType::Inline)
|
||||||
|
.with_filename(Some(body.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()),
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
|
@ -216,7 +225,6 @@ pub async fn get_content_thumbnail_route(
|
||||||
.media
|
.media
|
||||||
.upload_thumbnail(
|
.upload_thumbnail(
|
||||||
mxc,
|
mxc,
|
||||||
None,
|
|
||||||
get_thumbnail_response.content_type.as_deref(),
|
get_thumbnail_response.content_type.as_deref(),
|
||||||
body.width.try_into().expect("all UInts are valid u32s"),
|
body.width.try_into().expect("all UInts are valid u32s"),
|
||||||
body.height.try_into().expect("all UInts are valid u32s"),
|
body.height.try_into().expect("all UInts are valid u32s"),
|
||||||
|
|
|
@ -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
|
||||||
None
|
.try_into()
|
||||||
} else {
|
.map_err(|_| Error::bad_database("Content Disposition in mediaid_file is invalid."))?;
|
||||||
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue