diff --git a/Cargo.toml b/Cargo.toml index 857205d..175c415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,20 @@ tokio-macros = "2.4.0" reqwest = "0.12.5" log = "0.4.22" env_logger = "0.8" +clap = "3.0" url = "2.2" percent-encoding = "2.1" trust-dns-resolver = "0.20" select = "0.5" scraper = "0.12" arboard = "3.4.0" - +aes = "0.7" +block-modes = "0.8" +rand = "0.8" # 哈希库 winapi = { version = "0.3.9", features = ["winuser", "psapi"] } +base64 = "0.21" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [profile.test] env = { "RUST_LOG" = "debug" } diff --git a/info.json b/info.json new file mode 100755 index 0000000..c13ff43 --- /dev/null +++ b/info.json @@ -0,0 +1,6 @@ +{ + "cookies": "", + "magic": "3", + "password": "ZWtrb2Jhb19fX19fX19fXzlytojwgd3qVW7PNYs7Y9s=", + "user_name": "ekko.bao" +} \ No newline at end of file diff --git a/python_flask_http_server/server.py b/python_flask_http_server/server.py new file mode 100755 index 0000000..915f56b --- /dev/null +++ b/python_flask_http_server/server.py @@ -0,0 +1,24 @@ +from flask import Flask, send_from_directory, request,jsonify +import sys +app = Flask(__name__) + +@app.route('/SSWeb/rd/copy_paste.jsp') +def copy_paste(): + return send_from_directory('templates', 'copy_paste.html') + +@app.route('/SSWeb/rd/copy_paste.jsp', methods=['POST']) +def recive_msg(): + # 获取查询参数 + send_param = request.args.get('send') + if not send_param: + return jsonify({ + "zz": "must send query param" + }) + # 获取 JSON 数据 + data = request.get_data().decode() + print(f"msg is [{data}]\n") + return send_from_directory('templates', 'copy_paste.html') + +if __name__ == '__main__': + sys.stdout.reconfigure(encoding='utf-8') + app.run(host='0.0.0.0', port=5000) diff --git a/python_flask_http_server/templates/copy_paste.html b/python_flask_http_server/templates/copy_paste.html new file mode 100755 index 0000000..6ee7c8a --- /dev/null +++ b/python_flask_http_server/templates/copy_paste.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copy & Paste + + + + + +
+ + + + + + + + + + + + + + + +
Message (maxiumn 300 characters)ExecuteTips
+
+ + + + + + + + + + + + 59 items found, displaying 1 to 50.[First/Prev] + 1, 2 [Next/Last] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Message List
+ Message + + Create_Date_Time +
+
剪切板测试函数
+
2022-10-20 11:25:09.92
+
C:\Users\ekko.bao\Desktop\......
+
2022-10-20 11:24:14.543
+
http://sswiki:8090/pages/viewpage.action?pageId=41925635
+
2022-10-11 17:15:46.733
+
 ldc stop channel too long,so force stop&destroy channel resource,then when ldc pass task use channel resources, crash happen
+
+
2022-09-15 10:06:45.327
+
the return value is the number of milliseconds remaining in the originally requested sleep period
+
2022-09-14 15:18:48.05
+
find ./cmodel_ut/output -name "Dst*" |xargs ls -t | xargs -i md5sum {} > ./md5.txt
+
2022-09-08 11:53:33.86
+
https://blog.csdn.net/weixin_42319283/article/details/85089887
+
2022-09-05 10:04:31.997
+
eval $(find ./ -name "*0.bmp" | xargs -i printf "\"/mnt/d/software/Beyond\ Compare\ 4/BCompare.exe\" %s ../../%s;\n" {} {} | xargs -i printf "date;echo \"start %s\";%s echo end;" {} {})
+
2022-08-09 20:08:09.93
+
eval $(cat check_ut_result/test7/result.txt | grep FAIL | awk '{printf "./prog_ldc_ut -c %s -C;\n", $3}')
+
2022-08-09 20:01:22.627
+
f82d0b02-3a00-43ed-b33b-18e5766035e5
+
2022-07-11 11:20:22.407
+
92b100a1-8981-4c74-8a0e-ba4ab8975adb
+
2022-06-22 11:05:09.673
+
5fbf21f4-d9ed-4325-97ed-4109220d9021
+
2022-06-20 16:00:29.843
+
a173712a-3c38-4e38-9f61-75b03c9d5a68
+
2022-06-14 19:56:49.29
+
............: [LDC] ......LDC EIS .........bypass......
+............: 1. ..............................vpe_ldc...ldc............
+2. ......ldc&vpe_ldc.....................
+
+..................review..................
+
2022-03-02 21:45:44.06
+
..................review..................
+............: [LDC] EIS..............................
+............: 1. ............L3...CV......................................................
+
2022-03-02 21:45:16.637
+
1. ...............LDC mi_eptz_linux.h.................................lib.............................................................................................................................................................................................-----------............
+2.wiki...... eptz user guide...............................................................-----...........................
+
2022-03-01 21:42:29.107
+
1. ......ldc..................
+    EPTZ ...Cylinder Projection... EquiRectangularProjection...........................WM DM CM..................
+    Donut.................................
+    Bypass...RCS...MAP2BIN......
+.........STITCH...PERSPECTIVE......
+........................lib.......................................
+
2022-03-01 21:42:15.73
+
............: [LDC] EIS..............................
+............: 1. ......LDC.......................................mjson...............
+        .......................................................................................
+        ......claude.................................................................................UTjson.....................LDC................................................
+
2022-02-28 21:46:52.983
+
machine hcgit04   login ekko.bao password ve5hVjZePgMfo8YOBXeH09BzaLufKw+xBdNOq9h5Yw
+
2022-02-25 16:51:25.16
+
machine hcgit04 login USER_NAME password HTTP_PASSWORD
+machine hcgit04-master login USER_NAME password HTTP_PASSWORD
+machine hcgit04-sz login USER_NAME password HTTP_PASSWORD
+machine hcgit04-sh login USER_NAME password HTTP_PASSWORD
+
2022-02-25 15:40:49.633
+
............: [LDC] EIS......... (SOC_I7)
+............: 1. .....................proc fs..............................proc fs...............
+
+
+http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-MI-LDC%28EIS%29-flow
+http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-mi_ldcx_procfs_parse
+
2022-02-24 21:34:01.697
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-create-mi-ldc-eis-demo-env
+
2022-02-23 21:41:34.017
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-devlop_flow
+
2022-02-21 21:40:54.953
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-fourth_week-0207-0213#Ekko-learn-fourth_week-0207-0213-CMDQ%E6%80%BB%E7%BB%93
+
2022-02-08 21:26:28.69
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-fourth_week-0207-0213
+
2022-02-07 18:04:37.017
+
https://blog.csdn.net/abcwoabcwo/article/details/93099982
+
2022-01-27 10:10:16.68
+
http://sswiki.sigmastar.com.tw:8090/display/MI/MHAL_DIS-interface_doc
+
2022-01-27 10:10:14.037
+
http://sswiki.sigmastar.com.tw:8090/display/MI/MHAL_DIS-interface_doc
+
2022-01-26 21:51:48.77
+
http://sswiki.sigmastar.com.tw:8090/pages/editpage.action?pageId=28944730
+
2022-01-26 21:51:32.287
+
f output image height is 1512, then the height of each slice is 1512/6=252, which is close to 256 if align to 32. If you choose LdcDisSlice=7, then the height of each slice is 1512/7=216, which is not close to 256
+
2022-01-26 11:05:20.77
+
Set higher for more stable image but at the expense of CPU usage
+
2022-01-26 11:04:23.627
+
http://sswiki.sigmastar.com.tw:8090/display/MI/MHAL_DIS-interface_doc
+
2022-01-25 21:35:52.353
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-third_week-0124-0130
+
2022-01-25 21:35:44.177
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-third_week-0124-0130#Ekko-learn-third_week-0124-0130-%E5%A2%9E%E5%8A%A0procfs
+
2022-01-24 21:33:03.9
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-second_week-0117-0123#Ekko-learn-second_week-0117-0123-%E7%BC%96%E8%AF%91%E6%A1%86%E6%9E%B6%E8%A7%A3%E8%AF%BB
+
2022-01-24 21:32:46.773
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-second_week-0117-0123#Ekko-learn-second_week-0117-0123-%E8%B7%9F%E8%B8%AA%EF%BC%9A
+
2022-01-21 21:39:55.213
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-second_week-0117-0123#Ekko-learn-second_week-0117-0123-2022/01/19
+
2022-01-19 21:34:06.807
+
http://www.cnblogs.com/king-77024128/articles/2666230.html
+
2022-01-19 10:12:20.44
+
file://szswfs01/SSARTIFACTS/NONSYNC/
+
2022-01-18 16:48:57.65
+
sigmastar/L3/autorelease/alkaid/sdk/docs
+
2022-01-18 10:37:18.5
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learn-second_week-0117-0123
+
2022-01-17 21:40:56.903
+
Digital Overlap High Dynamic Range
+
2022-01-17 18:00:31.783
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learning-2022_01_14
+
2022-01-14 21:01:21.743
+
in/map/fisheye/new_AMTK_FHD/CalibPoly_new.bin
+
2022-01-14 10:06:00.52
+
in/map/fisheye/new_AMTK_FHD/CalibPoly_new.bin
+
2022-01-14 09:45:05.487
+
cfg/bin/TableMode/multiband/sh/dualsensor/1920x1080x2_st.bin
+
2022-01-14 09:44:40.43
+
http://sswiki.sigmastar.com.tw:8090/display/MI/Ekko-learning-2022_01_13
+
2022-01-13 21:55:37.52
+
boot.bin = IPL.bin + IPL_CUST.bin + u-boot 
+
2022-01-13 21:49:28.853
+
MI LDC Training
+
2022-01-13 16:39:14.647
+
declare -x
+
2022-01-13 11:24:59.05
+ + + + \ No newline at end of file diff --git a/src/encrypt/mod.rs b/src/encrypt/mod.rs new file mode 100755 index 0000000..39ff40b --- /dev/null +++ b/src/encrypt/mod.rs @@ -0,0 +1,96 @@ +use std::error::Error; +use std::process::Command; +extern crate log; +use aes::Aes128; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::Pkcs7; +use base64::{Engine as _, engine::general_purpose}; +type Aes128Cbc = Cbc; + +use std::fmt; + +#[derive(Debug)] +pub enum EncryptErr { + NotBase64, +} + +impl fmt::Display for EncryptErr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + EncryptErr::NotBase64 => write!(f, "String is not base64"), + } + } +} + +impl std::error::Error for EncryptErr {} + +pub fn get_key() -> String { + let output = Command::new("wmic") + .args(&["DISKDRIVE", "get", "serialnumber"]) + .output() + .expect("Failed to execute command"); + + let serial = String::from_utf8_lossy(&output.stdout) + .lines() + .nth(1) // 获取第二行,通常是序列号 + .unwrap() + .trim() + .to_string(); + log::trace!("hdd serial is {}", serial); + serial +} + +pub fn is_base64(encrypted_data: &str) -> bool { + match general_purpose::STANDARD.decode(encrypted_data) { + Ok(_) => true, + Err(_) => false, + } +} + +pub fn encrypt(raw_data: &str, key: &str, iv:&str) -> String { + let key = append_align(key); + let iv = append_align(iv); + + let cipher = Aes128Cbc::new_from_slices(&key, &iv).unwrap(); + + let pos = cipher.encrypt_vec(&raw_data.as_bytes()); + // println!("pos buff size is {} {}, {}", general_purpose::STANDARD.encode(&pos), pos.len(), data.len()); + let ret = [iv.to_vec(), pos.to_vec()].concat();// Prepend IV for decryption + general_purpose::STANDARD.encode(ret) +} + +pub fn decrypt(encrypted_data: &str, key: &str) -> Result> { + let key: Vec = append_align(key); + let encrypted_data = general_purpose::STANDARD.decode(encrypted_data)?; + let (iv, data) = encrypted_data.split_at(16); // First 16 bytes are IV + + let cipher = Aes128Cbc::new_from_slices(&key, &iv)?; + let decrypted_data = cipher.decrypt_vec(data).unwrap(); + + let result = String::from_utf8(decrypted_data)?; + Ok(result) +} + + +fn append_align(uuid: &str) -> Vec { + let mut key = uuid.as_bytes().to_vec(); + key.resize(16, b'_'); + key +} + +#[test] +fn test_get_key(){ + get_key(); +} + +#[test] +fn test_encrypt_decrypt() { + let user_name = "ekko.bao"; + let password = "PASSWORD"; + let machine_id = get_key(); + let encrypted_data = encrypt(password,&machine_id, user_name); + // println!("encrypted_data is {}", encrypted_data); + let ret = decrypt(&encrypted_data, &machine_id).unwrap(); + println!("decrypt is {}", ret); + assert_eq!(password, ret); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ae9b53b..577787e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,136 @@ + +use log::Log; use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use rand::{random, Rng}; use reqwest::header::HeaderMap; use reqwest::{Client, Response}; use scraper::{Html, Selector}; +use core::ascii; use std::error::Error; -use std::io; +use std::io::{self, Write}; use std::time::Duration; use tokio::sync::Mutex; use std::sync::Arc; use tokio::{task, time}; use std::collections::BTreeMap; extern crate log; +use serde_json::{self, to_string, Value}; +use serde::Serialize; +use std::fs::File; +use std::io::BufReader; pub mod wclip; pub mod sys_res; +pub mod encrypt; use sys_res::*; -pub struct ClipboardSync { + +#[derive(Debug, Serialize, Default)] +pub struct Config { user_name: String, password: String, + cookies: String, + magic: String, + + save_path: String, +} + +impl Config { + pub fn new(json_file: &str) -> Result> { + let file = File::open(json_file)?; + let reader = BufReader::new(file); + let json: Value = serde_json::from_reader(reader)?; + let user_name = json.get("user_name").expect("not found user_name in info.json").as_str().unwrap().to_string(); + log::info!("user_name is {}", user_name); + let mut password = json.get("password").expect("not found password in info.json").as_str().unwrap().to_string(); + let magic = match json.get("magic") { + Some(magic) => magic.as_str().unwrap().to_string(), + None => "~".to_string(), + }; + let cookies = match json.get("cookies") { + Some(cookies) => cookies.as_str().unwrap().to_string(), + None => "".to_string(), + }; + let gen_cfg = move |user_name:String, password:String|{ + let cfg = Config { + user_name, + password, + cookies, + magic, + save_path: json_file.to_string() + }; + cfg + }; + drop(json); + if encrypt::is_base64(&password) { + let key = encrypt::get_key(); + let _ = encrypt::decrypt(&password, &key)?; + Ok(gen_cfg(user_name, password)) + } else { + let key = encrypt::get_key(); + password = encrypt::encrypt(&password, &key, &user_name); + let cfg = gen_cfg(user_name, password); + cfg.update(); + Ok(cfg) + } + } + + pub fn update(&self) { + let json = serde_json::json!({ + "user_name": self.user_name, + "password": self.password, + "cookies": self.cookies, + "magic": self.magic, + }); + let json = serde_json::to_string_pretty(&json).unwrap(); + let mut f = File::options().truncate(true).write(true).create(true).open(&self.save_path).unwrap(); + let _ = f.write(json.as_bytes()); + } + + pub fn user_name(&mut self, value: Option<&str>) -> &str { + if let Some(value) = value { + self.user_name = value.to_string(); + self.update(); + } + &self.user_name + } + pub fn password(&mut self, value: Option<&str>) -> String { + if let Some(value) = value { + let key = encrypt::get_key(); + self.password = encrypt::encrypt(&value, &key, &self.user_name); + self.update(); + } + let key = encrypt::get_key(); + encrypt::decrypt(&self.password, &key).expect("password cant decrypt") + } + + pub fn cookies(&mut self, value: Option<&str>) -> &str { + if let Some(value) = value { + self.cookies = value.to_string(); + self.update(); + } + &self.cookies + } + + pub fn magic(&mut self, value: Option<&str>) -> &str { + if let Some(value) = value { + self.magic = value.to_string(); + self.update(); + } + &self.magic + } +} + +pub struct ClipboardSync { web: Client, ip: String, - cookies: String, clip: wclip::Wclip, - //last message magic - cache_magic: String, + cfg: Config, } const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42"; -fn quote(input: &String) -> String { +fn quote(input: &str) -> String { percent_encode(input.as_bytes(), NON_ALPHANUMERIC).to_string() } @@ -36,14 +138,49 @@ pub struct ClipboardMsgs { pub msgs: Vec, } +#[derive(Debug)] +enum MsgSyncState { + None, + ForceInVM, + NeedSync, +} + // 同步获取消息的线程 -async fn msg_sync(ctx:Arc>){ +async fn msg_sync(ctx:Arc>) { + let VM_EXE_NAME = "Code.exe".to_string(); let mut ctx = ctx.lock().await; + let mut sta = MsgSyncState::None; + let mut turn_time; + //let _ = ctx.update_msg().await; loop{ - let _ = ctx.update_msg().await; - time::sleep(Duration::new(10, 0)).await; - let prog_name = get_foredround_window_name(); - if prog_name == "VMware Horizon Client"{ + log::trace!("current state is {:?}", sta); + match sta{ + MsgSyncState::None => { + turn_time = 200; + let prog_name = get_foredround_window_name(); + if prog_name == VM_EXE_NAME { + sta = MsgSyncState::ForceInVM; + turn_time = 0; + } else { + log::trace!("foreground prog is [{}] {}, waiting: [{}] {}", prog_name, prog_name.len(), VM_EXE_NAME, VM_EXE_NAME.len()); + } + }, + MsgSyncState::ForceInVM => { + turn_time = 100; + let prog_name = get_foredround_window_name(); + if prog_name != VM_EXE_NAME { + turn_time = 0; + sta = MsgSyncState::NeedSync; + } + }, + MsgSyncState::NeedSync => { + turn_time = 0; + let _ = ctx.update_msg().await; + sta = MsgSyncState::None; + }, + } + if turn_time != 0 { + time::sleep(Duration::new(0, turn_time*1000*1000)).await; } } } @@ -54,26 +191,24 @@ pub async fn start_msg_sync(ctx:Arc>) -> Result{ // magic str, message context - Msg((String, &'a str)), + Msg(&'a str), //index of message pack, total message pack number, magic str, message context Sub((u8, u8, String, &'a str)), } impl ClipboardSync { - pub fn new(ip: &str, user_name: &str, password: &str) -> ClipboardSync { - ClipboardSync { - user_name: user_name.to_string(), - password: password.to_string(), + pub fn new(ip: &str, info_file:&str) -> Result> { + let cfg = Config::new(info_file)?; + Ok(ClipboardSync { web: Client::new(), ip: ip.to_string(), - cookies: "".to_string(), clip: wclip::Wclip::new(), - cache_magic: "0xff".to_string(), - } + cfg, + }) } - async fn verify(&self) -> Result> { + async fn verify(&mut self) -> Result> { // 开始验证 let security_check_url = format!("http://{}/SSWeb/rd/j_security_check", self.ip); let mut headers = HeaderMap::new(); @@ -100,11 +235,11 @@ impl ClipboardSync { .parse() .unwrap(), ); - headers.insert("Cookie", self.cookies.parse().unwrap()); + headers.insert("Cookie", self.cfg.cookies(None).parse().unwrap()); let contents = format!( "j_username={}&j_password={}", - quote(&self.user_name), - quote(&self.password) + quote(self.cfg.user_name(None)), + quote(&self.cfg.password(None)) ); let resp = self .web @@ -124,7 +259,7 @@ impl ClipboardSync { session = value.split(';').next().unwrap().to_string(); } } else { - log::error!("cookies not found"); + log::warn!("cookies not found"); } session } @@ -132,9 +267,10 @@ impl ClipboardSync { let mut headers = HeaderMap::new(); headers.insert("User-Agent", USER_AGENT.parse().unwrap()); let url = format!("http://{}/SSWeb/rd/copy_paste.jsp", self.ip); + let response = self.web.get(url).headers(headers).send().await?; - self.cookies = ClipboardSync::get_cookies(&response); - log::info!("cookies is {}", self.cookies); + self.cfg.cookies(Some(&ClipboardSync::get_cookies(&response))); + log::info!("cookies is [{}]", self.cfg.cookies(None)); let mut resp = response.text().await?; log::trace!("Response: {}", resp); if resp.contains("loginForm") { @@ -149,7 +285,7 @@ impl ClipboardSync { } } - pub async fn update_msg(&mut self) -> Result<(), Box> { + pub async fn get_newest_msg(&mut self) -> Result> { //http://ssweb/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2 //d-16544-s 2:时间从靠近现在开始排序 1:时间从最早开始排序 //d-16544-o mesage排序,不要设置 @@ -167,6 +303,11 @@ impl ClipboardSync { headers.insert("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6".parse().unwrap()); let response = self.web.get(url).headers(headers).send().await?; let resp = response.text().await?; + Ok(resp) + } + + pub async fn update_msg(&mut self) -> Result<(), Box> { + let resp = self.get_newest_msg().await?; self.msg2clip(&resp).await; Ok(()) } @@ -174,23 +315,21 @@ impl ClipboardSync { //解析可能存在的消息数据 fn parse_msg<'a>(msg: &'a str) -> Msg { //x|x|x| -> index|total|magic| - let head = &msg[0..7]; - let body = &msg[7..]; - let check = head.split('|').filter(|text| { - let text = text.as_bytes(); + let bmsg = msg.as_bytes(); + let head = &bmsg[0..7]; + let check = head.split(|c| *c == b'|').filter(|text| { if text.len() == 1 { if text[0] >= b'!' && text[0] < b'|' {true} else {false} } else { false } - }).map(|x|x.as_bytes()[0] - b'!').collect::>(); + }).map(|x|x[0] - b'!').collect::>(); if check.len() != 3 { - //TODO: get magic - return Msg::Msg(("maigic".to_string(), msg)) + return Msg::Msg(msg) } - return Msg::Sub((check[0], check[1], check[2].to_string(), body)); + return Msg::Sub((check[0], check[1], check[2].to_string(), &msg[7..])); } //将message解析并放置到剪切板上 @@ -199,19 +338,27 @@ impl ClipboardSync { let document = Html::parse_document(html); // 所有剪切板的数据都是在code这个html lable上的 let selector = Selector::parse("code").unwrap(); + //对于单个消息我们希望发送的时间作为magic,所以这里通过构建一个date selector获得 + let date_selector = Selector::parse("td.fixline").unwrap(); let mut msg_map = BTreeMap::new(); let mut magic_ref = "0xff".to_string(); - let mut is_first = true; + let mut msg_index = 0; for element in document.select(&selector).into_iter() { //let text = element.text().collect::>().concat(); let text = element.text().collect::(); - log::info!("Found: [{}]", text); + log::trace!("Found: [{}]", text); let msg = ClipboardSync::parse_msg(text.as_str()); match msg { - //一整个消息。直接复制到剪切板即可 + //一整个完整的消息。直接复制到剪切板即可 Msg::Msg(msg) => { if magic_ref == "0xff" { - self.clip.set(msg.1); + // 因为每个date和str都是对应的所以这里直接获取指定位置的date作为magic + let magic = document.select(&date_selector).into_iter().nth(msg_index).unwrap().text().collect::(); + if self.cfg.magic(None) != magic { + self.clip.set(msg); + self.cfg.magic(Some(&magic)); + log::info!("magic is {}, msg is {}", self.cfg.magic(None), msg); + } break;//处理完毕。直接退出 } }, @@ -220,13 +367,13 @@ impl ClipboardSync { magic_ref = magic; } else { if magic_ref != magic { // other message ignore - is_first = false; + msg_index += 1; continue } } if msg_map.len() == 0 && ( - self.cache_magic == magic_ref // message is repeated ignore it - || !is_first // we only process the newest msg + self.cfg.magic(None) == magic_ref // message is repeated ignore it + || (msg_index != 0) // we only process the newest msg ) { break; } @@ -234,13 +381,140 @@ impl ClipboardSync { msg_map.insert(index, body.to_string()); if msg_map.len() as u8 == total { let msg = msg_map.into_iter().map(|(_key, v)| v).collect::(); - self.cache_magic = magic_ref; // save for next msg - self.clip.set(&msg); + if self.cfg.magic(None) != magic_ref { + log::info!("magic is {}, msg is {}", self.cfg.magic(None), msg); + self.clip.set(&msg); + self.cfg.magic(Some(&magic_ref)); // save for next msg + } break; } } } - is_first = false; + msg_index += 1; + } + } + + pub async fn send_msg(&mut self, msg: &str) -> Result<(), Box> { + let url = format!("http://{}/SSWeb/rd/copy_paste.jsp?send=y", self.ip); + let mut headers = HeaderMap::new(); + headers.insert("Host", "ssweb".parse().unwrap()); + headers.insert("Connection", "keep-alive".parse().unwrap()); + headers.insert("Cache-Control", "max-age=0".parse().unwrap()); + headers.insert("Origin", "http://ssweb".parse().unwrap()); + headers.insert("DNT", "1".parse().unwrap()); + headers.insert("Upgrade-Insecure-Requests", "1".parse().unwrap()); + headers.insert("Content-Type", "application/x-www-form-urlencoded".parse().unwrap()); + headers.insert("User-Agent", USER_AGENT.parse().unwrap()); + headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse().unwrap()); + headers.insert("Referer", "http://ssweb/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2".parse().unwrap()); + headers.insert("Accept-Encoding", "gzip, deflate".parse().unwrap()); + headers.insert("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6".parse().unwrap()); + headers.insert("Cookie", format!("{}", self.cfg.cookies(None)).parse().unwrap()); + + let contents = format!("message={}", msg); + let _ = self.web.post(url).headers(headers).body(contents).send().await?; + // let resp = response.text().await?; + // Ok(resp) + Ok(()) + } + + pub async fn split_msg(&mut self, msg: &str) -> Result, i32> { + let mut start = 0; + let mut msgs = Vec::new(); + let mut cur = 0; + for c in msg.chars() { + let byte_count = c.len_utf8(); + if cur - start + byte_count + 7 >= 300 { + //超过限额300个字符,构造一个消息进行发送 + msgs.push(&msg[start..cur]); + start = cur; + if msgs.len() > b'|' as usize - b'!' as usize { + log::warn!("msg is too long, ignore it"); + return Err(-1); + } + } + cur += byte_count; + } + //如果拆包了的话。那么要把最后一点点数据打包一下,不然会丢msg + if msgs.len() > 0 && cur != start { + msgs.push(&msg[start..cur]); + } + fn gen_magic(cache: char) -> char { + let mut rng = rand::thread_rng(); + // ASCII 范围从 '!' (33) 到 '|' (124) + let start = b'!' as u8; + let end = b'|' as u8; + let mut ret = cache; + //随机生成一个和上次不一样的值即可 + while ret == cache { + ret = rng.gen_range(start..end) as char; + } + ret + } + let mut ret = Vec::new(); + let total = msgs.len() as u32; + if total > 0 { //大消息切片为多个小消息 + let magic = gen_magic(self.cfg.magic(None).chars().next().unwrap()); + for index in 0..total { + //x|x|x| -> index|total|magic| + let msg = format!("{}|{}|{}", char::from_u32(b'0' as u32 + index).unwrap(), + char::from_u32(b'0' as u32 + total).unwrap(), magic); + ret.push(msg + msgs[index as usize]); + } + self.cfg.magic(Some(&magic.to_string())); + } else { //单个消息 + ret.push(msg.to_string()); + } + Ok(ret) + } + pub async fn clip2msg(&mut self) { + let msg = match self.clip.get() { + Some(msg) => match self.split_msg(&msg).await { + Ok(msgs) => msgs, + Err(_) => return, + }, + None => return, + }; + //遍历所有分包并进行发送 + for item in msg { + let _ = self.send_msg(&item).await; } } } + +#[tokio::test] +async fn test_message2clip() +{ + use std::fs::File; + use std::io::Read; + let mut ins = ClipboardSync::new("127.0.0.1", "info.json").unwrap(); + let mut html = String::new(); + File::open("message.html").unwrap().read_to_string(&mut html).unwrap(); + ins.msg2clip(&html).await; +} + +#[tokio::test] +async fn test_start_msg_sync() +{ + let ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let ctx = Arc::new(Mutex::new(ins)); + let handle = start_msg_sync(ctx.clone()).await.unwrap(); + time::sleep(Duration::new(10, 0)).await; + handle.abort(); +} + +#[tokio::test] +async fn test_split_msg() { + let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let raw_msg = "在这个快速发展的世界中,technology is evolving at an unprecedented rate! 我们需要不断学习和适应新的变化。#创新@未来$梦想%实现^挑战&机遇*合作+成长=成功~体验|分享<知识>探索{可能性}。Let's work together to create a better tomorrow! 生活充满了各种可能性,我们要勇敢面对每一个挑战。1234567890!@#$%^&*()_+-=<>?[]{},.。;:‘’“”()【】《》——"; + let msgs = ins.split_msg(raw_msg).await.unwrap(); + println!("{:?}", msgs); +} + +#[tokio::test] +async fn test_send_send_msg() { + let mut ins = ClipboardSync::new("127.0.0.1:5000", "info.json").unwrap(); + let raw_msg = "在这个快速发展的世界中,technology is evolving at an unprecedented rate! 我们需要不断学习和适应新的变化。#创新@未来$梦想%实现^挑战&机遇*合作+成长=成功~体验|分享<知识>探索{可能性}。Let's work together to create a better tomorrow! 生活充满了各种可能性,我们要勇敢面对每一个挑战。1234567890!@#$%^&*()_+-=<>?[]{},.。;:‘’“”()【】《》——"; + ins.clip.set(&raw_msg); + ins.clip2msg().await; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ed029d3..195100d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,83 @@ use trust_dns_resolver::{ }; use tokio::sync::{Mutex}; use std::sync::Arc; +use clap::{App, Arg, ArgGroup}; + +// const SERVER:&str = "172.19.36.79"; +const SERVER:&str = "127.0.0.1:5000"; + +enum Character { + Producer, + Consumer, +} + +fn param_parser() -> (Character, String) { + let mut character = Character::Consumer; + let matches = App::new("sstar_clipboard_sync") + .version("1.0") + .author("Ekko.bao") + .about("sstar clipboard sync") + .arg( + Arg::with_name("producer") + .short('p') + .long("producer") + .conflicts_with("consumer") + .help("Data producer") + ) + .arg( + Arg::with_name("consumer") + .short('c') + .long("consumer") + .conflicts_with("producer") + .help("Data consumer") + ) + .group(clap::ArgGroup::new("Character") + .args(&["consumer", "producer"]) + .required(true) + .multiple(false)) + .arg( + Arg::with_name("info") + .short('i') + .long("info") + .value_name("info") + .default_value("info.json") + .help("The file of running info") + ) + .get_matches(); + character = if matches.is_present("producer") { + Character::Producer + } else if matches.is_present("consumer") { + Character::Consumer + } else { + character + }; + let info_file = matches.value_of("info").unwrap(); + (character, info_file.to_string()) +} + +async fn producer(info: &str) { + let mut ins: ClipboardSync = match ClipboardSync::new(SERVER, &info) { + Ok(ins) => ins, + Err(e) => { + log::error!("create syns instance fail: {}", e); + return; + } + }; + match ins.login().await { + Ok(()) => log::info!("login success"), + Err(e) => { + log::error!("login fail: {}", e); + return; + } + } + let ctx = Arc::new(Mutex::new(ins)); + let handle = clipboard_sync::start_msg_sync(ctx.clone()).await.unwrap(); + let _ = handle.await; +} + +async fn consumer(_info: &str) { + log::warn!("TODO: support Producer in the feature") +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -29,22 +106,14 @@ async fn main() -> Result<(), Box> { // println!("Error: {}", e); // } // } - - let mut ins = ClipboardSync::new("172.19.36.79", "ekko.bao", "qwer12345;"); - match ins.login().await { - Ok(()) => log::info!("登录成功"), - Err(e) => { - log::error!("登录失败: {}", e); + let (character, info) = param_parser(); + match character{ + Character::Producer => { + producer(&info).await; + } + Character::Consumer => { + consumer(&info).await; } } - let ctx = Arc::new(Mutex::new(ins)); - let handle = clipboard_sync::start_msg_sync(ctx.clone()).await.unwrap(); - let _ = handle.await; - // 获取 Cookies - //if let Some(cookies) = response.headers().get(COOKIE) { - // println!("Cookies: {:?}", cookies); - //} else { - // println!("No cookies found."); - //} Ok(()) } diff --git a/src/sys_res/mod.rs b/src/sys_res/mod.rs index b80401a..3e37508 100755 --- a/src/sys_res/mod.rs +++ b/src/sys_res/mod.rs @@ -1,6 +1,5 @@ extern crate winapi; extern crate log; -use std::path::Path; use winapi::um::winuser::{ GetCursorPos, GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN,GetWindowRect }; @@ -9,8 +8,6 @@ use winapi::shared::windef::{ RECT, }; use winapi::um::winuser::{GetForegroundWindow, GetWindowThreadProcessId}; -use std::ffi::OsString; -use std::os::windows::ffi::OsStringExt; use winapi::um::processthreadsapi::OpenProcess; use winapi::um::winnt::PROCESS_QUERY_INFORMATION; use winapi::um::psapi::GetProcessImageFileNameW; @@ -42,15 +39,14 @@ pub fn get_foredround_window_rect() -> Option<(i32, i32, i32, i32)> { println!("GetWindowRect: {:?}", rect); Some((rect.left, rect.top, rect.right, rect.bottom)) } else { - println!("cant get foregtound window rect"); + log::error!("cant get foregtound window rect"); None } } } - pub fn get_foredround_window_name() -> String { - let mut name = OsString::new(); + let mut name = String::new(); unsafe { let foreground_window = GetForegroundWindow(); @@ -62,24 +58,26 @@ pub fn get_foredround_window_name() -> String { let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, process_id); if process_handle.is_null() { - panic!("Cant open foreground process with {}!", process_id) + log::error!("Cant open foreground process with {}!", process_id); + return "".to_string(); } let mut image_name = [0u16; 1024]; if GetProcessImageFileNameW(process_handle, image_name.as_mut_ptr() as *mut _, image_name.len() as u32) == 0 { - panic!("Cant Get foreground prog name!") + log::error!("Cant Get foreground prog name!"); + return "".to_string(); } // let mut class_name: [u16; 256] = [0; 256]; // GetClassNameW(foreground_window, class_name.as_mut_ptr(), 256); - name = OsString::from_wide(&image_name); + name = String::from_utf16_lossy(&image_name); // let mut class_name_str = OsString::from_wide(&class_name); // log::info!("Class Name: {}", class_name_str.to_string_lossy().to_string()); } - let path = Path::new(&name); - let name = path.file_name().unwrap().to_string_lossy().to_string(); - println!("Current Proc Name: [{}]", name); + let name = name.split('\\').last().unwrap().trim_end_matches('\0').to_string(); + //let name = path.file_name().unwrap().to_string_lossy().to_string(); + // println!("Current Proc Name: [{}]", name); name }