From fc23cc1568b3801536d4bc53a23c8f31149b5e8e Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Tue, 17 Feb 2026 07:54:08 +0000 Subject: [PATCH] Fix custom profile field values being double-serialized with escapes. Signed-off-by: Jason Volk --- src/api/client/profile.rs | 27 ++++++++++++++++++++------- src/api/client/unstable.rs | 1 + src/api/server/query.rs | 14 ++++++++++++-- src/service/users/profile.rs | 8 +++++--- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index 1c487da4..52c6bc50 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -292,7 +292,7 @@ pub(crate) async fn get_profile_route( return Err!(Request(NotFound("Profile was not found."))); } - let mut custom_profile_fields: BTreeMap = services + let mut custom_profile_fields: BTreeMap = services .users .all_profile_keys(&body.user_id) .collect() @@ -317,11 +317,24 @@ pub(crate) async fn get_profile_route( ("m.tz", tz), ]; - let response = canonical_fields + Ok(canonical_fields .into_iter() - .filter_map(|(key, val)| val.map(|val| (key, val))) - .map(|(key, val)| (key.to_owned(), val.into())) - .chain(custom_profile_fields.into_iter()); - - Ok(response.collect::()) + .map(|(key, val)| (key.to_owned(), val)) + .filter_map(|(key, val)| { + val.map(serde_json::to_value) + .transpose() + .ok() + .flatten() + .map(|val| (key, val)) + }) + .chain( + custom_profile_fields + .into_iter() + .filter_map(|(key, val)| { + serde_json::to_value(val.json()) + .map(|val| (key, val)) + .ok() + }), + ) + .collect()) } diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index 9d5802a0..802c45f5 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -366,6 +366,7 @@ pub(crate) async fn get_profile_field_route( .users .profile_key(&body.user_id, body.field.as_str()) .await + .and_then(|val| serde_json::to_value(val.json()).map_err(Into::into)) .map_err(|_| err!(Request(NotFound("The requested profile key does not exist."))))?; let profile_key_value = ProfileFieldValue::new(body.field.as_str(), value)?; diff --git a/src/api/server/query.rs b/src/api/server/query.rs index d82a3ab5..c5ed1cc9 100644 --- a/src/api/server/query.rs +++ b/src/api/server/query.rs @@ -98,7 +98,11 @@ pub(crate) async fn get_profile_information_route( .await { Ok(Response { - custom_profile_fields: [(custom_field.to_string(), value)].into(), + custom_profile_fields: [( + custom_field.to_string(), + serde_json::to_value(value.json())?, + )] + .into(), ..Response::default() }) } else { @@ -117,11 +121,17 @@ pub(crate) async fn get_profile_information_route( let custom_profile_fields = services .users .all_profile_keys(&body.user_id) - .collect(); + .collect::>(); let (avatar_url, blurhash, custom_profile_fields, displayname, tz) = join5(avatar_url, blurhash, custom_profile_fields, displayname, tz).await; + let custom_profile_fields = custom_profile_fields + .into_iter() + .map(|(k, v)| (k, serde_json::to_value(v))) + .filter_map(|(k, v)| v.ok().map(|v| (k, v))) + .collect(); + Ok(Response { avatar_url, blurhash, diff --git a/src/service/users/profile.rs b/src/service/users/profile.rs index b8467f84..38719e8a 100644 --- a/src/service/users/profile.rs +++ b/src/service/users/profile.rs @@ -1,7 +1,9 @@ use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::join3}; use ruma::{ MxcUri, OwnedMxcUri, OwnedRoomId, UserId, + api::client::profile::ProfileFieldValue, events::room::member::{MembershipState, RoomMemberEventContent}, + serde::Raw, }; use tuwunel_core::{ Result, implement, @@ -216,8 +218,8 @@ pub async fn timezone(&self, user_id: &UserId) -> Result { pub fn all_profile_keys<'a>( &'a self, user_id: &'a UserId, -) -> impl Stream + 'a + Send { - type KeyVal = ((Ignore, String), serde_json::Value); +) -> impl Stream)> + 'a + Send { + type KeyVal = ((Ignore, String), Raw); let prefix = (user_id, Interfix); self.db @@ -253,7 +255,7 @@ pub async fn profile_key( &self, user_id: &UserId, profile_key: &str, -) -> Result { +) -> Result> { let key = (user_id, profile_key); self.db .useridprofilekey_value