1#![doc = include_str!("../../README.md")]
2#![cfg_attr(feature = "nightly", feature(doc_cfg))]
3
4use std::num::NonZero;
5
6use base64::{prelude::BASE64_STANDARD, Engine};
7
8mod types;
9pub use types::{Attachment, Cipher, CipherInfo, CompressionType};
10
11mod crypto;
12use crypto::decrypt_aes_256_gcm;
13
14mod error;
15pub use error::Error;
16
17#[allow(unused)]
19#[derive(Debug, Clone)]
20pub struct Paste<'a> {
21 key: Vec<u8>,
22 key_base58: &'a str,
23 pasteid: &'a str,
24 base_url: &'a str,
25}
26
27pub type Result<T> = std::result::Result<T, Error>;
29
30impl Paste<'_> {
31 pub fn parse_url<'a>(url: &'a str) -> Result<Paste<'a>> {
43 let (base_url, pasteinfo) = url.split_once('?').ok_or(Error::IllFormedURL)?;
44 let (pasteid, key_base58) = pasteinfo.split_once("#").ok_or(Error::IllFormedURL)?;
45
46 let paste = Self::try_from_key_and_pasteid(key_base58, pasteid)?;
47 Ok(Paste { base_url, ..paste })
48 }
49
50 pub fn try_from_key_and_pasteid<'a>(
73 key_base58: &'a str,
74 pasteid: &'a str,
75 ) -> Result<Paste<'a>> {
76 let key = bs58::decode(key_base58).into_vec()?;
77
78 if key.len() != 32 {
79 return Err(Error::KeyLengthMismatch(key.len()));
80 }
81
82 let base_url = "https://paste.fitgirl-repacks.site/";
83
84 Ok(Paste {
85 key,
86 key_base58,
87 pasteid,
88 base_url,
89 })
90 }
91
92 pub fn decrypt(&self, cipher: impl AsRef<CipherInfo>) -> Result<Attachment> {
94 let CipherInfo { adata, ct } = cipher.as_ref();
95
96 let Cipher {
97 cipher_iv,
98 kdf_salt,
99 kdf_iterations,
100 compression_type,
101 ..
102 } = &adata.cipher;
103
104 let master_key = &self.key;
105 let ct = BASE64_STANDARD.decode(ct)?;
106 let cipher_iv = BASE64_STANDARD.decode(cipher_iv)?;
107 let kdf_salt = BASE64_STANDARD.decode(kdf_salt)?;
108 let iterations = NonZero::new(*kdf_iterations).ok_or(Error::ZeroIterations)?;
109 let algorithm = ring::pbkdf2::PBKDF2_HMAC_SHA256;
110
111 let mut derived_key = [0u8; 32];
112 ring::pbkdf2::derive(
113 algorithm,
114 iterations,
115 &kdf_salt,
116 master_key,
117 &mut derived_key,
118 );
119
120 let adata_json = serde_json::to_string(&adata)?;
121
122 let data =
123 decrypt_aes_256_gcm(&ct, &derived_key, cipher_iv, &adata_json, compression_type)?;
124 Ok(serde_json::from_slice(&data)?)
125 }
126
127 #[cfg_attr(feature = "nightly", doc(cfg(feature = "ureq")))]
129 #[cfg(feature = "ureq")]
130 pub fn request(&self) -> Result<CipherInfo> {
131 use ureq::{http::header::ACCEPT, Agent};
132
133 let pasteid = self.pasteid;
134 let key_base58 = self.key_base58;
135
136 let base = self.base_url;
137 let init_cookies = format!("{base}?{pasteid}#{key_base58}");
138 let cipher_info = format!("{base}?pasteid={pasteid}");
139
140 let agent = Agent::new_with_defaults();
141
142 agent.get(init_cookies).call()?;
143
144 let resp = agent
145 .get(cipher_info)
146 .header(ACCEPT, "application/json")
147 .call()?
148 .body_mut()
149 .read_json()?;
150 Ok(resp)
151 }
152
153 #[cfg_attr(feature = "nightly", doc(cfg(feature = "reqwest")))]
163 #[cfg(feature = "reqwest")]
164 pub async fn request_async(&self) -> Result<CipherInfo> {
165 use reqwest::{header::ACCEPT, ClientBuilder};
166
167 let pasteid = self.pasteid;
168 let key_base58 = self.key_base58;
169
170 let base = self.base_url;
171 let init_cookies = format!("{base}?{pasteid}#{key_base58}");
172 let cipher_info = format!("{base}?pasteid={pasteid}");
173
174 let client = ClientBuilder::new().gzip(true).build()?;
175 client.get(init_cookies).send().await?;
176
177 let resp = client
178 .get(cipher_info)
179 .header(ACCEPT, "application/json")
180 .send()
181 .await?
182 .json()
183 .await?;
184 Ok(resp)
185 }
186
187 #[cfg_attr(feature = "nightly", doc(cfg(feature = "nyquest")))]
191 #[cfg(feature = "nyquest")]
192 pub async fn request_async_ny(&self) -> Result<CipherInfo> {
193 use nyquest::{r#async::Request, ClientBuilder};
194
195 let pasteid = self.pasteid;
196 let key_base58 = self.key_base58;
197
198 let base = self.base_url;
199 let init_cookies = format!("/?{pasteid}#{key_base58}");
200 let cipher_info = format!("/?pasteid={pasteid}");
201
202 let client = ClientBuilder::default()
203 .base_url(base)
204 .build_async()
205 .await?;
206 client.request(Request::get(init_cookies)).await?;
207
208 let resp = client
209 .request(Request::get(cipher_info).with_header("Accept", "application/json"))
210 .await?
211 .json()
212 .await?;
213 Ok(resp)
214 }
215}
216
217pub use base64;