1extern crate crypto;
5extern crate rand;
6
7use base64::{engine::general_purpose::URL_SAFE, Engine as _};
8use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer};
9use crypto::digest::Digest;
10use crypto::sha1::Sha1;
11use crypto::{aes, blockmodes, buffer, symmetriccipher};
12use rand::Rng;
13use serde::Serialize;
14
15static DEFAULT_SALT: &str = "A9F361C70BCB6182";
17
18static API_URL: &str = "https://api.simplepush.io";
20
21#[derive(Serialize)]
23struct Payload {
24 key: String,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 title: Option<String>,
27 msg: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 event: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 actions: Option<Vec<String>>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 encrypted: Option<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 iv: Option<String>,
36}
37
38pub struct Message {
40 pub key: String,
42 pub title: Option<String>,
44 pub message: String,
46 pub event: Option<String>,
48 pub actions: Option<Vec<String>>,
50 pub encrypt: bool,
52 pub password: Option<String>,
54 pub salt: Option<String>,
56}
57
58impl Message {
59 pub fn new(
61 key: &str,
62 title: Option<&str>,
63 message: &str,
64 event: Option<&str>,
65 actions: Option<Vec<&str>>,
66 ) -> Self {
67 Message {
68 key: String::from(key),
69 title: Self::stringify(title),
70 message: String::from(message),
71 event: Self::stringify(event),
72 actions: Self::stringify_vec(actions),
73 encrypt: false,
74 password: None,
75 salt: None,
76 }
77 }
78
79 pub fn new_with_encryption(
81 key: &str,
82 title: Option<&str>,
83 message: &str,
84 event: Option<&str>,
85 actions: Option<Vec<&str>>,
86 password: &str,
87 salt: Option<&str>,
88 ) -> Self {
89 Message {
90 key: String::from(key),
91 title: Self::stringify(title),
92 message: String::from(message),
93 event: Self::stringify(event),
94 actions: Self::stringify_vec(actions),
95 encrypt: true,
96 password: Some(String::from(password)),
97 salt: Self::stringify(salt.or(Some(DEFAULT_SALT))),
98 }
99 }
100
101 fn stringify(s: Option<&str>) -> Option<String> {
102 s.map(String::from)
103 }
104
105 fn stringify_vec(s: Option<Vec<&str>>) -> Option<Vec<String>> {
106 s.map(|t| t.iter().map(|s| String::from(*s)).collect())
107 }
108}
109
110pub struct SimplePush;
115
116impl SimplePush {
117 fn encrypt(
118 key: &[u8],
119 iv: &[u8],
120 buf: Vec<u8>,
121 ) -> Result<String, symmetriccipher::SymmetricCipherError> {
122 let mut encryptor =
123 aes::cbc_encryptor(aes::KeySize::KeySize128, key, iv, blockmodes::PkcsPadding);
124 let mut final_result = Vec::<u8>::new();
125 let mut read_buffer = buffer::RefReadBuffer::new(&buf);
126 let mut buffer = [0; 4096];
127 let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
128
129 loop {
130 let result = encryptor.encrypt(&mut read_buffer, &mut write_buffer, true)?;
131 final_result.extend(
132 write_buffer
133 .take_read_buffer()
134 .take_remaining()
135 .iter()
136 .copied(),
137 );
138
139 match result {
140 BufferResult::BufferUnderflow => break,
141 BufferResult::BufferOverflow => {}
142 }
143 }
144
145 Ok(URL_SAFE.encode(final_result))
146 }
147
148 fn process_message(message: &Message) -> Payload {
149 let message_iv: Option<String>;
150 let encrypted: Option<bool>;
151 let msg: String;
152 let title: Option<String>;
153 let actions: Option<Vec<String>>;
154
155 if message.encrypt {
156 let salt = message.salt.to_owned().expect("salt was None");
157 let password = message.password.to_owned().expect("password was None");
158 let mut hasher = Sha1::new();
159 hasher.input_str(format!("{}{}", password, salt).as_str());
160
161 let mut key = [0u8; 40];
162 hasher.result(&mut key);
163
164 let mut iv = [0u8; 16];
165 let mut rng = rand::rngs::OsRng;
166 rng.fill(&mut iv[..]);
167
168 msg = SimplePush::encrypt(&key[0..16], &iv, message.message.to_owned().into_bytes())
169 .expect("encryption failed!");
170
171 title = message.title.to_owned().map(|t| {
172 SimplePush::encrypt(&key[0..16], &iv, t.into_bytes()).expect("encryption failed")
173 });
174
175 actions = message.actions.to_owned().map(|t| {
176 t.iter()
177 .map(|s| {
178 SimplePush::encrypt(&key[0..16], &iv, s.clone().into_bytes())
179 .expect("encryption failed")
180 })
181 .collect()
182 });
183
184 message_iv = Some(SimplePush::hexify(iv.to_vec()).to_ascii_uppercase());
185 encrypted = Some(true);
186 } else {
187 msg = message.message.to_owned();
188 title = message.title.to_owned();
189 actions = message.actions.to_owned();
190 encrypted = None;
191 message_iv = None;
192 }
193
194 Payload {
195 key: message.key.to_owned(),
196 title,
197 msg,
198 event: message.event.to_owned(),
199 actions,
200 encrypted: encrypted.map(|v| v.to_string()),
201 iv: message_iv,
202 }
203 }
204
205 fn hexify(bytes: Vec<u8>) -> String {
206 let strs: Vec<String> = bytes.iter().map(|b| format!("{:02X}", b)).collect();
207 strs.join("")
208 }
209
210 fn validate(message: &Message) -> Result<(), String> {
211 if message.key.is_empty() {
212 return Err(String::from("key is required"));
213 }
214
215 if message.title.is_none() && message.message.is_empty() {
216 return Err(String::from("a message or title is required"));
217 }
218
219 if message.encrypt && message.password.is_none()
220 || message.password.as_ref().is_some_and(|p| p.is_empty())
221 {
222 return Err(String::from("password is required for encryption"));
223 }
224
225 Ok(())
226 }
227
228 pub fn send(message: Message) -> Result<(), String> {
259 SimplePush::validate(&message)?;
260
261 let client = reqwest::blocking::Client::new();
262 let response = client
263 .post(format!("{}/send", API_URL))
264 .json(&SimplePush::process_message(&message))
265 .send();
266 match response {
267 Ok(_) => {
268 Ok(())
269 }
270 Err(e) => {
271 Err(e.to_string())
272 }
273 }
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_empty_key() {
283 let result = SimplePush::send(Message::new("", Some("title"), "message", None, None));
284 assert!(result.is_err_and(|e| e == *"key is required"));
285 }
286
287 #[test]
288 fn test_empty_message() {
289 let result = SimplePush::send(Message::new("key", None, "", None, None));
290 assert!(result.is_err_and(|e| e == *"a message or title is required"));
291 }
292
293 #[test]
294 fn test_empty_password_with_encryption() {
295 let result = SimplePush::send(Message::new_with_encryption(
296 "key", None, "message", None, None, "", None,
297 ));
298 assert!(result.is_err_and(|e| e == *"password is required for encryption"));
299 }
300}