1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::{
store::{BlobEntry, StoreError, TABLE_AVATAR},
Agent, Event,
};
use tokio_xmpp::connect::ServerConnector;
use tokio_xmpp::parsers::{
avatar::{Data, Metadata},
iq::Iq,
ns,
pubsub::{
event::Item,
pubsub::{Items, PubSub},
NodeName,
},
sha1::{Digest, Sha1},
Jid,
};
pub(crate) async fn handle_metadata_pubsub_event<C: ServerConnector>(
from: &Jid,
agent: &mut Agent<C>,
items: Vec<Item>,
) -> Vec<Event> {
let mut events = Vec::new();
for item in items {
let payload = item.payload.clone().unwrap();
if payload.is("metadata", ns::AVATAR_METADATA) {
match Metadata::try_from(payload) {
Ok(metadata) => {
if let Some(avatar_store) = agent.store.table_option(&TABLE_AVATAR).await {
for info in metadata.infos {
let avatar_id = info.id.to_hex();
// Check if entry exists
if let Ok(true) = avatar_store.has(&avatar_id).await {
let entry = BlobEntry::new(TABLE_AVATAR, avatar_id);
events.push(Event::AvatarRetrieved(from.clone(), entry));
} else {
let iq = download_avatar(from);
let _ = agent.client.send_stanza(iq.into()).await;
}
}
}
}
Err(e) => error!("Error parsing avatar metadata: {}", e),
}
}
}
events
}
fn download_avatar(from: &Jid) -> Iq {
Iq::from_get(
"coucou",
PubSub::Items(Items {
max_items: None,
node: NodeName(String::from(ns::AVATAR_DATA)),
subid: None,
items: Vec::new(),
}),
)
.with_to(from.clone())
}
// The return value of this function will be simply pushed to a Vec in the caller function,
// so it makes no sense to allocate a Vec here - we're lazy instead
// NOTE: This function is called when:
// - in pubsub::handle_iq_result, ns::AVATAR_DATA is matched
// - called by Agent::handle_iq when IqType::Result and ns::PUBSUB is matched
// - called by Agent::wait_for_events when element is <iq>
pub(crate) async fn handle_data_pubsub_iq<'a, C: ServerConnector>(
agent: &'a mut Agent<C>,
from: &'a Jid,
items: &'a Items,
) -> impl IntoIterator<Item = Event> + 'a {
let from = from.clone();
let mut events = vec![];
for item in items.items.iter() {
match (&item.id, &item.payload) {
(Some(id), Some(payload)) => {
let data = Data::try_from(payload.clone()).unwrap();
// Try to save. We have Ok(Err(hash)) if hashes don't match
// TODO: error handling
match try_save_avatar(agent, &id.0, &data.data).await.unwrap() {
Ok(blob_entry) => {
events.push(Event::AvatarRetrieved(from.clone(), blob_entry));
}
Err(actual_hash) => {
// Hash mismatch
warn!(
"Received avatar id {} from {} with wrong SHA1 hash: {}",
&id.0, &from, actual_hash
);
}
}
}
_ => {}
}
}
events
}
/// Try save a received avatar to the DataStore
///
/// Returns outer DataStoreError if the store call failed
/// Returns inner String with actual hash if data does not match `sha1_hash`
/// Returns Ok(Ok(TableKey)) when successful.
pub async fn try_save_avatar<C: ServerConnector>(
agent: &mut Agent<C>,
sha1_hash: &str,
data: &[u8],
) -> Result<Result<BlobEntry, String>, StoreError> {
let mut hasher = Sha1::new();
hasher.update(data);
let hasher = hasher.finalize();
let data_hash = format!("{:x}", hasher);
if sha1_hash != data_hash {
return Ok(Err(data_hash));
}
let entry = BlobEntry::new(TABLE_AVATAR, sha1_hash.to_string());
agent.store.set_in_table(&entry, data).await?;
Ok(Ok(entry))
}