Merge branch 'mediafixes' into 'next'

fix: old media used spaces in content disposition without quotes

See merge request famedly/conduit!717
This commit is contained in:
Timo Kösters 2024-09-25 11:53:05 +00:00
commit bca8d1f70f
6 changed files with 114 additions and 33 deletions

27
Cargo.lock generated
View file

@ -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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.2.6", "indexmap 2.2.6",
@ -2341,7 +2341,7 @@ dependencies = [
[[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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"bytes", "bytes",
"http 1.1.0", "http 1.1.0",
@ -2359,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror", "thiserror",
@ -2368,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -2378,8 +2378,9 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.13.0" version = "0.13.0"
source = "git+https://github.com/ruma/ruma#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"cfg-if",
"once_cell", "once_cell",
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@ -2393,7 +2394,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -2405,7 +2406,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"headers", "headers",
"http 1.1.0", "http 1.1.0",
@ -2418,7 +2419,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",
@ -2434,7 +2435,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#82417e394076440089cd8ada87485d9a44cc4ba0" source = "git+https://github.com/ruma/ruma#c06af4385e0e30c48a8e9ca3d488da32102d0db9"
dependencies = [ dependencies = [
"itertools", "itertools",
"js_int", "js_int",

View file

@ -188,11 +188,11 @@ async fn get_content(
) -> Result<get_content::v1::Response, Error> { ) -> Result<get_content::v1::Response, Error> {
let mxc = format!("mxc://{}/{}", server_name, media_id); let mxc = format!("mxc://{}/{}", server_name, media_id);
if let Some(FileMeta { if let Ok(Some(FileMeta {
content_disposition, content_disposition,
content_type, content_type,
file, file,
}) = services().media.get(mxc.clone()).await? })) = services().media.get(mxc.clone()).await
{ {
Ok(get_content::v1::Response { Ok(get_content::v1::Response {
file, file,
@ -264,9 +264,9 @@ async fn get_content_as_filename(
) -> Result<get_content_as_filename::v1::Response, Error> { ) -> Result<get_content_as_filename::v1::Response, Error> {
let mxc = format!("mxc://{}/{}", server_name, media_id); let mxc = format!("mxc://{}/{}", server_name, media_id);
if let Some(FileMeta { if let Ok(Some(FileMeta {
file, content_type, .. file, content_type, ..
}) = services().media.get(mxc.clone()).await? })) = services().media.get(mxc.clone()).await
{ {
Ok(get_content_as_filename::v1::Response { Ok(get_content_as_filename::v1::Response {
file, file,
@ -348,9 +348,9 @@ async fn get_content_thumbnail(
) -> Result<get_content_thumbnail::v1::Response, Error> { ) -> Result<get_content_thumbnail::v1::Response, Error> {
let mxc = format!("mxc://{}/{}", server_name, media_id); let mxc = format!("mxc://{}/{}", server_name, media_id);
if let Some(FileMeta { if let Ok(Some(FileMeta {
file, content_type, .. file, content_type, ..
}) = services() })) = services()
.media .media
.get_thumbnail( .get_thumbnail(
mxc.clone(), mxc.clone(),
@ -359,9 +359,9 @@ async fn get_content_thumbnail(
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
height height
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?,
) )
.await? .await
{ {
Ok(get_content_thumbnail::v1::Response { file, content_type }) Ok(get_content_thumbnail::v1::Response { file, content_type })
} else if server_name != services().globals.server_name() && allow_remote { } else if server_name != services().globals.server_name() && allow_remote {

View file

@ -97,7 +97,7 @@ pub async fn join_room_by_id_or_alias_route(
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) { let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
Ok(room_id) => { Ok(room_id) => {
let mut servers = body.server_name.clone(); let mut servers = body.via.clone();
servers.extend( servers.extend(
services() services()
.rooms .rooms

View file

@ -10,7 +10,7 @@ use crate::{
services, utils, Error, PduEvent, Result, Ruma, services, utils, Error, PduEvent, Result, Ruma,
}; };
use axum::{response::IntoResponse, Json}; use axum::{response::IntoResponse, Json};
use axum_extra::headers::{authorization::Credentials, CacheControl, Header}; use axum_extra::headers::{CacheControl, Header};
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use http::header::AUTHORIZATION; use http::header::AUTHORIZATION;
@ -52,7 +52,6 @@ use ruma::{
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
}, },
serde::{Base64, JsonObject, Raw}, serde::{Base64, JsonObject, Raw},
server_util::authorization::XMatrix,
to_device::DeviceIdOrAllDevices, to_device::DeviceIdOrAllDevices,
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId,
@ -275,15 +274,15 @@ where
for s in signature_server { for s in signature_server {
http_request.headers_mut().insert( http_request.headers_mut().insert(
AUTHORIZATION, AUTHORIZATION,
XMatrix::parse(&format!( format!(
"X-Matrix origin=\"{}\",destination=\"{}\",key=\"{}\",sig=\"{}\"", "X-Matrix origin=\"{}\",destination=\"{}\",key=\"{}\",sig=\"{}\"",
services().globals.server_name(), services().globals.server_name(),
destination, destination,
s.0, s.0,
s.1 s.1
)) )
.expect("When Ruma signs JSON, it produces a valid base64 signature. All other types are valid ServerNames or OwnedKeyId") .try_into()
.encode(), .unwrap(),
); );
} }
} }
@ -343,7 +342,7 @@ where
response.map_err(|e| { response.map_err(|e| {
warn!( warn!(
"Invalid 200 response from {} on: {} {}", "Invalid 200 response from {} on: {} {:?}",
&destination, url, e &destination, url, e
); );
Error::BadServerResponse("Server returned bad 200 response.") Error::BadServerResponse("Server returned bad 200 response.")

View file

@ -63,9 +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 = content_disposition_bytes let content_disposition = content_disposition_bytes.try_into().unwrap_or_else(|_| {
.try_into() ContentDisposition::new(ruma::http_headers::ContentDispositionType::Inline)
.map_err(|_| Error::bad_database("Content Disposition in mediaid_file is invalid."))?; });
Ok((content_disposition, content_type, key)) Ok((content_disposition, content_type, key))
} }
} }

View file

@ -6,6 +6,7 @@ use crate::{
SERVICES, SERVICES,
}; };
use abstraction::{KeyValueDatabaseEngine, KvTree}; use abstraction::{KeyValueDatabaseEngine, KvTree};
use base64::{engine::general_purpose, Engine};
use directories::ProjectDirs; use directories::ProjectDirs;
use lru_cache::LruCache; use lru_cache::LruCache;
@ -424,7 +425,7 @@ impl KeyValueDatabase {
} }
// If the database has any data, perform data migrations before starting // If the database has any data, perform data migrations before starting
let latest_database_version = 13; let latest_database_version = 16;
if services().users.count()? > 0 { if services().users.count()? > 0 {
// MIGRATIONS // MIGRATIONS
@ -941,6 +942,86 @@ impl KeyValueDatabase {
warn!("Migration: 12 -> 13 finished"); warn!("Migration: 12 -> 13 finished");
} }
if services().globals.database_version()? < 16 {
// Reconstruct all media using the filesystem
db.mediaid_file.clear().unwrap();
for file in fs::read_dir(services().globals.get_media_folder()).unwrap() {
let file = file.unwrap();
let mediaid = general_purpose::URL_SAFE_NO_PAD
.decode(file.file_name().into_string().unwrap())
.unwrap();
let mut parts = mediaid.rsplit(|&b| b == 0xff);
let mut removed_bytes = 0;
let content_type_bytes = parts.next().unwrap();
removed_bytes += content_type_bytes.len() + 1;
let content_disposition_bytes = parts
.next()
.ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?;
removed_bytes += content_disposition_bytes.len();
let mut content_disposition =
utils::string_from_bytes(content_disposition_bytes).map_err(|_| {
Error::bad_database("Content Disposition in mediaid_file is invalid.")
})?;
if content_disposition.contains("filename=")
&& !content_disposition.contains("filename=\"")
{
println!("{}", &content_disposition);
content_disposition =
content_disposition.replacen("filename=", "filename=\"", 1);
content_disposition.push('"');
println!("{}", &content_disposition);
let mut new_key = mediaid[..(mediaid.len() - removed_bytes)].to_vec();
assert!(*new_key.last().unwrap() == 0xff);
let mut shorter_key = new_key.clone();
shorter_key.extend(
ruma::http_headers::ContentDisposition::new(
ruma::http_headers::ContentDispositionType::Inline,
)
.to_string()
.as_bytes(),
);
shorter_key.push(0xff);
shorter_key.extend_from_slice(content_type_bytes);
new_key.extend_from_slice(content_disposition.to_string().as_bytes());
new_key.push(0xff);
new_key.extend_from_slice(content_type_bytes);
// Some file names are too long. Ignore those.
match fs::rename(
services().globals.get_media_file(&mediaid),
services().globals.get_media_file(&new_key),
) {
Ok(_) => {
db.mediaid_file.insert(&new_key, &[])?;
}
Err(_) => {
fs::rename(
services().globals.get_media_file(&mediaid),
services().globals.get_media_file(&shorter_key),
)
.unwrap();
db.mediaid_file.insert(&shorter_key, &[])?;
}
}
} else {
db.mediaid_file.insert(&mediaid, &[])?;
}
}
services().globals.bump_database_version(16)?;
warn!("Migration: 13 -> 16 finished");
}
assert_eq!( assert_eq!(
services().globals.database_version().unwrap(), services().globals.database_version().unwrap(),
latest_database_version latest_database_version