cupslib/
lib.rs

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}