ncryptf/client/response.rs
1use base64::{engine::general_purpose, Engine as _};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum ResponseError {
6 #[error("decrypting response to plain text failed.")]
7 DecryptingResponseFailed,
8 #[error("the remote server is not implemented correctly.")]
9 ResponseImplementationError,
10 #[error("the server did not return a response.")]
11 ResponseMissing,
12}
13
14/// Client feature Response provides a simple API for handling encrypted responses
15///
16/// `ncryptf::Client::Response::response` contains the common elements you'd receive from a response, including the http status as a `reqwest::StatusCode`,
17/// the response headers as a `reqwest::header::HeaderMap`, and the raw body response as a `Option<String>`.
18///
19/// If you have a defined struct, you can use the `into` method to deserialize the object into a struct. Handle as you would a serde_json::from_str()
20///
21/// ```rust
22/// let structure = response.into::<MyStructure>();
23/// ```
24#[derive(Debug, Clone)]
25pub struct Response {
26 pub status: reqwest::StatusCode,
27 pub headers: reqwest::header::HeaderMap,
28 pub body: Option<String>,
29 pub pk: Option<Vec<u8>>,
30 pub sk: Option<Vec<u8>>,
31}
32
33impl Response {
34 /// Constructs a new response
35 pub async fn new(response: reqwest::Response, sk: Vec<u8>) -> Result<Self, ResponseError> {
36 let mut r = Self {
37 status: response.status(),
38 headers: response.headers().to_owned(),
39 body: None,
40 pk: None,
41 sk: None,
42 };
43
44 match response.text().await {
45 Ok(body) => {
46 match r.headers.get("Content-Type") {
47 Some(h) => match h.to_str() {
48 // If this is an NCRYPTF response, we need to decrypt it
49 Ok(crate::rocket::NCRYPTF_CONTENT_TYPE) => {
50 // If the body is empty, don't attempt to decrypt the response
51 if body.is_empty() {
52 r.body = None;
53 return Ok(r);
54 }
55 let body_bytes = general_purpose::STANDARD.decode(body).unwrap();
56 let ncryptf_response = crate::Response::from(sk.clone()).unwrap();
57 match ncryptf_response.decrypt(body_bytes.clone(), None, None) {
58 Ok(message) => {
59 // If we've already decrypted the response then these will succeed
60 let pk = crate::Response::get_public_key_from_response(
61 body_bytes.clone(),
62 )
63 .unwrap();
64 let sk = crate::Response::get_signing_public_key_from_response(
65 body_bytes.clone(),
66 )
67 .unwrap();
68 r.body = Some(message);
69 r.pk = Some(pk);
70 r.sk = Some(sk);
71 return Ok(r);
72 }
73 Err(_error) => return Err(ResponseError::DecryptingResponseFailed),
74 }
75 }
76 _ => {
77 if body.is_empty() {
78 r.body = None
79 } else {
80 r.body = Some(body)
81 }
82
83 return Ok(r);
84 }
85 },
86 // If we don't have a content type on the return, then the server isn't implemented
87 // correctly. We cannot proceed.
88 _ => return Err(ResponseError::ResponseImplementationError),
89 }
90 }
91 // There's no response
92 Err(_error) => return Err(ResponseError::ResponseMissing),
93 }
94 }
95
96 /// Converts the response into an actual struct object
97 pub fn into<T: for<'a> serde::Deserialize<'a>>(self) -> Result<T, serde_json::Error> {
98 return serde_json::from_str::<T>(&self.body.unwrap());
99 }
100}