1use failure::Error;
2use reqwest::{self as rq, Proxy, RequestBuilder};
3use sha3::{Digest, Sha3_256};
4use url::Host;
5
6pub struct Pubkey(pub [u8; 32]);
7impl AsRef<[u8; 32]> for Pubkey {
8 fn as_ref(&self) -> &[u8; 32] {
9 &self.0
10 }
11}
12
13pub fn onion_to_pubkey(onion: &str) -> Result<Pubkey, Error> {
14 let s = onion.split(".").next().unwrap();
15 let b = base32::decode(base32::Alphabet::RFC4648 { padding: false }, s)
16 .ok_or_else(|| failure::format_err!("invalid base32"))?;
17 failure::ensure!(b.len() >= 35, "invalid base32 length");
18 failure::ensure!(b[34] == 3, "invalid version");
19 let pubkey = &b[..32];
20 let mut hasher = Sha3_256::new();
21 hasher.input(b".onion checksum");
22 hasher.input(pubkey);
23 hasher.input(&[3]);
24 failure::ensure!(&b[32..34] == &hasher.result()[..2], "invalid checksum");
25 let mut pk = [0; 32];
26 pk.clone_from_slice(pubkey);
27 Ok(Pubkey(pk))
28}
29
30pub fn pubkey_to_onion(pubkey: &[u8]) -> Result<String, Error> {
31 if pubkey.len() != 32 {
32 failure::bail!("invalid pubkey length")
33 }
34 let mut hasher = Sha3_256::new();
35 hasher.input(b".onion checksum");
36 hasher.input(pubkey);
37 hasher.input(&[3]);
38 let mut onion = Vec::with_capacity(35);
39 onion.extend_from_slice(pubkey);
40 onion.extend_from_slice(&hasher.result()[..2]);
41 onion.push(3);
42 Ok(format!(
43 "{}.onion",
44 base32::encode(base32::Alphabet::RFC4648 { padding: false }, &onion).to_lowercase()
45 ))
46}
47
48#[derive(Clone, Debug)]
49pub struct Creds {
50 pub host: Host,
51 pub proxy: Option<Proxy>,
52 pub password: String,
53}
54impl AsRef<Creds> for Creds {
55 fn as_ref(&self) -> &Creds {
56 self
57 }
58}
59impl Creds {
60 pub fn get(&self, rel_url: &str) -> Result<RequestBuilder, Error> {
61 Ok(if let Some(proxy) = &self.proxy {
62 rq::Client::builder().proxy(proxy.clone()).build()?
63 } else {
64 rq::Client::new()
65 }
66 .get(&format!("http://{}:59001/{}", self.host, rel_url))
67 .basic_auth("me", Some(&self.password)))
68 }
69 pub fn post<T: Into<rq::Body>>(&self, body: T) -> Result<RequestBuilder, Error> {
70 Ok(if let Some(proxy) = &self.proxy {
71 rq::Client::builder().proxy(proxy.clone()).build()?
72 } else {
73 rq::Client::new()
74 }
75 .post(&format!("http://{}:59001", self.host))
76 .basic_auth("me", Some(&self.password))
77 .body(body))
78 }
79}
80
81#[derive(Clone, Debug)]
82pub struct UserData {
83 pub id: [u8; 32],
84 pub name: Option<String>,
85 pub unreads: u64,
86}
87
88pub async fn fetch_users<C: AsRef<Creds>>(creds: C) -> Result<Vec<UserData>, Error> {
89 use std::io::Read;
90 let mut users = Vec::new();
91
92 let res = creds.as_ref().get("?type=users")?.send().await?;
93 let status = res.status();
94 if !status.is_success() {
95 failure::bail!("{}", status.canonical_reason().unwrap_or("UNKNOWN STATUS"));
96 }
97 let mut b = std::io::Cursor::new(res.bytes().await?);
98 loop {
99 let mut id = [0; 32];
100 match b.read_exact(&mut id) {
101 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
102 a => a?,
103 };
104 let mut buf = [0; 8];
105 b.read_exact(&mut buf)?;
106 let unreads = u64::from_be_bytes(buf);
107 let mut buf = [0];
108 b.read_exact(&mut buf)?;
109 let name = if buf[0] == 0 {
110 None
111 } else {
112 let mut buf = vec![0; buf[0] as usize];
113 b.read_exact(&mut buf)?;
114 Some(String::from_utf8(buf)?)
115 };
116 users.push(UserData { id, name, unreads })
117 }
118 Ok(users)
119}
120
121pub async fn add_user(creds: &Creds, onion: &str, name: &str) -> Result<(), Error> {
122 let mut req = Vec::new();
123 req.push(1);
124 req.extend_from_slice(onion_to_pubkey(onion)?.as_ref());
125 req.extend_from_slice(name.as_bytes());
126 let status = creds.post(req)?.send().await?.status();
127 if !status.is_success() {
128 failure::bail!("{}", status.canonical_reason().unwrap_or("UNKNOWN STATUS"));
129 }
130 Ok(())
131}
132
133#[derive(Clone, Debug)]
134pub struct Message {
135 pub inbound: bool,
136 pub time: i64,
137 pub content: String,
138}
139
140pub async fn fetch_messages<C: AsRef<Creds>, I: AsRef<[u8; 32]>>(
141 creds: C,
142 id: I,
143 limit: Option<usize>,
144) -> Result<Vec<Message>, Error> {
145 use std::io::Read;
146
147 let mut msgs = Vec::new();
148 let res = creds
149 .as_ref()
150 .get(&if let Some(limit) = limit {
151 format!(
152 "?type=messages&pubkey={}&limit={}",
153 base32::encode(base32::Alphabet::RFC4648 { padding: false }, id.as_ref())
154 .to_lowercase(),
155 limit
156 )
157 } else {
158 format!(
159 "?type=messages&pubkey={}",
160 base32::encode(base32::Alphabet::RFC4648 { padding: false }, id.as_ref())
161 .to_lowercase()
162 )
163 })?
164 .send()
165 .await?;
166 let status = res.status();
167 if !status.is_success() {
168 failure::bail!("{}", status.canonical_reason().unwrap_or("UNKNOWN STATUS"));
169 }
170 let mut b = std::io::Cursor::new(res.bytes().await?);
171
172 loop {
173 let mut buf = [0];
174 match b.read_exact(&mut buf) {
175 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
176 a => a?,
177 };
178 let inbound = buf[0] != 0;
179 let mut buf = [0; 8];
180 b.read_exact(&mut buf)?;
181 let time = i64::from_be_bytes(buf);
182 let mut buf = [0; 8];
183 b.read_exact(&mut buf)?;
184 let len = u64::from_be_bytes(buf);
185 let mut buf = vec![0; len as usize];
186 b.read_exact(&mut buf)?;
187 msgs.push(Message {
188 inbound,
189 time,
190 content: String::from_utf8(buf)?,
191 });
192 }
193
194 Ok(msgs)
195}
196
197pub async fn send_message(creds: &Creds, id: &[u8; 32], content: &str) -> Result<(), Error> {
198 let mut req = Vec::new();
199 req.push(0);
200 req.extend_from_slice(id);
201 req.extend_from_slice(content.as_bytes());
202 let status = creds.post(req)?.send().await?.status();
203 if !status.is_success() {
204 failure::bail!(
205 "{}",
206 status.canonical_reason().unwrap_or("UNKNOWN STATUS CODE")
207 );
208 }
209 Ok(())
210}