Skip to content

Experiment: Webxdc: add virtual directory for fetching avatars and group memberlist #6429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1829,6 +1829,20 @@ impl CommandApi {
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
}

/// Returns webxdc memberlist, each member is a tuple (private user id, display_name)
/// Only includes members that have a known public key in the database
async fn get_webxdc_memberlist(
&self,
account_id: u32,
instance_msg_id: u32,
) -> Result<Vec<(String, String)>> {
let ctx = self.get_context(account_id).await?;
Message::load_from_db(&ctx, MsgId::new(instance_msg_id))
.await?
.get_webxdc_memberlist(&ctx)
.await
}

/// Get href from a WebxdcInfoMessage which might include a hash holding
/// information about a specific position or state in a webxdc app (optional)
async fn get_webxdc_href(
Expand Down
12 changes: 12 additions & 0 deletions src/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,18 @@ impl Contact {
&self.addr
}

/// Get display name. This is the name as defined by the contact himself,
/// modified by the user or, if both are unset, an empty string.
pub fn get_display_name_without_email(&self) -> String {
if !self.name.is_empty() {
return self.name.clone();
}
if !self.authname.is_empty() {
return self.authname.clone();
}
String::new()
}

/// Get a summary of authorized name and address.
///
/// The returned string is either "Name (email@domain.com)" or just
Expand Down
86 changes: 80 additions & 6 deletions src/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ use sha2::{Digest, Sha256};

use crate::chat::{self, Chat};
use crate::constants::Chattype;
use crate::contact::ContactId;
use crate::contact::{self, ContactId};
use crate::context::Context;
use crate::events::EventType;
use crate::key::{load_self_public_key, DcKey};
use crate::key::{load_self_public_key, DcKey, Fingerprint};
use crate::message::{Message, MessageState, MsgId, Viewtype};
use crate::mimefactory::wrapped_base64_encode;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::param::Params;
use crate::peerstate::Peerstate;
use crate::tools::create_id;
use crate::tools::{create_smeared_timestamp, get_abs_path};

Expand All @@ -57,6 +58,7 @@ const WEBXDC_API_VERSION: u32 = 1;
/// Suffix used to recognize webxdc files.
pub const WEBXDC_SUFFIX: &str = "xdc";
const WEBXDC_DEFAULT_ICON: &str = "__webxdc__/default-icon.png";
const WEBXDC_AVATAR_VIRTUAL_DIR: &str = "__webxdc__/avatar/";

/// Text shown to classic e-mail users in the visible e-mail body.
const BODY_DESCR: &str = "Webxdc Status Update";
Expand Down Expand Up @@ -882,6 +884,32 @@ impl Message {
name
};

// Virtual directory for accessing avatars
if name.starts_with(WEBXDC_AVATAR_VIRTUAL_DIR) {
let memberlist = self.get_internal_webxdc_memberlist(context).await?;
let user_id = name
.strip_prefix(WEBXDC_AVATAR_VIRTUAL_DIR)
.context("invalid avatar user id")?
.strip_suffix(".jpg")
.context("invalid avatar user id")?;
if let Some((_, contact_id)) = memberlist
.iter()
.find(|(member_user_id, _)| member_user_id == user_id)
{
if let Some(profile_image_path) = contact::Contact::get_by_id(context, *contact_id)
.await?
.get_profile_image(context)
.await?
{
return Ok(tokio::fs::read(profile_image_path).await?);
} else {
bail!("contact has no profile image")
}
} else {
bail!("user_id not found in group member list")
}
}

let archive = self.get_webxdc_archive(context).await?;

if name == "index.html" {
Expand Down Expand Up @@ -967,11 +995,57 @@ impl Message {
})
}

async fn get_webxdc_self_addr(&self, context: &Context) -> Result<String> {
let fingerprint = load_self_public_key(context).await?.dc_fingerprint().hex();
let data = format!("{}-{}", fingerprint, self.rfc724_mid);
fn get_webxdc_user_id(&self, pub_key_fingerprint: Fingerprint) -> String {
let data = format!("{}-{}", pub_key_fingerprint.hex(), self.rfc724_mid);
let hash = Sha256::digest(data.as_bytes());
Ok(format!("{:x}", hash))
format!("{:x}", hash)
}

async fn get_webxdc_self_addr(&self, context: &Context) -> Result<String> {
let fingerprint = load_self_public_key(context).await?.dc_fingerprint();
Ok(self.get_webxdc_user_id(fingerprint))
}

/// This is the internal memberlist, as it contains the contact id it should never be shared with the webxdc app
/// used by the function serving the virtual avatar directory
async fn get_internal_webxdc_memberlist(
&self,
context: &Context,
) -> Result<Vec<(String, ContactId)>> {
let contacts = chat::get_chat_contacts(context, self.get_chat_id()).await?;
let mut memberlist = Vec::with_capacity(contacts.len());
for contact in contacts {
if let Some(peerstate) =
Peerstate::from_addr(context, &contact.addr(context).await?).await?
{
// is this correct way to get the right/current key of contact?
if let Some(fingerprint) = peerstate.public_key_fingerprint {
memberlist.push((self.get_webxdc_user_id(fingerprint), contact));
}
}
}
Ok(memberlist)
}

/// Returns webxdc memberlist, each member is a tuple (private user id, display_name)
/// Only includes members that have a known public key in the database
pub async fn get_webxdc_memberlist(&self, context: &Context) -> Result<Vec<(String, String)>> {
// We could do the following to increase privacy:
// - remove displayname (not that big of a deal in reality)
// - only show people in the list that send an status update before in the group (would decrease usefulness, but would still bring enough benefit, if only as internal function to match avatars)
let members = self.get_internal_webxdc_memberlist(context).await?;
let mut memberlist = Vec::with_capacity(members.len());
for (member_id, contact) in members {
// TODO: think about wether we want to expose the nickname the user set for the contact here or just the name the contact set themselves?
// The former could be interpreted as privacy risk
// A. a webxdc could leak nicknames you set for users in the group,
// B. while the second could be seen as less useful/convinient for users "why are the contacts called differently in the webxdc"
let display_name = contact::Contact::get_by_id(context, contact)
.await?
.get_display_name_without_email();
memberlist.push((member_id, display_name));
}
Ok(memberlist)
}

/// Get link attached to an info message.
Expand Down
Loading