Skip to content

Commit

Permalink
Added Mediasending
Browse files Browse the repository at this point in the history
  • Loading branch information
wiomoc committed Jul 5, 2018
1 parent d33d841 commit 6f10723
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 70 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ Big thanks to [sigalor](https://github.com/sigalor) and contributors of [whatsap


## Features
* receive text-, image-, audiomessages
* send Textmessages
* send/receive text-, image-, audiomessages
* create/modify groups
* get userinfo (status, presence, profilepic)
* get contacts, chats
Expand All @@ -23,8 +22,9 @@ Big thanks to [sigalor](https://github.com/sigalor) and contributors of [whatsap
## TODO
* refactoring
* error messages
* send mediamessages
* ~~send mediamessages~~
* delete, unpin, unmute messages
* broadcast handling
* documention
* ...

Expand Down
75 changes: 75 additions & 0 deletions examples/media.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
extern crate simple_logger;
#[macro_use]
extern crate log;
extern crate qrcode;
extern crate image;
extern crate bincode;
extern crate whatsappweb;
extern crate reqwest;
extern crate base64;

use std::fs::{File, OpenOptions, remove_file};
use std::io::Read;
use std::sync::Arc;

use image::Luma;

use whatsappweb::connection;
use whatsappweb::connection::{DisconnectReason, PersistentSession, WhatsappWebHandler, WhatsappWebConnection, UserData, State};
use whatsappweb::message::{ChatMessage, ChatMessageContent};
use whatsappweb::media;
use whatsappweb::{Jid, MediaType};


const SESSION_FILENAME: &str = "session.bin";

struct Handler {}

impl WhatsappWebHandler for Handler {
fn on_state_changed(&self, connection: &WhatsappWebConnection<Handler>, state: State) {
info!("new state: {:?}", state);
if state == State::Connected {
let mut file = Vec::new();
File::open("path/to/image.jpg").unwrap().read_to_end(&mut file).unwrap();

let connection0 = connection.clone();
let (thumbnail, size) = media::generate_thumbnail_and_get_size(&file);
let thumbnail = Arc::new(thumbnail);

media::upload_file(&file, MediaType::Image, &connection, Box::new(move |file_info| {
let jid = Jid::from_phone_number("+49123456789".to_string()).unwrap();

connection0.send_message(ChatMessageContent::Image(file_info.unwrap(), size, thumbnail.to_vec()), jid);
}));
}
}

fn on_persistent_session_data_changed(&self, persistent_session: PersistentSession) {
bincode::serialize_into(OpenOptions::new().create(true).write(true).open(SESSION_FILENAME).unwrap(), &persistent_session).unwrap();
}
fn on_user_data_changed(&self, _: &WhatsappWebConnection<Handler>, _: UserData) {}
fn on_disconnect(&self, reason: DisconnectReason) {
info!("disconnected");

match reason {
DisconnectReason::Removed => {
remove_file(SESSION_FILENAME).unwrap();
}
_ => {}
}
}
fn on_message(&self, _: &WhatsappWebConnection<Handler>, _: bool, _: Box<ChatMessage>) {}
}

fn main() {
simple_logger::init_with_level(log::Level::Debug).unwrap();
let handler = Handler {};

if let Ok(file) = File::open(SESSION_FILENAME) {
let (_, join_handle) = connection::with_persistent_session(bincode::deserialize_from(file).unwrap(), handler);
join_handle.join().unwrap();
} else {
let (_, join_handle) = connection::new(|qr| { qr.render::<Luma<u8>>().module_dimensions(10, 10).build().save("login_qr.png").unwrap(); }, handler);
join_handle.join().unwrap();
}
}
12 changes: 6 additions & 6 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,17 +698,17 @@ impl<H: WhatsappWebHandler<H> + Send + Sync + 'static> Handler for WsHandler<H>
self.whatsapp_connection.ws_on_disconnected();
}
}

/// Stores the parameters to login without scanning the qrcode again.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct PersistentSession {
client_token: String,
server_token: String,
client_id: [u8; 8],
enc: [u8; 32],
mac: [u8; 32]
pub client_token: String,
pub server_token: String,
pub client_id: [u8; 8],
pub enc: [u8; 32],
pub mac: [u8; 32]
}


const ENDPOINT_URL: &str = "wss://w7.web.whatsapp.com/ws";

/// Create new connection and session.
Expand Down
42 changes: 32 additions & 10 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub fn encrypt_media_message(media_type: MediaType, file: &[u8]) -> (Vec<u8>, Ve
let iv = &media_key_expanded[0..16];

let size_with_padding = aes_encrypt(&cipher_key, iv, &file, &mut file_encrypted);
file_encrypted.truncate(10 + size_with_padding);
file_encrypted.truncate(size_with_padding);

let hmac_data = [iv, &file_encrypted].concat();

Expand All @@ -134,7 +134,7 @@ pub fn encrypt_media_message(media_type: MediaType, file: &[u8]) -> (Vec<u8>, Ve
pub fn decrypt_media_message(key: &[u8], media_type: MediaType, file_encrypted: &[u8]) -> Result<Vec<u8>> {
let media_key_expanded = derive_media_keys(key, media_type);

let mut file = vec![0u8; file_encrypted.len() - 16];
let mut file = vec![0u8; file_encrypted.len() - 10];

let mut cipher_key = Vec::with_capacity(32);
cipher_key.extend_from_slice(&media_key_expanded[16..48]);
Expand All @@ -147,7 +147,7 @@ pub fn decrypt_media_message(key: &[u8], media_type: MediaType, file_encrypted:
&hmac_data);

if file_encrypted[(size - 10)..] != signature.as_ref()[..10] {
bail!{"Invalid mac"}
bail! {"Invalid mac"}
}


Expand All @@ -162,7 +162,6 @@ pub(crate) fn aes_encrypt(key: &[u8], iv: &[u8], input: &[u8], output: &mut [u8]

let mut read_buffer = RefReadBuffer::new(input);


let mut write_buffer = RefWriteBuffer::new(output);

aes_encrypt.encrypt(&mut read_buffer, &mut write_buffer, true).unwrap();
Expand All @@ -185,6 +184,7 @@ mod tests {
use super::*;
use base64;
use node_wire::Node;
use std::io::stdin;


#[test]
Expand All @@ -194,16 +194,24 @@ mod tests {

let mac = base64::decode("").unwrap();

let msg = base64::decode("").unwrap();
let pos = msg.iter().position(|x| x == &b',').unwrap() + 3;
loop {
let mut line = String::new();
stdin().read_line(&mut line).unwrap();
let len = line.len();
line.truncate(len - 1);
let msg = base64::decode(&line).unwrap();
let pos = msg.iter().position(|x| x == &b',').unwrap() + 3;

let dec_msg = verify_and_decrypt_message(&enc, &mac, &msg[pos..]).unwrap();

let dec_msg = verify_and_decrypt_message(&enc, &mac, &msg[pos..]);
let node = Node::deserialize(&dec_msg).unwrap();

println!("{:?}", Node::deserialize(&dec_msg).unwrap());
println!("{:?}", node);
}
}

#[test]
fn test_sign_verify() {
fn test_encrypt_decrypt_message() {
let mut enc = vec![0u8; 32];
SystemRandom::new().fill(&mut enc).unwrap();

Expand All @@ -214,7 +222,21 @@ mod tests {
SystemRandom::new().fill(&mut msg).unwrap();
let enc_msg = sign_and_encrypt_message(&enc, &mac, &msg);

let dec_msg = verify_and_decrypt_message(&enc, &mac, &enc_msg);
let dec_msg = verify_and_decrypt_message(&enc, &mac, &enc_msg).unwrap();

assert_eq!(msg, dec_msg);
}

#[test]
fn test_encrypt_decrypt_media() {
let mut msg = vec![0u8; 300];
SystemRandom::new().fill(&mut msg).unwrap();

let media_type = MediaType::Image;

let (enc_msg, key) = encrypt_media_message(media_type, &msg);

let dec_msg = decrypt_media_message(&key, media_type, &enc_msg).unwrap();

assert_eq!(msg, dec_msg);
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ impl Jid {
}

pub fn from_phone_number(mut phonenumber: String) -> Result<Jid> {
if phonenumber.chars().next() == Some('+') {
if phonenumber.starts_with('+') {
phonenumber.remove(0);
}

if phonenumber.chars().find(|c| !c.is_digit(10)).is_some() {
if phonenumber.chars().any(|c| !c.is_digit(10)) {
return Err("not a valid phonenumber".into());
}

Expand Down
58 changes: 32 additions & 26 deletions src/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ extern crate base64;
extern crate json;
extern crate image;

use std::io::{Cursor, Read};
use std::io::Cursor;
use std::thread;
use std::sync::Arc;
use std::fs;

use json_protocol::JsonNonNull;
use image::GenericImage;
use image::{GenericImage, RGB};
use image::jpeg::JPEGEncoder;
use reqwest;

use MediaType;
Expand All @@ -18,59 +18,65 @@ use connection::{WhatsappWebConnection, WhatsappWebHandler};
use errors::*;



pub fn generate_thumbnail_and_get_size(image: &[u8]) -> (Vec<u8>, (u32, u32)) {
let image = image::load_from_memory(image).unwrap();

let size = (image.height(), image.width());
let thumbnail = image.thumbnail(200, 200);
let thumbnail = image.thumbnail(160, 160).to_rgb();

let mut thumbnail_writter = Cursor::new(Vec::new());

thumbnail.save("tmp.jpg").unwrap(); //Todo
JPEGEncoder::new(&mut thumbnail_writter).encode(&thumbnail, thumbnail.width(), thumbnail.height(), RGB(8)).unwrap();

let mut thumbnail = Vec::new();
fs::File::open("tmp.jpg").unwrap().read_to_end(&mut thumbnail).unwrap();
//fs::remove_file("tmp.jpg");
(thumbnail, size)
(thumbnail_writter.into_inner(), size)
}

/// Download file from servers and decrypt it
pub fn download_file<H>(file_info: FileInfo, media_type: MediaType, callback: Box<Fn(Result<Vec<u8>>) + Send + Sync>)
where H: WhatsappWebHandler + Send + Sync + 'static {
thread::spawn(move || {
let mut file_enc = Cursor::new(Vec::with_capacity(file_info.size));
let mut file_enc = Cursor::new(Vec::with_capacity(file_info.size));

callback(reqwest::get(&file_info.url)
callback(reqwest::get(&file_info.url)
.and_then(|mut response| response.copy_to(&mut file_enc))
.map_err(|e| Error::with_chain(e, "could not load file"))
.and_then(|_|crypto::decrypt_media_message(&file_info.key, media_type, &file_enc.into_inner())));

.and_then(|_| crypto::decrypt_media_message(&file_info.key, media_type, &file_enc.into_inner())));
});
}

/// Upload file to servers and encrypt it
pub fn upload_file<H>(file: Vec<u8>, media_type: MediaType, connection: &WhatsappWebConnection<H>, callback: Box<Fn(Result<FileInfo>) + Send + Sync>)
pub fn upload_file<H>(file: &[u8], media_type: MediaType, connection: &WhatsappWebConnection<H>, callback: Box<Fn(Result<FileInfo>) + Send + Sync>)
where H: WhatsappWebHandler + Send + Sync + 'static {
let file_hash = crypto::sha256(&file);
let file_hash = crypto::sha256(file);

let file = Arc::new(file);
let file_hash = Arc::new(file_hash);
let callback = Arc::new(callback);

let (file_encrypted, media_key) = crypto::encrypt_media_message(media_type, file);
let file_encrypted_hash = crypto::sha256(&file_encrypted);



//Todo refactoring, remove arc -> request_file_upload fnonce
let file_encrypted_hash = Arc::new(file_encrypted_hash);
let file_encrypted = Arc::new(file_encrypted);
let media_key = Arc::new(media_key);
let file_len = file.len();

connection.request_file_upload(&file_hash.clone(), media_type, Box::new(move |url: Result<&str>| {
match url {
Ok(url) => {
let url = url.to_string();
let file = file.clone();
let file_hash = file_hash.clone();
let file_encrypted_hash = file_encrypted_hash.clone();
let file_encrypted = file_encrypted.clone();
let media_key = media_key.clone();
let callback = callback.clone();

thread::spawn(move || {
let (file_encrypted, media_key) = crypto::encrypt_media_message(media_type, &file);

let file_encrypted_hash = crypto::sha256(&file_encrypted);
let form = reqwest::multipart::Form::new()
.text("hash", base64::encode(&file_encrypted_hash))
.part("file", reqwest::multipart::Part::reader(Cursor::new(file_encrypted))
.text("hash", base64::encode(&file_encrypted_hash.to_vec()))
.part("file", reqwest::multipart::Part::reader(Cursor::new(file_encrypted.to_vec()))
.mime(reqwest::mime::APPLICATION_OCTET_STREAM));

let file_info = reqwest::Client::new().post(url.as_str())
Expand All @@ -83,10 +89,10 @@ pub fn upload_file<H>(file: Vec<u8>, media_type: MediaType, connection: &Whatsap
.map(|url| FileInfo {
mime: "image/jpeg".to_string(),
sha256: file_hash.to_vec(),
enc_sha256: file_encrypted_hash,
key: media_key,
enc_sha256: file_encrypted_hash.to_vec(),
key: media_key.to_vec(),
url,
size: file.len() //Or encrypted file size ??
size: file_len, //Or encrypted file size ??
});
callback(file_info);
});
Expand Down
Loading

0 comments on commit 6f10723

Please sign in to comment.